[
  {
    "path": ".flake8",
    "content": "[flake8]\nexclude = \n    build,\n    .git,\n    .tox,\n    tests/.env,\n    */migrations/*\n    \nextend-ignore = E203\nmax-line-length = 88\n"
  },
  {
    "path": ".gitignore",
    "content": "*.log\n*.pot\n*.pyc\nlocal_settings.py\nbuild\nPyScada.egg-info\ndist\n/.project\ndocs/_build\n/.idea/*\n\n# Emacs backup files\n*~\n/tests/test/*\n/tests/project_template_tmp/*\n\n/docker/nginx/ssl/*\n/docker/pyscada/pyscada.zip\n/docker/pyscada/project_template.zip\nlogs_install.txt\nlogs_docker.txt\n/docker/mysql/Dockerfile\n/docker/docker-compose.yml\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/psf/black-pre-commit-mirror\n    rev: 24.4.2\n    hooks:\n    - id: black\n      exclude: \\.py-tpl$\n  - repo: https://github.com/adamchainz/blacken-docs\n    rev: 1.18.0\n    hooks:\n      - id: blacken-docs\n        additional_dependencies:\n        - black==24.4.2\n        files: 'docs/.*\\.txt$'\n        args: [\"--rst-literal-block\"]\n  - repo: https://github.com/PyCQA/isort\n    rev: 5.13.2\n    hooks:\n      - id: isort\n  - repo: https://github.com/PyCQA/flake8\n    rev: 7.1.0\n    hooks:\n      - id: flake8\n  - repo: https://github.com/pre-commit/mirrors-eslint\n    rev: v9.7.0\n    hooks:\n      - id: eslint\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# Read the Docs configuration file for Sphinx projects\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the OS, Python version and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\n    # You can also specify other tool versions:\n    # nodejs: \"20\"\n    # rust: \"1.70\"\n    # golang: \"1.20\"\n\n# Build documentation in the \"docs/\" directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n  # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs\n  # builder: \"dirhtml\"\n  # Fail on all warnings to avoid broken references\n  # fail_on_warning: true\n\n# Optionally build your docs in additional formats such as PDF and ePub\n# formats:\n#   - pdf\n#   - epub\n\n# Optional but recommended, declare the Python requirements required\n# to build your documentation\n# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\n# python:\n#   install:\n#     - requirements: docs/requirements.txt"
  },
  {
    "path": "AUTHORS",
    "content": "Current or previous core committers:\n\n* Martin Schröder\n* Camille Lavayssiere\n\nCurrent and previous core designers:\n\n* Martin Schröder\n* Camille Lavayssiere\n\nContributors (in alphabetical order):\n\n* Shannon McCullough\n* Gregor Kendzierski\n"
  },
  {
    "path": "CHANGELOG.txt",
    "content": "0.7.0b2\n - fixed modbus daemon\n - fixed modbus device\n - updated Docs\n - readded log notifications in HMI\n - added loading animation for data prefetch in HMI\n - changed id in Log model to timestame + id schema\n\n0.7.0b3\n - removed separate handler files for daq\n - removed seperate HMIVariable model\n - added restart of daq when Variable or Device model are changed\n - added support for different byte order of aquired data\n - fixed support for U/INT64 and U/INT32 datatypes\n - removed chart_set from hmi\n - updated docs\n - removed unused code\n\n0.7.0b4\n - fixed reinit loop in daq daemon\n - added restart of daq when Scaling model has changed\n - fixed hmi.widget save without a title\n - fixed export.handler (wrong start and end date)\n - fixed export to file, check of array index, import of numpy\n - added backend table (VariableState) for current values in the database\n - fixed crash of hmi admin when widget has no title\n - added unixtime value in web hmi (experimental)\n - fixed custom html panel variable not uptading in hmi\n - added restart_daemon field in background task model\n - added check of write permissions for pid and log file in DaemonHandler\n\n0.7.0b5\n - added color chooser to VariableAdmin\n - fixed display bool values in charts\n - added servertime to footer of hmi\n - new register handling structure in modbus device\n\n0.7.0b6\n - updated javascript libs\n    - jquery --> 1.12.4\n    - flot --> 0.8.3\n    - tablesorter --> 2.0.5b\n    - bootstrap --> 3.3.6\n - changed Title in HMI views\n - added pollinginterval field in device model\n - fixed chart legend template\n\n 0.7.0b7\n - fixed data export (record starts with 0)\n - fixed RecordedData manager\n - added APC UPS status info's to systemstat device\n\n0.7.0b8\n - added ability to add fake data to RecordedData manager\n - fixed add fake data in hmi\n - fixed query_first_value in RecordedData manager\n\n0.7.0b9\n - updated to Django 1.10\n - changed filter in Chart Model Admin\n\n0.7.0b10\n - added Visa and Phant Device support\n - minor fixes in the Export of Data\n\n0.7.0b11\n - fixed Export without mean values\n - fixed managementcommand for Data Export\n - added lock for Exports to prevent running of more than one export at a time\n\n0.7.0b12\n  - fixed export\n  - added datetime fields to Export\n  - added pytz to the requirements\n\n0.7.0b13\n  - fixed modbus write task\n  - fixed Event handling\n\n0.7.0b14\n  - fixed handling of int16 values in RecordedData Model\n  - added custom admin interface\n  - added filter to variable state admin view\n  - added unit column to variable admin\n\n0.7.0b15\n  - added 1-Wire support (experimental)\n\n0.7.0b16\n  - fixed handling of dead tasks in data export\n\n0.7.0b17\n  - added support for custom daq devices\n  - updated docs\n  - some hmi changes\n\n0.7.0b18\n  - added OWFS support to onewire device\n  - updated to django 1.11\n  - added download link for export files to export job\n  - fixed daemon handler\n\n0.7.0b19, 0.7.0b20, 0.7.0b21\n  - changed daemon handling to support multitasking\n  - rearranged admin for Device and Variables\n  - updated the Docs\n\n0.7.0b22, 0.7.0b23, 0.7.0b24, 0.7.0b25\n  - added BackgroundProcess restart on model change\n  - fixed problem with id of ControlItems in HMI\n  - updated Docker files (not working yet)\n  - updated SysV-init service examples\n  - updated systemd service examples\n\n0.7.0b26\n  - fixed Jofra350 Admin\n  - fixed OperationalError in scheduler\n\n0.7.0b27, 0.7.0b28, 0.7.0b29, 0.7.0b30, 0.7.0b31\n  - major update to the javascript part of the HMI Client\n  - added time selection bar in HMI\n\n0.7.0rc1\n  - updated Docs\n  - updated systemd unit file\n  - improved HMI\n\n0.7.0rc2\n  - fixed \"-\" bug in view link_title\n  - fixed visa daq restart handler\n  - fixed hmi \"AutoUpdateButton\" not working\n  - updated docs, updated readme\n  - added utils.blow_up function to convert database data from change of value to constant timestep data\n\n0.7.0rc3\n  - fixed double connect in visa device\n  - moved loading animation in hmi\n  - added 100 ms and 500 ms polling interval (experimental)\n  - added extension compatibility\n  - fixed onewire and smbus reinit handler\n  - added protocol id for the new GPIO Extension\n\n0.7.0rc4\n  - added missing migration file\n\n0.7.0rc5\n  - changed the method for namespace packages to the pkg_resources-style\n  - moved the version info to pyscada.core\n\n0.7.0rc6\n  - corrected typos\n  - fixes of AppConfig for setup.py develop\n  - added protocol id for scripting extension\n\n0.7.0rc7\n  - added admin for VariableProperty\n  - added capability of reading and writing VariableProperties from the HMI and devices\n  - altered arguments for device.write_data!\n\n0.7.0rc8\n  - changed VariableProperty handling in DeviceWriteTask\n  - changed VariableProperty handling in HMI\n\n0.7.0rc9\n  - fixed variable and variable_property attributes in hmi.ControlItem\n  - added traceback to Device.get_device_instance method\n  - updated Keithley DMM2000 device in visa.devices\n\n0.7.0rc10\n  - updated docs\n  - fixed no data download in pyscada.js\n  - fixed default polling interval in pyscada.device\n\n0.7.0rc11\n  - changed widget model to support content from plugins\n  - changed visable to visible in pyscada.hmi.models.Chart, SlidingPanelMenu, Widget\n  - some changes for python 3 compatibility in pyscada.export\n\n0.7.0rc12\n  - fixed issue #13, Mismatch between event limit elif cases and the displayed limit types.\n  - merged pull request #12, changing from source_format to target_format to match preceding setting of '2H' value\n\n0.7.0rc13\n  - updated docs, switching from python 2.7 to python 3\n  - added redirect to https to the nginx sample config\n  - fixed server error for BackgroundProcess View in Admin with python3\n  - fixed HDF5 export for python 3\n  - fixed ProcessFlowDiagram in HMI\n  - added support for multiple SlidingSidePanels on one Side\n  - added date_saved field to the RecordedData Model, renamed the RecordedData model without the field to RecordedDataOld,\n    migration #48 will copy some data to the new model, the rest can be copied by using the move_data.py script\n\n0.7.0rc14\n  - update to flot 2.1.6\n  - fixed process flow diagram value not displayed\n  - moved x y zoom selection in HMI to each Chart\n  - add option of redirecting to a custom login page\n  - add framer option for modbus communication\n  - add data courser in chart\n  - add XYChart\n  - add new form widget in HMI\n  - add new drop down control element in HMI\n  - fixed WidgetContent not being deleted\n  - moved signal related methods to dedicated signals.py\n  - added pyserial to dependency list\n  - improved hmi <--> db communication to avoid data loss on slow connections\n\n0.7.0rc15\n  - fixed zombi process problem\n  - fixed migrations with python3\n  - changed datetime_now to now from django timezone\n\n0.7.0rc16\n  - fixed re-login after logout (#22)\n  - added LINK_TARGET option to change the default behaviour of links in view overview (#23)\n  - Catching exceptions if DB close while pyscada is running\n\n0.7.0rc17, 0.7.0rc18\n  - add pre_delete signals to stop the background process before deleting a device\n  - move widget post_save signal to the model to remove the global receiver (not filtering by sender)\n  - move device handlers to core\n  - add stop in DAQ Process restart\n  - move the device and variable protocol specific configuration to core\n  - add protocol name in device __str__ for the variable js admin file\n  - add complex events\n\n0.7.0rc19\n  - add fk_name in admin for bacnet device with 2 ForeignKey to Device model\n\n0.7.0rc20\n  - add django channels to send informations between processes\n\n0.7.0rc21\n  - Update docker config file\n  - Add optional PID_FILE_NAME to settings to allow multiple instances\n  - Add custom periodic auto caltulated variable\n\n0.7.0rc22\n  - Add choose_login.html to have multiple login ways\n  - Add circular gauge to display control items\n  - Add silent delete option in admin for VariableState and Device to delete a lot of data\n  - Add grafana doc and config file to use Grafana to display data from a PyScada instance\n  - Add dictionaries to store string with a key. Allows to store strings for Variables\n\n0.7.0rc23\n  - Add svg to render ProcessFlowDiagram. Allows to resize to fit the window size\n  - Add OPC-UA protocol\n  - close DB connection in scheduler to allow multiple instance on the same DB to run\n  - Add INT8 and UINT8 variable value class\n  - change the date range picker JS library\n  - Add logrotate config file\n  - Add a slider to change the refresh rate value of data handling\n\n0.7.1rc1\n  - Update to Django 3\n  - Update docker config and doc to use pyscada repository\n\n0.7.1rc2\n  - Move group display permission items to inline and add exclude type for each item\n  - fix adding a new device and activate device in protocol process list\n  - fix signals for BP not done nor failed\n  - add theme files validator\n  - other fixes\n\n0.7.1rc3\n  - add bar chart\n  - add polling intervals\n  - add view theme\n  - add -config2 div in html to get all infos of items used in the user interface\n  - add calculated variable for aggregation\n  - add install script\n  - add css class arg to customize widget\n  - fixes\n\n0.7.1rc4\n  - Add MBus to protocol list\n  - add append and remove dictionary functions\n  - Fix device write task for VP (no variable defined)\n  - HTML: add span for legendLabel text\n  - JS: load visible chart variable first\n  - fixes\n\n0.7.1rc5\n  - add hide/show JS for fields in admin\n  - add get_prev_value overwritten by child class for systemstat non logging variable\n  - last_value use get_prev_value\n  - add systemstat timestamps\n  - add file protocol\n  - fix DeviceWriteTask for variable to change in complex events\n  - add value using get_prev_value in views.py for non logging variables\n  - Move handler to Device model.\n  - Wait 5 seconds to read the main pid (scheduler)\n\n0.8.0\n  - core : fix when no handler selected to use the GenericHandlerDevice of the selected protocol\n  - systemstat : Create systemstat device (allow remote over ssh)\n  - core : Fix timestamp not integer in RecordedData init\n  - systemstat : process pid find in cmdline and not only in processus name\n  - systemstat : fix pre_delete signal and information choice name\n  - core : fix config2 and add classes\n  - systemstat : add write_data to execute command\n  - JS : fixes\n  - core : fix protocol list for devices\n  - hmi : fix color for control items\n  - core : add related models to Config2\n  - JS : dateTimePicker Event : send pyscadaDateTimeChange event for all objects with the class pyscadaDateTimeChange when the datetime picker value change.\n  - various : replace ugettext_lazy by gettext_lazy\n  - core : replacing pyscada.core in INSTALLED_APPS by pyscada\n  - hmi : replace django.conf.urls.url by django.urls.path\n  - all : Moving to AGPL3 License\n  - core : Moving to django 4.2\n  - docs : fixed urls in the docs\n  - added a testscript for the instalation routine\n  - core : display value option refactoring. Control items display value option code refactoring. Allows more than 3 color.\n  - fix django requirements. It should be a coma separated list.\n  - moved to new namespace packet format (PEP420) : this was nessesary to make the use of venv possible\n  - Fix django 4.0 login : change the default value for LOGIN_REDIRECT_URL in settings.py\n  - Fix django 4.0 CSRF_TRUSTED_ORIGINS : change the nginx config to forward the protocol used (http or https) because : Changed in Django 4.0: The values in older versions must only include the hostname (possibly with a leading dot) and not the scheme or an asterisk.\n  - Fix when a plugin is uninstalled but the WidgetContent defined in this plugin remain and is selected in an active widget. Add an information log.\n  - modbus : fix migration test\n  - update install shell : system and docker options\n  - modbus : fix test : remove pyvisa from settings template\n  - HMI GroupDisplayPermission no groups :\n    - add GroupDisplayPermission for users without any group (blank=True).\n    - auto create in the hmi/0072 migration.\n    - this GroupDisplayPermission cannot be deleted in the admin interface.\n    - this group allow everything by default (exclude is empty for each OneToOne related model).\n    - add ValidationError for duplicate GroupDisplayPermission.\n    - auto collapse only empty inlines in GroupDisplayPermission admin.\n    - update get_group_display_permission_list in utils.\n    - use get_group_display_permission_list in read and write task.\n  - CompexEvent with multiple output variables and refactoring\n    - Change names :\n    - * ComplexEventGroup > ComplexEvent\n    - * ComplexEvent > ComplexEventLevel\n    - * ComplexEventItem > ComplexEventInput\n    - add ComplexEventOutput to set multiple output variable values when a ComplexEventLevel is active or when no level in active for a ComplexEvent.\n  - Remove unused import\n  - add informations to pyscada.mail\n  - init_db for event and mail add pyscada.core to installed app in init_db\n  - use concurrent_log_handler to rotate logs\n  - remove django_cas_ng config from settings\n  - send mails to admins and managers\n  - force channel layer to be empty\n    - since the version 4 of channel redis version, the channel layer is not\n    - empty after the first read\n    - we read it again to empty it\n    - maybe it is related to\n    - django/channels_redis#348\n    - or to django/channels_redis#366\n  - Create background process for generic device\n    - use id 16 for the generic process worker as 1 is taken by the scheduler\n    - but id 1 is taken for generic protocol id\n    - by defaut the generic device don't do nothing\n    - use the dummy handler to save\n  - Allow millisecond timestamp for recorded data\n    - Replace time() by time_ns / 1000000000 in\n    - GenericDevice (write)\n    - GenericHandlerDevice (time)\n    - RecordedData (init)\n    - Do not force timestamp in recorded data init to be integer beforce id calculation.\n  - add handler for dummy waveforms\n    - Create waveforms for a generic device.\n    - Type can be sinus, square and triangle.\n    - Properties are set using variable properties: type, amplitude, start_timestamp, frequency and duty cycle.\n    - Variable Property type should be a string.\n    - default is:\n    - \"type\": \"sinus\",  # sinus, square, triangle\n    - \"amplitude\": 1.0,  # peak to peak value\n    - \"start_timestamp\": 0.0,  # in second from 01/01/1970 00:00:00\n    - \"frequency\": 0.1,  # Hz\n    - \"duty_cycle\": 0.5,  # between 0 and 1, duty cycle for square and for\n    - triangle : Width of the rising ramp as a proportion of the total cycle.\n    - Default is 1, producing a rising ramp, while 0 produces a falling ramp.\n    - width = 0.5 produces a triangle wave. If an array, causes wave shape to\n    - change over time, and must be the same length as t.\n  - log for device write task : log when DWT for a variable not writeable\n  - fix boolean with display option\n    - use button.html for boolean with display options and for non boolean with color only display option\n  - add span for display button : use this span to display the value next to the control item label\n  - fix variable property control item\n    - fix dictionary, color for VP\n    - merge number and boolean in update data values to simplify the code\n    - remove unused boolean classes\n  - add offset property to generic waveform handler\n  - send mail fail not silently : show the error message in a warning log\n  - refactor logs\n    - remove some error logs\n    - replace error logs by warning logs\n    - use f-strings in error logs\n    - add exc_info=True to log traceback and send it to ADMINS (see settings.py)\n    - log as error when a process failed 3 times (to send a mail to ADMINS) then log as warning\n  - set AdminEmailHandler settings\n  - update gitignore for docker\n  - fix export when a filename is given\n\n0.8.1\n  - remove docker files created during the installation process\n  - docker install\n    - hide db password input\n    - remove need of django installed in the host system\n    - check if zip is installed\n  - system install\n    - hide db password input\n    - add input informations\n  - Update conf.py :\n    - removed sphinx.ext.autodoc : removed sphinx.ext.autodoc and therefore the dependency of import django this will fix the rtfd build\n    - added author : added Camille Lavayssiere as an author\n    - removed sphinx_js : this will fix the rtfd build\n  - fix license classifier for pypi\n  - fix install log\n  - JS : hide loading state : if the value of the loading state is >= 100 we hide the loading state.\n  - HMI: set navbar as fixed : remove relative position for the top navbar, add body padding for the pages content\n  - add css class to widget content models\n  - cascade control item deletion on process flow diagram item\n  - migration for css class length\n  - allow to delete the process flow diagram background image\n  - fix process flow diagram item str : if related control item label is empty, return the process flow diagram id\n  - remove process flow diagram image padding top\n  - add template variable to replace the site name : default to PyScada\n  - Variable admin site rendering : add hideshow JS to Variable, load protocol variable fk_name, FormSet, fieldsets in the inlines\n  - widget offset : now the column parameter for a widget will be respected, if a widget is in column 1 (from 0 to 3) and there is no other widget on the same page and row the widget will be offsetted this allow respecting the widget positioning with different windows size in comparaison of adding an widget with an empty html content\n  - doc use main branch for install\n  - pyscada installation in venv\n  - add developer and plugin installation documentation\n  - added pyscada-ems to the list of device protocols\n  - changed plugins url handling : the url settings of pyscada plugins will be added to the global urls, config automaticly\n  - Fix device read task str without variable or variable property\n  - add email timeout for error log to admins\n  - add traceback to error logs\n  - dictionary append item function : allow to update the label or the value if exist, else create\n  - add hidden variable-config for process flow diagram items\n  - add h5py import error log\n  - JS : timestamp conversion : if string value is a float, convert to float\n  - allow to use dictionary for timestamp variable : if a value is in the dictionary it will not be converted to a datetime string\n  - remove log for calculated variable\n  - add info to recorded data log if it already exist\n  - fix getting objects for html : fix exclude list for many2many objects\n  - add class to change process flow diagram items color\n  - set bool false control input display value color to grey\n  - add 403 error template displaying the exception message\n  - display message error on view list : on view not found (permission) and multiple views (duplicate link_title views)\n  - add core urls exception : log only if pyscada plugin has an urls.py file\n  - add mysqlclient dependency\n  - fix python venv install : add venv path to PATH\n  - add scipy to requirement for the waveform generic handler\n  - missing python3-venv requirement for venv install\n  - install-venv : missing PATH for django startproject\n  - replace setuptools find_packages by find_namespace_packages\n  - fix doc for ems protocol\n  - add exclude field list for gen_hiddenConfigHtml\n  - fix install in venv (partially broken by black formatting commit) :\n    - add python3-venv dependency\n    - add write right for settings template file to add install context\n    - run django code using pyscada user and venv\n    - add scipy for generic waveform handler\n  - fixed page alignment problem in views : when the user clicked on the page link in a view a second time the page has been moved so that the top of the page is below the navbar, this was not intended, the page should be hirisontaly aligned at the bottom of the navbar.\n  - JS : add event to anounce control item color change :\n    - event is linked to the control item config2 element\n    - event name is : \"changePyScadaControlItemColor_\" + control_item_id\n    - event.detail is the color formatted as #45bc65\n  - fix page anchor position to include navbar padding and border\n  - group display permisson :\n    - Fix for chart, custom html panel, pie, process flow diagram.\n    - Make the list of objects permissions more generic to allow any WidgetContentModel to be added.\n  - update tablesorter :\n    - to version 2.31.3 : https://github.com/Mottie/tablesorter\n    - fix sorting non working for unit\n    - remove unused logos\n    - create option column\n  - add LOGOUT_REDIRECT_URL : redirect to site home rather than django admin logout page after logout\n  - pip3 install user : use pyscada user to run install with pip\n  - calendar first day : set first calendar day to monday\n  - fix setting text for boolean value : check if the span field with class .boolean-value exist before writting the value for svg process flow diagram item the method is different so the span does not exist, todo : replace value by string for process diagram items\n  - aggregation type : check if '#aggregation-type-all-select-' + widget_id exist before adding new option\n  - Fix day name order and let first day to monday:\n    - The daysOfWeek order for DateTimePicker should start on sunday, if not the days are not well labeled. Then the firstDay select the first day to start weeks in the calendar\n    - The first day is set to monday untill we find how to get the browser locale and use the first week day for that country\n  - fix measurement_data_dumps location : set measurement_data_dumps directory in pyscada home during the venv installation use that folder in default settings.py\n  - replace save method by get_or_create for dictionary items : don't create it if it already exist\n  - add JS variable to for on before reload : if ONBEFORERELOAD_ASK is true, ask the user before reload o leaving the page, else don't ask\n  - fix query first value when no data found in time range : query_first_value in RecordedDataValueManager.db_data find last element from time=now when no data was found for a variable in the time range specified if data was found, time=time of first value\n  - fix the 1st diagram tooltip (was hidden by overflow)\n  - dispatch changePyScadaControlItemColor event on window : simplify the event catching\n  - load hidden config2 after view html load : to load the view quickly load the hidden config2 after the loading the view html code\n  - JS: load config2 before the pyscada hmi init then send event : Load config2, Init the pyscada hmi, send PyScadaCoreJSLoaded for plugins\n  - add loading page with svg icon : can be replaced by the loading_page block, default icon color (000, black) can be change with svg_loading_color, for example: svg_loading_color=\"00f\"\n  - kill setTimeout and xhr request on page unload : store all timeouts in the PYSCADA_TIMEOUTS dictionary, store the current xhr blocking request in PYSCADA_XHR variable\n  - JS: add PyScada HMI at the beginning of each console.log\n  - add an event for a variable when data change to allow plugin do a custom action\n\n0.8.2\n  - prevent loading and showing data after DATA_TO_TIMESTAMP: the bug was that the timestamp_to for the data handler ajax query could be higher than DATA_TO_TIMESTAMP and then change the datepicker end value after the ajax request finish\n  - fix generic device init import\n  - last_element_min_time: set to 0 if no min time set\n  - add transform data and template for control item display value: allow to control the data (transform data function) and graphical representation (template) of a control item set to display value\n  - add sliceDATAusingTimestamps JS function to get DATA for a variable key using the daterangepicker and the timeline slinder\n  - add \"Template not found\" template\n  - fix difference percent period calculation\n  - add readthedocs config file: from : https://docs.readthedocs.io/en/stable/config-file/\n  - update docs\n  - add operations and aggregation protocols\n  - migration: update Chart WidgetContent if exist\n  - plugin can add apps to INSTALLED_APPS in settings: a plugin can specify additional apps to add to INSTALLED_APPS\n    - in the __init__.py file of the plugin (in pyscada/pluginName) add a list named : additional_installed_app = [\"pyscada.otherAppX\", \"pyscada.otherAppY\"]\n    - exemple in the pyscada-operations plugin.\n  - get_objects_for_html check if field.name is attr of obj\n  - add DataSourceModel, Datasource and DjangoDatabase\n    - DataSourceModel :\n      - Used to define a data source type.\n      - The data source base model have a foreign key to this model to specify the configuration :\n        - the name,\n        - can add, modify on select in the admin panel,\n        - the model name of the inline having the specific config (fields, functions, manager).\n    - DataSource :\n      - The base model for all the data sources.\n      - A data source needs to inherit from this class, and should have the basic functions :\n        - last_value,\n        - read_multiple,\n        - write_multiple,\n        - get_first_element_timestamp,\n        - get_last_element_timestamp.\n    - DjangoDatabase :\n      - Specify a table to store the values. The table model should have a manager similar to the RecordedDataManager (functions).\n    - The default data source added is the RecordedData table.\n    - To add new data source, look at the example in pyscada-operations.\n  - add VariableManager, rename and update RecordedDataManager\n    - To switch to the data source architecture, use the VariableManager function in order to :\n      - filter Variable list by datasource (_get_variables_by_datasource)\n      - read values from datasources (read_multiple)\n      - write values to datasources (write_multiple)\n      - get first timestamp recorded for a variable list (get_first_element_timestamp)\n      - get last one (get_last_element_timestamp)\n    - Plugins and handlers should not use directly the RecordedDataManager but the VariableManager in order to talk to differents data sources.\n    - Look at the new way to save read data from GenericDevice and GenericDeviceHandler in pyscada/device.py and in the pyscada.operations plugin.\n  - Delete CalculatedVariable (moved to the pyscada.operations plugin) in order to not loose your calculated variables you should install pyscada.operations plugin before running the pyscada migration 108.\n  - log ProgrammingError and OperationalError while populating models\n\n0.8.3\n  - add view timedelta option to change de default time delta\n  - add control item offset option: to choose how to display or not data before the time range\n  - generic function to populate inlines for device, variable, variablestate\n  -  RecordedData: store uint64 as int64 shifted - unknown class as float: uint64 are shifted by -9223372036854775808 = 2**63 to be stored as a django BigIntegerField\n  - handler: add erase cache option in read data all: in order to call various times read data all in a handler, allow to not erase the cached results\n  - upgrade flot to 4.2.6\n  - limit legend max height to the chart height\n  - fixes and other small updates\n\n0.9.0\n  - fix glyficon alert and excalamation sign\n  - fix append dictionary item if multiple items : keep the first item and delete the others\n  - device handler found and content : show if handlers are found in the handler list, show the content of a handler or the pyscada_admin_content variable content if it exists in the handler file\n  - JS: check if moment exist before daterangepicker init\n  - added option to set a custom message for processes : run by the background-process scheduler, this is e.g. useful for kepping track of the state of single shot tasks.\n  - JS: update control-item type-numeric if no color_only option\n  - add view object to the template context\n  - use fetch api for data handling (#181) : remove use of ajax for data handling, better handle timeout, deauthenticate, use JS function instead of jQuery\n  - fixed all values=0 in, filename is None error, h5 timestamp format data export :\n    - export jobs will be markt as faild now\n    - the min and max time check is removed because it only checks the first variable\n    - fixed unit mismatch timestamps from read_multiple in ms, export expected them in s\n    - added more detailted status output to backgroundtask model\n    - changed timestamp format for h5 output from matlab to unix timestamps\n    - escape of \"/\" in variable name to prevent dataset generation in h5 and mat files\n  - fix time max to find previous last element\n  - add app init after scheduler start : in order to allow plugin to run code at start and not use the AppConfig.ready function\n  - allow a plugin to add a cov notification : a plugin can define a function to custom send cov notification add it in AppConfig as: def pyscada_send_cov_notification(self, variable)\n  - JS: variable to disable default data handling : Use to provide other data handling in a plugin\n  - check the rights of users in a view to read or write data : any model which has a group display permission or any WidgetContentModel subclass model shoud have a data_objects function. This function return a dict of:\n    - variable or variable_property the user is allowed to read data from\n    - variable_write and variable_property_write the user is allowed to write to\n  - add the view id to requests (get_cache_data, write_task and write_property)\n  - upgrade to django >= 5.2\n  - JS: compare new and old color before updating color and sending event\n  - remove pkg_resources import and setuptools dependency : replaced by importlib.metadata, see https://setuptools.pypa.io/en/latest/pkg_resources.html\n  - add numpy requirement and remove hdf5-103 which is in hdf5-dev\n  - remove access to not visible view\n  - fix variable protocol save and delete : when saving a variable, the related variable protocol (modbusvariable for example) should be saved , even if the modbus variable config is empty. When changing the device of a vaiable, if the protocol change, (modbus to visa for example), the related modbus variable should be deleted and the visa variable should be created.\n  - add tests for read values : fix timestamp and date_saved_max to pass the tests\n  - add js tests to add fetched data : fix fetched data fucntion to pass the tests\n  - Add device and variable handler parameters\n  - Allow anonymous access : use the anonymous group display permission to configure and allow in the settings using PYSCADA_ALLOW_ANONYMOUS and PYSCADA_ALLOW_ANONYMOUS_WRITE\n  - home page is set in settings using PYSCADA_HOME\n  - added option to add links to external urls in the view-overview\n  - renamed write_multible, read_multible, last_value to write_datapoints, query_datapoints and last_datapoint to make the funktion more clear\n  - added last_datapoint, query_datapoints, write_datapoints, write_raw_datapoints to Variable model as a shortcut to Variable.objects.METHOD(variable=self)\n  - consolidated unit of timestamps internaly to s in python and ms in JS code\n  - general code cleanup\n  - removed unused filter_time and get_values_in_time_range method from RecordedDataManager\n  - removed unused get_first_element_timestamp from DjangoDatabase datasource\n  - moved DjangoDatasource to separate sub app -> Update of settings.py is nessesary, see update.rst\n  - migrated from setup.py to pyproject.toml\n  - updated device_protocol list to include MQTT and Influxdb plugins\n  - added new datasource to store values only in the cache\n  - added new datasource to store only the most recent value in the database\n\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include AUTHORS\ninclude LICENSE\ninclude README.rst\ninclude CHANGELOG.txt\nrecursive-include pyscada/templates *\nrecursive-include pyscada/fixtures *\nrecursive-include pyscada/hmi/fixtures *\nrecursive-include pyscada/hmi/templates *\nrecursive-include pyscada/hmi/static *\nrecursive-include pyscada/core/*\nrecursive-include pyscada/django_datasource/*\nrecursive-include docs *\nrecursive-exclude * *.pyc\nrecursive-exclude pyscada/report *\n"
  },
  {
    "path": "README.rst",
    "content": "PyScada a open source SCADA system\n==================================\n\nA Open Source SCADA System with HTML5 HMI, build using the Django framework. If you like to setup your own SCADA system head over to http://pyscada.rtfd.io.\n\nFeatures\n--------\n\n* HTML5 based HMI\n* Supports the following\n\n * industrial Protocols\n\n  * `Modbus <https://github.com/pyscada/PyScada-Modbus>`_ TCP/IP - RTU - ASCII - Binary (using `pyModbus <https://github.com/pymodbus-dev/pymodbus>`_)\n  * `Phant <https://github.com/pyscada/PyScada-Phant>`_ (see http://phant.io/)\n  * `VISA <https://github.com/pyscada/PyScada-VISA>`_ (using `pyVISA <https://pypi.python.org/pypi/PyVISA>`_)\n  * `1-Wire <https://github.com/pyscada/PyScada-OneWire>`_\n  * `BACNet/IP <https://github.com/pyscada/PyScada-BACNet>`_ (in development) (using `BACpypes <https://github.com/JoelBender/bacpypes>`_ and `BAC0 <https://github.com/ChristianTremblay/BAC0>`_)\n  * `MeterBus (MBus) <https://github.com/pyscada/PyScada-MeterBus>`_ (in development) (using `pyMeterBus <https://github.com/ganehag/pyMeterBus/>`_)\n  * `SMBus <https://github.com/pyscada/PyScada-SMBus>`_ (using `smbus2 <https://github.com/kplindegaard/smbus2>`_)\n  * `GPIO <https://github.com/pyscada/PyScada-GPIO>`_ (using `RPi.GPIO <https://pypi.org/project/RPi.GPIO/>`_)\n  * `SystemStat <https://github.com/pyscada/PyScada-SystemStat>`_\n  * `OPC-UA <https://github.com/clavay/PyScada-OPCUA>`_ (using `opcua-asyncio <https://github.com/FreeOpcUa/opcua-asyncio>`_)\n  * `SML (Smart Meter Language) <https://github.com/gkend/PyScada-SML>`_ (using `pySML <https://github.com/mtdcr/pysml>`_)\n  * `File read/write <https://github.com/pyscada/PyScada-File>`_\n  * `Serial <https://github.com/clavay/PyScada-Serial>`_\n  * `WebService <https://github.com/clavay/PyScada-WebService>`_\n\n * devices\n\n  * Generic dummy device\n  * `PT104 <https://github.com/pyscada/PyScada-PT104>`_ (using `Pico PT-104 <https://www.picotech.com/data-logger/pt-104/high-accuracy-temperature-daq>`_)\n\n * scripts\n\n  * `Scripting <https://github.com/pyscada/PyScada-Scripting>`_\n\n * system tools\n\n  * `EMS <https://github.com/pyscada/PyScada-EMS>`_\n\n * event management, data export, mail notification\n\n* very low Hardware requirements for the server\n\nStructure\n---------\n\n.. image:: https://github.com/pyscada/PyScada/raw/master/docs/pic/PyScada_module_overview.png\n    :width: 600px\n\nDependencies\n------------\n\n- core/HMI\n\t* python>=3.8\n\t* django==4.2\n\t* numpy>=1.6.0\n\t* pillow\n\t* python-daemon\n\nWhat is Working\n---------------\n\n - Modbus TCP/RTU/BIN\n - Visa (at least for the devices in the visa/devices folder)\n - Systemstat\n - OneWire (only DS18B20)\n - phant (no known issues)\n - smbus (at least for the devices in the smbus/devices folder)\n - gpio (at least for the raspberry pi)\n - webservice (json and xml parsing)\n - systemstat\n - scripting\n - event (no known issues)\n - export (no known issues)\n - hmi (no known issues)\n\nWhat is not Working/Missing\n---------------------------\n\n - Documentation\n - SysV init daemon handling\n - BACNet (due to the lack of hardware to test)\n - OPC-UA (need more tests)\n - MeterBus (need more tests)\n\n\nInstallation\n------------\n\nDetailed installation instruction can be found at: http://pyscada.rtfd.io .\n\n\nContribute\n----------\n\n - Issue Tracker: https://github.com/pyscada/PyScada/issues\n - Source Code: https://github.com/pyscada/PyScada\n\n\nLicense\n-------\n\nThe project is licensed under the _GNU Affero General Public License v3 (AGPLv3).\n"
  },
  {
    "path": "docker/docker-compose.yml-tmp",
    "content": "version: '3'\nservices:\n  pyscada:\n    build: pyscada\n    container_name: pyscada\n    #volumes:\n    tty: true\n    depends_on:\n      - db\n    volumes:\n      - \"http:/src/pyscada/http\"\n      - \"sock:/src/pyscada/tmp\"\n\n  nginx:\n    image: nginx:latest\n    build: nginx\n    container_name: nginx\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - \"http:/var/www/http\"\n      - \"sock:/tmp\"\n    depends_on:\n      - pyscada\n\n  db:\n    image: mysql\n    container_name: mysql\n    restart: always\n    environment:\n      MYSQL_RANDOM_ROOT_PASSWORD: 'yes'\n      MYSQL_DATABASE: 'PyScada_db'\n      MYSQL_USER: 'PyScada-user'\n      MYSQL_PASSWORD: 'PyScada-user-password'\n    volumes:\n      - dbdata:/var/lib/mysql\nvolumes:\n  http:\n  sock:\n  dbdata:\n"
  },
  {
    "path": "docker/mysql/Dockerfile-tmp",
    "content": "\n## Pull the mysql:5.6 image\nFROM mysql:latest\n\n## The maintainer name and email\nLABEL maintainer=\"PyScada | Martin Schröder <info@martin-schroeder.net\"\n\n# Install requirement (wget)\n#RUN apt-get update && apt-get install -y wget\n\nRUN mysql -e \"CREATE DATABASE PyScada_db CHARACTER SET utf8;GRANT ALL PRIVILEGES ON PyScada_db.* TO 'PyScada-user'@'localhost' IDENTIFIED BY 'PyScada-user-password';\"\n"
  },
  {
    "path": "docker/nginx/Dockerfile",
    "content": "FROM nginx:latest\n\nLABEL maintainer=\"PyScada | Martin Schröder <info@martin-schroeder.net\"\n\nCOPY nginx.conf /etc/nginx/\nCOPY ssl/pyscada_server.crt /etc/nginx/ssl/\nCOPY ssl/pyscada_server.key /etc/nginx/ssl/\n#RUN ln -s /etc/nginx/sites-available/nginx.conf /etc/nginx/sites-enabled\nEXPOSE 80\nEXPOSE 443\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n"
  },
  {
    "path": "docker/nginx/nginx.conf",
    "content": "worker_processes auto;\npid /run/nginx.pid;\ninclude /etc/nginx/modules-enabled/*.conf;\n\nevents {\n        worker_connections 768;\n        # multi_accept on;\n}\n\nhttp {\n\n        ##\n        # Basic Settings\n        ##\n\n        sendfile on;\n        tcp_nopush on;\n        tcp_nodelay on;\n        keepalive_timeout 65;\n        types_hash_max_size 2048;\n        # server_tokens off;\n\n        # server_names_hash_bucket_size 64;\n        # server_name_in_redirect off;\n\n        include /etc/nginx/mime.types;\n        default_type application/octet-stream;\n        ##\n        # SSL Settings\n        ##\n\n        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE\n        ssl_prefer_server_ciphers on;\n\n        ##\n        # Logging Settings\n        ##\n\n        access_log /var/log/nginx/access.log;\n        error_log /var/log/nginx/error.log;\n\n        ##\n        # Gzip Settings\n        ##\n        gzip on;\n\n        # pyscada.conf\n\n        # the upstream component nginx needs to connect to\n        upstream app_server {\n                server unix:/tmp/gunicorn.sock fail_timeout=0; # for a file socket\n        }\n\n        # configuration of the server\n        #server {\n        #    listen      80;\n        #    listen   [::]:80;\n        #    server_name _;          # substitute your machine's IP address or FQDN\n        #    return 301 https://$host$request_uri;\n        #}\n\n        server {\n                listen      80 default_server;\n                listen   [::]:80;\n                listen   443 ssl;\n                listen [::]:443 ssl;\n\n                server_name _;          # substitute your machine's IP address or FQDN\n\n                charset utf-8;\n                keepalive_timeout 5;\n                client_max_body_size 75M;   # max upload size, adjust to taste\n                # please comment if https is not used\n                ssl_certificate     /etc/nginx/ssl/pyscada_server.crt; # The certificate file\n                ssl_certificate_key /etc/nginx/ssl/pyscada_server.key; # The private key file\n\n                # Django media\n                location /media  {\n                        alias /var/www/http/media;  # your Django project's media files - amend as required\n                }\n\n                location /static {\n                        alias /var/www/http/static; # your Django project's static files - amend as required\n                }\n\n                location /measurement {\n                        alias /var/www/http/measurement_data_dumps; # to support download of measurement files via admin backend - amend as required\n                }\n\n                location / {\n                    # checks for static file, if not found proxy to app\n                    try_files $uri @proxy_to_app;\n                }\n\n                location @proxy_to_app {\n                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                    proxy_set_header Host $http_host;\n                    proxy_redirect off;\n                    proxy_pass   http://app_server;\n                }\n        }\n}"
  },
  {
    "path": "docker/pyscada/Dockerfile",
    "content": "FROM python:latest\nLABEL maintainer=\"PyScada | Martin Schröder\"\nENV DEBIAN_FRONTEND noninteractive\n\n#RUN apt-get -y update && \\\n#    apt-get -y upgrade && \\\n#    apt-get -y install python3-mysqldb libmysqlclient-dev && \\\n#    apt-get purge && \\\n#    apt-get clean && \\\n#    rm -rf /var/lib/apt/lists/*\n\nENV LANG C.UTF-8\n\nCOPY project_template.zip /src/pyscada/\nCOPY pyscada.zip /src/pyscada/\nCOPY pyscada /src/pyscada/\nCOPY pyscada_init /src/pyscada/\nRUN touch /src/pyscada/pyscada_debug.log\nWORKDIR /src/pyscada\n\nRUN pip3 install gunicorn\nRUN pip3 install mysqlclient\nRUN pip3 install /src/pyscada/pyscada.zip\nRUN django-admin startproject PyScadaServer /src/pyscada/ --template /src/pyscada/project_template.zip\nRUN chmod +x /src/pyscada/pyscada\nRUN chmod +x /src/pyscada/pyscada_init\nCMD [\"/src/pyscada/pyscada\"]\n"
  },
  {
    "path": "docker/pyscada/pyscada",
    "content": "#!/usr/bin/env sh\necho \"Wait for MySQL to Start\"\nwhile ! wget --quiet db:3306; do\n    sleep 1\ndone\necho \"Start PyScada\"\npython3 /src/pyscada/manage.py pyscada_daemon start\n#python3 /src/pyscada/manage.py runserver \"0.0.0.0:8000\" --insecure\necho \"Start Gunicorn\"\n/usr/local/bin/gunicorn -b unix:/src/pyscada/tmp/gunicorn.sock -n PyScada -w 8 -- PyScadaServer.wsgi:application"
  },
  {
    "path": "docker/pyscada/pyscada_init",
    "content": "#!/usr/bin/env sh\nwhile ! wget --quiet db:3306; do\n    sleep 1\ndone\npython3 /src/pyscada/manage.py migrate\npython3 /src/pyscada/manage.py collectstatic --no-input\npython3 /src/pyscada/manage.py loaddata color\npython3 /src/pyscada/manage.py loaddata units\npython3 /src/pyscada/manage.py pyscada_daemon init"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nSPHINXPROJ    = PyScada\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)"
  },
  {
    "path": "docs/backend.rst",
    "content": "Using the Backend\n=================\n\nTo use the backend open the HMI in your browser by opening http://127.0.0.1\n(replace 127.0.0.1 withe the IP or hostname of your PyScada server) and sign in\nwith your admin account defined during the :doc:`installation process <../quick_install>`\n(*TODO link to createsuperuser* doc).\n\n.. image:: pic/frontend_login.png\n\nAfter successful login in your see the view overview, to open the admin panel\nclick on your username in the upper right corner and on *Admin*.\n\n.. image:: pic/frontend_view_overview_admin_empty.png\n\nNow you are in the backend or Admin panel.\n\n.. image:: pic/backend_overview.png\n\n\nAdd a new Device\n----------------\n\nTo add a new device (e.g. a PLC) open the *Device* Table in the *PyScada Core*\nsection.\n\n.. image:: pic/backend_core_add_device.png\n\nYou will see a empty list. Click on *add device* in the upper right\ncorner to add a new device (e.g. a modbus device).\n\n.. image:: pic/backend_save_device.png\n\n* Enter a name and a description.\n* Choose a pooling interval (time between two variable value read).\n* Choose a protocol.\n* Enter the necessary informations for that protocol.\n\n(*TODO device protocol setup*)\n\n.. image:: pic/backend_save_modbus_device.png\n\nAdd a new Variable\n------------------\n\nEnter to *Variables* table in the *PyScada Core* section of the admin panel.\nClick on *add variable* in the upper right corner.\n\n.. image:: pic/backend_core_add_variable.png\n\nA Variable has a name and a description, assign the Variable to a Device and\nselect a Unit of measurement (*TODO* add description off adding a new unit),\nactivate writable if the value should be changed from the HMI, if the value has\nto be scaled in order to be displayed right select the right scaling\n(*TODO* add description for adding a scaling).\n\nThe *value_class* is the data type in witch the value is represented on the Device\n(*TODO* add example).\n\nThe *Change Of Value (COV)* is the amount of change of the value to be stored in\nthe database. It will store new values for that variable if :\n\n.. math::\n\n\t| new\\_value - last\\_value | > COV\\_value\n\nor if the last value is older than 1 hour.\n\nSo if you want to save all the values, set the COV to ``-1``.\n\n\n.. image:: pic/backend_core_save_variable.png\n\nEnter the necessary informations for the variable depending of the device protocol.\n\n\nShort instructions to build the user HMI (frontend)\n---------------------------------------------------\n\nIn the backend HMI section:\n\n1. Charts, add a new Chart\n2. Page, add a Page\n3. Widget, add a Widget, select under Page the page you added in 2. and under `Content` the Chart from 1.\n\tA widget controls the position of every element on a Page.\n\tSet the position (row, column) and the width of the widget.\n4. View, add a View and select the page from 2.\n5. (optional) GroupDisplayPermissions, add a new GroupDisplayPermission, (if nessesary add a new Group and add your User to that Group, select all items you created in 1. to 4.)\n6. open http://IP/, you should see the new View, if the DAQ is running and there is Data already in the DB, you should see the last 2 Hours of data and the current Data.\n\n\nThe frontend structure :\n\n::\n\n\n\t+-View------------------------------------+\n\t|                                         |\n\t| +-Page--------------------------------+ |\n\t| |                                     | |\n\t| | +-Widget--------+ +-Widget--------+ | |\n\t| | |               | |               | | |\n\t| | | Row 1, Col 1  | | Row 1, Col 2  | | |\n\t| | | Width 1/2     | | Width 1/2     | | |\n\t| | | +-Chart-----+ | | +-Chart-----+ | | |\n\t| | | |           | | | |           | | | |\n\t| | | +-----------+ | | +---------- + | | |\n\t| | +---------------+ +---------------+ | |\n\t| +-------------------------------------+ |\n\t+-----------------------------------------+\n\n\n\n\nAdd a new View\n--------------\n\nto be continued...\n\n\nAdd a new Page\n--------------\n\nto be continued...\n\n\nAdd a new Chart\n---------------\n\nto be continued...\n\nAdd a new Control Panel\n-----------------------\n\nto be continued...\n"
  },
  {
    "path": "docs/command-line.rst",
    "content": "Command-line\n============\n\nRestart the PyScada Daemons\n---------------------------\n\nsystemd:\n\n.. code-block:: shell\n\n\tsudo systemctl restart pyscada\n\nRestart Gunicorn\n----------------\n\nsystemd:\n\n.. code-block:: shell\n\n\tsudo systemctl restart gunicorn.service\n\nRestart NGINX\n-------------\n\nsystemd:\n\n.. code-block:: shell\n\n\tsudo systemctl restart nginx\n\nGet Installed PyScada Version\n-----------------------------\n\n.. code-block:: shell\n\n\tcd /var/www/pyscada/PyScadaServer\n\tsudo -u pyscada python3 manage.py shell\n\timport pyscada\n\tpyscada.core.__version__\n\texit()\n\n\nExport Recorded Data Tables\n---------------------------\n\n.. code-block:: shell\n\n\tsudo -u pyscada python3 manage.py PyScadaExportData # last 24 houres\n\tsudo -u pyscada python3 manage.py PyScadaExportData --start_time \"01-03-2015 00:00:00\" # from 01. of March 2015 until now\n\t# from 01. of March until now, with the given filename\n\tsudo -u pyscada python3 manage.py PyScadaExportData --start_time \"01-Mar-2015 00:00:00\" --filename \"filename.h5\"\n\t# from 01. of March until 10. of March, with the given filename\n\tsudo -u pyscada python3 manage.py PyScadaExportData --start_time \"01-03-2015 00:00:00\" --filename \"filename.h5\" --stop_time \"10-03-2015 00:00:00\"\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Configuration file for the Sphinx documentation builder.\n#\n# This file does only contain a selection of the most common options. For a\n# full list see the documentation:\n# http://www.sphinx-doc.org/en/master/config\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\n# import os\n# import sys\n# sys.path.insert(0, os.path.abspath('.'))\n\n\n# -- Project information -----------------------------------------------------\n\nproject = \"PyScada\"\ncopyright = \"2023, Martin Schröder, Camille Lavayssiere\"\nauthor = \"Martin Schröder, Camille Lavayssiere\"\n\n# The short X.Y version\nversion = \"\"\n# The full version, including alpha/beta/rc tags\nrelease = \"0.8.3\"\n\n\n# -- General configuration ---------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = []\n\n# Set JavaScript source paths\njs_source_path = \"../pyscada/hmi/static/pyscada/js/pyscada\"\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\n# source_suffix = ['.rst', '.md']\nsource_suffix = \".rst\"\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = \"en\"\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path .\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = \"default\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\n# html_theme_options = {}\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\n# html_static_path = ['_static']\n\n# Custom sidebar templates, must be a dictionary that maps document names\n# to template names.\n#\n# The default sidebars (for documents that don't match any pattern) are\n# defined by theme itself.  Builtin themes are using these templates by\n# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',\n# 'searchbox.html']``.\n#\n# html_sidebars = {}\n\n\n# -- Options for HTMLHelp output ---------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"PyScadadoc\"\n\n\n# -- Options for LaTeX output ------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #\n    # 'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #\n    # 'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #\n    # 'preamble': '',\n    # Latex figure (float) alignment\n    #\n    # 'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (master_doc, \"PyScada.tex\", \"PyScada Documentation\", \"Martin Schröder\", \"manual\"),\n]\n\n\n# -- Options for manual page output ------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(master_doc, \"pyscada\", \"PyScada Documentation\", [author], 1)]\n\n\n# -- Options for Texinfo output ----------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        master_doc,\n        \"PyScada\",\n        \"PyScada Documentation\",\n        author,\n        \"PyScada\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    ),\n]\n\nhtml_context = {\n    \"display_github\": True,  # Integrate GitHub\n    \"github_user\": \"pyscada\",  # Username\n    \"github_repo\": \"PyScada\",  # Repo name\n    \"github_version\": \"master\",  # Version\n    \"conf_py_path\": \"/docs/\",  # Path in the checkout to the docs root\n}\n"
  },
  {
    "path": "docs/control_item.rst",
    "content": "Control item\n============\n\nControl items are elements of crontol panels which have two functions by default:\n\n- display the last value of a variable,\n- control the value of a variable.\n\nTo create one in the administration interface, you need at least to:\n\n- enter a label,\n- select a variable OR a variable property.\n- chose a type : display the value or control the value of the variable/variable property,\n\nYou can also:\n\n- select the order in the control panel using the position attribute : lower is at the top of the control panel,\n- add options using display value options or control element options.\n\nDisplay value options\n---------------------\n\nIt allows adding options to a control item configured to display value.\n\nTo create one in the administration interface, you need at least to:\n\n- enter a title,\n- choose a template to change the graphic rendering,\n- choose if you want to replace a timestamp value by a human readable format,\n- transform the data before show it in the user interface: see section below,\n- apply color levels: see section below.\n\n### Transform data\n\n#### Usage (configuration)\n\nYou can use a data transformation to pass the data through a function before displaying it (for example, display the minimum of the variable in the selected time range).\n\nYou may need to specify additional information at the bottom depending on the tranformation needs (as for the Count Value transformation).\n\n#### Creation (developer)\n\nA plugin can add a new transform data to the list.\n\nTo do so you can create them automatically in the *ready* function of the *AppConfig* class in *apps.py*.\nHave a look at the [*hmi.apps.py*](https://github.com/pyscada/PyScada/blob/main/pyscada/hmi/apps.py).\n\nThe fields of a transform data are :\n- inline_model_name : the model name to add an inline to the admin page which can add additional fields needed by the transform data function (as TransformDataCountValue for the Count Value function),\n- short_name : the name displayed in the admin interface,\n- js_function_name : the name of the JavaScript function which will be called to transform the data,\n- js_files : a coma separated list of the JavaScript files to add,\n- css_files : a coma separated list of the CSS files to add,\n- need_historical_data : set to True if the transform data function needs the variable data for the whole period selected by the date time range picker, set to False if it only needs the last value.\n\n### Template\n\nYou can choose a specific template to display you control item.\n\n#### Creation (developer)\n\nA plugin can add a new control item template.\n\nTo do so you can create them automatically in the *ready* function of the *AppConfig* class in *apps.py*.\nHave a look at the [*hmi.apps.py*](https://github.com/pyscada/PyScada/blob/main/pyscada/hmi/apps.py).\n\nThe fields of a template are :\n- label the template name to display,\n- template_name : the file name to use,\n- js_files : a coma separated list of the JavaScript files to add,\n- css_files : a coma separated list of the CSS files to add.\n"
  },
  {
    "path": "docs/develop.rst",
    "content": ".. IMPORTANT::\n    To use PyScada in developer mode, you should install it using the `pip editable mode (-e) <https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-e>`_\n\nFor developers\n==============\n\nActivate PyScada virtual environment\n------------------------------------\n\n.. code-block:: shell\n\n    source /home/pyscada/.venv/bin/activate\n\n\nCloning the repository\n----------------------\n\n.. code-block:: shell\n\n    git clone git@github.com:pyscada/PyScada.git\n\nFor a plugin like PyScada-Modbus :\n\n.. code-block:: shell\n\n    git clone git@github.com:pyscada/PyScada-Modbus.git\n\n\nPip editable installation\n-------------------------\n\nAfter activating the virtual environment :\n\n.. code-block:: shell\n\n    sudo -u pyscada -E env PATH=${PATH} pip3 install -e ./PyScada\n\nFor a plugin like PyScada-Modbus :\n\n.. code-block:: shell\n\n    sudo -u pyscada -E env PATH=${PATH} pip3 install -e ./PyScada-Modbus\n\n\nRestarting the application\n--------------------------\n\nAfter activating the virtual environment, to apply you changes, depending on them, may need to :\n\ncreate migrations\n\n.. code-block:: shell\n\n    python3 /var/www/pyscada/PyScadaServer/manage.py makemigrations\n\napply them\n\n.. code-block:: shell\n\n    python3 /var/www/pyscada/PyScadaServer/manage.py migrate\n\ncopy static files (answer yes).\n\n.. code-block:: shell\n\n    sudo -u pyscada -E env PATH=${PATH} python3 /var/www/pyscada/PyScadaServer/manage.py collectstatic\n\nThen you can :\n\nFor urls, views or admin changes, restart gunicorn.\n\n.. code-block:: shell\n\n    sudo systemctl restart gunicorn\n\nOtherwise restart PyScada.\n\n.. code-block:: shell\n\n    sudo systemctl restart pyscada\n\n\nOverride routes\n----------------\n\nThis use case is encountered when you wish to rewrite an existing view (and therefore an existing route).\n\nThe PyScada project's ``urls.py`` file is used to load the software's routes (see `here <https://docs.djangoproject.com/en/4.2/topics/http/urls/>`_).\n\n* python virtual environment installation: located in ``/var/www/pyscada/PyScadaServer/PyScadaServer``\n* Docker installation: located in ``/src/pyscada/PyScadaServer/PyScadaServer``\n\n\nBy default, the project's ``urls.py`` file loads only the ``urls.py`` file from ``pyscada.core``. The ``pyscada.core.urls`` file loads all the other modules ``urls.py`` files in random order.\n\nThe route used is the first valid one encountered, so if you want to replace an existing route, you have to load your route before the others, i.e. before loading ``pyscada.core.urls`` file.\n\nTo do this, you need to modify your project's ``urls.py`` file.\n\nFor a non-docker installation :\n\n.. code-block:: shell\n\n    sudo -u pyscada nano /var/www/pyscada/PyScadaServer/PyScadaServer/urls.py\n\n\nAnd include your route before pyscada.core.urls\n\n.. code-block:: shell\n\n    urlpatterns = [\n    path('', include('pyscada.yourPlugin.urls')), #Routing file yourPlugin\n    path('', include('pyscada.core.urls')),\n    ]\n\n\n"
  },
  {
    "path": "docs/device_protocol.rst",
    "content": "Device Protocol IDs\n--------------------\n\n\n- 1: Reserved (Scheduler)\n- 2: `SystemStat <https://github.com/pyscada/PyScada-SystemStat>`_\n- 3: `Modbus <https://github.com/pyscada/PyScada-Modbus>`_\n- 4: `BACNet <https://github.com/pyscada/PyScada-BACNet>`_\n- 5: `VISA <https://github.com/pyscada/PyScada-VISA>`_\n- 6: `1-Wire <https://github.com/pyscada/PyScada-OneWire>`_\n- 7: `Phant <https://github.com/pyscada/PyScada-Phant>`_\n- 8: `SMBus <https://github.com/pyscada/PyScada-SMBus>`_\n- 9: Reserved (Jofra350)\n- 10: `GPIO <https://github.com/pyscada/PyScada-GPIO>`_\n- 11: `Reserved (PT104) <https://github.com/pyscada/PyScada-PT104>`_\n- 12: `OPC-UA <https://github.com/clavay/PyScada-OPCUA>`_\n- 13: `SML (Smart Meter Language) <https://github.com/gkend/PyScada-SML>`_\n- 14: `File <https://github.com/pyscada/PyScada-File>`_\n- 15: `MeterBus (MBus) <https://github.com/pyscada/PyScada-MeterBus>`_\n- 16: Generic dummy device\n- 17: `EMS <https://github.com/pyscada/PyScada-EMS>`_\n- 18: `Operations <https://github.com/pyscada/PyScada-Operations>`_\n- 19: `Aggregation <https://github.com/pyscada/PyScada-Operations>`_\n- 20: `MQTT <https://github.com/pyscada/PyScada-MQTT>`_\n- 21: `Influxdb-Datasource <https://github.com/pyscada/PyScada-Influxdb>`_\n- 8X: Custom Worker\n- 93 `Reserved (Serial) <https://github.com/clavay/PyScada-Serial>`_\n- 94 `Reserved (WebService) <https://github.com/clavay/PyScada-WebService>`_\n- 95: `Reserved (Scripting) <https://github.com/pyscada/PyScada-Scripting>`_\n- 96: Reserved (Event)\n- 97: Reserved (Mail)\n- 98: Reserved (Report)\n- 99: reserved (Export)\n- 100+: reserved for dynamic\n"
  },
  {
    "path": "docs/django_settings.rst",
    "content": "Django Settings\n===============\n\n\nurls.py\n-------\n\n\nOpen the urls configuration file and add the necessary rewrite rule to the django URL dispatcher.\n\n::\n\n    nano /var/www/pyscada/PyScadaServer/PyScadaServer/urls.py\n\n\n::\n\n    ...\n    from django.conf.urls import url, include\n    from django.contrib import admin\n\n    urlpatterns = [\n        url(r'^admin/', admin.site.urls),\n        url(r'^', include('pyscada.core.urls')),\n    ]\n    ...\n\n\n\nsettings.py\n-----------\n\n\nOpen the django settings file and make the following modifications. See also the `django documentation <https://docs.djangoproject.com/en/1.8/ref/settings/>`_ for more Information.\n\n::\n\n    nano /var/www/pyscada/PyScadaServer/PyScadaServer/settings.py\n\n\nFirst deactivate the debugging, if debugging is active django will keep all SQL queries in the ram, the data-acquisition\nruns a massive amount of queries so your system will run fast out of memory. Keep in mind to restart gunicorn and the\npysada daemons after you change the debugging state.\n\n::\n\n    DEBUG = False\n\n\nAdd the host/domain of your machine, in this case every url that point to a ip of the machine is allowed.\n\n::\n\n    ALLOWED_HOSTS = ['*']\n\n\nAdd PyScada and the PyScada sub-apps to the installed apps list of Django.\n\n::\n\n    INSTALLED_APPS = [\n        ...\n        'pyscada',\n        'pyscada.modbus',\n        'pyscada.phant',\n        'pyscada.visa',\n        'pyscada.hmi',\n        'pyscada.systemstat',\n        'pyscada.export',\n        'pyscada.onewire',\n        'pyscada.smbus',\n    ]\n\nTo use the MySQL Database, fill in the database, the user and password as selected in the *create Database section*.\n\n::\n\n    DATABASES = {\n        'default': {\n            'ENGINE':   'django.db.backends.mysql',\n            'NAME':     'PyScada_db',\n            'USER':     'PyScada-user',\n            'PASSWORD': 'PyScada-user-password',\n            'OPTIONS': {\n                'init_command': \"SET sql_mode='STRICT_TRANS_TABLES'\",\n            }\n        }\n    }\n\n\nSet the static file and media dir as follows.\n\n::\n\n    ...\n    STATIC_URL = '/static/'\n\n    STATIC_ROOT = '/var/www/pyscada/http/static/'\n\n    MEDIA_URL = '/media/'\n\n    MEDIA_ROOT = '/var/www/pyscada/http/media/'\n\n\nAdd all PyScada specific settings, keep in mind to set the file right file encoding in the `settings.py` file header (see also https://www.python.org/dev/peps/pep-0263/).\n\n::\n\n    #!/usr/bin/python\n    # -*- coding: <encoding name> -*-\n\n\nAppend to the end of the `settings.py`:\n\n::\n\n    # PyScada settings\n    # https://github.com/trombastic/PyScada\n\n    # email settings\n    DEFAULT_FROM_EMAIL = 'example@host.com'\n    EMAIL_HOST = 'mail.host.com'\n    EMAIL_PORT = 587\n    EMAIL_HOST_USER = 'pyscada@host.com'\n    EMAIL_USE_TLS = True\n    EMAIL_USE_SSL = False\n    EMAIL_HOST_PASSWORD = 'password'\n    EMAIL_PREFIX = 'PREFIX' # Mail subject will be \"PREFIX subjecttext\"\n\n    # meta information's about the plant site\n    PYSCADA_META = {\n        'name':'A SHORT NAME',\n        'description':'A SHORT DESCRIPTION',\n    }\n\n    # export properties\n    #\n    PYSCADA_EXPORT = {\n        'file_prefix':'PREFIX_',\n        'output_folder':'~/measurement_data_dumps',\n    }\n\n    # View Options\n    #\n    LINK_TARGET = '_blank' # '_blank' for new tab or '_self' for opening it in the same window\n\n    LOGGING = {\n        'version': 1,\n        'disable_existing_loggers': False,\n        'formatters': {\n            'standard': {\n                'format' : \"[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s\",\n                'datefmt' : \"%d/%b/%Y %H:%M:%S\"\n            },\n        },\n        'handlers': {\n            'file': {\n                'level': 'DEBUG',\n                'class': 'logging.FileHandler',\n                'filename': BASE_DIR + '/pyscada_debug.log',\n                'formatter': 'standard',\n            },\n        },\n        'loggers': {\n            'django': {\n                'handlers': ['file'],\n                'level': 'INFO',\n                'propagate': True,\n            },\n            'pyscada': {\n                'handlers': ['file'],\n                'level': 'DEBUG',\n                'propagate': True,\n            },\n        },\n    }\n"
  },
  {
    "path": "docs/docker.rst",
    "content": ".. IMPORTANT::\n    This Version of PyScada is BETA software and may have serious bugs which may cause damage to your computer,\n    automation hardware and data. It is not intended for use in production systems! You use this Software on your own risk!\n\nDocker\n======\n\nThis guide covers the basic setup of PyScada with `Docker <https://www.docker.com/>`_ and `Docker Compose <https://docs.docker.com/compose/>`_.\n\nDownload the necessary files\n----------------------------\n\nFirst of all download the docker config files for building your images.\n\nUsing Git.\n\n::\n\n    git clone https://github.com/pyscada/PyScada.git\n    cd PyScada/docker\n\nUsing wget.\n\n::\n\n    wget -qO- -O tmp.zip https://github.com/pyscada/PyScada/archive/refs/heads/master.zip && unzip tmp.zip && rm tmp.zip\n    cd PyScada-master/docker\n\n\nGenerating SSL Certificates\n---------------------------\n\nGenerate ssl certificates for using ssl.\n\n::\n\n    mkdir nginx/ssl\n    cd nginx/ssl\n    openssl req -x509 -nodes -days 1780 -newkey rsa:2048 -keyout ./pyscada_server.key -out ./pyscada_server.crt\n\n\n\nBuild and Run the Image\n-----------------------\n\n\nBuild the PyScada Docker Image.\n\n::\n\n    sudo docker-compose build\n\nAfter the Images have been successfully build we need to initialize the Database and Create a superuser.\n\n::\n\n    sudo docker-compose run pyscada /src/pyscada/pyscada_init\n    sudo docker-compose run pyscada /src/pyscada/manage.py createsuperuser\n\nThe last step is to start the Container.\n\n::\n\n    sudo docker-compose up -d\n\nIf you have an error or a command is stuck, run :\n\n::\n\n    sudo docker-compose down\n    sudo docker system prune --force --volumes\n"
  },
  {
    "path": "docs/frontend.rst",
    "content": "Using the Front-End\n===================\n\nThe ``settings.py`` file is usually located in ``/var/www/pyscada/PyScadaServer/PyScadaServer/``.\n\nThe home page can be defined to be a specific view in the ``settings.py`` file using:\n\n  .. code-block::\n\n      PYSCADA_HOME = \"/view/TEST/\"\n\nAllowing anonymous access permission is defined in the ``settings.py`` file using:\n\n  .. code-block::\n\n      PYSCADA_ALLOW_ANONYMOUS = True\n      PYSCADA_ALLOW_ANONYMOUS_WRITE = True\n"
  },
  {
    "path": "docs/grafana.rst",
    "content": "Use Grafana\n===========\n\nMysql\n-----\n\nAdd user and give SELECT rights :\n\n.. code-block:: shell\n\n  sudo mysql -uroot -p -e \"GRANT SELECT ON PyScada_db.* TO 'Grafana-user'@'localhost' IDENTIFIED BY 'Grafana-user-password';\"\n\nNginx\n-----\n\nAdd in `/etc/nginx/nginx.conf` after ``http { ... }`` :\n\n::\n\n  include /etc/nginx/grafana-access.conf;\n\nCreate `/etc/nginx/grafana-access.conf` with :\n\n::\n\n  stream {\n    # MySQL server\n    server {\n      listen     3305;\n      proxy_pass 127.0.0.1:3306;\n      proxy_timeout 60s;\n      proxy_connect_timeout 30s;\n    }\n  }\n\nRestart Nginx :\n\n.. code-block:: shell\n\n  sudo systemctl restart nginx\n\nGrafana\n-------\n\nAdd MySQL datasource :\n\n- Host :\n\n  - Local : `/run/mysqld/mysqld.sock`\n  - Remote : SERVER_WITH_NGINX_IP:3305\n\n- Database : ``PyScada_db``\n- User : ``Grafana-user``\n- Password : ``Grafana-user-password``\n\nCreate a dashboard:\n\n- Or import the `example dashboard <https://github.com/pyscada/PyScada/blob/master/extras/Grafana-test-dashboard.json>`_.\n\n- Or for example, add theses variables : set ``refresh on dashboard load``, ``multi-value`` and ``all option`` :\n\n  - Add mysql datasource variable (type Datasource).\n  - Add variables with type query using ``$Datasource`` :\n\n    - Protocols : ``SELECT protocol AS __text, id AS __value FROM pyscada_deviceprotocol``\n    - Devices : ``SELECT d.short_name AS __text, d.id AS __value FROM pyscada_device d WHERE d.protocol_id IN (${Protocols}) AND d.active = 1``\n    - Units : ``SELECT u.unit AS __text, u.id AS __value FROM pyscada_unit u``\n    - Variables : ``SELECT v.name AS __text, v.id AS __value FROM pyscada_variable v WHERE v.device_id IN (${Devices}) AND v.unit_id IN (${Units}) AND v.active = 1``\n    - Time group (type Interval) : ``1s,10s,1m,10m,30m,1h,6h,12h,1d,7d,14d,30d,1M``\n    - Null as (type custom) : ``0, NULL, previous``\n\n - Example query :\n\n  .. code-block::\n\n    SELECT\n      $__timeGroupAlias(r.date_saved,$time_group),\n      avg(IFNULL(r.value_float64, 0.0) + IFNULL(r.value_int64, 0.0) + IFNULL(r.value_int32, 0.0) + IFNULL(r.value_int16, 0.0) + IFNULL(r.value_boolean, 0.0)),\n      v.name AS metric\n    FROM pyscada_recordeddata as r\n    JOIN pyscada_variable v ON r.variable_id = v.id\n    WHERE\n      $__timeFilter(r.date_saved) AND\n      r.variable_id IN (${Variables})\n    GROUP BY time, metric\n    ORDER BY $__timeGroup(r.date_saved,$time_group,$null_as)\n\nEmbed in pyscada HMI\n--------------------\n\nEdit Grafana config file:\n\n.. code-block:: shell\n\n  sudo nano /etc/grafana/grafana.ini\n\nFind and set :\n  - allow_embedding = true\n  - For localhost grafana : root_url = http://localhost:3000/grafana/\n\nFor localhost grafana add in `/etc/nginx/sites-enabled/pyscada.conf` :\n\n::\n\n  location /grafana/ {\n    proxy_pass http://127.0.0.1:3000/;\n  }\n\nRestart Grafana server:\n\n.. code-block:: shell\n\n  sudo systemctl restart grafana-server.service\n\nCreate a custom html panel with the code from a dashboard or a panel from sharing options in grafana\n\nOther\n-----\n\nuse ssl : http://www.turbogeek.co.uk/2020/09/30/grafana-how-to-configure-ssl-https-in-grafana/\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. PyScada documentation master file, created by\n   sphinx-quickstart on Mon May 27 13:14:28 2019.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\nWelcome to PyScada's documentation!\n===================================\n\nA Open Source SCADA System with HTML5 HMI, build using the Django framework. If you like to setup your own _SCADA_ system head over to the :doc:`installation` section.\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Installation and Commandline\n\n   quick_install\n   plugin_install\n   update\n   command-line\n   frontend\n   backend\n   device_protocol\n   grafana\n   develop\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/installation.rst",
    "content": "\n.. IMPORTANT::\n    This Version of PyScada is BETA software and may have serious bugs which may cause damage to your computer,\n    automation hardware and data. It is not intended for use in production systems! You use this Software on your own risk!\n\n\n\nInstallation\n============\n\nThis installation guide covers the installation of PyScada for `Debian 10/11 <https://www.debian.org/>`_ ,\n`Raspberry Pi OS <https://www.raspberrypi.com/software/>`_, `Fedora 22/23 <https://www.fedoraproject.org/>`_ based Linux systems\nusing `MySQL <https://www.mysql.com/>`_ / `MariaDB <https://mariadb.com/>`_ or `SQLite <https://www.sqlite.org/>`_ as Database,\n`Gunicorn <http://gunicorn.org/>`_ as WSGI HTTP Server and `nginx <http://nginx.org/>`_ as HTTP Server.\n\nAutomatic installation using a script\n-------------------------------------\n\nOn the Raspberry Pi with internet connection run :\n\n::\n\n    wget https://raw.githubusercontent.com/clavay/PyScada-Laborem/master/extras/install.sh -O install.sh\n    sudo chmod a+x install.sh\n    sudo ./install.sh\n\n\nDependencies\n------------\n\n.. js:autofunction:: toggle_timeline\n   :short-name:\n\n.. autofunction:: pyscada.models.Device.__str__\n\n\nDebian 9, Raspbian\n^^^^^^^^^^^^^^^^^^\n\n::\n\n    sudo -i\n    apt-get update\n    apt-get -y upgrade\n    # if you use MariaDB/MySQL as Database system (recommend)\n    apt-get -y install mariadb-server python3-mysqldb\n    apt-get install -y python3-pip libhdf5-103 libhdf5-dev python3-dev nginx\n\n    pip3 install gunicorn\n    pip3 install pyserial\n    pip3 install docutils\n\n\nmacOS\n^^^^^\n\n - `MySQL Server <https://www.mysql.de/>`\n - HDF5 TODO\t\n\n\n::\n\n        brew install python\n        export PATH=$PATH:/usr/local/mysql/bin\n        pip install MySQL-python\n\n\nall\n^^^^\n\n::\n\n    sudo -i\n    pip3 install https://github.com/pyscada/PyScada/archive/master.zip\n\n    # for VISA Protocol\n    pip3 install pyvisa pyvisa-py\n    # for 1Wire Protocol\n    apt-get install owfs #\n    pip3 install pyownet\n    # for smbus Protocol, install libffi-dev first!\n    apt-get install libffi-dev\n    pip3 install smbus-cffi\n\n\n\n\nAdd a new system-user for Pyscada (optional, recommend)\n-------------------------------------------------------\n\nAdd a dedicated user for the pyscada server instance and add a directory for `static`/`media` files.\n\n\nLinux\n^^^^^\n\n::\n\n    sudo -i\n    useradd -r pyscada\n    mkdir -p /var/www/pyscada/http\n    chown -R pyscada:pyscada /var/www/pyscada\n    mkdir -p /home/pyscada\n    chown -R pyscada:pyscada /home/pyscada\n\n\nmacOS\n^^^^^\n\n::\n\n    sudo -i\n    dscl . -create /Users/pyscada IsHidden 1\n    dscl . -create /Users/pyscada NFSHomeDirectory /Users/pyscada\n    LastID=`dscl . -list /Users UniqueID | awk '{print $2}' | sort -n | tail -1`\n    NextID=$((LastID + 1))\n    dscl . create /Users/pyscada UniqueID $NextID\n    dscl . create /Users/pyscada PrimaryGroupID 20\n    mkdir -p /var/www/pyscada/http\n    chown -R pyscada:staff /var/www/pyscada/\n\n\n\nCreate a MySql Database\n-----------------------\n\nCreate the Database and grand the nessesery permission. Replace `PyScada_db`, `PyScada-user` and `PyScada-user-password` as you like.\n\n::\n\n    mysql -uroot -p -e \"CREATE DATABASE PyScada_db CHARACTER SET utf8;GRANT ALL PRIVILEGES ON PyScada_db.* TO 'PyScada-user'@'localhost' IDENTIFIED BY 'PyScada-user-password';\"\n\n\n\nCreate a new Django Project\n---------------------------\n\n::\n\n    # Linux/OSX\n    cd /var/www/pyscada/\n    sudo -u pyscada django-admin startproject PyScadaServer\n\n\n\nsee :doc:`django_settings` for all necessary adjustments to the django settings.py and urls.py.\n\n\nInitialize Database And Copy Static Files\n-----------------------------------------\n\n::\n\n\n    cd /var/www/pyscada/PyScadaServer # linux\n    sudo -u pyscada python3 manage.py migrate\n    sudo -u pyscada python3 manage.py collectstatic\n\n    # load fixtures with default configuration for chart lin colors and units\n    sudo -u pyscada python3 manage.py loaddata color\n    sudo -u pyscada python3 manage.py loaddata units\n\n    # initialize the background service system of pyscada\n    sudo -u pyscada python3 manage.py pyscada_daemon init\n\n\n\nAdd a Admin User To Your Django Project\n---------------------------------------\n\n::\n\n    cd /var/www/pyscada/PyScadaServer\n    sudo -u pyscada python3 manage.py createsuperuser\n\n\nSetup the Webserver (nginx, gunicorn)\n-------------------------------------\n\n\n::\n\n\n    # debian\n    sudo wget https://raw.githubusercontent.com/pyscada/PyScada/master/extras/nginx_sample.conf -O /etc/nginx/sites-available/pyscada.conf\n\n    # Fedora\n    sudo wget https://raw.githubusercontent.com/pyscada/PyScada/master/extras/nginx_sample.conf -O /etc/nginx/conf.d/pyscada.conf\n\n\nafter editing, enable the configuration and restart nginx, optionally remove the default configuration\n\nto use ssl (https, recommend)\n-----------------------------\n\ngenerate ssl certificates.\n\n\n::\n\n        # for Debian, Ubuntu, Raspian\n        sudo mkdir /etc/nginx/ssl\n        # the certificate will be valid for 5 Years,\n        sudo openssl req -x509 -nodes -days 1780 -newkey rsa:2048 -keyout /etc/nginx/ssl/pyscada_server.key -out /etc/nginx/ssl/pyscada_server.crt\n\n::\n\n    # debian\n    sudo ln -s /etc/nginx/sites-available/pyscada.conf /etc/nginx/sites-enabled/\n    sudo rm /etc/nginx/sites-enabled/default\n\nnow it's time to [re]start nginx.\n\n::\n\n    # systemd (Debian 8, Fedora, Ubuntu > XX.XX)\n    sudo systemctl enable nginx.service # enable autostart on boot\n    sudo systemctl restart nginx\n\n    # SysV-Init (Debian 7, Ubuntu <= XX.XX, [Debian 8])\n    sudo service nginx restart\n\n\n\nfor Fedora you have to allow nginx to serve the static and media folder.\n\n::\n\n    sudo chcon -Rt httpd_sys_content_t /var/www/pyscada/http/\n\n\nadd gunicorn and pyscada unit files:\n\n::\n\n    # systemd\n    sudo wget https://raw.githubusercontent.com/pyscada/PyScada/master/extras/service/systemd/gunicorn.socket -O /etc/systemd/system/gunicorn.socket\n    sudo wget https://raw.githubusercontent.com/pyscada/PyScada/master/extras/service/systemd/gunicorn.service -O /etc/systemd/system/gunicorn.service\n    sudo wget https://raw.githubusercontent.com/pyscada/PyScada/master/extras/service/systemd/pyscada_daemon.service -O /etc/systemd/system/pyscada.service\n\n    # in some installations gunicorn is not at /usr/local/bin/gunicorn but at /usr/bin/gunicorn\n    # in this case you have to change the pat in the file /etc/systemd/system/gunicorn.service accordingly\n\n    # enable the services for autostart\n    sudo systemctl enable gunicorn\n    sudo systemctl start gunicorn\n    sudo systemctl enable pyscada\n\n\nStart PyScada\n-------------\n\n::\n\n    sudo systemctl start pyscada\n\n\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\nset BUILDDIR=_build\r\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\r\nset I18NSPHINXOPTS=%SPHINXOPTS% .\r\nif NOT \"%PAPER%\" == \"\" (\r\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\r\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\r\n)\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\nif \"%1\" == \"help\" (\r\n\t:help\r\n\techo.Please use `make ^<target^>` where ^<target^> is one of\r\n\techo.  html       to make standalone HTML files\r\n\techo.  dirhtml    to make HTML files named index.html in directories\r\n\techo.  singlehtml to make a single large HTML file\r\n\techo.  pickle     to make pickle files\r\n\techo.  json       to make JSON files\r\n\techo.  htmlhelp   to make HTML files and a HTML help project\r\n\techo.  qthelp     to make HTML files and a qthelp project\r\n\techo.  devhelp    to make HTML files and a Devhelp project\r\n\techo.  epub       to make an epub\r\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\r\n\techo.  text       to make text files\r\n\techo.  man        to make manual pages\r\n\techo.  texinfo    to make Texinfo files\r\n\techo.  gettext    to make PO message catalogs\r\n\techo.  changes    to make an overview over all changed/added/deprecated items\r\n\techo.  xml        to make Docutils-native XML files\r\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\r\n\techo.  linkcheck  to check all external links for integrity\r\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\r\n\techo.  coverage   to run coverage check of the documentation if enabled\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"clean\" (\r\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\r\n\tdel /q /s %BUILDDIR%\\*\r\n\tgoto end\r\n)\r\n\r\n\r\nREM Check if sphinx-build is available and fallback to Python version if any\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 goto sphinx_python\r\ngoto sphinx_ok\r\n\r\n:sphinx_python\r\n\r\nset SPHINXBUILD=python -m sphinx.__init__\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.http://sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\n:sphinx_ok\r\n\r\n\r\nif \"%1\" == \"html\" (\r\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"dirhtml\" (\r\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"singlehtml\" (\r\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pickle\" (\r\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the pickle files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"json\" (\r\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the JSON files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"htmlhelp\" (\r\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run HTML Help Workshop with the ^\r\n.hhp project file in %BUILDDIR%/htmlhelp.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"qthelp\" (\r\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\r\n.qhcp project file in %BUILDDIR%/qthelp, like this:\r\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\PyScada.qhcp\r\n\techo.To view the help file:\r\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\PyScada.ghc\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"devhelp\" (\r\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"epub\" (\r\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latex\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdf\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf\r\n\tcd %~dp0\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdfja\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf-ja\r\n\tcd %~dp0\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"text\" (\r\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The text files are in %BUILDDIR%/text.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"man\" (\r\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"texinfo\" (\r\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"gettext\" (\r\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"changes\" (\r\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.The overview file is in %BUILDDIR%/changes.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"linkcheck\" (\r\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Link check complete; look for any errors in the above output ^\r\nor in %BUILDDIR%/linkcheck/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"doctest\" (\r\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of doctests in the sources finished, look at the ^\r\nresults in %BUILDDIR%/doctest/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"coverage\" (\r\n\t%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of coverage in the sources finished, look at the ^\r\nresults in %BUILDDIR%/coverage/python.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"xml\" (\r\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pseudoxml\" (\r\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\r\n\tgoto end\r\n)\r\n\r\n:end\r\n"
  },
  {
    "path": "docs/nginx_setup.rst",
    "content": "Nginx Setup\n===========\n\n\nnginx configuration\n-------------------\n\n\n::\n        \n        # debian\n        sudo wget https://raw.githubusercontent.com/trombastic/PyScada/dev/0.7.x/extras/nginx_sample.conf -O /etc/nginx/sites-available/pyscada.conf\n        sudo nano /etc/nginx/sites-available/pyscada.conf\n        # Fedora\n        sudo wget https://raw.githubusercontent.com/trombastic/PyScada/dev/0.7.x/extras/nginx_sample.conf -O /etc/nginx/conf.d/pyscada.conf\n        sudo nano /etc/nginx/conf.d/pyscada.conf\n\n\nafter editing, enable the configuration and restart nginx, optionaly remove the default configuration\n\n::\n\n        # debian\n        sudo ln -s /etc/nginx/sites-available/pyscada.conf /etc/nginx/sites-enabled/\n        sudo rm /etc/nginx/sites-enabled/default\n\n\n\nto use ssl\n----------\n\ngenerate ssl certificates.\n\n\n\n::\n\n        # for Debian, Ubuntu, Raspian\n        sudo mkdir /etc/nginx/ssl\n        # the certificate will be valid for 5 Years,\n        sudo openssl req -x509 -nodes -days 1780 -newkey rsa:2048 -keyout /etc/nginx/ssl/pyscada_server.key -out /etc/nginx/ssl/pyscada_server.crt\n\n\n\n\n\nnow it's time to [re]start nginx.\n\n\n::\n\n\n        # SysV-Init\n        sudo service nginx restart\n        # systemd\n        systemctl enable nginx.service # enable autostart on boot\n        sudo systemctl restart nginx\n\n\nfor Fedora you have to allow nginx to serve the static and media folder.\n\n::\n        \n        sudo chcon -Rt httpd_sys_content_t /var/www/pyscada/http/"
  },
  {
    "path": "docs/phant.rst",
    "content": "Phant Installation\n==================\n\nadd the following line to the urls.py:\n\n::\n\n   url(r'^', include('pyscada.phant.urls')),\n\n\n"
  },
  {
    "path": "docs/plugin_install.rst",
    "content": "PyScada plugin installation\n===========================\n\n1. Choose a method to download  the PyScada plugin (exemple using PyScada-Modbus) :\n\n  - by cloning the repository :\n\n  .. code-block:: shell\n\n      sudo apt install git\n      cd /home/pyscada\n      sudo -u pyscada git clone https://github.com/pyscada/PyScada-Modbus.git\n      cd PyScada-Modbus\n\n\n  - by downloading the zip file and extracting it :\n\n  .. code-block:: shell\n\n      sudo apt install wget\n      cd /home/pyscada\n      sudo -u pyscada wget https://github.com/pyscada/PyScada-Modbus/archive/refs/heads/main.zip -O PyScada-Modbus-main.zip\n      sudo apt install unzip\n      sudo -u pyscada unzip ./PyScada-Modbus-main.zip\n      sudo -u pyscada rm ./PyScada-Modbus-main.zip\n      cd PyScada-Modbus-main\n\n2. Install the PyScada plugin\n\n  Run :\n\n  .. code-block:: shell\n\n      # activate the PyScada virtual environment\n      source /home/pyscada/.venv/bin/activate\n      # install the plugin\n      sudo -u pyscada -E env PATH=${PATH} pip3 install .\n      # run migrations\n      sudo -u pyscada -E env PATH=${PATH} python3 /var/www/pyscada/PyScadaServer/manage.py migrate\n      # copy static files\n      sudo -u pyscada -E env PATH=${PATH} python3 /var/www/pyscada/PyScadaServer/manage.py collectstatic --no-input\n      # restart gunicorn and PyScada\n      sudo systemctl restart gunicorn pyscada\n\n\nList PyScada plugin installed\n-----------------------------\n\n.. code-block:: shell\n\n    # activate the PyScada virtual environment\n    source /home/pyscada/.venv/bin/activate\n    pip3 list | grep cada\n\n\nUninstall a plugin\n----------------------\n\nTo uninstall a plugin\n\n.. code-block:: shell\n\n    sudo -u pyscada -E env PATH=${PATH} pip3 uninstall yourPlugin"
  },
  {
    "path": "docs/quick_install.rst",
    "content": ".. IMPORTANT::\n    This Version of PyScada is BETA software and may have serious bugs which may cause damage to your computer,\n    automation hardware and data. It is not intended for use in production systems! You use this Software on your own risk!\n\nInstallation\n============\n\nThis installation guide covers the installation of PyScada for `Debian 10/11 <https://www.debian.org/>`_ ,\n`Raspberry Pi OS <https://www.raspberrypi.com/software/>`_ based Linux systems\nusing `MariaDB <https://mariadb.com/>`_ as Database,\n`Gunicorn <http://gunicorn.org/>`_ as WSGI HTTP Server and `nginx <http://nginx.org/>`_ as HTTP Server.\n\nScripts available\n-----------------\n\nThe script ``install.sh`` let you choose between 2 installation type : system or docker and create a log file of the installation.\n\nThen it call the script ``install_system.sh`` or ``install_docker.sh`` depending on your choice.\n\nAutomatic installation on Debian and derivatives\n------------------------------------------------\n\n1. Choose a method to download PyScada (you need write rights in the current directory) :\n\n  - by cloning the repository :\n\n  .. code-block:: shell\n\n      sudo apt install git\n      git clone https://github.com/pyscada/PyScada.git\n      cd PyScada\n\n\n  - by downloading the zip file and extracting it :\n\n  .. code-block:: shell\n\n      sudo apt install wget\n      wget https://github.com/pyscada/PyScada/archive/refs/heads/main.zip -O PyScada-main.zip\n      sudo apt install unzip\n      unzip ./PyScada-main.zip\n      rm ./PyScada-main.zip\n      cd PyScada-main\n\n2. Install PyScada\n\n  .. IMPORTANT::\n      For a new installation, make sure to answer \"no\" to the question \"Update only\".\n\n  You will have to choose :\n\n  * if you want to install PyScada on the system or in a docker container.\n  * if the system date is correct (system install only)\n  * if you want to use a proxy (system install only)\n  * if you want to install channels and redis to speed up communications inter pyscada processes (system install only)\n  * if you want to update only, if not :\n\n    * the DB name, user and password\n    * admin name and mail to send error logs (need further django email configuration in settings.py)\n    * the first pyscada user credentials\n    * if you want installed pyscada plugins to be automatically loaded\n\n  Run :\n\n  .. code-block:: shell\n\n      sudo ./install.sh\n\nTroubleshooting\n---------------\n\nIf you already installed PyScada using docker, you need to delete the ``db_data`` docker volume using :\n\n.. code-block:: shell\n\n    docker volume rm docker_dbdata\n"
  },
  {
    "path": "docs/raspberryPiOS.rst",
    "content": "Raspberry Pi OS Installation\n============================\n\nDownload last Lite image from `here <https://www.raspberrypi.com/software/operating-systems/>`__\n\nCopy the image on the SD card using (change the name of the zip file and the path to the sd card)::\n\n    unzip -p the_zip_file.zip | sudo dd of=/dev/sdX bs=4M conv=fsync status=progress\n\n    or\n\n    xz -dc the_xz_file.xz | sudo dd of=/dev/sdX bs=4M conv=fsync status=progress\n\nMore informations `here <https://www.raspberrypi.com/documentation/computers/getting-started.html>`__\n"
  },
  {
    "path": "docs/uninstall.rst",
    "content": "Installation\n============\n\nDepending on what you need on your computer, you may NOT need to remove everything PyScada requires (such as django, mariaDB...).\n\nPyScada\n-------\n\n  .. code-block:: shell\n\n      # activate the PyScada virtual environment\n      source /home/pyscada/.venv/bin/activate\n      # uninstall PyScada\n      sudo -u pyscada -E env PATH=${PATH} pip3 uninstall pyscada\n      # clean static files\n      python /var/www/pyscada/PyScadaServer/manage.py collectstatic --clear --no-input\n      # drop the mariaDB database, by default called PyScada_db\n      sudo  mysql -u PyScada-user -p -e \"DROP DATABASE IF EXISTS database_name PyScada_db;\"\n\nUninstall prerequisites (IMPORTANT: only if not needed by other software !)\n\n  .. code-block:: shell\n\n      # Uninstall prerequisites\n      DEB_TO_UNINSTALL=\"\n        libatlas-base-dev\n        libffi-dev\n        libhdf5-103\n        libhdf5-dev\n        libjpeg-dev\n        libmariadb-dev\n        libopenjp2-7\n        mariadb-server\n        nginx\n        python3-dev\n        python3-mysqldb\n        python3-pip\n        python3-venv\n        zlib1g-dev\n        pkg-config\n      \"\n      sudo apt remove -y $DEB_TO_UNINSTALL\n\n      PIP_TO_UNINSTALL=\"\n        cffi\n        Cython\n        docutils\n        gunicorn\n        lxml\n        mysqlclient\n        numpy\n      \"\n      sudo -u pyscada -E env PATH=${PATH} pip3 uninstall $PIP_TO_UNINSTALL\n\nPyScada plugin\n--------------\n\n  .. code-block:: shell\n\n      # activate the PyScada virtual environment\n      source /home/pyscada/.venv/bin/activate\n      # uninstall PyScada-Plugin\n      sudo -u pyscada -E env PATH=${PATH} pip3 uninstall pyscada-plugin\n      # clean static files\n      python /var/www/pyscada/PyScadaServer/manage.py collectstatic --clear --no-input\n      python /var/www/pyscada/PyScadaServer/manage.py collectstatic --no-input\n      # clean db, may depend on the plugin\n      python /var/www/pyscada/PyScadaServer/manage.py migrate pyscada-plugin zero\n\nDepending on the plugin, you may need to uninstall some packages.\n"
  },
  {
    "path": "docs/update.rst",
    "content": "Update from old versions\n========================\n\n0.6.x to 0.7.x\n--------------\n\nSorry a direct upgrade is not possible, you have to install 0.7.x from scratch.\n\n\n0.7.0b18 to 0.7.0b19\n------------------------\n\n\n::\n\n    cd /var/www/pyscada/PyScadaServer\n    sudo -u pyscada python manage.py migrate\n    sudo -u pyscada python manage.py collectstatic\n    sudo -u pyscada python manage.py pyscada_daemon init\n\n0.8.x to 0.9x\n-------------\n\nBefor the Upgrade:\n\nThe folowing lines must be added to the `settings.py` after the `INSTALLED_APPS` section.\n\n::\n\n    pyscada = __import__(\"pyscada.core\")\n    if hasattr(pyscada.core, \"additional_installed_app\"):\n        for app in getattr(pyscada.core, \"additional_installed_app\"):\n            INSTALLED_APPS += [\n                app,\n            ]\n\nAfter the Upgrade:\n\n- Remove `\"pyscada.core\"`, `\"pyscada.hmi\"`, `\"pyscada.export\"` from `INSTALLED_APPS` in `settings.py`\n- (optinal) choose a alternative home page by adding `PYSCADA_HOME = \"/view/TEST/\"` to the `settings.py`\n- (optinal) add `PYSCADA_ALLOW_ANONYMOUS = True` to allow access to the pyscada hmi without login or add `PYSCADA_ALLOW_ANONYMOUS_WRITE = True` to allow write access to the pyscada hmi without login\n\t- Managing anonymous user display permission for IHM objects (view, page, widget, chart...) is done in the admin panel using the \"Group Display Permission\" -> \"Unauthenticated users\" configuration\n- Run the folowing command in your pyscada root (where `manage.py` is located) in the pyscada venv\n\n::\n\n    sudo -u pyscada python manage.py migrate\n    sudo -u pyscada python manage.py collectstatic\n    sudo -u pyscada python manage.py pyscada_daemon init\n\n\nsystemd\n-------\n\n\n::\n\n\n    sudo wget https://raw.githubusercontent.com/pyscada/PyScada/master/extras/service/systemd/pyscada_daemon.service -O /etc/systemd/system/pyscada_daemon.service\n    sudo systemctl enable pyscada_daemon\n    sudo systemctl disable pyscada_daq\n    sudo systemctl disable pyscada_event\n    sudo systemctl disable pyscada_mail\n    sudo systemctl disable pyscada_export\n    sudo rm /lib/systemd/system/pyscada_daq.service\n    sudo rm /lib/systemd/system/pyscada_mail.service\n    sudo rm /lib/systemd/system/pyscada_export.service\n    sudo rm /lib/systemd/system/pyscada_event.service\n    sudo systemctl daemon-reload\n"
  },
  {
    "path": "docs/visa.rst",
    "content": "Discover VISA devices\n=====================\n\nTo discover GPIB/USB(/LXI?) devices run:\n\n::\n\n    sudo python3\n    import pyvisa\n    rm=pyvisa.ResourceManager('@py')\n    rm.list_resources()  # example : ('ASRL/dev/ttyAMA0::INSTR', 'USB0::1689::1032::C023569::0::INSTR', 'USB0::1689::851::1525638::0::INSTR')\n    inst=rm.open_resource('USB0::1689::851::1525638::0::INSTR'')\n    inst.query('*IDN?')  # example : 'TEKTRONIX,AFG1022,1525638,SCPI:99.0 FV:V1.1.2\\n'\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "export default [\n    {\n        files: [\"**/*.js\"],\n        rules: {\n            \"semi\": \"warn\",\n            \"no-unused-vars\": \"warn\"\n        }\n    }\n];\n"
  },
  {
    "path": "extras/0.7to0.8.sh",
    "content": "#!/bin/bash\n\n# check if path is passed\nif [ $# -eq 0 ]\n  then\n    manage_path=\"/var/www/pyscada/PyScadaServer\"\n    if test -f \"$manage_path/manage.py\"; then\n      echo \"$manage_path/manage.py found.\"\n    else\n      echo \"File $manage_path/manage.py not found. Please specify the path of manage.py.\"\n      exit 1\n    fi\nelse\n  if test -f \"$1/manage.py\"; then\n    echo \"$1/manage.py found.\"\n    manage_path=$1\n  else\n    echo \"File $1/manage.py not found. Please specify the path of manage.py or let empty tu use the default value : /var/www/pyscada/PyScadaServer/manage.py\"\n    exit 1\n  fi\nfi\n\n# get the pyscada protocols with at least one device\nres=$(echo -e \"from pyscada.models import DeviceProtocol\n\nfor dp in DeviceProtocol.objects.all():\n  if len(dp.device_set.all()) > 0:\n    print(dp)\n  \" | python3 $manage_path/manage.py shell)\n\n\n# check and install plugins\nif [[ $res == *\"modbus\"* ]]; then\n  echo \"Modbus It's there!\"\n  pip3 install pyscada-modbus\nfi\nif [[ $res == *\"phant\"* ]]; then\n  echo \"phant It's there!\"\n  pip3 install pyscada-phant\nfi\nif [[ $res == *\"systemstat\"* ]]; then\n  echo \"systemstat It's there!\"\n  pip3 install pyscada-systemstat\nfi\nif [[ $res == *\"visa\"* ]]; then\n  echo \"visa It's there!\"\n  pip3 install pyscada-visa\nfi\nif [[ $res == *\"smbus\"* ]]; then\n  echo \"smbus It's there!\"\n  pip3 install pyscada-smbus\nfi\nif [[ $res == *\"onewire\"* ]]; then\n  echo \"onewire It's there!\"\n  pip3 install pyscada-onewire\nfi\n\n"
  },
  {
    "path": "extras/Grafana-test-dashboard.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PYSCADA\",\n      \"label\": \"PyScada\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"mysql\",\n      \"pluginName\": \"MySQL\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.2.5\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"mysql\",\n      \"name\": \"MySQL\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"timeseries\",\n      \"name\": \"Time series\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"limit\": 100,\n        \"name\": \"Annotations & Alerts\",\n        \"showIn\": 0,\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"gnetId\": null,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"iteration\": 1637571827897,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": \"$Datasource\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"hue\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineStyle\": {\n              \"fill\": \"solid\"\n            },\n            \"lineWidth\": 2,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"normal\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 15,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 13,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [\n            \"last\",\n            \"mean\",\n            \"max\"\n          ],\n          \"displayMode\": \"table\",\n          \"placement\": \"right\"\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\"\n        }\n      },\n      \"pluginVersion\": \"8.0.3\",\n      \"targets\": [\n        {\n          \"format\": \"time_series\",\n          \"group\": [],\n          \"hide\": false,\n          \"metricColumn\": \"none\",\n          \"rawQuery\": true,\n          \"rawSql\": \"SELECT\\n  $__timeGroupAlias(r.date_saved,$time_group),\\n  avg(IFNULL(r.value_float64, 0.0) + IFNULL(r.value_int64, 0.0) + IFNULL(r.value_int32, 0.0) + IFNULL(r.value_int16, 0.0) + IFNULL(r.value_boolean, 0.0)),\\n  v.name AS metric\\nFROM pyscada_recordeddata as r \\nJOIN pyscada_variable v ON r.variable_id = v.id\\nWHERE\\n  $__timeFilter(r.date_saved) AND\\n  r.variable_id IN (${Variables})\\nGROUP BY time, metric\\nORDER BY $__timeGroup(r.date_saved,$time_group,$null_as)\",\n          \"refId\": \"B\",\n          \"select\": [\n            [\n              {\n                \"params\": [\n                  \"id\"\n                ],\n                \"type\": \"column\"\n              }\n            ]\n          ],\n          \"table\": \"pyscada_device\",\n          \"timeColumn\": \"connection_time\",\n          \"timeColumnType\": \"timestamp\",\n          \"where\": []\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"Test group by time\",\n      \"transformations\": [],\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": null,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 8,\n      \"panels\": [],\n      \"title\": \"Row title\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": \"${DS_PYSCADA}\",\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 2,\n      \"options\": {\n        \"content\": \"# Title\\n\\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\\n         \\nProtocoles : ${Protocols.text}\\n\\nIntruments : ${Devices}\\n\\nUnités : ${Units}\\n\\nVariables : ${Variables}\\n\\n\",\n        \"mode\": \"markdown\"\n      },\n      \"pluginVersion\": \"8.2.5\",\n      \"repeat\": null,\n      \"repeatDirection\": \"v\",\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"PyScada\",\n      \"transformations\": [\n        {\n          \"id\": \"seriesToColumns\",\n          \"options\": {}\n        },\n        {\n          \"id\": \"organize\",\n          \"options\": {\n            \"excludeByName\": {\n              \"Time\": false,\n              \"id\": true\n            },\n            \"indexByName\": {},\n            \"renameByName\": {}\n          }\n        }\n      ],\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"schemaVersion\": 32,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"selected\": true,\n          \"text\": \"PyScada\",\n          \"value\": \"PyScada\"\n        },\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"Datasource\",\n        \"options\": [],\n        \"query\": \"mysql\",\n        \"queryValue\": \"\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"$Datasource\",\n        \"definition\": \"SELECT protocol AS __text, id AS __value FROM pyscada_deviceprotocol\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": true,\n        \"name\": \"Protocols\",\n        \"options\": [],\n        \"query\": \"SELECT protocol AS __text, id AS __value FROM pyscada_deviceprotocol\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"$Datasource\",\n        \"definition\": \"SELECT d.short_name AS __text, d.id AS __value FROM pyscada_device d WHERE d.protocol_id IN (${Protocols}) AND d.active = 1\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": true,\n        \"name\": \"Devices\",\n        \"options\": [],\n        \"query\": \"SELECT d.short_name AS __text, d.id AS __value FROM pyscada_device d WHERE d.protocol_id IN (${Protocols}) AND d.active = 1\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"$Datasource\",\n        \"definition\": \"SELECT u.unit AS __text, u.id AS __value FROM pyscada_unit u\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": true,\n        \"name\": \"Units\",\n        \"options\": [],\n        \"query\": \"SELECT u.unit AS __text, u.id AS __value FROM pyscada_unit u\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"$Datasource\",\n        \"definition\": \"SELECT v.name AS __text, v.id AS __value FROM pyscada_variable v WHERE v.device_id IN (${Devices}) AND v.unit_id IN (${Units}) AND v.active = 1\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": true,\n        \"name\": \"Variables\",\n        \"options\": [],\n        \"query\": \"SELECT v.name AS __text, v.id AS __value FROM pyscada_variable v WHERE v.device_id IN (${Devices}) AND v.unit_id IN (${Units}) AND v.active = 1\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"auto\": true,\n        \"auto_count\": 30,\n        \"auto_min\": \"10s\",\n        \"current\": {\n          \"selected\": true,\n          \"text\": \"1m\",\n          \"value\": \"1m\"\n        },\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"label\": \"Time group\",\n        \"name\": \"time_group\",\n        \"options\": [\n          {\n            \"selected\": false,\n            \"text\": \"auto\",\n            \"value\": \"$__auto_interval_time_group\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1s\",\n            \"value\": \"1s\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"10s\",\n            \"value\": \"10s\"\n          },\n          {\n            \"selected\": true,\n            \"text\": \"1m\",\n            \"value\": \"1m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"10m\",\n            \"value\": \"10m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"30m\",\n            \"value\": \"30m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1h\",\n            \"value\": \"1h\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"6h\",\n            \"value\": \"6h\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"12h\",\n            \"value\": \"12h\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1d\",\n            \"value\": \"1d\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"7d\",\n            \"value\": \"7d\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"14d\",\n            \"value\": \"14d\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"30d\",\n            \"value\": \"30d\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1M\",\n            \"value\": \"1M\"\n          }\n        ],\n        \"query\": \"1s,10s,1m,10m,30m,1h,6h,12h,1d,7d,14d,30d,1M\",\n        \"queryValue\": \"\",\n        \"refresh\": 2,\n        \"skipUrlSync\": false,\n        \"type\": \"interval\"\n      },\n      {\n        \"allValue\": null,\n        \"current\": {\n          \"selected\": true,\n          \"text\": \"previous\",\n          \"value\": \"previous\"\n        },\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Null as\",\n        \"multi\": false,\n        \"name\": \"null_as\",\n        \"options\": [\n          {\n            \"selected\": false,\n            \"text\": \"0\",\n            \"value\": \"0\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"NULL\",\n            \"value\": \"NULL\"\n          },\n          {\n            \"selected\": true,\n            \"text\": \"previous\",\n            \"value\": \"previous\"\n          }\n        ],\n        \"query\": \"0, NULL, previous\",\n        \"queryValue\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"custom\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-6h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"PyScada\",\n  \"uid\": \"bwhhV4pnz\",\n  \"version\": 7\n}"
  },
  {
    "path": "extras/nginx_sample.conf",
    "content": "# pyscada.conf\n\n# the upstream component nginx needs to connect to\nupstream app_server {\n        server unix:/tmp/gunicorn.sock fail_timeout=0; # for a file socket\n        #server 127.0.0.1:8000 fail_timeout=0; # for a file socket\n}\n\n# configuration of the server\nserver {\n    listen      80;\n    listen   [::]:80;\n    server_name _;          # substitute your machine's IP address or FQDN\n    #return 301 https://$server_name$request_uri;\n    return 301 https://$host$request_uri;\n}\n\nserver {\n        listen   443 default_server ssl;\n        listen [::]:443 ssl;\n\n        server_name _;          # substitute your machine's IP address or FQDN\n\n        charset utf-8;\n        keepalive_timeout 5;\n        client_max_body_size 75M;   # max upload size, adjust to taste\n        # please comment if https is not used\n        ssl_certificate     /etc/nginx/ssl/pyscada_server.crt; # The certificate file\n        ssl_certificate_key /etc/nginx/ssl/pyscada_server.key; # The private key file\n\n        # Django media\n        location /media  {\n                alias /var/www/pyscada/http/media;  # your Django project's media files - amend as required\n        }\n\n        location /static {\n                alias /var/www/pyscada/http/static; # your Django project's static files - amend as required\n        }\n\n        location /measurement {\n                alias /home/pyscada/measurement_data_dumps; # to support download of measurement files via admin backend - amend as required\n        }\n\n        location /favicon.ico {\n            proxy_pass http://127.0.0.1/static/pyscada/img/favicon.ico;\n        }\n\n        location / {\n            # checks for static file, if not found proxy to app\n            try_files $uri @proxy_to_app;\n        }\n\n        location @proxy_to_app {\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_set_header Host $http_host;\n            proxy_set_header X-Forwarded-Proto $scheme;\n            proxy_redirect off;\n\n            proxy_pass   http://app_server;\n        }\n}"
  },
  {
    "path": "extras/pyscada-logrotate",
    "content": "/var/log/pyscada_debug.log\n{\n\trotate 4\n\tweekly\n\tmissingok\n\tnotifempty\n\tcompress\n\tdelaycompress\n\tsize 1M\n}\n"
  },
  {
    "path": "extras/service/SysV-init/gunicorn_django",
    "content": "#!/bin/bash\n\n### BEGIN INIT INFO\n# Provides:\t\t\t gunicorn_django\n# Required-Start:\t $all\n# Required-Stop:\t $all\n# Default-Start:\t 2 3 4 5\n# Default-Stop:\t\t 0 1 6\n# Short-Description: starts the pyscada gunicorn server\n# Description:\t\t starts pyscada gunicorn using start-stop-daemon\n### END INIT INFO\n\n# sample configfile for /etc/default/gunicorn_django\n# APP_NAME='PyScadaServer'\n# DJANGO_DIR='/var/www/pyscada/PyScadaServer'\n# NUMBER_OF_WORKERS=10 \n# RUN_AS='pyscada'\n\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\nPID_FILE=/tmp/gunicorn.pid\nSOCKET_FILE=/tmp/gunicorn.sock\n\nif [ -f /etc/default/gunicorn_django ] ; then\n\t. /etc/default/gunicorn_django\nelse\n\t# use default values\n\tAPP_NAME='PyScadaServer'\n\tDJANGO_DIR=/var/www/pyscada/PyScadaServer/\n\tNUMBER_OF_WORKERS=10 \n\tRUN_AS='pyscada'\nfi\n\n\n\nstart () {\n\n\tif [ -e $PID_FILE ]\n\tthen\n\t\tPID=$(cat $PID_FILE)\n\t\tif ps -p $PID > /dev/null\n\t\tthen\n\t\t\techo \"gunicorn service is already running ($PID)\"\n\t\t\texit 0\n\t\tfi\n\tfi\n\n\techo \"Spawning $APP_NAME\"\n\t# Create the run directory if it doesn't exist\n\texport DJANGO_SETTINGS_MODULE=$APP_NAME.settings\n\texport PYTHONPATH=${data[1]}:$PYTHONPATH\n\t# Start your Django Unicorn\n\t# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)\n\tstart-stop-daemon --start --quiet -c $RUN_AS -d $DJANGO_DIR --pidfile $PID_FILE --exec /usr/local/bin/gunicorn -- $APP_NAME.wsgi:application -n $APP_NAME -w $NUMBER_OF_WORKERS -u $RUN_AS -b unix:$SOCKET_FILE -p $PID_FILE -D\n\n\treturn\n}\n\nstop () {\n\tif [ -e $PID_FILE ]\n\tthen\n\t\tkill -TERM $(<\"$PID_FILE\")\n\t\techo \"stopped service\"\n\telse\n\t\techo \"service not running\"\n\tfi\n}\n\ncase \"$1\" in\n  start)\n\t\techo \"Starting\"\n\t\tstart\n\t\t;;\n  stop)\n\t\techo \"Stopping\"\n\t\tstop\n\t\t;;\n  restart)\n\t\techo \"Restarting\"\n\t\tstop\n\t\tsleep 1\n\t\tstart\n\t\t;;\n  *)\n\t\tN=/etc/init.d/$NAME\n\t\techo \"Usage: $N {start|stop|restart} \" >&2\n\t\texit 1\n\t\t;;\nesac\n\nexit 0\n"
  },
  {
    "path": "extras/service/SysV-init/pyscada_daemon",
    "content": "#!/bin/bash\n \n### BEGIN INIT INFO\n# Provides:\t\t\t pyscada_daemon\n# Required-Start:\t $all\n# Required-Stop:\t $all\n# Default-Start:\t 2 3 4 5\n# Default-Stop:\t\t 0 1 6\n# Short-Description: starts the pyscada daemon\n# Description:\t\t starts pyscada daemon\n### END INIT INFO\n\nif [ -f /etc/default/pyscada_daemon ] ; then\n\t. /etc/default/pyscada_daemon\nelse\n\t# use default values\n\tDJANGO_DIR=/var/www/pyscada/PyScadaServer/\n\tRUN_AS='pyscada'\nfi\n\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n\nstart () {\n\techo \"Spawning PyScada Daemon\"\n\tstart-stop-daemon --start -c $RUN_AS -d $DJANGODIR --exec manage.py -- pyscada_daemon start\n\n\tdone\n\treturn\n}\n\n\nstop () {\n    echo \"Stop PyScada Daemon\"\n\tstart-stop-daemon --start -c $RUN_AS -d $DJANGODIR --exec manage.py -- pyscada_daemon stop\n\tdone\n\treturn\n}\n \ncase \"$1\" in\n  start)\n\t\techo \"Starting\"\n\t\tstart\n\t\t;;\n  stop)\n\t\techo \"Stopping\"\n\t\tstop\n\t\t;;\n  restart)\n\t\techo \"Restarting\"\n\t\tstop\n\t\tsleep 1\n\t\tstart\n\t\t;;\n  *)\n\t\tN=/etc/init.d/$NAME\n\t\techo \"Usage: $N {start|stop|restart} \" >&2\n\t\texit 1\n\t\t;;\nesac\n \nexit 0\n"
  },
  {
    "path": "extras/service/systemd/gunicorn.service",
    "content": "[Unit]\nDescription=gunicorn daemon\nRequires=gunicorn.socket\nAfter=network.target\n\n[Service]\nPIDFile=/tmp/gunicorn.pid\nUser=pyscada\nGroup=pyscada\nWorkingDirectory=/var/www/pyscada/PyScadaServer\n#ExecStart=/usr/local/bin/gunicorn --pid /tmp/gunicorn.pid --workers 8 PyScadaServer.wsgi:application\nExecStart=/home/pyscada/.venv/bin/gunicorn --pid /tmp/gunicorn.pid --workers 8 PyScadaServer.wsgi:application\nExecReload=/bin/kill -s HUP $MAINPID\nExecStop=/bin/kill -s TERM $MAINPID\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "extras/service/systemd/gunicorn.socket",
    "content": "[Unit]\nDescription=gunicorn socket\n\n[Socket]\nListenStream=/tmp/gunicorn.sock\nListenStream=0.0.0.0:9000\n#ListenStream=[::]:8000\n\n[Install]\nWantedBy=sockets.target"
  },
  {
    "path": "extras/service/systemd/owfs.service",
    "content": "[Unit]\nDescription=OWFS Daemon\nAfter=network.target\n\n[Service]\nType=forking\nPIDFile=/tmp/owfs_daemon.pid\n#User=pyscada\n#Group=pyscada\n#WorkingDirectory=/var/www/pyscada/PyScadaServer/\nExecStart=/usr/bin/owfs -P /tmp/owfs_daemon.pid\n#ExecStop=/usr/bin/python /var/www/pyscada/PyScadaServer/manage.py pyscada_daemon stop\nRestart=always\nRestartSec=60\n\n[Install]\nWantedBy=multi-user.target\n\n\n"
  },
  {
    "path": "extras/service/systemd/pyscada_daemon.service",
    "content": "[Unit]\nDescription=PyScada Daemon\nAfter=network.target\nAfter=mysql.service\nAfter=redis-server.service\n\n[Service]\nType=forking\nPIDFile=/tmp/pyscada_daemon.pid\nUser=pyscada\nGroup=pyscada\nWorkingDirectory=/var/www/pyscada/PyScadaServer/\n#ExecStart=/usr/bin/python3 /var/www/pyscada/PyScadaServer/manage.py pyscada_daemon start\n#ExecStop=/usr/bin/python3 /var/www/pyscada/PyScadaServer/manage.py pyscada_daemon stop\nExecStart=/home/pyscada/.venv/bin/python3 /var/www/pyscada/PyScadaServer/manage.py pyscada_daemon start\nExecStop=/home/pyscada/.venv/bin/python3 /var/www/pyscada/PyScadaServer/manage.py pyscada_daemon stop\nRestart=always\nRestartSec=60\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "extras/service/windows/register_windows_service.py",
    "content": "import win32serviceutil\nimport win32service\nimport win32event\nimport servicemanager\nimport os\n\n\nclass AppServerSvc(win32serviceutil.ServiceFramework):\n    _svc_name_ = \"PyScada Daemon\"\n    _svc_display_name_ = \"PyScada Daemon\"\n    _svc_description_ = \"a PyScada Daemon for the detection\"\n\n    def __init__(self, args):\n        win32serviceutil.ServiceFramework.__init__(self, args)\n        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)\n\n    def SvcStop(self):\n        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)\n        win32event.SetEvent(self.hWaitStop)\n        os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"PyScadaServer.settings\")\n        import django\n\n        django.setup()\n        # todo implement\n        raise NotImplementedError\n\n    def SvcDoRun(self):\n        servicemanager.LogMsg(\n            servicemanager.EVENTLOG_INFORMATION_TYPE,\n            servicemanager.PYS_SERVICE_STARTED,\n            (self._svc_name_, \"\"),\n        )\n        self.main()\n\n    def main(self):\n        os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"PyScadaServer.settings\")\n        import django\n\n        django.setup()\n        # todo implement\n        raise NotImplementedError\n\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"PyScadaServer.settings\")\n    win32serviceutil.HandleCommandLine(AppServerSvc)\n"
  },
  {
    "path": "extras/settings.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"\nDjango settings for PyScadaServer project.\n\nGenerated by 'django-admin startproject' using Django 2.2.9.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/2.2/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/2.2/ref/settings/\n\nLater customized for PyScada needs\n\"\"\"\n\nimport os\nimport pyvisa\nimport importlib.util\nimport pkg_resources\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = \"\"\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = False\n\nALLOWED_HOSTS = [\"*\"]\n\n# Application definition\n\nINSTALLED_APPS = [\n    \"django.contrib.admin\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"pyscada\"\n]\n\npyscada = __import__(\"pyscada.core\")\nif hasattr(pyscada.core, \"additional_installed_app\"):\n    for app in getattr(pyscada.core, \"additional_installed_app\"):\n        INSTALLED_APPS += [\n            app,\n        ]\n\ninstalled_packages = pkg_resources.working_set\nfor i in installed_packages:\n    if \"pyscada-\" in str(i):\n        lib = str(i).split(\" \")[0].split(\"-\")[1]\n        if importlib.util.find_spec(\"pyscada.\" + str(lib)) is not None:\n            INSTALLED_APPS += [\n                \"pyscada.\" + str(lib),\n            ]\n\nif importlib.util.find_spec(\"channels\") is not None:\n    INSTALLED_APPS += [\n        \"channels\",\n    ]\n\n    CHANNEL_LAYERS = {\n        \"default\": {\n            \"BACKEND\": \"channels_redis.core.RedisChannelLayer\",\n            \"CONFIG\": {\n                \"hosts\": [(\"127.0.0.1\", 6379)],\n            },\n        },\n    }\n\nif util.find_spec('django_redis') is not None:\n    # for cache_datasource\n    CACHES = {\n        \"default\": {\n            \"BACKEND\": \"django.core.cache.backends.redis.RedisCache\",\n            \"LOCATION\": \"redis://127.0.0.1:6379\",\n        }\n    }\nelse:\n    # as fallback setup file based cache\n    CACHES = {\n        \"default\": {\n            \"BACKEND\": \"django.core.cache.backends.filebased.FileBasedCache\",\n            \"LOCATION\": \"{{ project_root }}\",\n            \"TIMEOUT\": 3600,\n            \"OPTIONS\": {\"MAX_ENTRIES\": 1000},\n        }\n    }\n\nLOGIN_REDIRECT_URL = \"/\"\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nAUTHENTICATION_BACKENDS = [\n    \"django.contrib.auth.backends.ModelBackend\",\n]\n\nif importlib.util.find_spec(\"django_cas_ng\") is not None:\n    INSTALLED_APPS += [\n        \"django_cas_ng\",\n    ]\n    AUTHENTICATION_BACKENDS += [\n        \"django_cas_ng.backends.CASBackend\",\n    ]\n    CAS_SERVER_URL = \"https://sso.univ-pau.fr/cas/\"\n    CAS_VERSION = \"2\"\n    CAS_EXTRA_LOGIN_KWARGS = {\n        \"proxies\": {\"https\": \"http://cache.iutbayonne.univ-pau.fr:3128\"},\n        \"timeout\": 5,\n    }\n\n    LOGIN_URL = \"/accounts/choose_login/\"\n\nROOT_URLCONF = \"PyScadaServer.urls\"\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.template.context_processors.debug\",\n                \"django.template.context_processors.request\",\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n            ],\n            \"libraries\": {\n                \"staticfiles\": \"django.templatetags.static\",\n            },\n        },\n    },\n]\n\nWSGI_APPLICATION = \"PyScadaServer.wsgi.application\"\n\n# Database\n# https://docs.djangoproject.com/en/2.2/ref/settings/#databases\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.mysql\",\n        \"NAME\": \"PyScada_db\",\n        \"USER\": \"PyScada-user\",\n        \"PASSWORD\": \"PyScada-user-password\",\n        \"OPTIONS\": {\n            \"init_command\": \"SET sql_mode='STRICT_TRANS_TABLES'\",\n        }\n        # 'ENGINE': 'django.db.backends.sqlite3',\n        # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),\n    }\n}\n\n# Password validation\n# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.MinimumLengthValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.CommonPasswordValidator\",\n    },\n    {\n        \"NAME\": \"django.contrib.auth.password_validation.NumericPasswordValidator\",\n    },\n]\n\n# Internationalization\n# https://docs.djangoproject.com/en/2.2/topics/i18n/\n\nLANGUAGE_CODE = \"en-us\"\n\nTIME_ZONE = \"UTC\"\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/2.2/howto/static-files/\n\nSTATIC_URL = \"/static/\"\n\nSTATIC_ROOT = \"/var/www/pyscada/http/static/\"\n\nMEDIA_URL = \"/media/\"\n\nMEDIA_ROOT = \"/var/www/pyscada/http/media/\"\n\n# PyScada settings\n# https://github.com/trombastic/PyScada\n\n# email settings\nDEFAULT_FROM_EMAIL = \"\"\nEMAIL_HOST = \"\"\nEMAIL_PORT = 465\nEMAIL_HOST_USER = \"r\"\nEMAIL_USE_TLS = False\nEMAIL_USE_SSL = True\nEMAIL_HOST_PASSWORD = \"\"\nEMAIL_PREFIX = \"PREFIX\"  # Mail subject will be \"PREFIX subjecttext\"\n\n# meta information's about the plant site\nPYSCADA_META = {\n    \"name\": \"A SHORT NAME\",\n    \"description\": \"A SHORT DESCRIPTION\",\n}\n\n# export properties\nPYSCADA_EXPORT = {\n    \"file_prefix\": \"PREFIX_\",\n    \"output_folder\": \"~/measurement_data_dumps\",\n}\n\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"standard\": {\n            \"format\": \"[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s\",\n            \"datefmt\": \"%d/%b/%Y %H:%M:%S\",\n        },\n    },\n    \"handlers\": {\n        \"file\": {\n            \"level\": \"DEBUG\",\n            \"class\": \"logging.FileHandler\",\n            \"filename\": \"/var/log/pyscada_debug.log\",\n            \"formatter\": \"standard\",\n        },\n    },\n    \"loggers\": {\n        \"\": {\n            \"handlers\": [\"file\"],\n            \"level\": \"ERROR\",\n            \"propagate\": True,\n        },\n        \"django\": {\n            \"handlers\": [\"file\"],\n            \"level\": \"INFO\",\n            \"propagate\": False,\n        },\n        \"pyscada\": {\n            \"handlers\": [\"file\"],\n            \"level\": \"DEBUG\",\n            \"propagate\": False,\n        },\n        \"gunicorn\": {\n            \"handlers\": [\"file\"],\n            \"level\": \"INFO\",\n            \"propagate\": False,\n        },\n    },\n}\n\nVISA_DEVICE_SETTINGS = {\n    \"USB0\": {\n        \"open_timeout\": 5000,\n        \"timeout\": 15000,\n    },\n    \"GPIB0\": {\n        \"open_timeout\": 5000,\n        \"timeout\": 15000,\n    },\n    \"ASRL/dev/ttyAMA0\": {\n        \"baud_rate\": 9600,\n        \"data_bits\": 8,\n        \"parity\": pyvisa.constants.Parity.none,\n        \"stop_bits\": pyvisa.constants.StopBits.one,\n        \"write_termination\": \"\",\n    },\n    \"ASRL/dev/ttyUSB0\": {\n        \"open_timeout\": 5000,\n    },\n}\n"
  },
  {
    "path": "extras/urls.py",
    "content": "\"\"\"PyScadaServer URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/1.11/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.conf.urls import url, include\n    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))\n\"\"\"\nfrom django.urls import path, include\nfrom django.contrib import admin\n\nurlpatterns = [\n    path(\"\", include(\"pyscada.hmi.urls\")),\n]\n"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/bash\n\n# check if the script is run from script directory\nSOURCE=${BASH_SOURCE[0]}\nwhile [ -L \"$SOURCE\" ]; do # resolve $SOURCE until the file is no longer a symlink\n  DIR=$( cd -P \"$( dirname \"$SOURCE\" )\" >/dev/null 2>&1 && pwd )\n  SOURCE=$(readlink \"$SOURCE\")\n  [[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located\ndone\nDIR=$( cd -P \"$( dirname \"$SOURCE\" )\" >/dev/null 2>&1 && pwd )\n\nif [[ \"$DIR\" != \"$PWD\" ]]; then\n  echo \"You must run this script from $DIR\"\n  exit 1\nfi\n\n# Make sure only root can run our script\nif [[ $EUID -ne 0 ]]; then\n  echo \"This script must be run as root\" 1>&2\n  exit 1\nfi\n\n# Choose the config method (venv or docker)\nanswer_config=\"\"\necho \"choose your installation\"\n\nwhile [[ \"$answer_config\" == \"\" ]]; do\n\nread -p \"1: venv, 2: docker : \" answer_config\n  case $answer_config in\n    \"1\")\n    #remove logs file if exist (to avoid appending)\n    if [ -f logs_install.txt ]; then\n      rm logs_install.txt\n    fi\n\n      #execute the install_venv.sh script and output error in logs file\n      source install_venv.sh 2>&1 | tee -a logs_install.txt 1>&2 | { while IFS= read -r line; do echo \"$line\"; done; }\n      ;;\n    \"2\")\n    #remove logs file if exist (to avoid appending)\n    if [ -f logs_docker.txt ]; then\n      rm logs_docker.txt\n    fi\n      source install_docker.sh 2>&1 | tee -a logs_docker.txt 1>&2  | { while IFS= read -r line; do echo \"$line\"; done; }\n      ;;\n    *)\n      echo \"choose a valid configuration\"\n      answer_config=\"\"\n      ;;\n  esac\n  #statements\ndone\n"
  },
  {
    "path": "install_docker.sh",
    "content": "#!/bin/bash\n\n# VAR\n\nanswer_db_name=\"PyScada_db\"         # Name of the database\nanswer_db_user=\"PyScada-user\"         # Username for the database (not the root)\nanswer_db_password=\"PyScada-user-password\"     # Password for the database (not the root)\n\nanswer_auto_add_apps=\"True\"   # Auto add apps or manual add (in django configuration)\n\nanswer_admin_name=\"\"      # admin name (for the system)\nanswer_admin_mail=\"\"      # admin mail (for errors output)\n\nssl_dir=\"./nginx/ssl\"\nkey_file=\"$ssl_dir/pyscada_server.key\"\ncrt_file=\"$ssl_dir/pyscada_server.crt\"\n\nDOCKER_COMPOSE=\"./docker-compose.yml\"\nSETTINGS_TPL=\"../tests/project_template_tmp/project_name/settings.py-tpl\"\nDOCKERFILE_SQL=\"./mysql/Dockerfile\"\n\n# Make sure only root can run script\nif [[ $EUID -ne 0 ]]; then\n  >&2 echo \"This script must be run as root\"\n  exit -1\nfi\n\ncheck_exit_status() {\n  local message=$1\n  local exit_status=$2\n\n  if [ $exit_status -ne 0 ]; then\n    >&2 echo \"$message\" # >&2: stderr output\n    exit -1\n  fi\n  wait\n}\n\nfunction debug(){\n  message=$1\n  echo \"\"\n  echo $message 1>&2\n  echo \"\"\n}\n\nfunction db_setup(){\n  debug \"db_setup\"\n\n  # copy clean docker-compose\n  cp ./docker-compose.yml-tmp $DOCKER_COMPOSE\n  check_exit_status \"Unable to copy docker-compose.yml\" $?\n  # copy template clean mysql Dockerfile\n  cp ./mysql/Dockerfile-tmp $DOCKERFILE_SQL\n  check_exit_status \"Unable to copy Dockerfile\" $?\n\n  # add mysql Dockerfile with db informations\n  sed -i \"s|PyScada_db|$answer_db_name|g\"  $DOCKERFILE_SQL\n  sed -i \"s|PyScada-user-password|$answer_db_password|g\"  $DOCKERFILE_SQL\n  sed -i \"s|PyScada-user|$answer_db_user|g\"  $DOCKERFILE_SQL\n\n  # modify docker-compose.yml with db informations\n  sed -i \"s|PyScada_db|$answer_db_name|g\" $DOCKER_COMPOSE\n  sed -i \"s|PyScada-user-password|$answer_db_password|g\" $DOCKER_COMPOSE\n  sed -i \"s|PyScada-user|$answer_db_user|g\" $DOCKER_COMPOSE\n\n  debug \"db_setup end\"\n}\n\n# called in questions_clean_inst_setup\nfunction regex_mail(){\n  debug \"regex_mail\"\n\n  regex='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$';\n  while true; do\n    read -p \"admin mail ? \" answer_admin_mail\n    if [[ $answer_admin_mail =~ $regex ]]; then\n      break\n    else\n      echo \"Choose a valid mail\"\n    fi\n  done\n\n  debug \"regex_mail end\"\n}\n\nfunction questions_setup(){\n  debug \"questions_setup\"\n\n  admin_name_setup\n  regex_mail\n  project_admins=$(echo \"('${answer_admin_name}', '${answer_admin_mail}' )\")\n  echo $project_admins\n  echo $answer_db_name\n\n  # db params\n  read -p \"Database name [PyScada_db]: \" answer_db_name\n  read -p \"Database user [PyScada-user]: \" answer_db_user\n  read -sp \"Database password (your input is hidden) [PyScada-user-password]: \" answer_db_password\n  echo \"\"\n\n  if [[ \"$answer_db_name\" == \"\" ]]; then\n    answer_db_name=\"PyScada_db\"\n  fi\n  if [[ \"$answer_db_user\" == \"\" ]]; then\n    answer_db_user=\"PyScada-user\"\n  fi\n  if [[ \"$answer_db_password\" == \"\" ]]; then\n    answer_db_password=\"PyScada-user-password\"\n  fi\n\n  while true; do\n    read -p \"Auto add pyscada plugins ? [True/False]: \" answer_auto_add_apps\n    if [[ \"$answer_auto_add_apps\" == \"True\" ]]; then\n      echo 'You need to restart pyscada and gunicorn after (un)installing any pyscada plugin.'\n      break;\n    elif [[ \"$answer_auto_add_apps\" == \"False\" ]]; then\n      echo 'You need manually add a plugin to the django project settings and restart pyscada and gunicorn after (un)installing any pyscada plugin.'\n      break;\n    else\n      echo \"Please answer True or False.\"\n    fi\n  done\n\n  debug \"questions_setup end\"\n}\n\n# called in questions_clean_inst_setup\nfunction admin_name_setup(){\n  debug \"admin_name_setup\"\n\n  while true; do\n    read -p \"admin name ? \" answer_admin_name\n    if [[ \"$answer_admin_name\" == \"\" ]]; then\n      echo \"Choose a valid name\"\n    else\n      break\n    fi\n  done\n\n  debug \"admin_name_setup end\"\n}\n\nfunction ssl_setup(){\n  # Creation of openssl certificate\n  debug \"ssl_setup\"\n  if [ ! -d \"$ssl_dir\" ]; then\n    mkdir \"$ssl_dir\"\n    check_exit_status \"Unable to create ssl directory\" $?\n    echo \"SSL directory created\"\n  else\n    echo \"SSL directory already exist\"\n  fi\n\n  if [ ! -f \"$key_file\" ] && [ ! -f \"$crt_file\" ]; then\n    rm -f \"$ssl_dir/*\"\n    openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout \"$key_file\" -out \"$crt_file\" -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US'\n    check_exit_status \"SSL certificates creation failed.\" $?\n    echo \"SSL certificates created\"\n  else\n    echo \"SSL certificates already exist\"\n  fi\n  debug \"ssl_setup end\"\n}\n\nfunction template_setup(){\n  # add db informations to django template\n  debug \"template_setup\"\n  rm -r ../tests/project_template_tmp/\n  cp -r ../tests/project_template ../tests/project_template_tmp\n\n  # add mysql host name to the mysql container\n  sed -i \"/'PASSWORD': '/a \\ \\ \\ \\ \\ \\ \\ \\ 'HOST': 'db',\" \"$SETTINGS_TPL\"\n\n  # remove concurrent_log_handler config and use file handler\n  sed -i \"/ConcurrentRotatingFileHandler/c\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ 'class': 'logging.FileHandler',\" \"$SETTINGS_TPL\"\n  sed -i '/maxBytes/d' \"$SETTINGS_TPL\"\n  sed -i '/backupCount/d' \"$SETTINGS_TPL\"\n  check_exit_status \"add mysql HOST failed\" $?\n\n  # set up django settings file\n  sed -i \"s|{{ db_name }}|$answer_db_name|g\"  $SETTINGS_TPL\n  sed -i \"s|{{ db_user }}|$answer_db_user|g\"  $SETTINGS_TPL\n  sed -i \"s|{{ db_password }}|$answer_db_password|g\"  $SETTINGS_TPL\n  sed -i \"s|{{ project_root }}|\\/src\\/pyscada\\/|g\"  $SETTINGS_TPL\n  sed -i \"s|{{ log_file_dir }}|\\/src\\/pyscada\\/|g\"  $SETTINGS_TPL\n  sed -i \"s|{{ project_admins\\|safe }}|$project_admins|g\"  $SETTINGS_TPL\n  sed -i \"s|{{ auto_add_apps }}|$answer_auto_add_apps|g\"  $SETTINGS_TPL\n  sed -i \"s|{{ additional_apps }}||g\"  $SETTINGS_TPL\n  sed -i \"s|{{ additional_settings }}||g\"  $SETTINGS_TPL\n\n  rm ./pyscada/project_template.zip\n  cd ../tests/project_template_tmp\n  zip -r ../../docker/pyscada/project_template.zip .\n  check_exit_status \"Unable to create project_template.zip\" $?\n  cd ../../docker\n\n  rm ./pyscada/pyscada.zip\n  zip -r ./pyscada/pyscada.zip .. -x \"../docs/*\" \"../.git/*\" \"../tests/*\" \"../docker/*\" \"../__pycache__/*\"\n\n  debug \"template_setup end\"\n}\n\n# Verify if docker-compose is installed\nif ! command -v docker-compose >/dev/null 2>&1; then\n  >&2 echo \"Docker Compose is not installed.\"\n  exit -1\nfi\n\nsystemctl is-active docker.service\ncheck_exit_status \"docker service is not running. Start it using : sudo systemctl start docker.service\" $?\n\necho \"The installation may take some time\"\nsleep 0\n\ncommand -v zip >/dev/null 2>&1\ncheck_exit_status \"Zip is not installed, install it with : sudo apt install zip\" $?\n\n# Execute commands from ./docker\ncd docker\ncheck_exit_status \"The docker directory is missing.\" $?\n\nssl_setup\nwait\n\nquestions_setup\nwait\n\ndb_setup\n\ntemplate_setup\nwait\n\n# Build the image with the --no-cache argument\ndocker-compose build --no-cache\ncheck_exit_status \"The image build failed.\" $?\n\n# Execute pyscada init in the docker container\ndocker-compose run pyscada /src/pyscada/pyscada_init\ncheck_exit_status \"pyscada_init failed.\" $?\n\n# Execute docker-compose up in the background\ndocker-compose up -d\nsleep 10\n\n# Execute into the container the superuseer creation\n\necho \"Create admin account for the web interface : \"\ndocker-compose run pyscada python3 /src/pyscada/manage.py createsuperuser\ncheck_exit_status \"Superuser creation failed.\" $?\n\n# Verify if the containers are running\nif docker ps | grep \"pyscada\" >/dev/null; then\n  echo \"PyScada installed\"\n  echo \"Connect to http://127.0.0.1 using admin account created previously\"\nelse\n  >&2 echo \"docker-compose up failed.\"\n  exit -1\nfi\n"
  },
  {
    "path": "install_venv.sh",
    "content": "#!/bin/bash\n\n# todo : add inputs for django admin name, password\n\n# check if the script is run from script directory\nSOURCE=${BASH_SOURCE[0]}\nwhile [ -L \"$SOURCE\" ]; do # resolve $SOURCE until the file is no longer a symlink\n  DIR=$( cd -P \"$( dirname \"$SOURCE\" )\" >/dev/null 2>&1 && pwd )\n  SOURCE=$(readlink \"$SOURCE\")\n  [[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located\ndone\nDIR=$( cd -P \"$( dirname \"$SOURCE\" )\" >/dev/null 2>&1 && pwd )\n\nif [[ \"$DIR\" != \"$PWD\" ]]; then\n  echo \"You must run this script from $DIR\"\n  exit 1\nfi\n\n# Make sure only root can run our script\nif [[ $EUID -ne 0 ]]; then\n  echo \"This script must be run as root\"\n  exit 1\nfi\n\n\n# PATHS CONST\nINSTALL_ROOT=/var/www/pyscada # files will be installed here\nlog_file_dir=\"/var/log/pyscada/\" # log files will be here\nSERVER_ROOT=$INSTALL_ROOT/PyScadaServer # django project root\npyscada_home=/home/pyscada\npyscada_venv=$pyscada_home/.venv\n\n# VAR\nanswer_date=\"\"            # Is the date correct\nanswer_proxy=\"\"           # Setup of proxy\nanswer_config=\"\"          # Will it be install on docker or on venv\n\nanswer_db_name=\"\"         # Name of the database\nanswer_db_user=\"\"         # Username for the database (not the root)\nanswer_db_password=\"\"     # Password for the database (not the root)\n\nanswer_channels=\"\"        # Install or not channels and redis\nanswer_update=\"\"          # The installation is just an update or not\nanswer_auto_add_apps=\"\"   # Auto add apps or manual add (in django configuration)\n\nanswer_admin_name=\"\"      # admin name (for errors output)\nanswer_admin_mail=\"\"      # admin mail (for errors output)\n\nanswer_web_name=\"\"        # web interface admin name\nanswer_web_password=\"\"    # web interface admin password\n\necho -e \"\\nPyScada python packages will be installed in the virtual environment $pyscada_venv\"\n\nfunction debug(){\n  message=$1\n  echo \"\"\n  echo $message 1>&2\n  echo \"\"\n}\n\n# called in questions_setup\nfunction regex_proxy(){\n  echo \"regex_proxy\" 1>&2\n  regex='^(https?|ftp)://[0-9a-zA-Z.-]+:[0-9]+$';\n  while true; do\n    read -p \"Use proxy? [http://proxy:port or n]: \" answer_proxy\n    if [[ $answer_proxy == \"n\" || $answer_proxy =~ $regex ]]; then\n      break\n    else\n      echo \"Choose a valid proxy\"\n    fi\n  done\n  echo \"regex_proxy end\" 1>&2\n}\n\nfunction pip3_proxy(){\n  if [[ \"$answer_proxy\" == \"n\" ]]; then\n    pip3 $*\n  else\n    echo \"pip3 using\" $answer_proxy \"for\" $* > /dev/tty\n    pip3 --proxy=$answer_proxy $*\n  fi\n}\n\nfunction pip3_proxy_not_rust(){\n  if [[ \"$answer_proxy\" == \"n\" ]]; then\n    CRYPTOGRAPHY_DONT_BUILD_RUST=1 pip3 install cryptography==3.4.6 --no-cache-dir\n    pip3 $*\n  else\n    echo \"pip3 using\" $answer_proxy \"for\" $* > /dev/tty\n    CRYPTOGRAPHY_DONT_BUILD_RUST=1 pip3 --proxy=$answer_proxy install cryptography==3.4.6 --no-cache-dir\n    pip3 --proxy=$answer_proxy $*\n  fi\n}\n\nfunction apt_proxy(){\n  if [[ \"$answer_proxy\" == \"n\" ]]; then\n    apt-get $*\n  else\n    echo \"apt using\" $answer_proxy \"for\" $* > /dev/tty\n    export http_proxy=$answer_proxy\n    apt-get $*\n    unset http_proxy\n  fi\n}\n\n# called in questions_clean_inst_setup\nfunction regex_mail(){\n  debug \"regex_mail\"\n\n  regex='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$';\n  while true; do\n    read -p \"admin mail ? \" answer_admin_mail\n    if [[ $answer_admin_mail =~ $regex ]]; then\n      break\n    else\n      echo \"Choose a valid mail\"\n    fi\n  done\n\n  debug \"regex_mail end\"\n}\n\n# called in questions_clean_inst_setup\nfunction admin_name_setup(){\n  debug \"admin_name_setup\"\n\n  while true; do\n    read -p \"admin name ? \" answer_admin_name\n    if [[ \"$answer_admin_name\" == \"\" ]]; then\n      echo \"Choose a valid name\"\n    else\n      break\n    fi\n  done\n\n  debug \"admin_name_setup end\"\n}\n\n# called in questions_setup\nfunction questions_clean_install_setup(){\n  debug \"questions_clean_install_setup\"\n\n  read -p \"DB name ? [PyScada_db]: \" answer_db_name\n  read -p \"DB user ? [PyScada-user]: \" answer_db_user\n  read -sp \"DB password ? [PyScada-user-password]: \" answer_db_password\n  echo \"\"\n\n  admin_name_setup\n  regex_mail\n  project_admins=$(echo \"('${answer_admin_name}', '${answer_admin_mail}' )\")\n  echo $project_admins\n  echo $answer_db_name\n\n  read -p \"web interface admin name [pyscada]: \" answer_web_name\n  read -p \"web interface admin password [password]: \" answer_web_password\n\n  if [[ \"$answer_db_name\" == \"\" ]]; then\n    answer_db_name=\"PyScada_db\"\n  fi\n  if [[ \"$answer_db_user\" == \"\" ]]; then\n    answer_db_user=\"PyScada-user\"\n  fi\n  if [[ \"$answer_db_password\" == \"\" ]]; then\n    answer_db_password=\"PyScada-user-password\"\n  fi\n\n  if [[ \"$answer_web_name\" == \"\" ]]; then\n    answer_web_name=\"pyscada\"\n  fi\n  if [[ \"$answer_web_password\" == \"\" ]]; then\n    answer_web_password=\"password\"\n  fi\n\n  while true; do\n    read -p \"Auto load pyscada plugins installed ? If False, you need to edit the settings.py file manually to load a plugin. [True/False]: \" answer_auto_add_apps\n    if [[ \"$answer_auto_add_apps\" == \"True\" ]]; then\n      echo 'You need to restart pyscada and gunicorn after (un)installing any pyscada plugin.'\n      break;\n    elif [[ \"$answer_auto_add_apps\" == \"False\" ]]; then\n      echo 'You need manually add a plugin to the django project settings and restart pyscada and gunicorn after (un)installing any pyscada plugin.'\n      break;\n    else\n      echo \"Please answer True or False.\"\n    fi\n  done\n\n  debug \"questions_clean_install_setup end\"\n}\n\n# called in the core of the script\nfunction questions_setup(){\n  debug \"questions_setup\"\n\n  # Date verification\n  echo 'date :'\n  echo $(date)\n  read -p \"Is the date and time correct ? [y/n]: \" answer_date\n  if [[ \"$answer_date\" != \"y\" ]]; then\n    echo \"please set the date correctly or enter 'y'\"\n    exit 1\n  fi\n\n  # Proxy setup\n  regex_proxy\n\n  # Channels and redis\n  read -p \"Install channels and redis to speed up inter pyscada process communications ? [y/n]: \" answer_channels\n\n  # Clean installation or not\n  while true; do\n    read -p \"Update only : if 'y' it will not create DB, superuser, copy services, settings and urls... On a fresh install you should answer 'n' ? [y/n]: \" answer_update\n    if [[ \"$answer_update\" == \"y\" ]]; then\n      break\n    elif [[ \"$answer_update\" == \"n\" ]]; then\n      break\n    else\n      echo \"Please answer y or n.\"\n    fi\n  done\n\n  if [[ \"$answer_update\" == \"n\" ]]; then\n    questions_clean_install_setup\n  fi\n\n  debug \"questions_setup end\"\n}\n\n# called in the core of the script\nfunction install_dependences(){\n  debug \"install_dependences\"\n\n  apt_proxy install -y python3-pip\n  echo 'Some python3 packages installed:'\n  # Install prerequisites\n  DEB_TO_INSTALL=\"\n    libatlas-base-dev\n    libopenblas-dev\n    libffi-dev\n    libhdf5-dev\n    libjpeg-dev\n    libmariadb-dev\n    libopenjp2-7\n    mariadb-server\n    nginx\n    python3-dev\n    python3-mysqldb\n    python3-pip\n    python3-venv\n    zlib1g-dev\n    pkg-config\n  \"\n  apt_proxy install -y $DEB_TO_INSTALL\n\n  # Create virtual environment\n  sudo -u pyscada python3 -m venv $pyscada_venv\n  # activate\n  source $pyscada_venv/bin/activate\n\n  PIP_TO_INSTALL=\"\n    cffi\n    Cython\n    docutils\n    gunicorn\n    lxml\n    mysqlclient\n    numpy\n  \"\n  pip3_proxy install --upgrade $PIP_TO_INSTALL\n\n  debug \"install_dependences end\"\n}\n\n# called in pyscada_init\nfunction web_setup(){\n  debug \"web_setup\"\n\n  (\n    cd $SERVER_ROOT\n  sudo -u pyscada -E env PATH=${PATH} python3 manage.py shell << EOF\ntry:\n  from django.contrib.auth import get_user_model\n  from django.db.utils import IntegrityError\n  User = get_user_model()\n  User.objects.create_superuser('$answer_web_name',\n                                'team@pyscada.org',\n                                '$answer_web_password')\nexcept IntegrityError:\n  print('User pyscada already exist')\nEOF\n  )\n  # Nginx\n  cp extras/nginx_sample.conf /etc/nginx/sites-available/pyscada.conf\n  ln -sf ../sites-available/pyscada.conf /etc/nginx/sites-enabled/\n  rm -f /etc/nginx/sites-enabled/default\n  mkdir -p /etc/nginx/ssl\n  # the certificate will be valid for 5 Years,\n  openssl req -x509 -nodes -days 1780 -newkey rsa:2048 -keyout /etc/nginx/ssl/pyscada_server.key -out /etc/nginx/ssl/pyscada_server.crt -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US'\n  systemctl enable nginx.service # enable autostart on boot\n  systemctl restart nginx\n\n  # Gunicorn and PyScada as systemd units\n  cp extras/service/systemd/{gunicorn.{socket,service},pyscada_daemon.service} /etc/systemd/system\n  # Rename PyScada service file\n  mv /etc/systemd/system/pyscada_daemon.service /etc/systemd/system/pyscada.service\n\n  # Fix if gunicorn installed in /usr/bin and not /usr/local/bin -> create symbolic link\n  if [[ $(which gunicorn) != /usr/local/bin/gunicorn ]] && [[ ! -f /usr/local/bin/gunicorn ]] && [[ -f /usr/bin/gunicorn ]]; then\n      echo \"Creating symcolic link to gunicorn\" ;\n      ln -s /usr/bin/gunicorn /usr/local/bin/gunicorn;\n  fi\n\n  debug \"web_setup end\"\n}\n\n# called in the core of the script\nfunction install_channel_redis(){\n  debug \"install_channel_redis\"\n\n  apt_proxy -y install redis-server\n  if grep -R \"Raspberry Pi 3\"  \"/proc/device-tree/model\" ; then\n    echo \"Don't install Rust for RPI3\"\n    pip3_proxy_not_rust install --upgrade channels channels-redis asgiref\n  else\n    pip3_proxy install --upgrade cryptography==3.4.6 channels channels-redis asgiref\n  fi\n\n  debug \"install_channel_redis end\"\n}\n\n# called in the core of the script\nfunction pyscada_init(){\n  debug \"pyscada_init\"\n\n  (\n      cd $SERVER_ROOT\n      # Migration and static files\n      sudo -u pyscada -E env PATH=${PATH} python3 manage.py migrate\n      sudo -u pyscada -E env PATH=${PATH} python3 manage.py collectstatic --noinput\n\n      # Load fixtures with default configuration for chart lin colors and units\n      sudo -u pyscada -E env PATH=${PATH} python3 manage.py loaddata color\n      sudo -u pyscada -E env PATH=${PATH} python3 manage.py loaddata units\n\n      # Initialize the background service system of pyscada\n      sudo -u pyscada -E env PATH=${PATH} python3 manage.py pyscada_daemon init\n  )\n\n  if [[ \"$answer_update\" == \"n\" ]]; then\n    web_setup\n  fi\n\n  # enable the services for autostart\n  systemctl enable gunicorn\n  systemctl restart gunicorn\n  systemctl enable pyscada\n  systemctl restart pyscada\n  sleep 1\n  systemctl --quiet is-active pyscada\n  if [ $? != 0 ] ; then\n      echo \"Can't start pyscada systemd service.\" > /dev/stderr\n      exit 1\n  fi\n\n  if [[ \"$answer_update\" == \"n\" ]]; then\n    echo \"PyScada installed\"\n    echo \"Connect to http://127.0.0.1 using :\"\n    echo \"username : $answer_web_name\"\n    echo \"password : $answer_web_password\"\n  else\n    echo \"PyScada updated\"\n  fi\n\n  debug \"pyscada_init end\"\n}\n\n# called in the core of the script\nfunction db_setup(){\n  debug \"db_setup\"\n\n  # Create DB\n  mysql << EOF\n    CREATE DATABASE IF NOT EXISTS ${answer_db_name}\n      CHARACTER SET utf8;\n    GRANT ALL PRIVILEGES ON ${answer_db_name}.*\n      TO '${answer_db_user}'@'localhost'\n      IDENTIFIED BY '${answer_db_password}';\nEOF\n\n  debug \"db_setup end\"\n}\n\n#called in the core of the script\nfunction template_setup(){\n  debug \"template_setup\"\n\n  # add db informations to django template\n  rm -r ./tests/project_template_tmp/\n  cp -r ./tests/project_template ./tests/project_template_tmp/\n  chmod a+w ./tests/project_template_tmp/project_name/settings.py-tpl\n  sudo -u pyscada -E env PATH=${PATH} python3 << EOF\nimport django\nfrom django.conf import settings\nfrom django.template.loader import render_to_string\n\nsettings.configure(\n    TEMPLATES=[{\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': ['./'],  # script dir\n  'OPTIONS': {'string_if_invalid': '{{ %s }}'}, # prevents the other template tags to be replaced by ''\n    }]\n)\n\ndjango.setup()\nfrom django.template import Template, Context\nwith open(\"./tests/project_template_tmp/project_name/settings.py-tpl\", \"r+\") as f:\n  template = Template(f.read())\n  context = Context({\n                \"db_name\": \"${answer_db_name}\",\n                \"db_user\": \"${answer_db_user}\",\n                \"db_password\": \"${answer_db_password}\",\n                \"project_root\": \"${INSTALL_ROOT}\",\n                \"pyscada_home\": \"${pyscada_home}\",\n                \"log_file_dir\": \"${log_file_dir}\",\n                \"project_admins\": \"${project_admins}\",\n                \"auto_add_apps\": \"${answer_auto_add_apps}\",\n                \"additional_apps\": \"\",\n                \"additional_settings\": \"\",\n                })\n  f.seek(0)\n  f.write(template.render(context))\n  f.truncate()\nEOF\n\n  sudo -u pyscada mkdir -p $SERVER_ROOT\n  sudo -u pyscada -E env PATH=${PATH} django-admin startproject PyScadaServer $SERVER_ROOT --template ./tests/project_template_tmp\n  rm -rf ./tests/project_template_tmp\n\n  debug \"template_setup end\"\n}\n\n# called in the core of the script\nfunction user_setup(){\n  debug \"user_setup\"\n\n  # Create pyscada user\n  echo \"Creating system user pyscada...\"\n  useradd -r pyscada\n  mkdir -p $pyscada_home\n  chown -R pyscada:pyscada $pyscada_home\n  mkdir -p $INSTALL_ROOT\n  chown -R pyscada:pyscada $INSTALL_ROOT\n  mkdir -p $pyscada_home/measurement_data_dumps\n  chown -R pyscada:pyscada $pyscada_home/measurement_data_dumps\n\n  mkdir ${log_file_dir}\n  chown pyscada:pyscada ${log_file_dir}\n  touch ${log_file_dir}pyscada_{daemon,debug}.log\n  chown pyscada:pyscada ${log_file_dir}pyscada_{daemon,debug}.log\n\n  # Add rights for usb, i2c and serial\n  adduser www-data pyscada\n  adduser pyscada dialout\n\n  debug \"user_setup end\"\n}\n\n# stop pyscada and show some python3 packages installed\nfunction stop_pyscada(){\n  debug \"stop_pyscada\"\n\n  echo \"Stopping PyScada\"\n  systemctl stop pyscada gunicorn gunicorn.socket\n  sleep 1 # Give systemd time to shutdown\n  systemctl --quiet is-active pyscada\n  if [ $? == 0 ] ; then\n    echo \"Can't stop pyscada systemd service. Aborting.\"\n    exit 1\n  fi\n  echo \"PyScada stopped\"\n\n  debug \"stop_pyscada end\"\n}\n\n# install process: * means depending on the user answer\n: <<'END'\n- questions_setup\n  - regex_proxy\n  - *questions_clean_install_setup\n    - admin_name_setup\n    - regex_mail\n- stop_pyscada\n- user_setup\n- install_dependences\n  - apt_proxy\n  - pip3_proxy\n- pyscada install\n- *install_channel_redis\n  - apt_proxy\n  - pip3_proxy_not_rust\n  - pip3_proxy\n- *db_setup\n- *template_setup\n- pyscada_init\n  - *web_setup\nEND\n\nquestions_setup\n\nstop_pyscada\n\nuser_setup\n\ninstall_dependences\n\n# Install PyScada\npip3_proxy install --upgrade .\n\n\nif [[ \"$answer_channels\" == \"y\" ]]; then\n  install_channel_redis\nfi\n\nif [[ \"$answer_update\" == \"n\" ]]; then\n  db_setup\n  template_setup\nfi\n\npyscada_init\n\n# fix owner in /home/pyscada\nchown -R pyscada:pyscada $pyscada_home\n"
  },
  {
    "path": "move_data.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\ncopies data from pyscada.models.RecordedDataOld to RecordedData\nthe script will terminate after 5 minutes, if you would like to copy more content change the timeout accordingly\n\"\"\"\n\nfrom __future__ import unicode_literals\nimport os\n\nimport gc\nfrom datetime import datetime\nfrom time import time\nfrom pytz import UTC\nimport logging\n\nlogger = logging.getLogger(\"pyscada.core.move_data\")\n\n\ndef queryset_iterator(queryset, chunk_size=100000):\n    counter = 0\n    count = chunk_size\n    while count == chunk_size:\n        offset = counter - counter % chunk_size\n        count = 0\n        for item in queryset.all()[offset : offset + chunk_size]:\n            count += 1\n            yield item\n        counter += count\n        gc.collect()\n\n\nif __name__ == \"__main__\":\n    os.chdir(\"/var/www/pyscada/PyScadaServer\")\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"PyScadaServer.settings\")\n    import django\n\n    django.setup()\n    from pyscada.models import RecordedData, RecordedDataOld\n\n    min_pk = RecordedData.objects.first().pk\n    recoded_data_set = queryset_iterator(\n        RecordedDataOld.objects.filter(pk__lt=min_pk).order_by(\"-pk\")\n    )\n    # recoded_data_set = queryset_iterator(RecordedDataOld.objects.all().order_by('-pk'))\n    # logger.info('%d'%min_pk)\n    # logger.info('%d'%RecordedDataOld.objects.filter(pk__lt=min_pk).count())\n    count = 0\n    count_all = 0\n    timeout = time() + 60 * 10  # <-- set the timeout in minutes\n    items = []\n    for item in recoded_data_set:\n        # if RecordedData.objects.filter(pk=item.id):\n        #    continue\n        items.append(\n            RecordedData(\n                id=item.id,\n                variable_id=item.variable_id,\n                value_boolean=item.value_boolean,\n                value_int16=item.value_int16,\n                value_int32=item.value_int32,\n                value_int64=item.value_int64,\n                value_float64=item.value_float64,\n                date_saved=datetime.fromtimestamp(\n                    (item.pk - item.variable.pk) / 2097152 / 1000.0, UTC\n                ),\n            )\n        )\n        if time() > timeout:\n            break\n\n        count += 1\n        if count >= 10000:\n            RecordedData.objects.bulk_create(items)\n            items = []\n            count_all += count\n            count = 0\n            logger.info(\"wrote %d lines in total\\n\" % count_all)\n\n    if len(items) > 0:\n        RecordedData.objects.bulk_create(items)\n        count_all += len(items)\n        logger.info(\"wrote %d lines in total\\n\" % count_all)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=61.0.0,<69.3.0\"]\nbuild-backend = \"setuptools.build_meta:__legacy__\"\n\n[project]\nname = \"PyScada\"\ndynamic = [\"version\"]\nrequires-python = \">= 3.10\"\ndependencies = [\n    \"django>=5.2,<5.3\",\n    \"numpy>=1.6.0\",\n    \"h5py>=2.2.1\",\n    \"pillow\",\n    \"python-daemon>=2.0.0\",\n    \"pytz\",\n    \"python-dateutil\",\n    # 'channels',\n    # 'channels-redis',\n    \"asgiref\",\n    \"monthdelta\",\n    \"six\",\n    \"concurrent-log-handler\",  # rotating logs for multiprocess\n    \"scipy\",\n]\nauthors = [\n  {name = \"Martin Schröder\", email = \"team@pyscada.org\"},\n  {name = \"Camille Lavayssiere\", email = \"team@pyscada.org\"}\n]\ndescription=\"A Python and Django based Open Source SCADA System\"\nreadme = \"README.rst\"\nlicense = {text = \"AGPLv3\"}\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Environment :: Web Environment\",\n    \"Environment :: Console\",\n    \"Framework :: Django\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)\",\n    \"Operating System :: POSIX\",\n    \"Operating System :: MacOS :: MacOS X\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: JavaScript\",\n    \"Topic :: Internet :: WWW/HTTP :: Dynamic Content\",\n    \"Topic :: Scientific/Engineering :: Visualization\",\n]\n\n\n[project.urls]\nHomepage = \"https://www.pyscada.org/\"\nDocumentation = \"https://pyscada.readthedocs.io\"\nSource = \"https://github.com/pyscada/PyScada\"\nTracker = \"https://github.com/pyscada/PyScada/issues\"\n\n[tool.black]\ntarget-version = [\"py310\"]\nforce-exclude = \"tests/test_runner_apps/tagged/tests_syntax_error.py\"\n\n[tool.isort]\nprofile = \"black\"\ndefault_section = \"THIRDPARTY\"\nknown_first_party = \"pyscada\"\n\n[tool.setuptools.packages.find]\ninclude = [\"pyscada*\"]\n"
  },
  {
    "path": "pyscada/__init__.py",
    "content": "from pkgutil import extend_path\n\n__path__ = extend_path(__path__, __name__)\n"
  },
  {
    "path": "pyscada/admin.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.models import Device, DeviceProtocol, DeviceHandler\nfrom pyscada.models import Variable, VariableProperty, DataSource, DataSourceModel\nfrom pyscada.models import Scaling, Color\nfrom pyscada.models import Unit, Dictionary, DictionaryItem\nfrom pyscada.models import DeviceWriteTask, DeviceReadTask\nfrom pyscada.models import Log\nfrom pyscada.models import BackgroundProcess\nfrom pyscada.models import (\n    ComplexEvent,\n    ComplexEventLevel,\n    ComplexEventInput,\n    ComplexEventOutput,\n)\nfrom pyscada.models import Event\nfrom pyscada.models import RecordedEvent\nfrom pyscada.models import Mail\nfrom pyscada.models import DeviceHandlerParameter, VariableHandlerParameter\n\nfrom django.contrib import messages\nfrom django.contrib import admin\nfrom django import forms\nfrom django.contrib.admin import AdminSite\nfrom django.contrib.auth.models import User, Group\nfrom django.contrib.auth.admin import UserAdmin, GroupAdmin\nfrom django.utils.translation import gettext_lazy as _\nfrom django.db.models.fields.related import OneToOneRel\nfrom django.utils.html import mark_safe\nfrom django.forms import BaseInlineFormSet\nfrom django.core.exceptions import ValidationError\n\nfrom django import forms\nfrom django.db.utils import ProgrammingError, OperationalError\nfrom django.conf import settings\n\nimport sys\nimport datetime\nimport signal\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\n# Custom AdminSite\n\n\nclass PyScadaAdminSite(AdminSite):\n    site_header = \"PyScada Administration\"\n\n\ndef populate_inline(items, form_model=None, output=[], stacked=admin.StackedInline):\n    for item in items:\n        if form_model is None:\n            item_dict = dict(model=item.related_model)\n        else:\n            item_dict = dict(model=item.related_model, form=form_model)\n        if hasattr(item.related_model, \"fk_name\"):\n            item_dict[\"fk_name\"] = item.related_model.fk_name\n        if hasattr(item.related_model, \"FormSet\"):\n            item_dict[\"formset\"] = item.related_model.FormSet\n        if hasattr(item.related_model, \"fieldsets\"):\n            item_dict[\"fieldsets\"] = item.related_model.fieldsets\n        if hasattr(item.related_model, \"filter_horizontal\"):\n            item_dict[\"filter_horizontal\"] = item.related_model.filter_horizontal\n        if hasattr(item.related_model, \"filter_vertical\"):\n            item_dict[\"filter_vertical\"] = item.related_model.filter_vertical\n        if hasattr(item.related_model, \"Form\"):\n            items_dict[\"form\"] = item.related_model.Form\n        # if hasattr(item.related_model, \"formfield_for_foreignkey\"):\n        #    item_dict[\"formfield_for_foreignkey\"] = item.related_model.formfield_for_foreignkey\n        out = type(item.name, (stacked,), item_dict)  # classes=['collapse']\n        output.append(out)\n    return output\n\n\n## admin actions\ndef restart_process(_modeladmin, request, queryset):\n    \"\"\"\n    restarts a dedicated process\n    :return:\n    \"\"\"\n    for process in queryset:\n        process.restart()\n\n\nrestart_process.short_description = \"Restart Processes\"\n\n\ndef stop_process(_modeladmin, request, queryset):\n    \"\"\"\n    restarts a dedicated process\n    :return:\n    \"\"\"\n    for process in queryset:\n        process.stop()\n\n\nstop_process.short_description = \"Terminate Processes\"\n\n\ndef kill_process(_modeladmin, request, queryset):\n    \"\"\"\n    restarts a dedicated process\n    :return:\n    \"\"\"\n    for process in queryset:\n        process.stop(signum=signal.SIGKILL)\n\n\nkill_process.short_description = \"Kill Processes\"\n\n\ndef silent_delete(self, _request, queryset):\n    queryset.delete()\n\n\nsilent_delete.short_description = \"Silent delete (lot of records)\"\n\n\n# Custom Filters\nclass BackgroundProcessFilter(admin.SimpleListFilter):\n    # Human-readable title which will be displayed in the\n    # right admin sidebar just above the filter options.\n    title = _(\"parent process filter\")\n\n    # Parameter for the filter that will be used in the URL query.\n    parameter_name = \"parent_process\"\n\n    def lookups(self, request, model_admin):\n        \"\"\"\n        Returns a list of tuples. The first element in each\n        tuple is the coded value for the option that will\n        appear in the URL query. The second element is the\n        human-readable name for the option that will appear\n        in the right sidebar.\n        \"\"\"\n        qs = model_admin.get_queryset(request)\n        qs.filter(id__range=(1, 99))\n        for item in qs:\n            dp = DeviceProtocol.objects.filter(pk=item.id).first()\n            if dp:\n                yield (dp.pk, dp.app_name)\n\n    def queryset(self, request, queryset):\n        \"\"\"\n        Returns the filtered queryset based on the value\n        provided in the query string and retrievable via\n        `self.value()`.\n        \"\"\"\n        if self.value() is not None:\n            if int(self.value()) > 0:\n                return queryset.filter(parent_process_id=self.value())\n\n\n# Admin models\nclass VariableInlineAdminFrom(forms.ModelForm):\n    def has_changed(self):\n        # Force save inline for the good protocol if selected device and protocol_id exists\n        if (\n            self.data.get(\"device\", None) != \"\"\n            and self.data.get(\"device\", None) is not None\n        ):\n            d = Device.objects.get(id=int(self.data.get(\"device\", None)))\n            if (\n                hasattr(self.instance, \"protocol_id\")\n                and d is not None\n                and d.protocol.id == self.instance.protocol_id\n            ):\n                return True\n        return False\n\n\nclass VariableAdminFrom(forms.ModelForm):\n    def __init__(self, *args, **kwargs):\n        super(VariableAdminFrom, self).__init__(*args, **kwargs)\n\n        # disable delete button for datasource foreign key\n        if \"datasource\" in self.fields:\n            self.fields[\"datasource\"].widget.can_delete_related = False\n\n        if isinstance(self.instance, Variable):\n            wtf = Color.objects.all()\n            if \"chart_line_color\" in self.fields:\n                w = self.fields[\"chart_line_color\"].widget\n                color_choices = []\n                for choice in wtf:\n                    color_choices.append((choice.id, choice.color_code()))\n                w.choices = color_choices\n\n                def create_option_color(\n                    self, name, value, label, selected, index, subindex=None, attrs=None\n                ):\n                    font_color = hex(int(\"ffffff\", 16) - int(label[1::], 16))[2::]\n                    # attrs = self.build_attrs(attrs,{'style':'background: %s; color: #%s'%(label,font_color)})\n                    self.option_inherits_attrs = True\n                    return self._create_option(\n                        name,\n                        value,\n                        label,\n                        selected,\n                        index,\n                        subindex,\n                        attrs={\n                            \"style\": \"background: %s; color: #%s\" % (label, font_color)\n                        },\n                    )\n\n                import types\n\n                # from django.forms.widgets import Select\n                w.widget._create_option = w.widget.create_option  # copy old method\n                w.widget.create_option = types.MethodType(\n                    create_option_color, w.widget\n                )  # replace old with new\n                w.widget.attrs = {\n                    \"onchange\": \"this.style.backgroundColor=this.options[this.selectedIndex].style.\"\n                    \"backgroundColor;this.style.color=this.options[this.selectedIndex].style.color\"\n                }\n\n\nclass VariableState(Variable):\n    class Meta:\n        proxy = True\n\n\nclass VariableStateAdmin(admin.ModelAdmin):\n    list_display = (\"name\", \"last_value\")\n    list_filter = (\"device__short_name\", \"active\", \"unit__unit\", \"value_class\")\n    list_display_links = ()\n    list_per_page = 10\n    actions = [silent_delete]\n    search_fields = (\"name\",)\n    form = VariableAdminFrom\n\n    # Add inlines for any model with OneToOne relation with Device\n    related_variables = [\n        field\n        for field in Variable._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    inlines = populate_inline(\n        related_variables,\n        VariableInlineAdminFrom,\n        output=[],\n        stacked=admin.StackedInline,\n    )\n\n    class Media:\n        js = (\n            # To be sure the jquery files are loaded before our js file\n            \"admin/js/vendor/jquery/jquery.min.js\",\n            \"admin/js/jquery.init.js\",\n            \"pyscada/js/admin/display_inline_protocols_variable.js\",\n        )\n\n    def last_value(self, instance):\n        try:\n            v = Variable.objects.get(id=instance.pk)\n            if v.check_last_datapoint():\n                try:\n                    return f\"{datetime.datetime.fromtimestamp(v.timestamp_old).strftime('%Y-%m-%d %H:%M:%S')} : {v.prev_value.__str__()} {instance.unit.unit}\"\n                except ValueError as e:\n                    return f\"ValueError {e} - with timestamp {v.timestamp_old} : {v.prev_value.__str__()} {instance.unit.unit}\"\n        except Variable.DoesNotExist:\n            return \"Variable does not exist\"\n        except TimeoutError:\n            return \"Timeout on value query\"\n        return f\" - : NaN {instance.unit.unit}\"\n\n\nclass DeviceForm(forms.ModelForm):\n    def has_changed(self):\n        # Force save inline for the right protocol if parent_device() and protocol_id exists\n        if self.data.get(\"protocol\", None) is not None:\n            if hasattr(self.instance, \"protocol_id\") and self.data.get(\n                \"protocol\", None\n            ) == str(self.instance.protocol_id):\n                return True\n        else:\n            if (\n                hasattr(self.instance, \"protocol_id\")\n                and hasattr(self.instance, \"parent_device\")\n                and self.instance.parent_device() is not None\n                and self.instance.parent_device().protocol is not None\n                and self.instance.parent_device().protocol.id\n                == self.instance.protocol_id\n            ):\n                return True\n        return super().has_changed()\n\n\nclass DeviceHandlerParameterInlineForm(forms.ModelForm):\n    class Meta:\n        model = DeviceHandlerParameter\n        fields = (\"value\",)\n\n\nclass DeviceHandlerParameterInlineFormSet(BaseInlineFormSet):\n    def clean(self):\n        super().clean()\n        raise_error = []\n        if self.instance.instrument_handler is not None:\n            parameters = self.instance.instrument_handler.get_device_parameters()\n        else:\n            parameters = {}\n        for parameter in parameters:\n            parameters[parameter][\"found\"] = False\n        result_forms = []\n        for form in self.forms:\n            if form.instance.name in parameters.keys():\n                if parameters[form.instance.name][\"found\"]:\n                    # DeviceHandlerParameter already found, delete duplicate\n                    form.instance.delete()\n                else:\n                    parameters[form.instance.name][\"found\"] = True\n                    if (\n                        not parameters[form.instance.name].get(\"null\", True)\n                        and form.instance.value == \"\"\n                    ):\n                        # value is needed\n                        raise_error.append(form.instance.name)\n                result_forms.append(form)\n            else:\n                # DeviceHandlerParameter not needed\n                try:\n                    form.instance.delete()\n                except ValueError:\n                    pass\n        self.forms = result_forms\n        # TODO : redirect to a specific page if parameters was missing on the add/change page, in order to create these parameters\n        if len(raise_error):\n            raise ValidationError(\n                f\"Value is required for parameters {','.join([str(x) for x in raise_error])}\"\n            )\n\n\nclass DeviceAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"short_name\",\n        \"description\",\n        \"protocol\",\n        \"active\",\n        \"polling_interval\",\n        \"instrument_handler\",\n    )\n    list_editable = (\"active\", \"polling_interval\", \"instrument_handler\")\n    list_display_links = (\n        \"short_name\",\n        \"description\",\n    )\n    list_filter = (\n        \"protocol\",\n        \"active\",\n        \"polling_interval\",\n    )\n    actions = [silent_delete]\n    save_as = True\n    save_as_continue = True\n    form = DeviceForm\n\n    # Add inlines for any model with OneToOne relation with Device\n    devices = [\n        field\n        for field in Device._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    inlines = populate_inline(\n        devices, DeviceForm, output=[], stacked=admin.StackedInline\n    )\n\n    item_dict = dict(\n        model=DeviceHandlerParameter,\n        max_num=0,\n        can_delete=False,\n        formset=DeviceHandlerParameterInlineFormSet,\n        form=DeviceHandlerParameterInlineForm,\n    )\n    inlines.append(type(\"DeviceHandlerParameter\", (admin.StackedInline,), item_dict))\n\n    def get_form(self, request, obj=None, **kwargs):\n        if (\n            kwargs.get(\"fields\", False)\n            and \"instrument_handler\" in kwargs[\"fields\"]\n            and obj is None\n        ):\n            help_texts = kwargs.get(\"help_texts\", {})\n            help_texts.update(\n                {\n                    \"instrument_handler\": \"If the handler needs specific configuration, save the device and you will add the config next.\"\n                }\n            )\n            kwargs.update({\"help_texts\": help_texts})\n        return super().get_form(request, obj, **kwargs)\n\n    def formfield_for_foreignkey(self, db_field, request, **kwargs):\n        # For new device, show all the protocols from the installed apps in settings.py\n        # For existing device, show only the selected protocol to avoid changing\n        if db_field.name == \"protocol\":\n            if (\n                \"object_id\" in request.resolver_match.kwargs\n                and Device.objects.get(id=request.resolver_match.kwargs[\"object_id\"])\n                is not None\n                and Device.objects.get(\n                    id=request.resolver_match.kwargs[\"object_id\"]\n                ).protocol\n            ):\n                kwargs[\"queryset\"] = DeviceProtocol.objects.filter(\n                    protocol__in=[\n                        Device.objects.get(\n                            id=request.resolver_match.kwargs[\"object_id\"]\n                        ).protocol.protocol,\n                    ]\n                )\n            else:\n                # List only activated protocols\n                protocol_list = list()\n                protocol_list.append(\"generic\")\n                if hasattr(settings, \"INSTALLED_APPS\"):\n                    try:\n                        for protocol in DeviceProtocol.objects.filter(\n                            app_name__in=settings.INSTALLED_APPS\n                        ):\n                            protocol_list.append(protocol.protocol)\n                    except (ProgrammingError, OperationalError) as e:\n                        logger.debug(e)\n\n                kwargs[\"queryset\"] = DeviceProtocol.objects.filter(\n                    protocol__in=protocol_list\n                )\n        return super().formfield_for_foreignkey(db_field, request, **kwargs)\n\n    # Add JS file to display the right inline and to hide/show fields\n    class Media:\n        js = (\n            # To be sure the jquery files are loaded before our js file\n            \"admin/js/vendor/jquery/jquery.min.js\",\n            \"admin/js/jquery.init.js\",\n            \"pyscada/js/admin/display_inline_protocols_device.js\",\n            \"pyscada/js/admin/hideshow.js\",\n        )\n\n\nclass DeviceHandlerAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"name\",\n        \"handler_class\",\n        \"handler_path\",\n        \"found\",\n    )\n    list_editable = (\n        \"handler_class\",\n        \"handler_path\",\n    )\n    list_display_links = (\"name\",)\n    save_as = True\n    save_as_continue = True\n    readonly_fields = [\n        \"content\",\n    ]\n\n    def has_module_permission(self, request):\n        return False\n\n    @admin.display(boolean=True)\n    def found(self, instance):\n        try:\n            if instance.handler_path is not None:\n                sys.path.append(instance.handler_path)\n            mod = __import__(instance.handler_class, fromlist=[\"Handler\"])\n            return True\n        except ModuleNotFoundError:\n            return False\n        except Exception as e:\n            logger.error(f\"Handler {self} failed to be found : {e}\")\n            return False\n\n    def content(self, instance):\n        try:\n            if instance.handler_path is not None:\n                sys.path.append(instance.handler_path)\n            if instance.handler_class in sys.modules:\n                del sys.modules[instance.handler_class]\n            mod = __import__(instance.handler_class, fromlist=[\"Handler\"])\n            if hasattr(mod, \"pyscada_admin_content\"):\n                return mark_safe(getattr(mod, \"pyscada_admin_content\"))\n            with open(mod.__file__) as f:\n                return f.read()\n        except ModuleNotFoundError:\n            return \"Handler file not found.\"\n        except Exception as e:\n            return f\"Handler reading failed : {e}\"\n\n    def get_form(self, request, obj=None, **kwargs):\n        if kwargs.get(\"fields\", False) and \"content\" in kwargs[\"fields\"]:\n            help_texts = kwargs.get(\"help_texts\", {})\n            help_texts.update(\n                {\n                    \"content\": \"Return the content of 'pyscada_admin_content' variable if defined in the handler file, otherwise return the whole file content.\"\n                }\n            )\n            kwargs.update({\"help_texts\": help_texts})\n        return super().get_form(request, obj, **kwargs)\n\n    def get_readonly_fields(self, request, obj=None):\n        if obj == None:\n            return []\n        return super().get_readonly_fields(request, obj)\n\n    class Media:\n        js = (\n            # show the contet as preformatted code\n            \"pyscada/js/admin/handler_content_as_pre.js\",\n        )\n\n\nclass VariableHandlerParameterInlineForm(forms.ModelForm):\n    class Meta:\n        model = VariableHandlerParameter\n        fields = (\"value\",)\n\n\nclass VariableHandlerParameterInlineFormSet(BaseInlineFormSet):\n    def clean(self):\n        super().clean()\n        raise_error = []\n        if self.instance.device.instrument_handler is not None:\n            parameters = (\n                self.instance.device.instrument_handler.get_variable_parameters()\n            )\n        else:\n            parameters = {}\n        for parameter in parameters:\n            parameters[parameter][\"found\"] = False\n        result_forms = []\n        for form in self.forms:\n            if form.instance.name in parameters.keys():\n                if parameters[form.instance.name][\"found\"]:\n                    # VariableHandlerParameter already found, delete duplicate\n                    form.instance.delete()\n                else:\n                    parameters[form.instance.name][\"found\"] = True\n                    if (\n                        not parameters[form.instance.name].get(\"null\", True)\n                        and form.instance.value == None\n                    ):\n                        # value is needed\n                        raise_error.append(form.instance.name)\n                result_forms.append(form)\n            else:\n                # VariableHandlerParameter not needed\n                try:\n                    form.instance.delete()\n                except ValueError:\n                    pass\n            # your custom formset validation\n        self.forms = result_forms\n        # TODO : redirect to a specific page if parameters was missing on the add/change page, in order to create these parameters\n        if len(raise_error):\n            raise ValidationError(\n                f\"Value is required for parameters {','.join([str(x) for x in raise_error])}\"\n            )\n\n\nclass VariableAdmin(admin.ModelAdmin):\n    list_filter = (\n        \"device__protocol\",\n        \"device\",\n        \"active\",\n        \"writeable\",\n        \"unit__unit\",\n        \"value_class\",\n        \"scaling\",\n    )\n    search_fields = [\n        \"name\",\n    ]\n    list_per_page = 10\n    form = VariableAdminFrom\n    save_as = True\n    save_as_continue = True\n\n    # Add inlines for any model with OneToOne relation with Device\n    related_variables = [\n        field\n        for field in Variable._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    inlines = populate_inline(\n        related_variables,\n        VariableInlineAdminFrom,\n        output=[],\n        stacked=admin.StackedInline,\n    )\n    item_dict = dict(\n        model=VariableHandlerParameter,\n        max_num=0,\n        can_delete=False,\n        formset=VariableHandlerParameterInlineFormSet,\n        form=VariableHandlerParameterInlineForm,\n    )\n    inlines.append(type(\"VariableHandlerParameter\", (admin.StackedInline,), item_dict))\n\n    def get_form(self, request, obj=None, **kwargs):\n        if kwargs.get(\"fields\", False) and \"device\" in kwargs[\"fields\"] and obj is None:\n            help_texts = kwargs.get(\"help_texts\", {})\n            help_texts.update(\n                {\n                    \"device\": \"If the device handler needs specific configuration, save the variable and you will add the config next.\"\n                }\n            )\n            kwargs.update({\"help_texts\": help_texts})\n        return super().get_form(request, obj, **kwargs)\n\n    # Add JS file to display the right inline\n    class Media:\n        js = (\n            # To be sure the jquery files are loaded before our js file\n            \"admin/js/vendor/jquery/jquery.min.js\",\n            \"admin/js/jquery.init.js\",\n            \"pyscada/js/admin/display_inline_protocols_variable.js\",\n            \"pyscada/js/admin/hideshow.js\",\n        )\n\n    def device_name(self, instance):\n        return instance.device.short_name\n\n    def unit(self, instance):\n        return instance.unit.unit\n\n    def color_code(self, instance):\n        return instance.chart_line_color.color_code()\n\n\nclass CoreVariableAdmin(VariableAdmin):\n    list_display = (\n        \"id\",\n        \"name\",\n        \"description\",\n        \"unit\",\n        \"scaling\",\n        \"device\",\n        \"value_class\",\n        \"active\",\n        \"writeable\",\n        \"dictionary\",\n    )\n    list_editable = (\n        \"active\",\n        \"writeable\",\n        \"unit\",\n        \"scaling\",\n        \"dictionary\",\n    )\n    list_display_links = (\"name\",)\n\n    def formfield_for_foreignkey(self, db_field, request, **kwargs):\n        # show only datasource with can_select as True\n        if db_field.name == \"datasource\":\n            ids = []\n            for d in DataSource.objects.all():\n                if d.datasource_model.can_select:\n                    ids.append(d.id)\n            kwargs[\"queryset\"] = DataSource.objects.filter(id__in=ids)\n        return super().formfield_for_foreignkey(db_field, request, **kwargs)\n\n\nclass ScalingAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"description\",\n        \"input_low\",\n        \"input_high\",\n        \"output_low\",\n        \"output_high\",\n        \"limit_input\",\n    )\n    list_editable = (\n        \"input_low\",\n        \"input_high\",\n        \"output_low\",\n        \"output_high\",\n        \"limit_input\",\n    )\n    list_display_links = (\"description\",)\n    save_as = True\n    save_as_continue = True\n\n\nclass DeviceWriteTaskAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"name\",\n        \"value\",\n        \"user_name\",\n        \"start_time\",\n        \"done\",\n        \"failed\",\n    )\n    # list_editable = ('active','writeable',)\n    list_display_links = (\"name\",)\n    list_filter = (\n        \"done\",\n        \"failed\",\n    )\n    raw_id_fields = (\"variable\",)\n    save_as = True\n    save_as_continue = True\n\n    def name(self, instance):\n        return instance.__str__()\n\n    def user_name(self, instance):\n        try:\n            return instance.user.username\n        except:\n            return \"None\"\n\n    def start_time(self, instance):\n        return datetime.datetime.fromtimestamp(int(instance.start)).strftime(\n            \"%Y-%m-%d %H:%M:%S\"\n        )\n\n    def has_delete_permission(self, request, obj=None):\n        return True\n\n\nclass DeviceReadTaskAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"name\",\n        \"user_name\",\n        \"start_time\",\n        \"done\",\n        \"failed\",\n    )\n    list_display_links = (\"name\",)\n    list_filter = (\n        \"done\",\n        \"failed\",\n    )\n    raw_id_fields = (\"variable\",)\n    save_as = True\n    save_as_continue = True\n\n    def name(self, instance):\n        return instance.__str__()\n\n    def user_name(self, instance):\n        try:\n            return instance.user.username\n        except:\n            return \"None\"\n\n    def start_time(self, instance):\n        return datetime.datetime.fromtimestamp(int(instance.start)).strftime(\n            \"%Y-%m-%d %H:%M:%S\"\n        )\n\n\nclass LogAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"time\",\n        \"level\",\n        \"message_short\",\n        \"user_name\",\n    )\n    list_display_links = (\"message_short\",)\n    list_filter = (\"level\", \"user\")\n    search_fields = [\n        \"message\",\n    ]\n\n    def user_name(self, instance):\n        try:\n            return instance.user.username\n        except:\n            return \"None\"\n\n    def time(self, instance):\n        return datetime.datetime.fromtimestamp(int(instance.timestamp)).strftime(\n            \"%Y-%m-%d %H:%M:%S\"\n        )\n\n    def has_add_permission(self, request):\n        return False\n\n    def has_delete_permission(self, request, obj=None):\n        return False\n\n\nclass BackgroundProcessAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"pid\",\n        \"label\",\n        \"message\",\n        \"last_update\",\n        \"running_since\",\n        \"enabled\",\n        \"done\",\n        \"failed\",\n    )\n    list_filter = (BackgroundProcessFilter, \"enabled\", \"done\", \"failed\")\n    list_display_links = (\"id\", \"label\", \"message\")\n    readonly_fields = (\"message\", \"last_update\", \"running_since\", \"done\", \"failed\")\n    actions = [restart_process, stop_process, kill_process]\n\n\nclass RecordedEventAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"event\",\n        \"complex_event\",\n        \"time_begin\",\n        \"time_end\",\n        \"active\",\n    )\n    list_display_links = (\n        \"event\",\n        \"complex_event\",\n    )\n    list_filter = (\"event\", \"active\")\n    readonly_fields = (\n        \"time_begin\",\n        \"time_end\",\n    )\n    save_as = True\n    save_as_continue = True\n\n\nclass MailAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"subject\",\n        \"message\",\n        \"html_message\",\n        \"last_update\",\n        \"done\",\n        \"send_fail_count\",\n    )\n    list_display_links = (\"subject\",)\n    list_filter = (\"done\",)\n\n    def last_update(self, instance):\n        return datetime.datetime.fromtimestamp(int(instance.timestamp)).strftime(\n            \"%Y-%m-%d %H:%M:%S\"\n        )\n\n\nclass ComplexEventOutputAdminInline(admin.TabularInline):\n    model = ComplexEventOutput\n    extra = 0\n    show_change_link = False\n    fields = (\"variable\", \"value\")\n\n\nclass ComplexEventLevelAdminInline(admin.TabularInline):\n    model = ComplexEventLevel\n    extra = 0\n    show_change_link = True\n    readonly_fields = (\"active\",)\n\n\nclass ComplexEventInputAdminInline(admin.StackedInline):\n    model = ComplexEventInput\n    extra = 0\n    fieldsets = (\n        (\n            None,\n            {\n                \"fields\": (\n                    (\n                        \"fixed_limit_low\",\n                        \"variable_limit_low\",\n                        \"limit_low_type\",\n                        \"hysteresis_low\",\n                    ),\n                    (\n                        \"variable\",\n                        \"variable_property\",\n                    ),\n                    (\n                        \"fixed_limit_high\",\n                        \"variable_limit_high\",\n                        \"limit_high_type\",\n                        \"hysteresis_high\",\n                    ),\n                ),\n            },\n        ),\n    )\n    raw_id_fields = (\n        \"variable\",\n        \"variable_limit_low\",\n        \"variable_limit_high\",\n    )\n\n\nclass ComplexEventAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"label\",\n        \"default_send_mail\",\n        \"last_level\",\n    )\n    list_display_links = (\n        \"id\",\n        \"label\",\n    )\n    list_filter = (\"default_send_mail\",)\n    filter_horizontal = (\"complex_mail_recipients\",)\n    inlines = [ComplexEventLevelAdminInline, ComplexEventOutputAdminInline]\n    readonly_fields = (\"last_level\",)\n    save_as = True\n    save_as_continue = True\n\n\nclass ComplexEventLevelAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"level\",\n        \"send_mail\",\n        \"complex_event\",\n        \"order\",\n        \"active\",\n    )\n    list_display_links = (\"id\",)\n    list_filter = (\n        \"complex_event__label\",\n        \"level\",\n        \"send_mail\",\n    )\n    inlines = [ComplexEventInputAdminInline, ComplexEventOutputAdminInline]\n    readonly_fields = (\"active\",)\n    save_as = True\n    save_as_continue = True\n\n    def has_module_permission(self, request):\n        return False\n\n\nclass ComplexEventInputAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"fixed_limit_low\",\n        \"variable_limit_low\",\n        \"limit_low_type\",\n        \"hysteresis_low\",\n        \"variable\",\n        \"fixed_limit_high\",\n        \"variable_limit_high\",\n        \"limit_high_type\",\n        \"hysteresis_high\",\n    )\n    list_display_links = (\"id\",)\n    list_filter = (\"variable\",)\n    save_as = True\n    save_as_continue = True\n    raw_id_fields = (\"variable\",)\n\n\nclass EventAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"label\",\n        \"variable\",\n        \"limit_type\",\n        \"level\",\n        \"action\",\n    )\n    list_display_links = (\n        \"id\",\n        \"label\",\n    )\n    list_filter = (\n        \"level\",\n        \"limit_type\",\n        \"action\",\n    )\n    filter_horizontal = (\"mail_recipients\",)\n    save_as = True\n    save_as_continue = True\n    raw_id_fields = (\"variable\",)\n\n\nclass VariablePropertyAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"variable\",\n        \"name\",\n        \"property_class\",\n        \"value\",\n        \"timestamp\",\n        \"last_modified\",\n    )\n    list_display_links = (\"id\", \"variable\", \"name\", \"property_class\")\n    list_filter = (\n        \"variable\",\n        \"name\",\n        \"property_class\",\n    )\n    raw_id_fields = (\"variable\",)\n    readonly_fields = [\"last_modified\"]\n    save_as = True\n    save_as_continue = True\n\n    def value(self, instance):\n        return instance.value()\n\n\nclass DictionaryItemInline(admin.TabularInline):\n    model = DictionaryItem\n    extra = 1\n\n\nclass DictionaryAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"name\",\n    )\n    list_filter = (\"variable\",)\n    save_as = True\n    save_as_continue = True\n    inlines = [DictionaryItemInline]\n\n    def has_module_permission(self, request):\n        return False\n\n\nclass DataSourceModelSelect(forms.Select):\n    def create_option(\n        self, name, value, label, selected, index, subindex=None, attrs=None\n    ):\n        option = super().create_option(\n            name, value, label, selected, index, subindex, attrs\n        )\n        if value:\n            option[\"attrs\"][\n                \"data-inline-datasource-model-name\"\n            ] = value.instance.inline_model_name\n\n        return option\n\n\nclass DataSourceAdminChangeForm(forms.ModelForm):\n    class Meta:\n        model = DataSource\n        fields = \"__all__\"\n        widgets = {\"datasource_model\": DataSourceModelSelect}\n\n\nclass DataSourceAdminAddForm(forms.ModelForm):\n    class Meta:\n        model = DataSource\n        fields = \"__all__\"\n\n    def __init__(self, *args, **kwargs):\n        wtf = DataSourceModel.objects.all()\n        super().__init__(*args, **kwargs)\n        w = self.fields[\"datasource_model\"].widget\n\n        datasource_choices = []\n        for choice in wtf:\n            if choice.can_add:\n                datasource_choices.append((choice.id, choice.__str__()))\n        w.choices = datasource_choices\n\n        def create_option_datasource(\n            self, name, value, label, selected, index, subindex=None, attrs=None\n        ):\n            inline_datasource_model_name = DataSourceModel.objects.get(\n                id=value\n            ).inline_model_name\n            self.option_inherits_attrs = True\n            return self._create_option(\n                name,\n                value,\n                label,\n                selected,\n                index,\n                subindex,\n                attrs={\n                    \"data-inline-datasource-model-name\": inline_datasource_model_name,\n                },\n            )\n\n        import types\n\n        # from django.forms.widgets import Select\n        w._create_option = w.create_option  # copy old method\n        w.create_option = types.MethodType(\n            create_option_datasource, w\n        )  # replace old with new\n\n\nclass DataSourceAdmin(admin.ModelAdmin):\n    list_display = (\n        \"datasource_model\",\n        \"datasource_name\",\n    )\n\n    form = DataSourceAdminChangeForm\n\n    # Add inlines for any model with OneToOne relation with Device\n    items = [\n        field\n        for field in DataSource._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    inlines = populate_inline(items, None, output=[], stacked=admin.StackedInline)\n\n    # Add JS file to display the right inline and to hide/show fields\n    class Media:\n        js = (\n            # To be sure the jquery files are loaded before our js file\n            \"pyscada/js/admin/display_inline_datasource.js\",\n        )\n\n    def get_form(self, request, obj=None, change=None, **kwargs):\n        if not obj:\n            # Use a different form only when adding a new record\n            return DataSourceAdminAddForm\n\n        return super().get_form(request, obj=obj, change=change, **kwargs)\n\n    def get_formsets_with_inlines(self, request, obj=None):\n        # disable all the of an inline if the data source model can_change field is false\n        def get_formset(self, request, obj=None, **kwargs):\n            formset = self.get_formset(request, obj=None, **kwargs)\n            for field in formset.form.base_fields:\n                if not obj.datasource_model.can_change:\n                    formset.form.base_fields[field].disabled = True\n            return formset\n\n        for inline in self.get_inline_instances(request, obj):\n            if obj is not None:\n                yield get_formset(inline, request, obj), inline\n            else:\n                yield inline.get_formset(request, obj), inline\n\n    def datasource_name(self, obj):\n        return obj.__str__()\n\n    def get_deleted_objects(self, objs, request):\n        # Not allow to delete the data source with id = 1\n        new_objs = list()\n        for obj in objs:\n            if obj.id != 1:\n                new_objs.append(obj.get_related_datasource())\n        (\n            deleted_objects,\n            model_count,\n            perms_needed,\n            protected,\n        ) = super().get_deleted_objects(objs, request)\n        return deleted_objects, model_count, perms_needed, protected\n\n    def has_view_permission(self, request, obj=None):\n        return True\n\n    def has_add_permission(self, request, obj=None):\n        return True\n\n    def has_change_permission(self, request, obj=None):\n        return True\n\n    def has_delete_permission(self, request, obj=None):\n        if obj is not None and obj.id == 1:\n            messages.error(request, f\"You cannot delete the {obj} !\")\n            return False\n        return super().has_delete_permission(request, obj)\n\n    def formfield_for_foreignkey(self, db_field, request, **kwargs):\n        # For new data source, show all the data source models\n        # For existing data source, show only the selected data source model to avoid changing\n        if db_field.name == \"datasource_model\":\n            if (\n                \"object_id\" in request.resolver_match.kwargs\n                and DataSource.objects.get(\n                    id=request.resolver_match.kwargs[\"object_id\"]\n                )\n                is not None\n                and DataSource.objects.get(\n                    id=request.resolver_match.kwargs[\"object_id\"]\n                ).datasource_model\n            ):\n                kwargs[\"queryset\"] = DataSourceModel.objects.filter(\n                    id=DataSource.objects.get(\n                        id=request.resolver_match.kwargs[\"object_id\"]\n                    ).datasource_model.id,\n                )\n        return super().formfield_for_foreignkey(db_field, request, **kwargs)\n\n\nadmin_site = PyScadaAdminSite(name=\"pyscada_admin\")\nadmin_site.register(Device, DeviceAdmin)\nadmin_site.register(DeviceHandler, DeviceHandlerAdmin)\nadmin_site.register(Variable, CoreVariableAdmin)\nadmin_site.register(VariableProperty, VariablePropertyAdmin)\nadmin_site.register(Scaling, ScalingAdmin)\nadmin_site.register(Unit)\nadmin_site.register(ComplexEvent, ComplexEventAdmin)\nadmin_site.register(ComplexEventLevel, ComplexEventLevelAdmin)\nadmin_site.register(Event, EventAdmin)\nadmin_site.register(RecordedEvent, RecordedEventAdmin)\nadmin_site.register(Mail, MailAdmin)\nadmin_site.register(DeviceWriteTask, DeviceWriteTaskAdmin)\nadmin_site.register(DeviceReadTask, DeviceReadTaskAdmin)\nadmin_site.register(Log, LogAdmin)\nadmin_site.register(BackgroundProcess, BackgroundProcessAdmin)\nadmin_site.register(VariableState, VariableStateAdmin)\nadmin_site.register(User, UserAdmin)\nadmin_site.register(Group, GroupAdmin)\nadmin_site.register(Dictionary, DictionaryAdmin)\nadmin_site.register(DataSource, DataSourceAdmin)\n# admin_site.register(DataSourceModel)\n"
  },
  {
    "path": "pyscada/apps.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport os\nimport logging\n\nfrom django.apps import AppConfig\nfrom django.utils.translation import gettext_lazy as _\nfrom django.db.utils import ProgrammingError, OperationalError\nfrom django.conf import settings\n\n\nlogger = logging.getLogger(__name__)\n\nclass PyScadaConfig(AppConfig):\n    name = \"pyscada\"\n    label = \"pyscada\"\n    verbose_name = _(\"PyScada Core\")\n    path = os.path.dirname(os.path.realpath(__file__))\n    default_auto_field = \"django.db.models.AutoField\"\n\n    def ready(self):\n        import pyscada.signals\n        from pyscada.core import additional_installed_app\n        for app_name in additional_installed_app:\n            if app_name not in settings.INSTALLED_APPS:\n                logger.error(f\"{app_name} missing in INSTALLED_APPS\")\n\n\n    def pyscada_app_init(self):\n        logger.debug(\"Core init app\")\n        try:\n            from .hmi.models import Theme\n\n            if Theme.objects.filter().count():\n                Theme.objects.first().check_all_themes()\n        except (ProgrammingError, OperationalError) as e:\n            logger.debug(e)\n\n        try:\n            from .models import DataSourceModel, DataSource\n            from .django_datasource.models import DjangoDatabase\n            from .cache_datasource.models import DjangoCache\n            from .single_value_datasource.models import DjangoSingleValue\n\n            # create the default data source model\n            # only one data source linked to the RecordedData table can exist\n            dsm, _ = DataSourceModel.objects.get_or_create(\n                inline_model_name=\"DjangoDatabase\",\n                name=\"Django database\",\n                defaults={\n                    # \"name\": \"Django database\",\n                    \"can_add\": False,\n                    \"can_change\": False,\n                    \"can_select\": True,\n                },\n            )\n            ds, _ = DataSource.objects.get_or_create(\n                id=1,\n                defaults={\n                    \"datasource_model\": dsm,\n                },\n            )\n            dd, _ = DjangoDatabase.objects.get_or_create(\n                datasource=ds,\n                defaults={\n                    \"data_model_app_name\": \"pyscada.django_datasource\",\n                    \"data_model_name\": \"RecordedData\",\n                },\n            )\n\n            # For RecordedDataOld, hidden by default\n            # set can_select to True to show it in the admin panel.\n            # TODO : test read and write, test how it appears in the variable admin\n            # panel config if mannualy added (using shell)\n            dsm, _ = DataSourceModel.objects.get_or_create(\n                inline_model_name=\"DjangoDatabase\",\n                name=\"Django database hidden\",\n                defaults={\n                    # \"name\": \"Django database\",\n                    \"can_add\": False,\n                    \"can_change\": False,\n                    \"can_select\": False,\n                },\n            )\n            ds, _ = DataSource.objects.get_or_create(\n                id=2,\n                defaults={\n                    \"datasource_model\": dsm,\n                },\n            )\n            dd, _ = DjangoDatabase.objects.get_or_create(\n                datasource=ds,\n                defaults={\n                    \"data_model_app_name\": \"pyscada.django_datasource\",\n                    \"data_model_name\": \"RecordedDataOld\",\n                },\n            )\n\n            # Update datasource name after move to subapp\n            DjangoDatabase.objects.filter(\n                data_model_app_name=\"pyscada\",\n                pk__lte=2).update(data_model_app_name=\"pyscada.django_datasource\")\n\n            # Django Cache datastore\n            dsm, _ = DataSourceModel.objects.get_or_create(\n                inline_model_name=\"DjangoCache\",\n                name=\"Django Cache\",\n                defaults={\n                    \"can_add\": True,\n                    \"can_change\": True,\n                    \"can_select\": True,\n                },\n            )\n            ds, _ = DataSource.objects.get_or_create(\n                datasource_model=dsm\n                )\n            dd, _ = DjangoCache.objects.get_or_create(\n                datasource=ds,\n                defaults={\n                    \"data_lifetime\": 3600,\n                },\n            )\n\n            # Django Single Value datastore\n            dsm, _ = DataSourceModel.objects.get_or_create(\n                inline_model_name=\"DjangoSingleValue\",\n                name=\"Django Single Value\",\n                defaults={\n                    \"can_add\": False,\n                    \"can_change\": False,\n                    \"can_select\": True,\n                },\n            )\n            ds, _ = DataSource.objects.get_or_create(\n                datasource_model=dsm\n                )\n            dd, _ = DjangoSingleValue.objects.get_or_create(\n                datasource=ds,\n            )\n\n\n        except (ProgrammingError, OperationalError) as e:\n            logger.debug(e)\n"
  },
  {
    "path": "pyscada/cache_datasource/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada import core\n\n__version__ = core.__version__\n__author__ = core.__author__\n__email__ = core.__email__\n__description__ = (\n    \"Cache Datasource for PyScada a Python and Django based Open Source SCADA System\"\n)\n__app_name__ = \"cache_datasource\"\n"
  },
  {
    "path": "pyscada/cache_datasource/apps.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport logging\n\nfrom django.apps import AppConfig\nfrom django.utils.translation import gettext_lazy as _\n\nlogger = logging.getLogger(__name__)\n\n\nclass PyScadaCacheDatasourceConfig(AppConfig):\n    name = \"pyscada.cache_datasource\"\n    verbose_name = _(\"PyScada Cache Datasource\")\n    default_auto_field = \"django.db.models.AutoField\"\n"
  },
  {
    "path": "pyscada/cache_datasource/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.10 on 2026-02-10 15:03\n\nimport django.db.models.deletion\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('pyscada', '0116_variable_pause_recording'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='DjangoCache',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('data_lifetime', models.PositiveIntegerField(default=3600)),\n                ('datasource', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='pyscada.datasource')),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/cache_datasource/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/cache_datasource/models.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport logging\nimport time\n\nfrom django.core.cache import cache\nfrom django.db import models\nfrom django.utils.timezone import now\n\nfrom pyscada.models import DataSource, Variable\nfrom pyscada.utils import timestamp_to_datetime\n\nlogger = logging.getLogger(__name__)\n\n\nclass DjangoCache(models.Model):\n    datasource = models.OneToOneField(DataSource, on_delete=models.CASCADE)\n    data_lifetime = models.PositiveIntegerField(default=3600)\n\n    def __str__(self):\n        return f\"Django Cache (data lifetime : {self.data_lifetime} seconds)\"\n\n    @staticmethod\n    def now_ms() -> int:\n        \"\"\"returns a unixtimestamp in ms\"\"\"\n        return int(now().timestamp() * 1000)\n\n    @property\n    def _data_lifetime_ms(self) -> int:\n        \"\"\"returns the lifetime of data in the cache in ms\"\"\"\n        return self.data_lifetime * 1000\n\n    def _make_key(self, variable_id: int) -> str:\n        \"\"\"returns the key to find the Variable data in the cache\n\n        Args:\n            variable_id: Primary Key of the Variable for which the data is stored\n\n        Returns:\n            cache key\n        \"\"\"\n        return f\"{self.pk}_{variable_id}\"\n\n    def _get_data(self, variable_id: int):\n        \"\"\"returns the data for a given Variable from the cache\n\n        Args:\n            variable_id: Primary Key of the Variable for which the data is stored\n\n        Returns:\n            cache data or None\n        \"\"\"\n        return cache.get(self._make_key(variable_id))\n\n    def _set_data(self, variable_id: int, data: list) -> bool:\n        \"\"\"stores the data for a given Variable in the cache\n\n        Args:\n            variable_id: Primary Key of the Variable for which the data is stored\n            data: in the form [[timestamp, value, date_saved], ...]\n\n        Returns:\n            cache status\n        \"\"\"\n        return cache.set(self._make_key(variable_id), data, timeout=self.data_lifetime)\n\n    def _update_variable_data(self, variable_id: int, new_data=None, date_saved=None):\n        \"\"\"stores the data for a given Variable in the cache and checks for stale data\n\n        Args:\n            variable_id: Primary Key of the Variable for which the data is stored\n            new_data: in the form [[timestamp, value, (date_saved)], ...]\n            date_saved (optional): date when the data was saved\n\n        Returns:\n            cache status\n        \"\"\"\n        data = self._get_data(variable_id=variable_id)\n        if data is None:\n            data = {}\n        if new_data is None:\n            new_data = []\n        date_saved = date_saved if date_saved is not None else now()\n\n        # check for old data\n        for key in list(data.keys()):\n            if key < (self.now_ms() - self._data_lifetime_ms):\n                logger.error(\n                    f\"{key} smaler than {(self.now_ms() - self._data_lifetime_ms)}\"\n                )\n                del data[key]\n\n        for value in new_data:\n            data[int(value[0] * 1000)] = [value[0], value[1], date_saved.timestamp()]\n\n        return self._set_data(variable_id=variable_id, data=data)\n\n    def last_datapoint(self, variable=None, use_date_saved=False, **kwargs):\n        \"\"\"returns the last data for a given Variable in the cache and checks for stale\n        data\n\n        Args:\n            variable_id: Primary Key of the Variable for which the data is stored\n            new_data: in the form [[timestamp, value, (date_saved)], ...]\n            date_saved (optional): date when the data was saved\n\n        Returns:\n            last element\n        \"\"\"\n        if variable is None:\n            logger.info(\n                \"No variable defined for DjangoCache last_datapoint function\"\n            )\n            return None\n        data = self._get_data(variable_id=variable.pk)\n\n        if data is None:\n            logger.debug(\n                \"No data\"\n            )\n            return None\n        last_element = None\n        for timestamp_ms, item in data.items():\n            timestamp = item[0]\n            value = item[1]\n            if last_element is None:\n                last_element = [timestamp, value]\n                continue\n\n            if last_element[0] < timestamp:\n                last_element = [timestamp, value]\n\n        if last_element is None:\n            return None\n\n        return last_element\n\n    def query_datapoints(\n        self,\n        variable_ids=[],\n        time_min=0,\n        time_max=None,\n        query_first_value=False,\n        time_min_excluded=False,\n        time_max_excluded=False,\n        **kwargs,\n    ):\n        \"\"\"returns all data for a list of Variables in the cache in a given periode\n\n        Args:\n            variable_ids: Primary Key of the Variable for which the data is stored\n            time_min (optional):\n            time_min (optional):\n            query_first_value (optional):\n            time_min_excluded (optional):\n            time_max_excluded (optional):\n\n\n        Returns:\n            datapoints\n        \"\"\"\n        if time_max is None:\n            time_max = time.time()\n\n        variable_ids = self.datasource.datasource_check(\n            items=variable_ids, items_as_id=True, ids_model=Variable\n        )\n        output = {}\n        output[\"timestamp\"] = 0\n        output[\"date_saved_max\"] = 0\n\n        for variable_id in variable_ids:\n            data = self._get_data(variable_id=variable_id)\n            if data is None:\n                continue\n\n            first_value = None\n            for timestamp_ms, item in data.items():\n                timestamp = item[0]\n                value = item[1]\n                date_saved = item[2]\n\n                if timestamp > time_max:\n                    continue\n                if time_max_excluded and timestamp >= time_max:\n                    continue\n                if timestamp < time_min or (\n                    time_min_excluded and timestamp <= time_min\n                ):\n                    if not query_first_value:\n                        continue\n                    if first_value is None:\n                        first_value = [timestamp, value, date_saved]\n                    elif first_value[0] < timestamp:\n                        first_value = [timestamp, value, date_saved]\n                    continue\n                if variable_id not in output:\n                    output[variable_id] = []\n                output[variable_id].append([timestamp, value])\n                output[\"timestamp\"] = max(output[\"timestamp\"], timestamp)\n                output[\"date_saved_max\"] = max(output[\"date_saved_max\"], date_saved)\n\n            if query_first_value and first_value is not None:\n                if variable_id not in output:\n                    output[variable_id] = []\n                # prepend last value\n                output[variable_id] = [first_value[0:2]] + output[variable_id]\n                output[\"timestamp\"] = max(output[\"timestamp\"], first_value[0])\n                output[\"date_saved_max\"] = max(output[\"date_saved_max\"], first_value[2])\n\n        return output\n\n    def write_datapoints(self, items=[], date_saved=None, **kwargs):\n        \"\"\"\n        Args:\n            datapoints:  { variable_id: [[timestamp, value, date_saved]] } with\n                timestamp in s and date_saved as datetime, timestamp in s or None\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n\n        Returns:\n            None\n        \"\"\"\n        items = self.datasource.datasource_check(items)\n        date_saved = date_saved if date_saved is not None else now()\n        for item in items:\n            logger.debug(f\"{item} has {len(item.cached_values_to_write)} to write.\")\n            if not hasattr(item, \"date_saved\") or item.date_saved is None:\n                item.date_saved = date_saved\n            self._update_variable_data(\n                variable_id=item.pk,\n                new_data=item.cached_values_to_write,\n                date_saved=item.date_saved,\n            )\n            item.date_saved = None\n            item.erase_cache()\n\n    def write_raw_datapoints(self, datapoints: dict, date_saved=None):\n        \"\"\"writes raw datapoints to the database in the form\n\n        Args:\n            datapoints:  { variable_id: [[timestamp, value, date_saved]] } with\n                timestamp in s and date_saved as datetime, timestamp in s or None\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n\n        Returns:\n            None\n        \"\"\"\n        for variable_id in datapoints.keys():\n            for datapoint in datapoints[variable_id]:\n                if len(datapoint) == 2:\n                    if date_saved is None:\n                        datapoint.append(now())\n                    else:\n                        datapoint.append(date_saved)\n\n                elif len(datapoint) == 3:\n                    if datapoint[2] is None:\n                        if date_saved is None:\n                            datapoint[2] = now()\n                        else:\n                            datapoint[2] = date_saved\n\n                    elif type(datapoint[2]) is int or type(datapoint[2]) is float:\n                        datapoint[2] = timestamp_to_datetime(datapoint[2])\n\n                self._update_variable_data(\n                    variable_id=variable_id,\n                    new_data=[[datapoint[0], datapoint[1]]],\n                    date_saved=datapoint[2],\n                )\n"
  },
  {
    "path": "pyscada/core/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\n__version__ = \"0.9.0\"\n__author__ = \"Martin Schröder, Camille Lavayssiere\"\n__email__ = \"team@pyscada.org\"\n__description__ = \"PyScada a Python and Django based Open Source SCADA System\"\n__app_name__ = \"PyScada\"\n\ndefault_app_config = \"pyscada.apps.PyScadaConfig\"\nadditional_installed_app = [\n    \"pyscada.core\",\n    \"pyscada.hmi\",\n    \"pyscada.export\",\n    \"pyscada.django_datasource\",\n    \"pyscada.cache_datasource\",\n    \"pyscada.single_value_datasource\"\n    ]\n\nparent_process_list = [\n    {\n        \"pk\": 97,\n        \"label\": \"pyscada.mail\",\n        \"process_class\": \"pyscada.mail.worker.Process\",\n        \"process_class_kwargs\": '{\"dt_set\":30}',\n        \"enabled\": True,\n    },\n    {\n        \"pk\": 96,\n        \"label\": \"pyscada.event\",\n        \"process_class\": \"pyscada.event.worker.Process\",\n        \"process_class_kwargs\": '{\"dt_set\":5}',\n        \"enabled\": True,\n    },\n    {\n        \"pk\": 16,\n        \"label\": \"pyscada.generic\",\n        \"process_class\": \"pyscada.generic.worker.Process\",\n        \"process_class_kwargs\": '{\"dt_set\":5}',\n        \"enabled\": True,\n    },\n]\n\n\ndef version():\n    return __version__\n"
  },
  {
    "path": "pyscada/core/urls.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.apps import apps\nimport logging\n\nlogger = logging.getLogger(__name__)\n\nurlpatterns = []\n\nfor app_config in apps.get_app_configs():\n    if app_config.name.startswith(\"pyscada.\") and app_config.name != \"pyscada.core\":\n        try:\n            m = __import__(f\"{app_config.name}.urls\", fromlist=[str(\"a\")])\n            urlpatterns += m.urlpatterns\n        except ModuleNotFoundError:\n            pass\n        except Exception as e:\n            logger.warning(e, exc_info=True)\n"
  },
  {
    "path": "pyscada/device.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.models import DeviceProtocol, VariableProperty\nimport pyscada\n\nimport sys\nfrom time import time, sleep, time_ns\nimport logging\n\nlogger = logging.getLogger(__name__)\n\nPROTOCOL_ID = None\ndriver_ok = True\n\n\nclass GenericHandlerDevice:\n    \"\"\"\n    Generic handler device\n    \"\"\"\n\n    def __init__(self, pyscada_device, variables):\n        self._device = pyscada_device\n        self._variables = variables\n        self.inst = None\n        self._device_not_accessible = 0\n        self._not_accessible_reason = None\n        if not hasattr(self, \"_protocol\"):\n            self._protocol = PROTOCOL_ID\n        if not hasattr(self, \"driver_ok\"):\n            self.driver_ok = driver_ok\n\n    def connect(self):\n        \"\"\"\n        establish a connection to the Instrument\n        \"\"\"\n        if not self.driver_ok:\n            return False\n\n        if self._device.protocol.id != self._protocol:\n            try:\n                p = DeviceProtocol.objects.get(id=self._protocol)\n            except DeviceProtocol.DoesNotExist:\n                p = None\n            logger.warning(\n                f\"Wrong handler selected : it's for {p} device while device protocol is {self._device.protocol}\"\n            )\n            return False\n\n        # self.accessibility()\n        return True\n\n    def accessibility(self):\n        if self.inst is not None:\n            if self._device_not_accessible < 1:\n                self._device_not_accessible = 1\n                logger.info(f\"Connected to device : {self._device}\")\n        else:\n            if self._device_not_accessible > -1:\n                self._device_not_accessible = -1\n                msg = f\"Device {self._device} is not accessible.\"\n                if self._not_accessible_reason is not None:\n                    msg += f\" Reason : {self._not_accessible_reason}\"\n                    self._not_accessible_reason = None\n                logger.info(msg)\n        return True\n\n    def disconnect(self):\n        if self.inst is not None:\n            self.inst = None\n        return True\n\n    def before_read(self):\n        \"\"\"\n        will be called before the first read_data\n        \"\"\"\n        return self.connect()\n\n    def after_read(self):\n        \"\"\"\n        will be called after the last read_data\n        \"\"\"\n        return self.disconnect()\n\n    def read_data(self, variable_instance):\n        \"\"\"\n        read values from the device\n        \"\"\"\n        logger.warning(\n            f\"Handler of {self._device} should overwrite read_data function.\"\n        )\n        return None\n\n    def read_data_and_time(self, variable_instance):\n        \"\"\"\n        read values and timestamps from the device\n        \"\"\"\n\n        return self.read_data(variable_instance), self.time()\n\n    def read_data_all(self, variables_dict, erase_cache=False):\n        output = []\n\n        if self.before_read():\n            for item in variables_dict.values():\n                if item.readable:\n                    value, read_time = self.read_data_and_time(item)\n                    if (\n                        value is not None\n                        and read_time is not None\n                        and item.update_values(\n                            value, read_time, erase_cache=erase_cache\n                        )\n                    ):\n                        output.append(item)\n        self.after_read()\n        return output\n\n    def write_data(self, variable_id, value, task):\n        \"\"\"\n        write values to the device\n        \"\"\"\n        if self.connect():\n            for var in self._variables:\n                var = self._variables[var]\n                if variable_id == var.id:\n                    logger.warning(\n                        f\"Handler of {self._device} should overwrite write_data function.\"\n                    )\n                    return None\n\n            logger.warning(\n                f\"Variable {variable_id} not in variable list {self._variables} of device {self._device}\"\n            )\n        return None\n\n    def time(self):\n        return time_ns() / 1000000000\n\n\nclass GenericDevice:\n    \"\"\"\n    Generic device\n    \"\"\"\n\n    def __init__(self, device):\n        self.variables = {}\n        self.device = device\n        if not hasattr(self, \"driver_ok\"):\n            self.driver_ok = driver_ok\n\n        if not self.driver_ok:\n            logger.warning(f\"Driver import failed for {self.device}\")\n\n        try:\n            if (\n                hasattr(self.device, \"instrument_handler\")\n                and self.device.instrument_handler is not None\n            ):\n                if self.device.instrument_handler.handler_path is not None:\n                    sys.path.append(self.device.instrument_handler.handler_path)\n                mod = __import__(\n                    self.device.instrument_handler.handler_class, fromlist=[\"Handler\"]\n                )\n                device_handler = getattr(mod, \"Handler\")\n                self._h = device_handler(self.device, self.variables)\n            elif hasattr(self, \"handler_class\"):\n                self._h = self.handler_class(self.device, self.variables)\n            else:\n                self._h = GenericHandlerDevice(self.device, self.variables)\n            self.driver_handler_ok = True\n        except (ImportError, ModuleNotFoundError) as e:\n            self.driver_handler_ok = False\n            logger.error(\n                f\"Handler import error ({e}) : {self.device.short_name}\", exc_info=True\n            )\n\n        # Wait 5 seconds to let changes appears in DB.\n        sleep(5)\n\n        for var in self.device.variable_set.filter(active=1):\n            if not hasattr(var, str(self.device.protocol.protocol) + \"variable\"):\n                continue\n            self.variables[var.pk] = var\n\n    def request_data(self):\n        output = []\n\n        if not self.driver_ok or not self.driver_handler_ok:\n            logger.info(\n                f\"Cannot request data for {self.device}. Driver is {self.driver_ok}. Handler is {self.driver_handler_ok}.\"\n            )\n            return output\n\n        output = self._h.read_data_all(self.variables)\n\n        return output\n\n    def write_data(self, variable_id, value, task):\n        \"\"\"\n        write value to a Serial Device\n        \"\"\"\n\n        output = []\n        if not self.driver_ok or not self.driver_handler_ok:\n            return output\n\n        for item in self.variables:\n            if self.variables[item].id == variable_id:\n                if not self.variables[item].writeable:\n                    logger.info(\n                        f\"Variable '{self.variables[item]}' is not writeable. Write task refused.\"\n                    )\n                    return False\n                read_value = self._h.write_data(variable_id, value, task)\n                if read_value is not None and self.variables[item].update_values(\n                    [read_value], [time_ns() / 1000000000]\n                ):\n                    output.append(self.variables[item])\n        return output\n"
  },
  {
    "path": "pyscada/django_datasource/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada import core\n\n__version__ = core.__version__\n__author__ = core.__author__\n__email__ = core.__email__\n__description__ = (\n    \"Django Datasource for PyScada a Python and Django based Open Source SCADA System\"\n)\n__app_name__ = \"django_datasource\"\n"
  },
  {
    "path": "pyscada/django_datasource/apps.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.apps import AppConfig\nfrom django.utils.translation import gettext_lazy as _\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass PyScadaDjangoDatasourceConfig(AppConfig):\n    name = \"pyscada.django_datasource\"\n    verbose_name = _(\"PyScada Django Datasource\")\n    default_auto_field = \"django.db.models.AutoField\"\n"
  },
  {
    "path": "pyscada/django_datasource/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.10 on 2026-02-02 12:29\n\nimport django.db.models.deletion\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('pyscada', '0115_remove_recordeddata_variable_and_more'),\n    ]\n\n    operations = [\n        migrations.SeparateDatabaseAndState(\n            state_operations=[\n                migrations.CreateModel(\n                    name='DjangoDatabase',\n                    fields=[\n                        ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                        ('data_model_app_name', models.CharField(max_length=50)),\n                        ('data_model_name', models.CharField(max_length=50)),\n                        ('datasource', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='pyscada.datasource')),\n                    ],\n                    options={\n                        'db_table': 'pyscada_djangodatabase',\n                    },\n                ),\n                migrations.CreateModel(\n                    name='RecordedData',\n                    fields=[\n                        ('id', models.BigIntegerField(primary_key=True, serialize=False)),\n                        ('date_saved', models.DateTimeField(blank=True, db_index=True, null=True)),\n                        ('value_boolean', models.BooleanField(blank=True, default=False)),\n                        ('value_int16', models.SmallIntegerField(blank=True, null=True)),\n                        ('value_int32', models.IntegerField(blank=True, null=True)),\n                        ('value_int64', models.BigIntegerField(blank=True, null=True)),\n                        ('value_float64', models.FloatField(blank=True, null=True)),\n                        ('variable', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='pyscada.variable')),\n                    ],\n                    options={\n                        'db_table': 'pyscada_recordeddata',\n                    },\n                ),\n                migrations.CreateModel(\n                    name='RecordedDataOld',\n                    fields=[\n                        ('id', models.BigIntegerField(primary_key=True, serialize=False)),\n                        ('value_boolean', models.BooleanField(blank=True, default=False)),\n                        ('value_int16', models.SmallIntegerField(blank=True, null=True)),\n                        ('value_int32', models.IntegerField(blank=True, null=True)),\n                        ('value_int64', models.BigIntegerField(blank=True, null=True)),\n                        ('value_float64', models.FloatField(blank=True, null=True)),\n                        ('variable', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='pyscada.variable')),\n                    ],\n                    options={\n                        'db_table': 'pyscada_recordeddataold',\n                    },\n                ),\n            ],\n            database_operations=[],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/django_datasource/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/django_datasource/models.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport logging\nimport time\n\nfrom django.core.exceptions import ValidationError\nfrom django.db import models\nfrom django.db.utils import IntegrityError\nfrom django.utils.timezone import now\n\nfrom pyscada.models import DataSource, Variable\nfrom pyscada.utils import timestamp_to_datetime\n\nlogger = logging.getLogger(__name__)\n\n\nclass RecordedDataManager(models.Manager):\n    def create_data_element_from_variable(\n        self, variable, value, timestamp, date_saved=None, **kwargs\n    ):\n        if value is None:\n            return None\n\n        if date_saved is None:\n            date_saved = (\n                variable.date_saved if hasattr(variable, \"date_saved\") else now()\n            )\n\n        return RecordedData(\n            timestamp=timestamp,\n            variable=variable,\n            value=value,\n            date_saved=date_saved,\n        )\n\n    def last_element(\n        self,\n        time_min=0,\n        time_max=None,\n        use_date_saved=True,\n        timeout=None,\n        time_min_excluded=False,\n        time_max_excluded=False,\n        **kwargs,\n    ):\n\n        if time_max is None:\n            time_max = time.time()\n\n        # if True, remove the tim_min point from the range\n        time_min_offset = 0\n        if time_min_excluded:\n            time_min_offset = 2097152\n\n        # if True, remove the tim_max point from the range\n        time_max_offset = 0\n        if time_max_excluded:\n            time_max_offset = 2097152\n\n        if use_date_saved:\n            result = (\n                super()\n                .get_queryset()\n                .filter(\n                    date_saved__range=(\n                        timestamp_to_datetime(time_min),\n                        timestamp_to_datetime(time_max),\n                    ),\n                    **kwargs,\n                )\n            )\n            if result is not None and len(result) > 0:\n                if time_min_excluded and result[0].date_saved == timestamp_to_datetime(\n                    time_min\n                ):\n                    result = result[1:]\n                if time_max_excluded and result[\n                    len(result) - 1\n                ].date_saved == timestamp_to_datetime(time_max):\n                    result = result[: len(result) - 1]\n            return result.last()\n        else:\n            return (\n                super()\n                .get_queryset()\n                .filter(\n                    id__range=(\n                        time_min * 2097152 * 1000 + time_min_offset,\n                        time_max * 2097152 * 1000 + 2097151 - time_max_offset,\n                    ),\n                    **kwargs,\n                )\n                .last()\n            )\n\n    def db_data(\n        self,\n        variable_ids,\n        time_min,\n        time_max,\n        query_first_value=False,\n        **kwargs,\n    ):\n        \"\"\"\n\n        :return:\n        \"\"\"\n\n        if kwargs.get(\"time_min_excluded\", False):\n            time_min = time_min + 0.001\n        if kwargs.get(\"time_max_excluded\", False):\n            time_max = time_max - 0.001\n\n        variable_ids = [int(pk) for pk in variable_ids]\n        tmp = list(\n            super()\n            .get_queryset()\n            .filter(\n                id__range=(\n                    time_min * 2097152 * 1000,\n                    time_max * 2097152 * 1000 + 2097151,\n                ),\n                # date_saved__range=(\n                #    timestamp_to_datetime(\n                #        time_min - 3660 if query_first_value else time_min\n                #    ),\n                #    timestamp_to_datetime(time_max),\n                # ),\n                variable_id__in=variable_ids,\n            )\n            .values_list(\n                \"variable_id\",\n                \"pk\",\n                \"value_float64\",\n                \"value_int64\",\n                \"value_int32\",\n                \"value_int16\",\n                \"value_boolean\",\n                \"date_saved\",\n            )\n        )\n\n        values = dict()\n        times = dict()\n        date_saved_max = 0\n        tmp_time_max = 0\n        tmp_time_min = time_max\n\n        def get_rd_value(rd_resp):\n            # return the value from a RecordedData Response\n            if rd_resp[2] is not None:  # float64\n                return rd_resp[2]  # time, value\n            elif rd_resp[3] is not None:  # int64\n                return rd_resp[3]  # time, value\n            elif rd_resp[4] is not None:  # int32\n                return rd_resp[4]  # time, value\n            elif rd_resp[5] is not None:  # int16\n                return rd_resp[5]  # time, value\n            elif rd_resp[6] is not None:  # boolean\n                return rd_resp[6]  # time, value\n            else:\n                return 0\n\n        for item in tmp:\n            if item[0] not in variable_ids:\n                continue\n            if not item[0] in values:\n                values[item[0]] = []\n                times[item[0]] = {\"time_min\": time_max, \"time_max\": 0}\n            tmp_time = float(item[1] - item[0]) / (\n                2097152.0 * 1000\n            )  # calc the timestamp in seconds\n            if item[7] is None:\n                continue\n            date_saved_max = max(\n                date_saved_max,\n                time.mktime(item[7].utctimetuple()) + item[7].microsecond / 1e6,\n            )\n            tmp_time_max = max(tmp_time, tmp_time_max)\n            tmp_time_min = min(tmp_time, tmp_time_min)\n            values[item[0]].append([tmp_time, get_rd_value(item)])\n            if tmp_time < times[item[0]][\"time_min\"]:\n                times[item[0]][\"time_min\"] = tmp_time\n            if tmp_time > times[item[0]][\"time_max\"]:\n                times[item[0]][\"time_max\"] = tmp_time\n\n        if query_first_value:\n            for pk in variable_ids:\n                if pk not in values:\n                    values[pk] = []\n                time_max_last_value = time_max\n                time_max_excluded = False\n                if pk in times:\n                    time_max_last_value = times[pk][\"time_min\"]\n                    time_max_excluded = True\n                last_element = self.last_element(\n                    use_date_saved=False,\n                    time_min=0,\n                    time_max=time_max_last_value,\n                    time_max_excluded=time_max_excluded,\n                    variable_id=pk,\n                )\n                if last_element is not None and last_element.date_saved is not None:\n                    tmp_time = last_element.time_value()\n                    values[pk].insert(\n                        0,\n                        [\n                            tmp_time,\n                            last_element.value(),\n                        ],\n                    )\n                    date_saved_max = max(\n                        date_saved_max,\n                        time.mktime(last_element.date_saved.utctimetuple())\n                        + last_element.date_saved.microsecond / 1e6,\n                    )\n                    tmp_time_max = max(tmp_time, tmp_time_max)\n\n        # values[\"timestamp\"] = max(tmp_time_max, time_min)\n        values[\"timestamp\"] = tmp_time_max\n        values[\"date_saved_max\"] = date_saved_max\n\n        return values\n\n\nclass DjangoDatabase(models.Model):\n    datasource = models.OneToOneField(DataSource, on_delete=models.CASCADE)\n    data_model_app_name = models.CharField(\n        max_length=50,\n    )\n    data_model_name = models.CharField(\n        max_length=50,\n    )\n\n    def __str__(self):\n        return f\"Django database ({self.data_model_app_name}.{self.data_model_name})\"\n\n    def _import_model(self):\n        class_name = self.data_model_name\n        class_path = self.data_model_app_name + \".models\"\n        try:\n            mod = __import__(class_path, fromlist=[class_name])\n            content_class = getattr(mod, class_name)\n            if isinstance(content_class, models.base.ModelBase):\n                return content_class\n        except ModuleNotFoundError:\n            logger.info(\n                f\"{class_name} of {class_path} not found. A module is not installed ?\"\n            )\n        except:  # noqa: E722\n            logger.error(f\"{class_path} unhandled exception\", exc_info=True)\n        return None\n\n    def last_value(self, **kwargs):\n        logger.info(\n            \"the use of 'last_value' method is deprecated use 'last_datapoint' instead\"\n        )\n        return self.last_datapoint(**kwargs)\n\n    def last_datapoint(self, variable=None, use_date_saved=False, **kwargs):\n        if variable is None:\n            logger.info(\n                \"No variable defined for DjangoDatabase last_datapoint function\"\n            )\n            return None\n        data_model = self._import_model()\n        last_element = data_model.objects.last_element(\n            use_date_saved=use_date_saved, variable_id=variable.pk, **kwargs\n        )\n        if last_element is not None:\n            return [last_element.time_value(), last_element.value()]\n        else:\n            return None\n\n    def read_multiple(self, **kwargs):\n        logger.info(\n            \"the use of 'read_multiple' method is deprecated use 'query_datapoints' instead\"  # noqa: E501\n        )\n        return self.query_datapoints(**kwargs)\n\n    def query_datapoints(\n        self,\n        variable_ids=[],\n        time_min=0,\n        time_max=None,\n        query_first_value=False,\n        **kwargs,\n    ):\n        if time_max is None:\n            time_max = time.time()\n\n        variable_ids = self.datasource.datasource_check(\n            items=variable_ids, items_as_id=True, ids_model=Variable\n        )\n        return self._import_model().objects.db_data(\n            variable_ids=variable_ids,\n            time_min=time_min,\n            time_max=time_max,\n            query_first_value=query_first_value,\n            **kwargs,\n        )\n\n    def write_multiple(self, **kwargs):\n        logger.info(\n            \"the use of 'write_multiple' method is deprecated use 'write_datapoints' instead\"  # noqa: E501\n        )\n        return self.write_datapoints(**kwargs)\n\n    def write_datapoints(self, items=[], date_saved=None, batch_size=1000, **kwargs):\n        \"\"\"\n        Args:\n            datapoints:  { variable_id: [[timestamp, value, date_saved]] } with\n                timestamp in s and date_saved as datetime, timestamp in s or None\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n            batch_size (int): Number of values to safe in bulk_create at once. Defauls\n                to 1000\n\n        Returns:\n            None\n        \"\"\"\n        data_model = self._import_model()\n        items = self.datasource.datasource_check(items)\n        recorded_datas = []\n        date_saved = date_saved if date_saved is not None else now()\n        for item in items:\n            logger.debug(f\"{item} has {len(item.cached_values_to_write)} to write.\")\n            if len(item.cached_values_to_write):\n                for cached_value in item.cached_values_to_write:\n                    # add date saved if not exist in variable object, if date_saved is\n                    # in kwargs it will be used instead of the variable.date_saved\n                    # (see the create_data_element_from_variable function)\n                    if not hasattr(item, \"date_saved\") or item.date_saved is None:\n                        item.date_saved = date_saved\n                    # create the recorded data object\n                    rc = data_model.objects.create_data_element_from_variable(\n                        item, cached_value[1], cached_value[0], **kwargs\n                    )\n                    # append the object to the elements to save\n                    if rc is not None:\n                        recorded_datas.append(rc)\n\n        try:\n            data_model.objects.bulk_create(recorded_datas, **kwargs)\n        except IntegrityError:\n            logger.debug(\n                f'{data_model._meta.object_name} objects already exists, retrying ignoring conflicts for : {\", \".join(str(i.id) + \" \" + str(i.variable.id) for i in recorded_datas)}'  # noqa: E501\n            )\n            data_model.objects.bulk_create(\n                recorded_datas, ignore_conflicts=True, **kwargs\n            )\n        for item in items:\n            item.date_saved = None\n            item.erase_cache()\n\n    def write_raw_datapoints(self, datapoints: dict, date_saved=None, batch_size=1000):\n        \"\"\"writes raw datapoints to the database in the form\n\n        Args:\n            datapoints:  { variable_id: [[timestamp, value, date_saved]] } with\n                timestamp in s and date_saved as datetime, timestamp in s or None\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n            batch_size (int): Number of values to safe in bulk_create at once. Defauls\n                to 1000\n\n        Returns:\n            None\n        \"\"\"\n        data_model = self._import_model()\n        recorded_datas = []\n        for variable_id in datapoints.keys():\n            variable = Variable.objects.filter(pk=variable_id).first()\n            for datapoint in datapoints[variable_id]:\n                if len(datapoint)==2:\n                    if date_saved is None:\n                        datapoint.append(now())\n                    else:\n                        datapoint.append(date_saved)\n\n                elif len(datapoint)==3:\n                    if datapoint[2] is None:\n                        if date_saved is None:\n                            datapoint[2]=now()\n                        else:\n                            datapoint[2]=date_saved\n\n                    elif (type(datapoint[2]) is int or type(datapoint[2]) is float):\n                        datapoint[2]=timestamp_to_datetime(datapoint[2])\n\n                #datapoint[0] *= 1000 # convert timestamp from s to ms\n\n                rc = data_model.objects.create_data_element_from_variable(\n                    variable=variable,\n                    value=datapoint[1],\n                    timestamp=datapoint[0],\n                    date_saved=datapoint[2],\n                )\n\n                if rc is not None:\n                    recorded_datas.append(rc)\n\n        try:\n            data_model.objects.bulk_create(recorded_datas, batch_size=batch_size)\n        except IntegrityError:\n            logger.debug(\n                f'{data_model._meta.object_name} objects already exists, retrying ignoring conflicts for : {\", \".join(str(i.id) + \" \" + str(i.variable.id) for i in recorded_datas)}'  # noqa: E501\n            )\n            data_model.objects.bulk_create(\n                recorded_datas, ignore_conflicts=True, batch_size=batch_size\n            )\n\n    class Meta:\n        db_table = \"pyscada_djangodatabase\"\n\n\nclass RecordedDataOld(models.Model):\n    \"\"\"\n    Big Int first 42 bits are used for the unixtime in ms, unsigned because we only\n    store time values that are later than 1970, rest 21 bits are used for the\n    variable id to have a uniqe primary key\n    63 bit 111111111111111111111111111111111111111111111111111111111111111\n    42 bit 111111111111111111111111111111111111111111000000000000000000000\n    21 bit \t\t\t\t\t\t\t\t\t\t    1000000000000000000000\n    \"\"\"\n\n    id = models.BigIntegerField(primary_key=True)\n    value_boolean = models.BooleanField(default=False, blank=True)  # boolean\n    value_int16 = models.SmallIntegerField(null=True, blank=True)  # int16, uint8, int8\n    value_int32 = models.IntegerField(\n        null=True, blank=True\n    )  # uint8, int16, uint16, int32\n    value_int64 = models.BigIntegerField(null=True, blank=True)  # uint32, int64\n    value_float64 = models.FloatField(null=True, blank=True)  # float64\n    variable = models.ForeignKey(Variable, null=True, on_delete=models.SET_NULL)\n\n    def __init__(self, *args, **kwargs):\n        if \"timestamp\" in kwargs:\n            timestamp = kwargs.pop(\"timestamp\")\n        else:\n            timestamp = time.time()\n        if \"variable_id\" in kwargs:\n            variable_id = kwargs[\"variable_id\"]\n        elif \"variable\" in kwargs:\n            variable_id = kwargs[\"variable\"].pk\n        else:\n            variable_id = None\n\n        if variable_id is not None and \"id\" not in kwargs:\n            kwargs[\"id\"] = int(int(int(timestamp * 1000) * 2097152) + variable_id)\n        if \"variable\" in kwargs and \"value\" in kwargs:\n            if kwargs[\"variable\"].value_class.upper() in [\n                \"FLOAT\",\n                \"FLOAT64\",\n                \"DOUBLE\",\n                \"FLOAT32\",\n                \"SINGLE\",\n                \"REAL\",\n            ]:\n                kwargs[\"value_float64\"] = float(kwargs.pop(\"value\"))\n            elif kwargs[\"variable\"].scaling and not kwargs[\n                \"variable\"\n            ].value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n                kwargs[\"value_float64\"] = float(kwargs.pop(\"value\"))\n            elif kwargs[\"variable\"].value_class.upper() in [\"INT64\", \"UINT32\", \"DWORD\"]:\n                kwargs[\"value_int64\"] = int(kwargs.pop(\"value\"))\n                # See https://docs.djangoproject.com/en/stable/ref/models/fields/#bigintegerfield # noqa: E501\n                if (\n                    kwargs[\"value_int64\"] < -9223372036854775808\n                    or kwargs[\"value_int64\"] > 9223372036854775807\n                ):\n                    raise ValueError(\n                        f\"Saving value to RecordedDataOld for {kwargs['variable']} with value class {kwargs['variable'].value_class.upper()} should be in the interval [-9223372036854775808:9223372036854775807], it is {kwargs['value_int64']}\"  # noqa: E501\n                    )\n            elif kwargs[\"variable\"].value_class.upper() in [\n                \"WORD\",\n                \"UINT\",\n                \"UINT16\",\n                \"INT32\",\n            ]:\n                kwargs[\"value_int32\"] = int(kwargs.pop(\"value\"))\n                # See https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield # noqa: E501\n                if (\n                    kwargs[\"value_int32\"] < -2147483648\n                    or kwargs[\"value_int32\"] > 2147483647\n                ):\n                    raise ValueError(\n                        f\"Saving value to RecordedDataOld for {kwargs['variable']} with value class {kwargs['variable'].value_class.upper()} should be in the interval [-2147483648:2147483647], it is {kwargs['value_int32']}\"  # noqa: E501\n                    )\n            elif kwargs[\"variable\"].value_class.upper() in [\n                \"INT16\",\n                \"INT8\",\n                \"UINT8\",\n                \"INT\",\n            ]:\n                kwargs[\"value_int16\"] = int(kwargs.pop(\"value\"))\n                # See https://docs.djangoproject.com/en/stable/ref/models/fields/#smallintegerfield # noqa: E501\n                if kwargs[\"value_int16\"] < -32768 or kwargs[\"value_int16\"] > 32767:\n                    raise ValueError(\n                        f\"Saving value to RecordedDataOld for {kwargs['variable']} with value class {kwargs['variable'].value_class.upper()} should be in the interval [-32768:32767], it is {kwargs['value_int16']}\"  # noqa: E501\n                    )\n            elif kwargs[\"variable\"].value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n                kwargs[\"value_boolean\"] = bool(kwargs.pop(\"value\"))\n\n        # call the django model __init__\n        super(RecordedDataOld, self).__init__(*args, **kwargs)\n        self.timestamp = self.time_value()\n\n    def calculate_pk(self, timestamp=None):\n        \"\"\"\n        calculate the primary key from the timestamp in seconds\n        \"\"\"\n        if timestamp is None:\n            timestamp = time.time()\n        self.pk = int(int(int(timestamp * 1000) * 2097152) + self.variable.pk)\n\n    def __str__(self):\n        return str(self.value())\n\n    def time_value(self):\n        \"\"\"\n        return the timestamp in seconds calculated from the id\n        \"\"\"\n        return (self.pk - self.variable.pk) / 2097152 / 1000.0  # value in seconds\n\n    def value(self, value_class=None):\n        \"\"\"\n        return the stored value\n        \"\"\"\n        if value_class is None:\n            value_class = self.variable.value_class\n\n        if value_class.upper() in [\n            \"FLOAT\",\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT32\",\n            \"SINGLE\",\n            \"REAL\",\n        ]:\n            return self.value_float64\n        elif self.variable.scaling and not value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return self.value_float64\n        elif value_class.upper() in [\"INT64\", \"UINT32\", \"DWORD\"]:\n            return self.value_int64\n        elif value_class.upper() in [\"WORD\", \"UINT\", \"UINT16\", \"INT32\"]:\n            return self.value_int32\n        elif value_class.upper() in [\"INT16\", \"INT8\", \"UINT8\"]:\n            return self.value_int16\n        elif value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return self.value_boolean\n        else:\n            return None\n\n    class Meta:\n        db_table = \"pyscada_recordeddataold\"\n\n\nclass RecordedData(models.Model):\n    \"\"\"\n    id: Big Int first 42 bits are used for the unix time in ms, unsigned because we only\n    store values that are past 1970, the last 21 bits are used for the\n    variable id to have a unique primary key\n    63 bit 111111111111111111111111111111111111111111111111111111111111111\n    42 bit 111111111111111111111111111111111111111111000000000000000000000\n    21 bit \t\t\t\t\t\t\t\t\t\t    1000000000000000000000\n    date_saved: datetime when the model instance is saved in the database\n    (will be set in the save method)\n    \"\"\"\n\n    id = models.BigIntegerField(primary_key=True)\n    date_saved = models.DateTimeField(blank=True, null=True, db_index=True)\n    value_boolean = models.BooleanField(default=False, blank=True)  # boolean\n    value_int16 = models.SmallIntegerField(null=True, blank=True)  # int16, uint8, int8\n    value_int32 = models.IntegerField(\n        null=True, blank=True\n    )  # uint8, int16, uint16, int32\n    value_int64 = models.BigIntegerField(null=True, blank=True)  # uint32, int64, int48\n    value_float64 = models.FloatField(null=True, blank=True)  # float64, float48\n    variable = models.ForeignKey(Variable, null=True, on_delete=models.SET_NULL)\n\n    objects = RecordedDataManager()\n\n    def __init__(self, *args, **kwargs):\n\n        timestamp = kwargs.get(\"timestamp\", time.time_ns() / 1000000000)\n        if \"timestamp\" in kwargs:\n            kwargs.pop(\"timestamp\")\n        if \"variable\" in kwargs:\n            variable_id = kwargs[\"variable\"].pk\n        elif \"variable_id\" in kwargs:\n            variable_id = kwargs[\"variable_id\"]\n            try:\n                kwargs[\"variable\"] = Variable.objects.get(id=variable_id)\n            except Variable.DoesNotExist:\n                raise ValidationError(\n                    f\"Variable with id {variable_id} not found. Cannot save data.\"\n                )\n        else:\n            variable_id = None\n\n        if variable_id is not None and \"id\" not in kwargs:\n            try:\n                kwargs[\"id\"] = int(\n                    int(int(float(timestamp) * 1000) * 2097152) + variable_id\n                )\n            except (TypeError, ValueError) as e:\n                raise ValidationError(\n                    f\"Cannot save data for variable {kwargs['variable']}, timestamp error : {e}\"  # noqa: E501\n                )\n        if \"variable\" in kwargs and \"value\" in kwargs:\n            if kwargs[\"variable\"].value_class.upper() in [\n                \"FLOAT\",\n                \"FLOAT64\",\n                \"DOUBLE\",\n                \"FLOAT32\",\n                \"SINGLE\",\n                \"REAL\",\n                \"FLOAT48\",\n            ]:\n                kwargs[\"value_float64\"] = float(kwargs.pop(\"value\"))\n            elif kwargs[\"variable\"].scaling and not kwargs[\n                \"variable\"\n            ].value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n                kwargs[\"value_float64\"] = float(kwargs.pop(\"value\"))\n            elif kwargs[\"variable\"].value_class.upper() in [\"UINT64\"]:\n                # moving the uint64 range [0, 2**64 - 1] to the int64\n                # [-2**63, 2**63 - 1] to be stored as a django BigIntegerField\n                kwargs[\"value_int64\"] = int(kwargs.pop(\"value\")) - 2**63\n                # See https://docs.djangoproject.com/en/stable/ref/models/fields/#bigintegerfield # noqa: E501\n                if (\n                    kwargs[\"value_int64\"] < -9223372036854775808\n                    or kwargs[\"value_int64\"] > 9223372036854775807\n                ):\n                    raise ValueError(\n                        f\"Saving value to RecordedData for {kwargs['variable']} with value class {kwargs['variable'].value_class.upper()} should be in the interval [0:18446744073709551615], it is {kwargs['value_int64'] + 2**63}\"  # noqa: E501\n                    )\n            elif kwargs[\"variable\"].value_class.upper() in [\n                \"INT64\",\n                \"UINT32\",\n                \"DWORD\",\n                \"INT48\",\n            ]:\n                kwargs[\"value_int64\"] = int(kwargs.pop(\"value\"))\n                # See https://docs.djangoproject.com/en/stable/ref/models/fields/#bigintegerfield # noqa: E501\n                if (\n                    kwargs[\"value_int64\"] < -9223372036854775808\n                    or kwargs[\"value_int64\"] > 9223372036854775807\n                ):\n                    raise ValueError(\n                        f\"Saving value to RecordedData for {kwargs['variable']} with value class {kwargs['variable'].value_class.upper()} should be in the interval [-9223372036854775808:9223372036854775807], it is {kwargs['value_int64']}\"  # noqa: E501\n                    )\n            elif kwargs[\"variable\"].value_class.upper() in [\n                \"WORD\",\n                \"UINT\",\n                \"UINT16\",\n                \"INT32\",\n            ]:\n                kwargs[\"value_int32\"] = int(kwargs.pop(\"value\"))\n                # See https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield # noqa: E501\n                if (\n                    kwargs[\"value_int32\"] < -2147483648\n                    or kwargs[\"value_int32\"] > 2147483647\n                ):\n                    raise ValueError(\n                        f\"Saving value to RecordedData for {kwargs['variable']} with value class {kwargs['variable'].value_class.upper()} should be in the interval [-2147483648:2147483647], it is {kwargs['value_int32']}\"  # noqa: E501\n                    )\n            elif kwargs[\"variable\"].value_class.upper() in [\n                \"INT16\",\n                \"INT8\",\n                \"UINT8\",\n                \"INT\",\n            ]:\n                kwargs[\"value_int16\"] = int(kwargs.pop(\"value\"))\n                # See https://docs.djangoproject.com/en/stable/ref/models/fields/#smallintegerfield # noqa: E501\n                if kwargs[\"value_int16\"] < -32768 or kwargs[\"value_int16\"] > 32767:\n                    raise ValueError(\n                        f\"Saving value to RecordedData for {kwargs['variable']} with value class {kwargs['variable'].value_class.upper()} should be in the interval [-32768:32767], it is {kwargs['value_int16']}\"  # noqa: E501\n                    )\n            elif kwargs[\"variable\"].value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n                kwargs[\"value_boolean\"] = bool(kwargs.pop(\"value\"))\n            else:\n                logger.warning(\n                    f\"The {kwargs['variable'].value_class.upper()} variable value class is not defined in RecordedData __init__ function. Default storing value as float.\"  # noqa: E501\n                )\n                kwargs[\"value_float64\"] = float(kwargs.pop(\"value\"))\n\n        # call the django model __init__\n        super(RecordedData, self).__init__(*args, **kwargs)\n        if self.variable is not None:\n            self.timestamp = self.time_value()\n        elif self.date_saved is not None:\n            self.timestamp = self.date_saved.timestamp()\n        else:\n            self.timestamp = time.time()\n\n    def calculate_pk(self, timestamp=None):\n        \"\"\"\n        calculate the primary key from the timestamp in seconds\n        \"\"\"\n        if timestamp is None:\n            timestamp = time.time()\n        self.pk = int(int(int(timestamp * 1000) * 2097152) + self.variable.pk)\n\n    def __str__(self):\n        return str(self.value())\n\n    def time_value(self):\n        \"\"\"\n        return the timestamp in seconds calculated from the id\n        \"\"\"\n        return (self.pk - self.variable.pk) / 2097152 / 1000.0  # value in seconds\n\n    def value(self, value_class=None):\n        \"\"\"\n        return the stored value\n        \"\"\"\n        if self.variable is None:\n            return None\n\n        if value_class is None:\n            value_class = self.variable.value_class\n\n        if value_class.upper() in [\n            \"FLOAT\",\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT32\",\n            \"SINGLE\",\n            \"REAL\",\n            \"FLOAT48\",\n        ]:\n            return self.value_float64\n        elif self.variable.scaling and not value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return self.value_float64\n        elif value_class.upper() in [\"UINT64\"]:\n            # moving the int64 range [2**63, 2**63 - 1] stored as a django\n            # BigIntegerField to the int64 [0, 2**64 - 1]\n            value = self.value_int64\n            if value is None:\n                return value\n            return value + 2**63\n        elif value_class.upper() in [\"INT64\", \"UINT32\", \"DWORD\", \"INT48\"]:\n            return self.value_int64\n        elif value_class.upper() in [\"WORD\", \"UINT\", \"UINT16\", \"INT32\"]:\n            return self.value_int32\n        elif value_class.upper() in [\"INT16\", \"INT8\", \"UINT8\"]:\n            return self.value_int16\n        elif value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return self.value_boolean\n        else:\n            logger.warning(\n                f\"The {value_class.upper()} variable value class is not defined in RecordedData value function. Default reading value as float.\"  # noqa: E501\n            )\n            return self.value_float64\n\n    def save(self, *args, **kwargs):\n        if self.date_saved is None:\n            self.date_saved = now()\n        super(RecordedData, self).save(*args, **kwargs)\n\n    class Meta:\n        db_table = \"pyscada_recordeddata\"\n"
  },
  {
    "path": "pyscada/event/__init__.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n"
  },
  {
    "path": "pyscada/event/worker.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.models import Event, ComplexEvent\nfrom pyscada.utils.scheduler import Process as BaseProcess\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass Process(BaseProcess):\n    def __init__(self, dt=5, **kwargs):\n        super(Process, self).__init__(dt=dt, **kwargs)\n\n    def loop(self):\n        \"\"\"\n        check for events and trigger actions\n        \"\"\"\n        for item in Event.objects.all():\n            item.do_event_check()\n\n        for item in ComplexEvent.objects.all():\n            item.do_event_check()\n\n        return 1, None\n"
  },
  {
    "path": "pyscada/export/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\nfrom pyscada import core\n\n\n__version__ = core.__version__\n__author__ = core.__author__\n\nparent_process_list = [\n    {\n        \"pk\": 99,\n        \"label\": \"pyscada.export\",\n        \"process_class\": \"pyscada.export.worker.MasterProcess\",\n        \"process_class_kwargs\": '{\"dt_set\":30}',\n        \"enabled\": True,\n    }\n]\n"
  },
  {
    "path": "pyscada/export/admin.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.admin import admin_site\nfrom pyscada.export.models import ScheduledExportTask, ExportTask\n\nfrom django.contrib import admin\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass ScheduledExportTaskAdmin(admin.ModelAdmin):\n    filter_horizontal = (\"variables\",)\n\n\nclass ExportTaskAdmin(admin.ModelAdmin):\n    filter_horizontal = (\"variables\",)\n    list_display = (\n        \"id\",\n        \"label\",\n        \"datetime_start\",\n        \"datetime_finished\",\n        \"mean_value_period\",\n        \"file_format\",\n        \"done\",\n        \"busy\",\n        \"failed\",\n        \"downloadlink\",\n    )\n    list_display_links = (\"id\", \"label\")\n    readonly_fields = (\n        \"filename\",\n        \"datetime_finished\",\n        \"done\",\n        \"busy\",\n        \"failed\",\n        \"backgroundprocess\",\n        \"downloadlink\",\n    )\n    save_as = True\n\n\nadmin_site.register(ScheduledExportTask, ScheduledExportTaskAdmin)\nadmin_site.register(ExportTask, ExportTaskAdmin)\n"
  },
  {
    "path": "pyscada/export/apps.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.apps import AppConfig\nfrom django.utils.translation import gettext_lazy as _\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass PyScadaExportConfig(AppConfig):\n    name = \"pyscada.export\"\n    verbose_name = _(\"PyScada Export\")\n    default_auto_field = \"django.db.models.AutoField\"\n"
  },
  {
    "path": "pyscada/export/csv_file.py",
    "content": "from __future__ import unicode_literals\nimport csv\nimport os\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef unix_time_stamp_to_excel_datenum(timestamp):\n    \"\"\"\n    convert from unix time (seconds since 01/01/1970) to Excel datenum (days since 01/01/1900)\n    \"\"\"\n    return (timestamp / 86400.0) + 25569.0\n\n\nclass ExcelCompatibleCSV:\n    def __init__(self, filename, **kwargs):\n        self.filename = os.path.expanduser(filename)\n        self.data = {}\n        self.header = {}\n        self.data_rows = 0\n        self.header_is_writen = False\n        self.dialect = \"excel\"  # default is excel\n\n    def write_data(self, name, data, **kwargs):\n        if name in self.data:\n            # append data\n            self.data[name] += data\n            self.data_rows = max(self.data_rows, len(self.data[name]))\n        else:\n            # new entry\n            self.data[name] = data\n            self.data_rows = max(self.data_rows, len(self.data[name]))  # update len\n            self.header[name] = kwargs.copy()\n\n    def write_file(self):\n        # check the data\n        # data has to have the same len\n        del_keys = []\n        for key in self.data:\n            if len(self.data[key]) != self.data_rows:\n                del_keys.append(key)\n        for i in del_keys:\n            self.data.pop(i, None)\n            self.header.pop(i, None)\n        # construct the write arrays\n        keys = self.data.keys()\n        output = zip(*self.data.values())\n        # truncate file on first write, otherwise append\n        write_mode = \"a\" if self.header_is_writen else \"w\"\n        with open(self.filename, write_mode) as f:\n            writer = csv.writer(f, dialect=self.dialect)\n            if not self.header_is_writen:\n                writer.writerow(keys)\n                self.header_is_writen = True\n            writer.writerows(output)\n        # reset internal data\n        self.data = {}\n        self.data_rows = 0\n        self.header = {}\n\n    def reopen(self):\n        self.write_file()\n\n    def close_file(self):\n        self.write_file()\n"
  },
  {
    "path": "pyscada/export/export.py",
    "content": "# PyScada\nfrom __future__ import unicode_literals\n\nfrom pyscada.utils import validate_value_class\n\nfrom pyscada.models import Variable, BackgroundProcess\nfrom pyscada.export.hdf5_file import MatCompatibleH5\nfrom pyscada.export.hdf5_file import unix_time_stamp_to_matlab_datenum\nfrom pyscada.export.csv_file import ExcelCompatibleCSV\nfrom pyscada.export.csv_file import unix_time_stamp_to_excel_datenum\nfrom pyscada.export.models import ExportTask\nfrom six import string_types\n\n# Django\nfrom django.conf import settings\nfrom django.utils.timezone import now\n\n# other\nfrom datetime import datetime\nimport os\nfrom time import time, strftime, mktime\nfrom numpy import float64, float32, int32, uint16, int16, uint8, arange\nimport numpy as np\nimport math\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef export_recordeddata_to_file(\n    time_min=None,\n    time_max=None,\n    filename=None,\n    active_vars=None,\n    file_extension=None,\n    append_to_file=False,\n    no_mean_value=False,\n    mean_value_period=5.0,\n    backgroundprocess_id=None,\n    export_task_id=None,\n    **kwargs\n):\n    \"\"\"\n    read all data\n    \"\"\"\n    if backgroundprocess_id is not None:\n        tp = BackgroundProcess.objects.get(id=backgroundprocess_id)\n        tp.message = \"init\"\n        tp.last_update = now()\n        tp.save()\n    else:\n        tp = None\n\n    if isinstance(time_max, string_types):\n        # convert date strings\n        time_max = mktime(datetime.strptime(time_max, \"%d-%m-%Y %H:%M:%S\").timetuple())\n    if isinstance(time_min, string_types):\n        # convert date strings\n        time_min = mktime(datetime.strptime(time_min, \"%d-%m-%Y %H:%M:%S\").timetuple())\n\n    # add default time_min\n    if time_max is None:\n        time_max = time()  # now\n    if time_min is None:\n        time_min = time() - 24 * 60 * 60  # last 24 hours\n\n    # add default extension if no extension is given\n    if file_extension is None and filename is None:\n        file_extension = \".h5\"\n    elif filename is not None:\n        file_extension = \".\" + filename.split(\".\")[-1]\n        filename = filename[: len(filename) - len(filename.split(\".\")[-1]) - 1]\n    # validate file type\n    if file_extension not in [\".h5\", \".mat\", \".csv\"]:\n        if tp is not None:\n            tp.last_update = now()\n            tp.message = \"failed wrong file type\"\n            tp.failed = True\n            tp.save()\n\n        if export_task_id is not None:\n            job = ExportTask.objects.filter(pk=export_task_id).first()\n            if job:\n                job.failed = True\n                job.save()\n        return\n\n    #\n    if active_vars is None:\n        active_vars = Variable.objects.filter(active=1, device__active=1)\n    else:\n        if type(active_vars) is str:\n            if active_vars == \"all\":\n                active_vars = Variable.objects.all()\n            else:\n                active_vars = Variable.objects.filter(active=1, device__active=1)\n        else:\n            active_vars = Variable.objects.filter(\n                pk__in=active_vars, active=1, device__active=1\n            )\n\n    #\n    if hasattr(settings, \"PYSCADA_EXPORT\"):\n        if \"output_folder\" in settings.PYSCADA_EXPORT:\n            backup_file_path = os.path.expanduser(\n                settings.PYSCADA_EXPORT[\"output_folder\"]\n            )\n        else:\n            backup_file_path = os.path.expanduser(\"~/measurement_data_dumps\")\n    else:\n        backup_file_path = os.path.expanduser(\"~/measurement_data_dumps\")\n\n    # add filename prefix\n    backup_file_name = \"measurement_data\"\n    if hasattr(settings, \"PYSCADA_EXPORT\"):\n        if \"file_prefix\" in settings.PYSCADA_EXPORT:\n            backup_file_name = settings.PYSCADA_EXPORT[\"file_prefix\"] + backup_file_name\n    # create output dir if not existing\n    if not os.path.exists(backup_file_path):\n        os.mkdir(backup_file_path)\n\n    # filename  and suffix\n    cdstr_from = datetime.fromtimestamp(time_min).strftime(\"%Y_%m_%d_%H%M\")\n    cdstr_to = datetime.fromtimestamp(time_max).strftime(\"%Y_%m_%d_%H%M\")\n\n    if filename is None:\n        if \"filename_suffix\" in kwargs:\n            filename = os.path.join(\n                backup_file_path,\n                backup_file_name\n                + \"_\"\n                + cdstr_from\n                + \"_\"\n                + cdstr_to\n                + \"_\"\n                + kwargs[\"filename_suffix\"],\n            )\n        else:\n            filename = os.path.join(\n                backup_file_path, backup_file_name + \"_\" + cdstr_from + \"_\" + cdstr_to\n            )\n    else:\n        filename = os.path.join(backup_file_path, filename)\n\n    # check if file exists\n    if os.path.exists(filename + file_extension) and not append_to_file:\n        count = 0\n        filename_old = filename\n        while os.path.exists(filename + file_extension):\n            filename = filename_old + \"_%03.0f\" % count\n            count += 1\n\n    # append the extension\n    filename = filename + file_extension\n\n    # add Filename to ExportTask\n    if export_task_id is not None:\n        job = ExportTask.objects.filter(pk=export_task_id).first()\n        if job:\n            job.filename = filename\n            job.save()\n\n    if mean_value_period == 0:\n        no_mean_value = True\n        mean_value_period = 5.0  # todo get from DB, default is 5 seconds\n\n    # calculate time vector\n\n    timevalues = arange(\n        math.ceil(time_min / mean_value_period) * mean_value_period,\n        math.floor(time_max / mean_value_period) * mean_value_period,\n        mean_value_period,\n    )\n\n    # get Meta from Settings\n    if hasattr(settings, \"PYSCADA_META\"):\n        if \"description\" in settings.PYSCADA_META:\n            description = settings.PYSCADA_META[\"description\"]\n        else:\n            description = \"None\"\n        if \"name\" in settings.PYSCADA_META:\n            name = settings.PYSCADA_META[\"name\"]\n        else:\n            name = \"None\"\n    else:\n        description = \"None\"\n        name = \"None\"\n\n    if file_extension == \".mat\":\n        bf = MatCompatibleH5(\n            filename,\n            version=\"1.1\",\n            description=description,\n            name=name,\n            creation_date=strftime(\"%d-%b-%Y %H:%M:%S\"),\n        )\n        out_timevalues = [\n            unix_time_stamp_to_matlab_datenum(element) for element in timevalues\n        ]\n\n    elif file_extension == \".h5\":\n        bf = MatCompatibleH5(\n            filename,\n            version=\"1.1\",\n            description=description,\n            name=name,\n            creation_date=strftime(\"%d-%b-%Y %H:%M:%S\"),\n        )\n        out_timevalues = timevalues\n\n    elif file_extension == \".csv\":\n        bf = ExcelCompatibleCSV(\n            filename,\n            version=\"1.1\",\n            description=description,\n            name=name,\n            creation_date=strftime(\"%d-%b-%Y %H:%M:%S\"),\n        )\n        out_timevalues = [\n            unix_time_stamp_to_excel_datenum(element) for element in timevalues\n        ]\n\n    else:\n        return\n\n    # less than 24\n    # read everything\n    bf.write_data(\n        \"time\",\n        float64(out_timevalues),\n        id=0,\n        description=\"global time vector\",\n        value_class=validate_value_class(\"FLOAT64\"),\n        unit=\"Days since 0000-1-1 00:00:00\",\n        color=\"#000000\",\n        short_name=\"time\",\n        chart_line_thickness=3,\n    )\n\n    for var_idx in range(0, active_vars.count(), 10):\n        if tp is not None:\n            tp.last_update = now()\n            tp.message = \"reading values from database (%d)\" % var_idx\n            tp.save()\n        # query data\n        var_slice = active_vars[var_idx : var_idx + 10]\n        data = Variable.objects.query_datapoints(\n            variable_ids=list(var_slice.values_list(\"pk\", flat=True)),\n            time_min=time_min,\n            time_max=time_max,\n            query_first_value=True,\n        )\n\n        for var in var_slice:\n            # write background task info\n            if tp is not None:\n                tp.last_update = now()\n                tp.message = \"writing values for %s (%d) to file\" % (var.name, var.pk)\n                tp.save()\n            # check if variable is scalled\n            if var.scaling is None or var.value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n                value_class = var.value_class\n            else:\n                value_class = \"FLOAT64\"\n\n            # read unit\n            if hasattr(var.unit, \"udunit\"):\n                udunit = var.unit.udunit\n            else:\n                udunit = \"None\"\n\n            if var.pk not in data:\n                # write dummy data\n                bf.write_data(\n                    var.name,\n                    _cast_value(\n                        [0] * len(timevalues), validate_value_class(value_class)\n                    ),\n                    id=var.pk,\n                    description=var.description,\n                    value_class=validate_value_class(value_class),\n                    unit=udunit,\n                    color=var.chart_line_color_code(),\n                    short_name=var.short_name,\n                    chart_line_thickness=var.chart_line_thickness,\n                )\n                if tp is not None:\n                    tp.last_update = now()\n                    tp.message = \"no values for %s (%d) to file\" % (var.name, var.pk)\n                    tp.save()\n                continue\n\n            out_data = np.zeros(len(timevalues))\n            # i                            # time data index\n            ii = 0  # source data index\n            # calculate mean values\n            last_value = None\n            max_ii = len(data[var.pk]) - 1\n            for i in range(len(timevalues)):  # iter over time values\n                if ii >= max_ii + 1:\n                    # if not more data in data source break\n                    if last_value is not None:\n                        out_data[i] = last_value\n\n                    continue\n                # init mean value vars\n                tmp = 0.0  # sum\n                tmp_i = 0.0  # count\n\n                if data[var.pk][ii][0] / 1000.0 < timevalues[i]:\n                    # skip elements that are befor current time step\n                    while data[var.pk][ii][0] / 1000.0 < timevalues[i] and ii < max_ii:\n                        last_value = data[var.pk][ii][1]\n                        ii += 1\n\n                if ii >= max_ii:\n                    if last_value is not None:\n                        out_data[i] = last_value\n\n                    continue\n\n                # calc mean value\n                if (\n                    timevalues[i]\n                    <= data[var.pk][ii][0] / 1000.0\n                    < timevalues[i] + mean_value_period\n                ):\n                    # there is data in time range\n                    while (\n                        timevalues[i]\n                        <= data[var.pk][ii][0] / 1000.0\n                        < timevalues[i] + mean_value_period\n                        and ii < max_ii\n                    ):\n                        # calculate mean value\n                        if no_mean_value:\n                            tmp = data[var.pk][ii][1]\n                            tmp_i = 1\n                        else:\n                            tmp += data[var.pk][ii][1]\n                            tmp_i += 1\n\n                        last_value = data[var.pk][ii][1]\n                        ii += 1\n                    # calc and store mean value\n                    if tmp_i > 0:\n                        out_data[i] = tmp / tmp_i\n                    else:\n                        out_data[i] = data[var.pk][ii][1]\n                        last_value = data[var.pk][ii][1]\n                else:\n                    # there is no data in time range, keep last value, not mean value\n                    if last_value is not None:\n                        out_data[i] = last_value\n\n            # write data\n            if file_extension == \".h5\" or file_extension == \".mat\":\n                var_name = var.name.replace(\"/\", \"_\")  # escape / character,\n            else:\n                var_name = var.name\n\n            bf.write_data(\n                var_name,\n                _cast_value(out_data, validate_value_class(value_class)),\n                id=var.pk,\n                description=var.description,\n                value_class=validate_value_class(value_class),\n                unit=udunit,\n                color=var.chart_line_color_code(),\n                short_name=var.short_name,\n                chart_line_thickness=var.chart_line_thickness,\n            )\n\n    bf.close_file()\n    if tp is not None:\n        tp.last_update = now()\n        tp.message = \"done\"\n        tp.done = True\n        tp.save()\n\n\ndef _cast_value(value, _type):\n    \"\"\"\n    cast value to _type\n    \"\"\"\n    if _type.upper() == \"FLOAT64\":\n        return float64(value)\n    elif _type.upper() == \"FLOAT32\":\n        return float32(value)\n    elif _type.upper() == \"INT32\":\n        return int32(value)\n    elif _type.upper() == \"UINT16\":\n        return uint16(value)\n    elif _type.upper() == \"INT16\":\n        return int16(value)\n    elif _type.upper() == \"BOOLEAN\":\n        return uint8(value)\n    else:\n        return float64(value)\n"
  },
  {
    "path": "pyscada/export/hdf5_file.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nCreated on Sat Nov 30 14:22:58 2013\n\n@author: Martin Schröder\n\"\"\"\nfrom __future__ import unicode_literals\nimport os\nimport io\nimport h5py\nimport time\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef unix_time_stamp_to_matlab_datenum(timestamp):\n    \"\"\"\n    convert dtype to maltab class string\n    \"\"\"\n    return (timestamp / 86400) + 719529\n\n\ndef dtype_to_matlab_class(dtype):\n    \"\"\"\n    convert dtype to matlab class string\n    \"\"\"\n    if dtype.str in [\"<f8\"]:\n        return \"double\"\n    elif dtype.str in [\"<f4\"]:\n        return \"single\"\n    elif dtype.str in [\"<i8\"]:\n        return \"int64\"\n    elif dtype.str in [\"<u8\"]:\n        return \"uint64\"\n    elif dtype.str in [\"<i4\"]:\n        return \"int32\"\n    elif dtype.str in [\"<u4\"]:\n        return \"uint32\"\n    elif dtype.str in [\"<i2\"]:\n        return \"int16\"\n    elif dtype.str in [\"<u2\"]:\n        return \"uint16\"\n    elif dtype.str in [\"|i1\"]:\n        return \"int8\"\n    elif dtype.str in [\"|u1\"]:\n        return \"uint8\"\n\n\nclass MatCompatibleH5:\n    def __init__(self, filename, **kwargs):\n        \"\"\" \"\"\"\n        self.filename = os.path.expanduser(filename)\n        self.filepath = []\n        self.CHUNCK = 4320  # 12V/Min * 60 Min/Hour * 6 Hours (1/4 Day)\n        self.GZIP_LEVEL = 3\n        if not os.path.exists(self.filename):\n            self.create_file()\n        else:\n            self.open_file()\n\n        for key, value in kwargs.items():\n            if isinstance(value, bytes):\n                self._f.attrs[key] = value\n            elif isinstance(value, str):\n                self._f.attrs[key] = value.encode(\"utf-8\").__str__()\n            else:\n                self._f.attrs[key] = value.__str__()\n        self.reopen()\n\n    def create_file(self):\n        self._f = h5py.File(self.filename, \"a\", userblock_size=512)\n        self._f.close()\n        userblock_data = (\n            \"MATLAB 7.3 MAT-file, Platform: PCWIN64, Created on: %s HDF5 schema 1.00 .\"\n            % time.strftime(\"%a %b %d %H:%M:%S %Y\")\n        )\n        while len(userblock_data) < 116:\n            userblock_data += \" \"\n        userblock_data += chr(0) * 9\n        userblock_data += \"\u0002IM\"\n        with io.open(self.filename, \"rb+\") as f:\n            f.write(userblock_data.encode(\"utf-8\"))\n        self.reopen()\n\n    def close_file(self):\n        if self._f:\n            self._f.close()\n\n    def reopen(self):\n        self.close_file()\n        self.open_file()\n\n    def open_file(self):\n        self._f = h5py.File(self.filename, \"r+\")\n        self._d = {}\n        self._cd = {}\n        for d in self._f.values():\n            self._d[d.name[1::]] = d\n            if d.__class__.__name__ == \"Group\":\n                for gm in d.values():\n                    self._cd[gm.name[1::]] = gm\n\n    def __del__(self):\n        self.close_file()\n\n    def create_dataset(self, name, dtype):\n        if name in self._d:\n            return False\n        self._d[name] = self._f.create_dataset(\n            name,\n            shape=(0,),\n            dtype=dtype,\n            maxshape=(None,),\n            chunks=(self.CHUNCK,),\n            compression=\"gzip\",\n            compression_opts=self.GZIP_LEVEL,\n        )\n        self._d[name].attrs[\"MATLAB_class\"] = dtype_to_matlab_class(dtype)\n        return self._d[name]\n\n    def create_group(self, name):\n        self._d[name] = self._f.create_group(name)\n\n    def create_complex_dataset(self, gname, dtype):\n        if gname in self._d:\n            return False\n        self.create_group(gname)\n        self._cd[gname + \"/values\"] = self._d[gname].create_dataset(\n            \"values\",\n            shape=(0,),\n            dtype=dtype,\n            maxshape=(None,),\n            chunks=(self.CHUNCK,),\n            compression=\"gzip\",\n            compression_opts=self.GZIP_LEVEL,\n        )\n\n        self._cd[gname + \"/time\"] = self._d[gname].create_dataset(\n            \"time\",\n            shape=(0,),\n            dtype=\"f8\",\n            maxshape=(None,),\n            chunks=(self.CHUNCK,),\n            compression=\"gzip\",\n            compression_opts=self.GZIP_LEVEL,\n        )\n        self._cd[gname + \"/time\"].attrs[\"MATLAB_class\"] = \"double\"\n        self._cd[gname + \"/values\"].attrs[\"MATLAB_class\"] = dtype_to_matlab_class(dtype)\n        return True\n\n    def write_data(self, name, data, **kwargs):\n        if self.create_dataset(name, data.dtype):\n            for key, value in kwargs.items():\n                if isinstance(value, bytes):\n                    self._d[name].attrs[key] = value\n                elif isinstance(value, str):\n                    self._f.attrs[key] = value.encode(\"utf-8\").__str__()\n                else:\n                    self._d[name].attrs[key] = value.__str__()\n\n        dl = self._d[name].len()\n        self._d[name].resize((dl + data.size,))\n        self._d[name][dl::] = data\n\n    def write_complex_data(self, gname, data, times):\n        self.create_complex_dataset(gname, data.dtype)\n        dl = self._cd[gname + \"/values\"].len()\n        self._cd[gname + \"/values\"].resize((dl + data.size,))\n        self._cd[gname + \"/time\"].resize((dl + data.size,))\n        self._cd[gname + \"/values\"][dl::] = data\n        self._cd[gname + \"/time\"][dl::] = times\n\n    def batch_write(self, data_list):\n        for name in data_list:\n            self.write_data(name, data_list[name])\n\n    def batch_complex_write(self, data_list):\n        times = data_list.pop(\"time\")\n        self.write_data(\"time\", times)\n        for name in data_list:\n            self.write_complex_data(name, data_list[name], times)\n"
  },
  {
    "path": "pyscada/export/management/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/export/management/commands/PyScadaExportData.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\nimport os\nfrom pyscada.export.export import export_recordeddata_to_file\nfrom django.core.management.base import BaseCommand\n\n\nclass Command(BaseCommand):\n    help = \"export data to file\"\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            \"--filename\",\n            dest=\"filename\",\n            default=None,\n            type=str,\n            help=\"the filename and path to write to\",\n        )\n        parser.add_argument(\n            \"--start_time\",\n            dest=\"start_time\",\n            default=None,\n            type=str,\n            help='the starting time to begin the export from, can be either unixtimestamp or string in \"%d-%b-%Y %H:%M:%S\" style',\n        )\n        parser.add_argument(\n            \"--stop_time\",\n            dest=\"stop_time\",\n            default=None,\n            type=str,\n            help='the last time to export, can be either unixtimestamp or string in \"%d-%b-%Y %H:%M:%S\" style',\n        )\n\n    def handle(self, *args, **options):\n        if options[\"filename\"] is None:\n            export_recordeddata_to_file(options[\"start_time\"], options[\"stop_time\"])\n        elif options[\"filename\"] is not None:\n            # export_recordeddata_to_file(options['start_time'],options['stop_time'],os.path.abspath(options['filename']))\n            export_recordeddata_to_file(\n                options[\"start_time\"], options[\"stop_time\"], options[\"filename\"]\n            )\n"
  },
  {
    "path": "pyscada/export/management/commands/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/export/migrations/0001_initial.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0006_auto_20151130_1449\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ExportJob\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"label\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"day_time\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        help_text=\"day time wenn the job will start be started\",\n                        choices=[\n                            (0, \"0:00\"),\n                            (1, \"1:00\"),\n                            (2, \"2:00\"),\n                            (3, \"3:00\"),\n                            (4, \"4:00\"),\n                            (5, \"5:00\"),\n                            (6, \"6:00\"),\n                            (7, \"7:00\"),\n                            (8, \"8:00\"),\n                            (9, \"9:00\"),\n                            (10, \"10:00\"),\n                            (11, \"11:00\"),\n                            (12, \"12:00\"),\n                            (13, \"13:00\"),\n                            (14, \"14:00\"),\n                            (15, \"15:00\"),\n                            (16, \"16:00\"),\n                            (17, \"17:00\"),\n                            (18, \"18:00\"),\n                            (19, \"19:00\"),\n                            (20, \"20:00\"),\n                            (21, \"21:00\"),\n                            (22, \"22:00\"),\n                            (23, \"23:00\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"mean_value_period\",\n                    models.PositiveSmallIntegerField(\n                        default=0, help_text=\"in Seconds (0 = no mean value)\"\n                    ),\n                ),\n                (\n                    \"active\",\n                    models.BooleanField(\n                        default=False, help_text=\"to activate scheduled export\"\n                    ),\n                ),\n                (\"file_format\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"export_period\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        help_text=b\"\",\n                        choices=[\n                            (1, \"1 Day\"),\n                            (2, \"2 Days (on every even Day of the year)\"),\n                            (7, \"7 Days (on Mondays)\"),\n                            (14, \"14 Days\"),\n                            (30, \"30 Days\"),\n                        ],\n                    ),\n                ),\n                (\"variables\", models.ManyToManyField(to=\"pyscada.Variable\")),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0002_auto_20151201_1617.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0001_initial\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"exportjob\",\n            name=\"file_format\",\n            field=models.CharField(\n                default=\"hdf5\",\n                max_length=400,\n                choices=[\n                    (\"hdf5\", \"Hierarchical Data Format Version 5\"),\n                    (\"mat\", \"Matlab\\xc2\\xae mat v7.3 compatible file\"),\n                    (\n                        \"CSV_EXCEL\",\n                        \"Microsoft\\xc2\\xae Excel\\xc2\\xae compatible csv file\",\n                    ),\n                ],\n            ),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0003_auto_20160315_1140.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0002_auto_20151201_1617\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\n            old_name=\"ExportJob\",\n            new_name=\"ScheduledExportTask\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0004_exporttask.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nfrom django.conf import settings\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0023_auto_20160314_1817\"),\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        (\"export\", \"0003_auto_20160315_1140\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ExportTask\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"label\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"mean_value_period\",\n                    models.PositiveSmallIntegerField(\n                        default=0, help_text=\"in Seconds (0 = no mean value)\"\n                    ),\n                ),\n                (\n                    \"file_format\",\n                    models.CharField(\n                        default=\"hdf5\",\n                        max_length=400,\n                        choices=[\n                            (\"hdf5\", \"Hierarchical Data Format Version 5\"),\n                            (\"mat\", \"Matlab\\xc2\\xae mat v7.3 compatible file\"),\n                            (\n                                \"CSV_EXCEL\",\n                                \"Microsoft\\xc2\\xae Excel\\xc2\\xae compatible csv file\",\n                            ),\n                        ],\n                    ),\n                ),\n                (\"time_min\", models.FloatField(default=None, null=True, blank=True)),\n                (\"time_max\", models.FloatField(default=None, null=True, blank=True)),\n                (\"start\", models.FloatField(default=0)),\n                (\"fineshed\", models.FloatField(default=0, blank=True)),\n                (\"done\", models.BooleanField(default=False)),\n                (\"failed\", models.BooleanField(default=False)),\n                (\n                    \"backgroundtask\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.BackgroundTask\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"user\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=settings.AUTH_USER_MODEL,\n                        null=True,\n                    ),\n                ),\n                (\"variables\", models.ManyToManyField(to=\"pyscada.Variable\")),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0005_auto_20160403_1454.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nfrom django.conf import settings\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0004_exporttask\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"busy\",\n            field=models.BooleanField(default=False),\n        ),\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"filename_suffix\",\n            field=models.CharField(default=\"\", max_length=400, blank=True),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"label\",\n            field=models.CharField(default=\"\", max_length=400, blank=True),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"user\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                blank=True,\n                to=settings.AUTH_USER_MODEL,\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0006_auto_20160404_0949.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0005_auto_20160403_1454\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"backgroundtask\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                blank=True,\n                to=\"pyscada.BackgroundTask\",\n                null=True,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"label\",\n            field=models.CharField(default=\"None\", max_length=400, blank=True),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"start\",\n            field=models.FloatField(default=0, blank=True),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"time_max\",\n            field=models.FloatField(default=None, null=True),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"time_min\",\n            field=models.FloatField(default=None, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0007_auto_20161124_1002.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2016-11-24 10:02\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0006_auto_20160404_0949\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"datetime_fineshed\",\n            field=models.DateTimeField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"datetime_max\",\n            field=models.DateTimeField(default=None, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"datetime_min\",\n            field=models.DateTimeField(default=None, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"datetime_start\",\n            field=models.DateTimeField(blank=True, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0008_auto_20161124_1003.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2016-11-24 10:03\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\nfrom pytz import UTC\nfrom datetime import datetime\n\n\ndef convert_unixtime_float_to_datetime(apps, schema_editor):\n    ExportTask = apps.get_model(\"export\", \"ExportTask\")\n    for item in ExportTask.objects.using(schema_editor.connection.alias).all():\n        item.datetime_fineshed = datetime.fromtimestamp(item.fineshed, UTC)\n        item.datetime_start = datetime.fromtimestamp(item.start, UTC)\n        item.datetime_max = datetime.fromtimestamp(item.time_max, UTC)\n        item.datetime_min = datetime.fromtimestamp(item.time_min, UTC)\n        item.save()\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0007_auto_20161124_1002\"),\n    ]\n\n    operations = [\n        migrations.RunPython(\n            convert_unixtime_float_to_datetime, reverse_code=migrations.RunPython.noop\n        )\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0009_auto_20161128_0948.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2016-11-28 09:48\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0008_auto_20161124_1003\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"exporttask\",\n            name=\"fineshed\",\n        ),\n        migrations.RemoveField(\n            model_name=\"exporttask\",\n            name=\"start\",\n        ),\n        migrations.RemoveField(\n            model_name=\"exporttask\",\n            name=\"time_max\",\n        ),\n        migrations.RemoveField(\n            model_name=\"exporttask\",\n            name=\"time_min\",\n        ),\n        migrations.AlterField(\n            model_name=\"scheduledexporttask\",\n            name=\"day_time\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"0:00\"),\n                    (1, \"1:00\"),\n                    (2, \"2:00\"),\n                    (3, \"3:00\"),\n                    (4, \"4:00\"),\n                    (5, \"5:00\"),\n                    (6, \"6:00\"),\n                    (7, \"7:00\"),\n                    (8, \"8:00\"),\n                    (9, \"9:00\"),\n                    (10, \"10:00\"),\n                    (11, \"11:00\"),\n                    (12, \"12:00\"),\n                    (13, \"13:00\"),\n                    (14, \"14:00\"),\n                    (15, \"15:00\"),\n                    (16, \"16:00\"),\n                    (17, \"17:00\"),\n                    (18, \"18:00\"),\n                    (19, \"19:00\"),\n                    (20, \"20:00\"),\n                    (21, \"21:00\"),\n                    (22, \"22:00\"),\n                    (23, \"23:00\"),\n                ],\n                default=0,\n                help_text=\"day time wenn the job will be started in UTC\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0010_auto_20161128_1049.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2016-11-28 10:49\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport pyscada.export.models\nfrom django.utils.timezone import now\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0009_auto_20161128_0948\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"datetime_start\",\n            field=models.DateTimeField(default=now),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0011_exporttask_filename.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2017-04-06 10:24\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0010_auto_20161128_1049\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"filename\",\n            field=models.CharField(blank=True, max_length=1000, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0012_exporttask_backgroundprocess.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-07-07 14:52\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0038_auto_20170707_1209\"),\n        (\"export\", \"0011_exporttask_filename\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"exporttask\",\n            name=\"backgroundprocess\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.BackgroundProcess\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0013_auto_20170711_0729.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-07-11 07:29\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0012_exporttask_backgroundprocess\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"exporttask\",\n            old_name=\"datetime_fineshed\",\n            new_name=\"datetime_finished\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0014_auto_20170711_1326.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-07-11 13:26\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0013_auto_20170711_0729\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"file_format\",\n            field=models.CharField(\n                choices=[\n                    (\"hdf5\", \"Hierarchical Data Format Version 5\"),\n                    (\"mat\", \"Matlab\\xae mat v7.3 compatible file\"),\n                    (\"CSV_EXCEL\", \"Microsoft\\xae Excel\\xae compatible csv file\"),\n                ],\n                default=\"hdf5\",\n                max_length=400,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"scheduledexporttask\",\n            name=\"export_period\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (1, \"1 Day\"),\n                    (2, \"2 Days (on every even Day of the year)\"),\n                    (7, \"7 Days (on Mondays)\"),\n                    (14, \"14 Days\"),\n                    (30, \"30 Days\"),\n                ],\n                default=0,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"scheduledexporttask\",\n            name=\"file_format\",\n            field=models.CharField(\n                choices=[\n                    (\"hdf5\", \"Hierarchical Data Format Version 5\"),\n                    (\"mat\", \"Matlab\\xae mat v7.3 compatible file\"),\n                    (\"CSV_EXCEL\", \"Microsoft\\xae Excel\\xae compatible csv file\"),\n                ],\n                default=\"hdf5\",\n                max_length=400,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0015_remove_exporttask_backgroundtask.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-09-05 09:42\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0014_auto_20170711_1326\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"exporttask\",\n            name=\"backgroundtask\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/0016_auto_20191004_0912.py",
    "content": "# Generated by Django 2.2.6 on 2019-10-04 09:12\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0015_remove_exporttask_backgroundtask\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"busy\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"done\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"exporttask\",\n            name=\"failed\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"scheduledexporttask\",\n            name=\"active\",\n            field=models.BooleanField(\n                blank=True, default=False, help_text=\"to activate scheduled export\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/export/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/export/models.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.models import Variable, BackgroundProcess\n\nfrom django.conf import settings\nfrom django.contrib.auth.models import User\nfrom django.db import models\nfrom django.utils.safestring import mark_safe\n\nimport os\nimport time\nfrom datetime import datetime\nfrom pytz import UTC\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n#\n# Model\n#\nfrom django.utils.timezone import now\n\n\nclass ScheduledExportTask(models.Model):\n    id = models.AutoField(primary_key=True)\n    label = models.CharField(max_length=400, default=\"\")\n    variables = models.ManyToManyField(Variable)\n    day_time_choices = [(x, \"%d:00\" % x) for x in range(0, 24)]\n    day_time = models.PositiveSmallIntegerField(\n        default=0,\n        choices=day_time_choices,\n        help_text=\"day time wenn the job will be started in UTC\",\n    )\n    mean_value_period = models.PositiveSmallIntegerField(\n        default=0, help_text=\"in Seconds (0 = no mean value)\"\n    )\n    active = models.BooleanField(\n        default=False, blank=True, help_text=\"to activate scheduled export\"\n    )\n    file_format_choices = (\n        (\"hdf5\", \"Hierarchical Data Format Version 5\"),\n        (\"mat\", \"Matlab® mat v7.3 compatible file\"),\n        (\"CSV_EXCEL\", \"Microsoft® Excel® compatible csv file\"),\n    )\n    file_format = models.CharField(\n        max_length=400, default=\"hdf5\", choices=file_format_choices\n    )\n    export_period_choices = (\n        (1, \"1 Day\"),\n        (2, \"2 Days (on every even Day of the year)\"),\n        (7, \"7 Days (on Mondays)\"),\n        (14, \"14 Days\"),\n        (30, \"30 Days\"),\n    )\n    export_period = models.PositiveSmallIntegerField(\n        default=0, choices=export_period_choices, help_text=\"\"\n    )\n\n    def __str__(self):\n        return self.label\n\n\nclass ExportTask(models.Model):\n    id = models.AutoField(primary_key=True)\n    label = models.CharField(max_length=400, default=\"None\", blank=True)\n    backgroundprocess = models.ForeignKey(\n        BackgroundProcess, null=True, blank=True, on_delete=models.SET_NULL\n    )\n    variables = models.ManyToManyField(Variable)\n    mean_value_period = models.PositiveSmallIntegerField(\n        default=0, help_text=\"in Seconds (0 = no mean value)\"\n    )\n    file_format_choices = (\n        (\"hdf5\", \"Hierarchical Data Format Version 5\"),\n        (\"mat\", \"Matlab® mat v7.3 compatible file\"),\n        (\"CSV_EXCEL\", \"Microsoft® Excel® compatible csv file\"),\n    )\n    file_format = models.CharField(\n        max_length=400, default=\"hdf5\", choices=file_format_choices\n    )\n    filename_suffix = models.CharField(max_length=400, default=\"\", blank=True)\n    datetime_min = models.DateTimeField(default=None, null=True)\n    datetime_max = models.DateTimeField(default=None, null=True)\n    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)\n    datetime_start = models.DateTimeField(default=now)\n    datetime_finished = models.DateTimeField(null=True, blank=True)\n    done = models.BooleanField(default=False, blank=True)  # label task has been done\n    busy = models.BooleanField(\n        default=False, blank=True\n    )  # label task is in operation done\n    failed = models.BooleanField(default=False, blank=True)  # label task has failed\n    filename = models.CharField(blank=True, null=True, max_length=1000)\n\n    def __str__(self):\n        return self.label\n\n    def time_min(self):\n        return time.mktime(self.datetime_min.timetuple())\n\n    def time_max(self):\n        return time.mktime(self.datetime_max.timetuple())\n\n    def start(self):\n        return time.mktime(self.datetime_start.timetuple())\n\n    def finished(self):\n        return time.mktime(self.datetime_finished.timetuple())\n\n    def downloadlink(self):\n        if not self.done:\n            return \"busy...\"\n        backup_file_path = os.path.expanduser(\"~/measurement_data_dumps\")\n        if hasattr(settings, \"PYSCADA_EXPORT\"):\n            if \"output_folder\" in settings.PYSCADA_EXPORT:\n                backup_file_path = os.path.expanduser(\n                    settings.PYSCADA_EXPORT[\"output_folder\"]\n                )\n        if self.filename is None:\n            return \"error, file not exported\"\n\n        return mark_safe(\n            '<a href=\"%s\">%s</a>'\n            % (\n                self.filename.replace(backup_file_path, \"/measurement\"),\n                self.filename.replace(backup_file_path, \"/measurement\"),\n            )\n        )\n"
  },
  {
    "path": "pyscada/export/worker.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nfrom pyscada.export.export import export_recordeddata_to_file\nfrom pyscada.export.models import ScheduledExportTask, ExportTask\nfrom pyscada.utils.scheduler import Process as BaseProcess\nfrom pyscada.models import BackgroundProcess\nfrom django.utils.timezone import now\n\nfrom time import time, gmtime, mktime\nfrom datetime import date, datetime, timedelta\nfrom pytz import UTC\nimport json\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import h5py\nexcept:\n    logger.error(\"Cannot import h5py\", exc_info=True)\n\n\nclass ExportProcess(BaseProcess):\n    def __init__(self, dt=5, **kwargs):\n        self.job_id = 0\n        super(ExportProcess, self).__init__(dt=dt, **kwargs)\n\n    def loop(self):\n        # todo try catch or filter.last()\n        job = ExportTask.objects.get(pk=self.job_id)\n        if job.file_format.upper() == \"HDF5\":\n            file_ext = \".h5\"\n        elif job.file_format.upper() == \"MAT\":\n            file_ext = \".mat\"\n        elif job.file_format.upper() == \"CSV_EXCEL\":\n            file_ext = \".csv\"\n        else:\n            return -1, None\n\n        bp = BackgroundProcess.objects.filter(\n            enabled=True,\n            done=False,\n            pid=self.pid,\n            parent_process__pk=self.parent_process_id,\n        ).first()\n\n        if bp is None:\n            logger.debug(\"export job %d no BP found\" % self.job_id)\n            return -1, None\n\n        job.busy = True\n        job.backgroundprocess = bp\n        job.save()\n\n        export_recordeddata_to_file(\n            job.time_min(),\n            job.time_max(),\n            filename=None,\n            active_vars=job.variables.values_list(\"pk\", flat=True),\n            file_extension=file_ext,\n            filename_suffix=job.filename_suffix,\n            backgroundprocess_id=bp.pk,\n            export_task_id=job.pk,\n            mean_value_period=job.mean_value_period,\n        )\n        job = ExportTask.objects.get(pk=job.pk)\n        job.done = True\n        job.busy = False\n        job.datetime_finished = datetime.now(UTC)\n        job.save()\n        bp = BackgroundProcess.objects.filter(\n            enabled=True,\n            done=False,\n            pid=self.pid,\n            parent_process__pk=self.parent_process_id,\n        ).first()\n\n        if bp:\n            bp.done = True\n            bp.last_update = now()\n\n            if not bp.failed:\n                bp.message = \"stopped\"\n\n            bp.save()\n\n        return 0, None\n\n\nclass MasterProcess(BaseProcess):\n    \"\"\"\n    handle the registration of new export tasks, and monitor running export tasks\n    \"\"\"\n\n    def __init__(self, dt=5, **kwargs):\n        super(MasterProcess, self).__init__(dt=dt, **kwargs)\n        self._current_day = gmtime().tm_yday\n\n    def loop(self):\n        \"\"\"\n        this function will be called every self.dt_set seconds\n\n        request data\n\n        tm_wday 0=Monday\n        tm_yday\n        \"\"\"\n        today = date.today()\n        # only start new jobs after change the day changed\n        if self._current_day != gmtime().tm_yday:\n            self._current_day = gmtime().tm_yday\n            for job in ScheduledExportTask.objects.filter(\n                active=1\n            ):  # get all active jobs\n                add_task = False\n                if job.export_period == 1:  # daily\n                    start_time = \"%s %02d:00:00\" % (\n                        (today - timedelta(1)).strftime(\"%d-%b-%Y\"),\n                        job.day_time,\n                    )  # \"%d-%b-%Y %H:%M:%S\"\n                    start_time = mktime(\n                        datetime.strptime(start_time, \"%d-%b-%Y %H:%M:%S\").timetuple()\n                    )\n                    filename_suffix = \"daily_export_%d_%s\" % (job.pk, job.label)\n                    add_task = True\n                elif (\n                    job.export_period == 2 and gmtime().tm_yday % 2 == 0\n                ):  # on even days (2,4,...)\n                    start_time = \"%s %02d:00:00\" % (\n                        (today - timedelta(2)).strftime(\"%d-%b-%Y\"),\n                        job.day_time,\n                    )  # \"%d-%b-%Y %H:%M:%S\"\n                    start_time = mktime(\n                        datetime.strptime(start_time, \"%d-%b-%Y %H:%M:%S\").timetuple()\n                    )\n                    filename_suffix = \"two_day_export_%d_%s\" % (job.pk, job.label)\n                    add_task = True\n                elif (\n                    job.export_period == 7 and gmtime().tm_wday == 0\n                ):  # on every monday\n                    start_time = \"%s %02d:00:00\" % (\n                        (today - timedelta(7)).strftime(\"%d-%b-%Y\"),\n                        job.day_time,\n                    )  # \"%d-%b-%Y %H:%M:%S\"\n                    start_time = mktime(\n                        datetime.strptime(start_time, \"%d-%b-%Y %H:%M:%S\").timetuple()\n                    )\n                    filename_suffix = \"weekly_export_%d_%s\" % (job.pk, job.label)\n                    add_task = True\n                elif (\n                    job.export_period == 14 and gmtime().tm_yday % 14 == 0\n                ):  # on every second monday\n                    start_time = \"%s %02d:00:00\" % (\n                        (today - timedelta(14)).strftime(\"%d-%b-%Y\"),\n                        job.day_time,\n                    )  # \"%d-%b-%Y %H:%M:%S\"\n                    start_time = mktime(\n                        datetime.strptime(start_time, \"%d-%b-%Y %H:%M:%S\").timetuple()\n                    )\n                    filename_suffix = \"two_week_export_%d_%s\" % (job.pk, job.label)\n                    add_task = True\n                elif (\n                    job.export_period == 30 and gmtime().tm_yday % 30 == 0\n                ):  # on every 30 days\n                    start_time = \"%s %02d:00:00\" % (\n                        (today - timedelta(30)).strftime(\"%d-%b-%Y\"),\n                        job.day_time,\n                    )  # \"%d-%b-%Y %H:%M:%S\"\n                    start_time = mktime(\n                        datetime.strptime(start_time, \"%d-%b-%Y %H:%M:%S\").timetuple()\n                    )\n                    filename_suffix = \"30_day_export_%d_%s\" % (job.pk, job.label)\n                    add_task = True\n\n                if job.day_time == 0:\n                    end_time = \"%s %02d:59:59\" % (\n                        (today - timedelta(1)).strftime(\"%d-%b-%Y\"),\n                        23,\n                    )  # \"%d-%b-%Y %H:%M:%S\"\n                else:\n                    end_time = \"%s %02d:59:59\" % (\n                        today.strftime(\"%d-%b-%Y\"),\n                        job.day_time - 1,\n                    )  # \"%d-%b-%Y %H:%M:%S\"\n                end_time = mktime(\n                    datetime.strptime(end_time, \"%d-%b-%Y %H:%M:%S\").timetuple()\n                )\n                # create ExportTask\n                if add_task:\n                    et = ExportTask(\n                        label=filename_suffix,\n                        datetime_max=datetime.fromtimestamp(end_time, UTC),\n                        datetime_min=datetime.fromtimestamp(start_time, UTC),\n                        filename_suffix=filename_suffix,\n                        mean_value_period=job.mean_value_period,\n                        file_format=job.file_format,\n                        datetime_start=datetime.fromtimestamp(end_time + 60, UTC),\n                    )\n                    et.save()\n\n                    et.variables.add(*job.variables.all())\n\n        # check running tasks and start the next Export Task\n        running_jobs = ExportTask.objects.filter(busy=True, failed=False)\n        if running_jobs:\n            for job in running_jobs:\n                if time() - job.start() < 30:\n                    # only check Task when it is running longer then 30s\n                    continue\n\n                if job.backgroundprocess is None:\n                    # if the job has no backgroundprocess assosiated mark as failed\n                    job.failed = True\n                    job.save()\n                    continue\n\n                if now() - timedelta(hours=1) > job.backgroundprocess.last_update:\n                    # if the Background Process has been updated in the past 60s wait\n                    continue\n\n                if job.backgroundprocess.pid == 0:\n                    # if the job has no valid pid mark as failed\n                    job.failed = True\n                    job.save()\n                    continue\n\n        else:\n            # start the next Export Task\n            job = ExportTask.objects.filter(\n                done=False,\n                busy=False,\n                failed=False,\n                datetime_start__lte=datetime.now(UTC),\n            ).first()  # get all jobs\n            if job:\n                bp = BackgroundProcess(\n                    label=\"pyscada.export-%d\" % job.pk,\n                    message=\"waiting..\",\n                    enabled=True,\n                    parent_process_id=self.parent_process_id,\n                    process_class=\"pyscada.export.worker.ExportProcess\",\n                    process_class_kwargs=json.dumps({\"job_id\": job.pk}),\n                )\n                bp.save()\n                if job.datetime_start is None:\n                    job.datetime_start = datetime.now(UTC)\n                job.busy = True\n                job.save()\n\n        # delete all done jobs older the 60 days\n        for job in ExportTask.objects.filter(\n            done=True,\n            busy=False,\n            datetime_start__gte=datetime.fromtimestamp(time() + 60 * 24 * 60 * 60, UTC),\n        ):\n            job.delete()\n        # delete all failed jobs older the 60 days\n        for job in ExportTask.objects.filter(\n            failed=True,\n            datetime_start__gte=datetime.fromtimestamp(time() + 60 * 24 * 60 * 60, UTC),\n        ):\n            job.delete()\n\n        return 1, None  # because we have no data to store\n"
  },
  {
    "path": "pyscada/fixtures/color.json",
    "content": "[\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 0,\n        \"name\": \"auto\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 1\n},\n{\n    \"fields\": {\n        \"B\": 64,\n        \"R\": 237,\n        \"name\": \"\",\n        \"G\": 194\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 2\n},\n{\n    \"fields\": {\n        \"B\": 248,\n        \"R\": 175,\n        \"name\": \"\",\n        \"G\": 216\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 3\n},\n{\n    \"fields\": {\n        \"B\": 75,\n        \"R\": 203,\n        \"name\": \"\",\n        \"G\": 75\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 4\n},\n{\n    \"fields\": {\n        \"B\": 77,\n        \"R\": 77,\n        \"name\": \"\",\n        \"G\": 167\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 5\n},\n{\n    \"fields\": {\n        \"B\": 237,\n        \"R\": 148,\n        \"name\": \"\",\n        \"G\": 64\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 6\n},\n{\n    \"fields\": {\n        \"B\": 51,\n        \"R\": 189,\n        \"name\": \"\",\n        \"G\": 155\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 7\n},\n{\n    \"fields\": {\n        \"B\": 198,\n        \"R\": 140,\n        \"name\": \"\",\n        \"G\": 172\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 8\n},\n{\n    \"fields\": {\n        \"B\": 60,\n        \"R\": 162,\n        \"name\": \"\",\n        \"G\": 60\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 9\n},\n{\n    \"fields\": {\n        \"B\": 61,\n        \"R\": 61,\n        \"name\": \"\",\n        \"G\": 133\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 10\n},\n{\n    \"fields\": {\n        \"B\": 189,\n        \"R\": 118,\n        \"name\": \"\",\n        \"G\": 51\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 11\n},\n{\n    \"fields\": {\n        \"B\": 76,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 232\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 12\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 210,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 13\n},\n{\n    \"fields\": {\n        \"B\": 90,\n        \"R\": 243,\n        \"name\": \"\",\n        \"G\": 90\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 14\n},\n{\n    \"fields\": {\n        \"B\": 92,\n        \"R\": 92,\n        \"name\": \"\",\n        \"G\": 200\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 15\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 177,\n        \"name\": \"\",\n        \"G\": 76\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 16\n},\n{\n    \"fields\": {\n        \"B\": 38,\n        \"R\": 142,\n        \"name\": \"\",\n        \"G\": 116\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 17\n},\n{\n    \"fields\": {\n        \"B\": 148,\n        \"R\": 105,\n        \"name\": \"\",\n        \"G\": 129\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 18\n},\n{\n    \"fields\": {\n        \"B\": 45,\n        \"R\": 121,\n        \"name\": \"\",\n        \"G\": 45\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 19\n},\n{\n    \"fields\": {\n        \"B\": 46,\n        \"R\": 46,\n        \"name\": \"\",\n        \"G\": 100\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 20\n},\n{\n    \"fields\": {\n        \"B\": 142,\n        \"R\": 88,\n        \"name\": \"\",\n        \"G\": 38\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 21\n},\n{\n    \"fields\": {\n        \"B\": 89,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 22\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 244,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 23\n},\n{\n    \"fields\": {\n        \"B\": 105,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 105\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 24\n},\n{\n    \"fields\": {\n        \"B\": 107,\n        \"R\": 107,\n        \"name\": \"\",\n        \"G\": 233\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 25\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 207,\n        \"name\": \"\",\n        \"G\": 89\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 26\n},\n{\n    \"fields\": {\n        \"B\": 25,\n        \"R\": 94,\n        \"name\": \"\",\n        \"G\": 77\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 27\n},\n{\n    \"fields\": {\n        \"B\": 99,\n        \"R\": 69,\n        \"name\": \"\",\n        \"G\": 86\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 28\n},\n{\n    \"fields\": {\n        \"B\": 29,\n        \"R\": 81,\n        \"name\": \"\",\n        \"G\": 29\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 29\n},\n{\n    \"fields\": {\n        \"B\": 30,\n        \"R\": 30,\n        \"name\": \"\",\n        \"G\": 66\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 30\n},\n{\n    \"fields\": {\n        \"B\": 94,\n        \"R\": 59,\n        \"name\": \"\",\n        \"G\": 25\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 31\n},\n{\n    \"fields\": {\n        \"B\": 102,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 32\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 33\n},\n{\n    \"fields\": {\n        \"B\": 120,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 120\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 34\n},\n{\n    \"fields\": {\n        \"B\": 123,\n        \"R\": 123,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 35\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 236,\n        \"name\": \"\",\n        \"G\": 102\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 36\n},\n{\n    \"fields\": {\n        \"B\": 64,\n        \"R\": 237,\n        \"name\": \"\",\n        \"G\": 194\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 37\n},\n{\n    \"fields\": {\n        \"B\": 248,\n        \"R\": 175,\n        \"name\": \"\",\n        \"G\": 216\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 38\n},\n{\n    \"fields\": {\n        \"B\": 75,\n        \"R\": 203,\n        \"name\": \"\",\n        \"G\": 75\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 39\n},\n{\n    \"fields\": {\n        \"B\": 77,\n        \"R\": 77,\n        \"name\": \"\",\n        \"G\": 167\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 40\n},\n{\n    \"fields\": {\n        \"B\": 237,\n        \"R\": 148,\n        \"name\": \"\",\n        \"G\": 64\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 41\n},\n{\n    \"fields\": {\n        \"B\": 51,\n        \"R\": 189,\n        \"name\": \"\",\n        \"G\": 155\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 42\n},\n{\n    \"fields\": {\n        \"B\": 198,\n        \"R\": 140,\n        \"name\": \"\",\n        \"G\": 172\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 43\n},\n{\n    \"fields\": {\n        \"B\": 60,\n        \"R\": 162,\n        \"name\": \"\",\n        \"G\": 60\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 44\n},\n{\n    \"fields\": {\n        \"B\": 61,\n        \"R\": 61,\n        \"name\": \"\",\n        \"G\": 133\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 45\n},\n{\n    \"fields\": {\n        \"B\": 189,\n        \"R\": 118,\n        \"name\": \"\",\n        \"G\": 51\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 46\n},\n{\n    \"fields\": {\n        \"B\": 76,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 232\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 47\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 210,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 48\n},\n{\n    \"fields\": {\n        \"B\": 90,\n        \"R\": 243,\n        \"name\": \"\",\n        \"G\": 90\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 49\n},\n{\n    \"fields\": {\n        \"B\": 92,\n        \"R\": 92,\n        \"name\": \"\",\n        \"G\": 200\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 50\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 177,\n        \"name\": \"\",\n        \"G\": 76\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 51\n},\n{\n    \"fields\": {\n        \"B\": 143,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 52\n},\n{\n    \"fields\": {\n        \"B\": 159,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 53\n},\n{\n    \"fields\": {\n        \"B\": 175,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 54\n},\n{\n    \"fields\": {\n        \"B\": 191,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 55\n},\n{\n    \"fields\": {\n        \"B\": 207,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 56\n},\n{\n    \"fields\": {\n        \"B\": 223,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 57\n},\n{\n    \"fields\": {\n        \"B\": 239,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 58\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 59\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 16\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 60\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 32\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 61\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 48\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 62\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 64\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 63\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 80\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 64\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 96\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 65\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 112\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 66\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 128\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 67\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 143\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 68\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 159\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 69\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 175\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 70\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 191\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 71\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 207\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 72\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 223\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 73\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 239\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 74\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 75\n},\n{\n    \"fields\": {\n        \"B\": 239,\n        \"R\": 16,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 76\n},\n{\n    \"fields\": {\n        \"B\": 223,\n        \"R\": 32,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 77\n},\n{\n    \"fields\": {\n        \"B\": 207,\n        \"R\": 48,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 78\n},\n{\n    \"fields\": {\n        \"B\": 191,\n        \"R\": 64,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 79\n},\n{\n    \"fields\": {\n        \"B\": 175,\n        \"R\": 80,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 80\n},\n{\n    \"fields\": {\n        \"B\": 159,\n        \"R\": 96,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 81\n},\n{\n    \"fields\": {\n        \"B\": 143,\n        \"R\": 112,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 82\n},\n{\n    \"fields\": {\n        \"B\": 128,\n        \"R\": 128,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 83\n},\n{\n    \"fields\": {\n        \"B\": 112,\n        \"R\": 143,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 84\n},\n{\n    \"fields\": {\n        \"B\": 96,\n        \"R\": 159,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 85\n},\n{\n    \"fields\": {\n        \"B\": 80,\n        \"R\": 175,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 86\n},\n{\n    \"fields\": {\n        \"B\": 64,\n        \"R\": 191,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 87\n},\n{\n    \"fields\": {\n        \"B\": 48,\n        \"R\": 207,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 88\n},\n{\n    \"fields\": {\n        \"B\": 32,\n        \"R\": 223,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 89\n},\n{\n    \"fields\": {\n        \"B\": 16,\n        \"R\": 239,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 90\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 91\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 239\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 92\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 223\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 93\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 207\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 94\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 191\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 95\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 175\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 96\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 159\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 97\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 143\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 98\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 128\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 99\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 112\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 100\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 96\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 101\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 80\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 102\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 64\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 103\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 48\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 104\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 32\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 105\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 16\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 106\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 107\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 239,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 108\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 223,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 109\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 207,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 110\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 191,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 111\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 175,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 112\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 159,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 113\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 143,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 114\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 128,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 115\n},\n{\n    \"fields\": {\n        \"B\": 255,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 116\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 128\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 117\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 255,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 118\n},\n{\n    \"fields\": {\n        \"B\": 191,\n        \"R\": 0,\n        \"name\": \"\",\n        \"G\": 191\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 119\n},\n{\n    \"fields\": {\n        \"B\": 191,\n        \"R\": 191,\n        \"name\": \"\",\n        \"G\": 0\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 120\n},\n{\n    \"fields\": {\n        \"B\": 0,\n        \"R\": 191,\n        \"name\": \"\",\n        \"G\": 191\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 121\n},\n{\n    \"fields\": {\n        \"B\": 64,\n        \"R\": 64,\n        \"name\": \"\",\n        \"G\": 64\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 122\n},\n{\n    \"fields\": {\n        \"B\": 230,\n        \"R\": 251,\n        \"name\": \"pink\",\n        \"G\": 101\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 123\n},\n{\n    \"fields\": {\n        \"B\": 153,\n        \"R\": 255,\n        \"name\": \"hell gelb\",\n        \"G\": 255\n    },\n    \"model\": \"pyscada.color\",\n    \"pk\": 124\n}\n]\n"
  },
  {
    "path": "pyscada/fixtures/units.json",
    "content": "[{\"fields\": {\"description\": \"celsius\", \"unit\": \"\\u00b0C\", \"udunit\": \"celsius\"}, \"model\": \"pyscada.unit\", \"pk\": 1}, {\"fields\": {\"description\": \"bit\", \"unit\": \"-\", \"udunit\": \"bit\"}, \"model\": \"pyscada.unit\", \"pk\": 2}, {\"fields\": {\"description\": \"meter3/hour\", \"unit\": \"m\\u00b3/h\", \"udunit\": \"meter3/hour\"}, \"model\": \"pyscada.unit\", \"pk\": 3}, {\"fields\": {\"description\": \"percent\", \"unit\": \"%\", \"udunit\": \"percent\"}, \"model\": \"pyscada.unit\", \"pk\": 4}, {\"fields\": {\"description\": \"millibar\", \"unit\": \"mbar\", \"udunit\": \"millibar\"}, \"model\": \"pyscada.unit\", \"pk\": 5}, {\"fields\": {\"description\": \"bar\", \"unit\": \"bar\", \"udunit\": \"bar\"}, \"model\": \"pyscada.unit\", \"pk\": 6}, {\"fields\": {\"description\": \"liter/sec\", \"unit\": \"l/s\", \"udunit\": \"liter/sec\"}, \"model\": \"pyscada.unit\", \"pk\": 7}, {\"fields\": {\"description\": \"sec\", \"unit\": \"sec\", \"udunit\": \"sec\"}, \"model\": \"pyscada.unit\", \"pk\": 8}, {\"fields\": {\"description\": \"kilogram/kilogram\", \"unit\": \"kg/kg\", \"udunit\": \"kilogram/kilogram\"}, \"model\": \"pyscada.unit\", \"pk\": 9}, {\"fields\": {\"description\": \"kilogram/sec\", \"unit\": \"kg/s\", \"udunit\": \"kilogram/sec\"}, \"model\": \"pyscada.unit\", \"pk\": 10}, {\"fields\": {\"description\": \"kilojoule/kilogramkelvin\", \"unit\": \"kJ/kgK\", \"udunit\": \"kilojoule/kilogramkelvin\"}, \"model\": \"pyscada.unit\", \"pk\": 11}, {\"fields\": {\"description\": \"kilowatt\", \"unit\": \"kW\", \"udunit\": \"kilowatt\"}, \"model\": \"pyscada.unit\", \"pk\": 12}, {\"fields\": {\"description\": \"kelvin\", \"unit\": \"K\", \"udunit\": \"kelvin\"}, \"model\": \"pyscada.unit\", \"pk\": 13}, {\"fields\": {\"description\": \"kilowatthour\", \"unit\": \"kWh\", \"udunit\": \"kilowatthour\"}, \"model\": \"pyscada.unit\", \"pk\": 14}, {\"fields\": {\"description\": \"meter3\", \"unit\": \"m\\u00b3\", \"udunit\": \"meter3\"}, \"model\": \"pyscada.unit\", \"pk\": 15}, {\"fields\": {\"description\": \"\", \"unit\": \"\", \"udunit\": \"\"}, \"model\": \"pyscada.unit\", \"pk\": 16}]"
  },
  {
    "path": "pyscada/generic/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\nfrom pyscada import core\n\n__version__ = core.__version__\n__author__ = core.__author__\n__email__ = core.__email__\n__description__ = (\n    \"Generic extension for PyScada a Python and Django based Open Source SCADA System\"\n)\n__app_name__ = \"Generic\"\n\nPROTOCOL_ID = 1\n"
  },
  {
    "path": "pyscada/generic/device.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.device import GenericDevice\nfrom .devices import GenericDevice as GenericHandlerDevice\n\ndriver_ok = True\n\nfrom time import time\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass Device(GenericDevice):\n    def __init__(self, device):\n        self.driver_ok = driver_ok\n        self.handler_class = GenericHandlerDevice\n        super().__init__(device)\n\n        for var in self.device.variable_set.filter(active=1):\n            self.variables[var.pk] = var\n\n        if self.driver_ok and self.driver_handler_ok:\n            self._h.connect()\n        else:\n            logger.warning(f\"Cannot import handler for {self.device}\")\n"
  },
  {
    "path": "pyscada/generic/devices/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\nfrom .. import PROTOCOL_ID\nfrom pyscada.models import DeviceProtocol\nfrom pyscada.device import GenericHandlerDevice\n\nfrom django.conf import settings\n\ndriver_ok = True\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass GenericDevice(GenericHandlerDevice):\n    def __init__(self, pyscada_device, variables):\n        super().__init__(pyscada_device, variables)\n        self._protocol = PROTOCOL_ID\n        self.driver_ok = driver_ok\n\n    def connect(self):\n        return True\n\n    def read_data(self, variable_instance):\n        \"\"\"\n        Generic dummy device : Don't read nothing.\n        \"\"\"\n        return None\n\n    def write_data(self, variable_id, value, task):\n        \"\"\"\n        Generic dummy device : Don't write nothing.\n        \"\"\"\n        return None\n"
  },
  {
    "path": "pyscada/generic/devices/dummy.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.generic.devices import GenericDevice\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass Handler(GenericDevice):\n    \"\"\"\n    Generic dummy device\n    \"\"\"\n\n    def write_data(self, variable_id, value, task):\n        \"\"\"\n        Generic dummy device : return the value to write.\n        \"\"\"\n        return value\n"
  },
  {
    "path": "pyscada/generic/devices/waveform.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.generic.devices import GenericDevice\nfrom pyscada.models import VariableProperty\n\nfrom scipy import signal\nimport numpy as np\nfrom time import time_ns\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass Handler(GenericDevice):\n    \"\"\"\n    Generic dummy device\n    \"\"\"\n\n    def read_data_and_time(self, variable_instance):\n        \"\"\"\n        Generic waveform device : use variable properties to configure the waveform.\n        \"\"\"\n        t = time_ns() / 1000000000.0\n        default = {\n            \"type\": \"sinus\",  # sinus, square, triangle\n            \"amplitude\": 1.0,  # peak to peak value\n            \"offset\": 0.0,  # waveform offset value\n            \"start_timestamp\": 0.0,  # in second from 01/01/1970 00:00:00\n            \"frequency\": 0.1,  # Hz\n            \"duty_cycle\": 0.5,  # between 0 and 1, duty cycle for square and for triangle : Width of the rising ramp as a proportion of the total cycle. Default is 1, producing a rising ramp, while 0 produces a falling ramp. width = 0.5 produces a triangle wave. If an array, causes wave shape to change over time, and must be the same length as t.\n        }\n\n        def type_default(fct):\n            if str(fct) in [\"sinus\", \"square\", \"triangle\"]:\n                return str(fct)\n            else:\n                return \"sinus\"\n\n        def positive_float(i):\n            if i >= 0:\n                return i\n            else:\n                return 0\n\n        def number(i):\n            try:\n                return float(i)\n            except Exception as e:\n                logger.warning(f\"A waveform property is not a number, it is {i}\")\n                return 0\n\n        def positive_float_strict(i):\n            if i > 0:\n                return i\n            else:\n                return 1\n\n        def duty_cycle(i):\n            if i >= 0 and i <= 1:\n                return i\n            else:\n                return 0.5\n\n        default_allowed = {\n            \"type\": type_default,\n            \"amplitude\": positive_float_strict,\n            \"offset\": number,\n            \"start_timestamp\": positive_float,\n            \"frequency\": positive_float_strict,\n            \"duty_cycle\": duty_cycle,\n        }\n\n        for prop in default:\n            vp = VariableProperty.objects.filter(variable=variable_instance, name=prop)\n            if vp.exists():\n                default[prop] = default_allowed[prop](vp.first().value())\n\n        if default[\"type\"] == \"sinus\":\n            out = (\n                default[\"amplitude\"]\n                / 2.0\n                * np.sin(\n                    2.0\n                    * np.pi\n                    * (t - default[\"start_timestamp\"])\n                    * default[\"frequency\"]\n                )\n                + default[\"offset\"]\n            )\n        elif default[\"type\"] == \"square\":\n            out = (\n                default[\"amplitude\"]\n                / 2.0\n                * signal.square(\n                    2.0\n                    * np.pi\n                    * (t - default[\"start_timestamp\"])\n                    * default[\"frequency\"],\n                    default[\"duty_cycle\"],\n                )\n                + default[\"offset\"]\n            )\n        elif default[\"type\"] == \"triangle\":\n            out = (\n                default[\"amplitude\"]\n                / 2.0\n                * signal.sawtooth(\n                    2.0\n                    * np.pi\n                    * (t - default[\"start_timestamp\"])\n                    * default[\"frequency\"],\n                    default[\"duty_cycle\"],\n                )\n                + default[\"offset\"]\n            )\n\n        return out, t\n\n    def write_data(self, variable_id, value, task):\n        \"\"\"\n        Generic dummy device : return the value to write.\n        \"\"\"\n        return value\n"
  },
  {
    "path": "pyscada/generic/worker.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nfrom pyscada.utils.scheduler import SingleDeviceDAQProcessWorker\nfrom pyscada.generic import PROTOCOL_ID, __app_name__\n\nimport logging\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Process(SingleDeviceDAQProcessWorker):\n    device_filter = dict(protocol_id=PROTOCOL_ID)\n    bp_label = \"pyscada.\" + __app_name__.lower() + \"-%s\"\n\n    def __init__(self, dt=5, **kwargs):\n        super(SingleDeviceDAQProcessWorker, self).__init__(dt=dt, **kwargs)\n"
  },
  {
    "path": "pyscada/hmi/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada import core\n\n\n__version__ = core.__version__\n__author__ = core.__author__\n"
  },
  {
    "path": "pyscada/hmi/admin.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.admin import admin_site\n\nfrom pyscada.models import Variable\nfrom pyscada.models import Color\nfrom pyscada.hmi.models import ControlItem\nfrom pyscada.hmi.models import Chart, ChartAxis\nfrom pyscada.hmi.models import Form\nfrom pyscada.hmi.models import SlidingPanelMenu\nfrom pyscada.hmi.models import Page\nfrom pyscada.hmi.models import GroupDisplayPermission\nfrom pyscada.hmi.models import ControlPanel\nfrom pyscada.hmi.models import (\n    DisplayValueOption,\n    ControlElementOption,\n    DisplayValueColorOption,\n    DisplayValueOptionTemplate,\n)\nfrom pyscada.hmi.models import CustomHTMLPanel\nfrom pyscada.hmi.models import Widget\nfrom pyscada.hmi.models import View, ExternalView\nfrom pyscada.hmi.models import ProcessFlowDiagram\nfrom pyscada.hmi.models import ProcessFlowDiagramItem\nfrom pyscada.hmi.models import Pie\nfrom pyscada.hmi.models import Theme\nfrom pyscada.hmi.models import CssClass\nfrom pyscada.hmi.models import TransformData\n\nfrom django.utils.translation import gettext_lazy as _\nfrom django.contrib import admin\nfrom django import forms\nfrom django.db.models.fields.related import OneToOneRel\nfrom django.core.exceptions import ValidationError\nfrom django.template.exceptions import TemplateDoesNotExist, TemplateSyntaxError\nfrom django.template.loader import get_template\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass FormListFilter(admin.SimpleListFilter):\n    # Human-readable title which will be displayed in the\n    # right admin sidebar just above the filter options.\n    title = _(\"form filter\")\n\n    # Parameter for the filter that will be used in the URL query.\n    parameter_name = \"form\"\n\n    def lookups(self, request, model_admin):\n        \"\"\"\n        Returns a list of tuples. The first element in each\n        tuple is the coded value for the option that will\n        appear in the URL query. The second element is the\n        human-readable name for the option that will appear\n        in the right sidebar.\n        \"\"\"\n        result = list()\n        for form in Form.objects.all():\n            result.append(\n                (form.pk, form.title),\n            )\n        return result\n\n    def queryset(self, request, queryset):\n        \"\"\"\n        Returns the filtered queryset based on the value\n        provided in the query string and retrievable via\n        `self.value()`.\n        \"\"\"\n        # Compare the requested value (either '80s' or '90s')\n        # to decide how to filter the queryset.\n        if self.value() is not None:\n            return queryset.filter(control_items_form__in=self.value())\n\n\nclass ChartForm(forms.ModelForm):\n    def __init__(self, *args, **kwargs):\n        super(ChartForm, self).__init__(*args, **kwargs)\n        wtf = Variable.objects.all()\n        w = self.fields[\"variables\"].widget\n        choices = []\n        for choice in wtf:\n            choices.append(\n                (choice.id, choice.name + \"( \" + choice.unit.description + \" )\")\n            )\n        w.choices = choices\n\n\nclass ChartAxisInline(admin.TabularInline):\n    model = ChartAxis\n    filter_vertical = [\"variables\"]\n\n    def get_extra(self, request, obj=None, **kwargs):\n        return 0 if obj else 1\n\n\nclass ChartAdmin(admin.ModelAdmin):\n    list_per_page = 100\n    # ordering = ['position',]\n    search_fields = [\n        \"title\",\n    ]\n    List_display_link = (\"title\",)\n    list_display = (\n        \"id\",\n        \"title\",\n        \"x_axis_label\",\n        \"x_axis_linlog\",\n    )\n    # list_filter = ('widget__page__title', 'widget__title',)\n    # form = ChartForm\n    save_as = True\n    save_as_continue = True\n    inlines = [ChartAxisInline]\n\n    def name(self, instance):\n        return instance.variables.name\n\n    def formfield_for_foreignkey(self, db_field, request, **kwargs):\n        if db_field.name == \"x_axis_var\":\n            kwargs[\"empty_label\"] = \"Time series\"\n        return super(ChartAdmin, self).formfield_for_foreignkey(\n            db_field, request, **kwargs\n        )\n\n\nclass PieForm(forms.ModelForm):\n    def __init__(self, *args, **kwargs):\n        super(PieForm, self).__init__(*args, **kwargs)\n        wtf = Variable.objects.all()\n        w = self.fields[\"variables\"].widget\n        choices = []\n        for choice in wtf:\n            choices.append(\n                (choice.id, choice.name + \"( \" + choice.unit.description + \" )\")\n            )\n        w.choices = choices\n\n\nclass PieAdmin(admin.ModelAdmin):\n    list_per_page = 100\n    # ordering = ['position',]\n    search_fields = [\n        \"name\",\n    ]\n    filter_horizontal = (\"variables\", \"variable_properties\")\n    List_display_link = (\"title\",)\n    list_display = (\"id\", \"title\")\n    form = PieForm\n    save_as = True\n    save_as_continue = True\n\n    def name(self, instance):\n        return instance.variables.name\n\n\nclass FormAdmin(admin.ModelAdmin):\n    filter_horizontal = (\n        \"control_items\",\n        \"hidden_control_items_to_true\",\n    )\n    list_filter = (\"controlpanel\",)\n    save_as = True\n    save_as_continue = True\n\n\nclass DisplayValueOptionAdminFrom(forms.ModelForm):\n    def __init__(self, *args, **kwargs):\n        super(DisplayValueOptionAdminFrom, self).__init__(*args, **kwargs)\n        wtf = Color.objects.all()\n        w = self.fields[\"color\"].widget\n        color_choices = [(None, None)]\n        for choice in wtf:\n            color_choices.append((choice.id, choice.color_code()))\n        w.choices = color_choices\n\n        def create_option_color(\n            self, name, value, label, selected, index, subindex=None, attrs=None\n        ):\n            if label is None:\n                return self._create_option(\n                    name, value, label, selected, index, subindex, attrs=None\n                )\n            font_color = hex(int(\"ffffff\", 16) - int(label[1::], 16))[2::]\n            # attrs = self.build_attrs(attrs,{'style':'background: %s; color: #%s'%(label,font_color)})\n            self.option_inherits_attrs = True\n            return self._create_option(\n                name,\n                value,\n                label,\n                selected,\n                index,\n                subindex,\n                attrs={\"style\": \"background: %s; color: #%s\" % (label, font_color)},\n            )\n\n        import types\n\n        # from django.forms.widgets import Select\n        w.widget._create_option = w.widget.create_option  # copy old method\n        w.widget.create_option = types.MethodType(\n            create_option_color, w.widget\n        )  # replace old with new\n        w.widget.attrs = {\n            \"onchange\": \"this.style.backgroundColor=this.options[this.selectedIndex].style.\"\n            \"backgroundColor;this.style.color=this.options[this.selectedIndex].style.color\"\n        }\n\n    def clean(self):\n        super().clean()\n        color_options = set()\n\n        if self.data.get(\"gradient\", False) == \"on\":\n            for d in self.data:\n                if (\n                    \"displayvaluecoloroption_set-\" in d\n                    and d[\n                        len(\"displayvaluecoloroption_set-\") : len(\n                            \"displayvaluecoloroption_set-\"\n                        )\n                        + 1\n                    ].isdigit()\n                    and \"displayvaluecoloroption_set-\"\n                    + d[\n                        len(\"displayvaluecoloroption_set-\") : len(\n                            \"displayvaluecoloroption_set-\"\n                        )\n                        + 1\n                    ]\n                    + \"-DELETE\"\n                    not in self.data\n                ):\n                    color_options.update(\n                        d[\n                            len(\"displayvaluecoloroption_set-\") : len(\n                                \"displayvaluecoloroption_set-\"\n                            )\n                            + 1\n                        ]\n                    )\n                if len(color_options) > 1:\n                    raise ValidationError(\"1 color option needed for gradient.\")\n            if \"displayvaluecoloroption_set-0-color_level\" in self.data and float(\n                self.data[\"gradient_higher_level\"]\n            ) <= float(self.data.get(\"displayvaluecoloroption_set-0-color_level\")):\n                raise ValidationError(\n                    \"gradient higher level must be strictly higher than the color option level.\"\n                )\n            if not len(color_options) == 1:\n                raise ValidationError(\"1 color option needed for gradient.\")\n            if self.data.get(\"displayvaluecoloroption_set-0-color\") == \"\":\n                raise ValidationError(\n                    \"Color for Display value color options cannot be null for gradient.\"\n                )\n            if self.data[\"color\"] == \"\":\n                raise ValidationError(\"Color cannot be null for gradient.\")\n\n    class Meta:\n        widgets = {\n            \"gradient\": forms.CheckboxInput(\n                attrs={\n                    \"--hideshow-fields\": \"gradient_higher_level,\",\n                    # gradient_higher_level visible if checkbox checked\n                    \"--show-on-checked\": \"gradient_higher_level,\",\n                }\n            ),\n        }\n\n    class Media:\n        js = (\"pyscada/js/admin/hideshow.js\",)\n\n\nclass DisplayValueColorOptionAdminFrom(forms.ModelForm):\n    def __init__(self, *args, **kwargs):\n        super(DisplayValueColorOptionAdminFrom, self).__init__(*args, **kwargs)\n        wtf = Color.objects.all()\n        w = self.fields[\"color\"].widget\n        color_choices = [(None, None)]\n        for choice in wtf:\n            color_choices.append((choice.id, choice.color_code()))\n        w.choices = color_choices\n\n        def create_option_color(\n            self, name, value, label, selected, index, subindex=None, attrs=None\n        ):\n            if label is None:\n                return self._create_option(\n                    name, value, label, selected, index, subindex, attrs=None\n                )\n            font_color = hex(int(\"ffffff\", 16) - int(label[1::], 16))[2::]\n            # attrs = self.build_attrs(attrs,{'style':'background: %s; color: #%s'%(label,font_color)})\n            self.option_inherits_attrs = True\n            return self._create_option(\n                name,\n                value,\n                label,\n                selected,\n                index,\n                subindex,\n                attrs={\"style\": \"background: %s; color: #%s\" % (label, font_color)},\n            )\n\n        import types\n\n        # from django.forms.widgets import Select\n        w.widget._create_option = w.widget.create_option  # copy old method\n        w.widget.create_option = types.MethodType(\n            create_option_color, w.widget\n        )  # replace old with new\n        w.widget.attrs = {\n            \"onchange\": \"this.style.backgroundColor=this.options[this.selectedIndex].style.\"\n            \"backgroundColor;this.style.color=this.options[this.selectedIndex].style.color\"\n        }\n\n\nclass DisplayValueColorOptionInline(admin.TabularInline):\n    model = DisplayValueColorOption\n    form = DisplayValueColorOptionAdminFrom\n    extra = 0\n\n\nclass TransformDataAdmin(admin.ModelAdmin):\n    # only allow viewing and deleting\n    def has_module_permission(self, request):\n        return False\n\n    def has_add_permission(self, request):\n        return False\n\n    def has_change_permission(self, request, obj=None):\n        return False\n\n    def has_delete_permission(self, request, obj=None):\n        return True\n\n\nclass DisplayValueOptionTemplateAdmin(admin.ModelAdmin):\n    # only allow viewing and deleting\n    def has_module_permission(self, request):\n        return False\n\n    def has_add_permission(self, request):\n        return False\n\n    def has_change_permission(self, request, obj=None):\n        return False\n\n    def has_delete_permission(self, request, obj=None):\n        return True\n\n\nclass DisplayValueOptionAdmin(admin.ModelAdmin):\n    fieldsets = (\n        (\n            None,\n            {\n                \"fields\": (\n                    \"title\",\n                    \"template\",\n                    \"timestamp_conversion\",\n                    \"transform_data\",\n                    \"from_timestamp_offset\",\n                ),\n            },\n        ),\n        (\n            \"Color\",\n            {\n                \"fields\": (\n                    \"color\",\n                    \"color_only\",\n                    \"gradient\",\n                    \"gradient_higher_level\",\n                ),\n            },\n        ),\n    )\n    form = DisplayValueOptionAdminFrom\n    save_as = True\n    save_as_continue = True\n    inlines = [DisplayValueColorOptionInline]\n    # Add inlines for any model with OneToOne relation with Device\n    related_models = [\n        field\n        for field in DisplayValueOption._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    for m in related_models:\n        model_dict = dict(model=m.related_model)\n        if hasattr(m.related_model, \"FormSet\"):\n            model_dict[\"formset\"] = m.related_model.FormSet\n        cl = type(m.name, (admin.StackedInline,), model_dict)  # classes=['collapse']\n        inlines.append(cl)\n\n    def has_module_permission(self, request):\n        return False\n\n    class Media:\n        js = (\n            # To be sure the jquery files are loaded before our js file\n            \"admin/js/vendor/jquery/jquery.min.js\",\n            \"admin/js/jquery.init.js\",\n            # only the inline corresponding to the transform data selected\n            \"pyscada/js/admin/display_inline_transform_data_display_value_option.js\",\n        )\n\n\nclass ControlElementOptionAdmin(admin.ModelAdmin):\n    save_as = True\n    save_as_continue = True\n\n    def has_module_permission(self, request):\n        return False\n\n\nclass ControlItemAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"position\",\n        \"label\",\n        \"type\",\n        \"variable\",\n        \"variable_property\",\n        \"display_value_options\",\n        \"control_element_options\",\n    )\n    list_filter = (\n        \"controlpanel\",\n        FormListFilter,\n        \"type\",\n    )\n    list_editable = (\n        \"position\",\n        \"label\",\n        \"type\",\n        \"variable\",\n        \"variable_property\",\n        \"display_value_options\",\n        \"control_element_options\",\n    )\n    raw_id_fields = (\"variable\",)\n    save_as = True\n    save_as_continue = True\n\n\nclass SlidingPanelMenuForm(forms.ModelForm):\n    def __init__(self, *args, **kwargs):\n        super(SlidingPanelMenuForm, self).__init__(*args, **kwargs)\n        wtf = ControlItem.objects.all()\n        w = self.fields[\"items\"].widget\n        choices = []\n        for choice in wtf:\n            choices.append(\n                (\n                    choice.id,\n                    choice.label\n                    + \" (\"\n                    + choice.variable.name\n                    + \", \"\n                    + choice.get_type_display()\n                    + \")\",\n                )\n            )\n        w.choices = choices\n\n\nclass SlidingPanelMenuAdmin(admin.ModelAdmin):\n    # search_fields = ['name',]\n    # filter_horizontal = ('items',)\n    # form = SlidingPanelMenuForm\n    list_display = (\"id\", \"title\", \"position\", \"visible\")\n    save_as = True\n    save_as_continue = True\n\n\nclass WidgetAdmin(admin.ModelAdmin):\n    list_display_links = (\"id\",)\n    list_display = (\n        \"id\",\n        \"title\",\n        \"page\",\n        \"row\",\n        \"col\",\n        \"size\",\n        \"content\",\n        \"visible\",\n        \"extra_css_class\",\n    )\n    list_editable = (\n        \"title\",\n        \"page\",\n        \"row\",\n        \"col\",\n        \"size\",\n        \"content\",\n        \"visible\",\n        \"extra_css_class\",\n    )\n    list_filter = (\"page\",)\n    save_as = True\n    save_as_continue = True\n\n\nclass GroupDisplayPermissionForm(forms.ModelForm):\n    def clean(self):\n        super().clean()\n        hmi_group = self.cleaned_data.get(\"hmi_group\", None)\n        id = self.instance.pk\n        unauthenticated_users = self.instance.unauthenticated_users\n        if len(\n            GroupDisplayPermission.objects.filter(hmi_group=hmi_group, unauthenticated_users=unauthenticated_users).exclude(id=id)\n        ):\n            raise ValidationError(\"This group display permission already exist.\")\n\n\nclass GroupDisplayPermissionAdmin(admin.ModelAdmin):\n    filter_horizontal = ()\n    save_as = True\n    save_as_continue = True\n    form = GroupDisplayPermissionForm\n    readonly_fields = [\"unauthenticated_users\"]\n\n    def get_fields(self, request, obj=None):\n        # show unauthenticated_users field only for the GroupDisplayPermission used for unauthenticated users and hide hmi_group\n        # show hmi_group field in other cases and hide the unauthenticated_users field\n        if obj is not None and obj.unauthenticated_users:\n            return (\"unauthenticated_users\",)\n        return ('hmi_group',)\n\n    def get_inlines(self, request, obj=None):\n        # Add inlines for any model with OneToOne relation with Device\n        items = [\n            field\n            for field in GroupDisplayPermission._meta.get_fields()\n            if issubclass(type(field), OneToOneRel)\n        ]\n        inlines = []\n        for d in items:\n            filter_horizontal_inline = ()\n            for field in d.related_model._meta.local_many_to_many:\n                filter_horizontal_inline += (field.name,)\n            # Collapse inline only if empty\n            if hasattr(obj, d.name):\n                collapse = None\n            else:\n                collapse = [\"collapse\"]\n            device_dict = dict(\n                model=d.related_model,\n                filter_horizontal=filter_horizontal_inline,\n                classes=collapse,\n            )\n            cl = type(d.name, (admin.TabularInline,), device_dict)\n            inlines.append(cl)\n        return inlines\n\n    def has_delete_permission(self, request, obj=None):\n        if obj is not None and obj.hmi_group is None:\n            return False\n        return super().has_delete_permission(request, obj)\n\n\nclass ControlPanelAdmin(admin.ModelAdmin):\n    filter_horizontal = (\n        \"items\",\n        \"forms\",\n    )\n    save_as = True\n    save_as_continue = True\n\n\nclass ViewAdmin(admin.ModelAdmin):\n    filter_horizontal = (\"pages\", \"sliding_panel_menus\")\n    save_as = True\n    save_as_continue = True\n\nclass ExternalViewAdmin(admin.ModelAdmin):\n    save_as = True\n    save_as_continue = True\n\nclass CustomHTMLPanelAdmin(admin.ModelAdmin):\n    filter_horizontal = (\"variables\", \"variable_properties\")\n    save_as = True\n    save_as_continue = True\n\n\nclass PageAdmin(admin.ModelAdmin):\n    list_display_links = (\"id\",)\n    list_display = (\n        \"id\",\n        \"title\",\n        \"link_title\",\n        \"position\",\n    )\n    list_editable = (\n        \"title\",\n        \"link_title\",\n        \"position\",\n    )\n    list_filter = (\"view__title\",)\n    save_as = True\n    save_as_continue = True\n\n\nclass ProcessFlowDiagramItemAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"control_item\",\n        \"top\",\n        \"left\",\n        \"width\",\n        \"height\",\n        \"visible\",\n        \"font_size\",\n    )\n    list_editable = (\n        \"control_item\",\n        \"top\",\n        \"left\",\n        \"width\",\n        \"height\",\n        \"visible\",\n        \"font_size\",\n    )\n    # raw_id_fields = ('variable',)\n    save_as = True\n    save_as_continue = True\n\n\nclass ProcessFlowDiagramAdmin(admin.ModelAdmin):\n    list_display = (\n        \"id\",\n        \"title\",\n        \"type\",\n        \"background_image\",\n    )\n    list_editable = (\n        \"title\",\n        \"type\",\n        \"background_image\",\n    )\n    filter_horizontal = (\"process_flow_diagram_items\",)\n    save_as = True\n    save_as_continue = True\n\n\nclass ThemeForm(forms.ModelForm):\n    def clean(self):\n        super().clean()\n        try:\n            get_template(self.cleaned_data[\"base_filename\"] + \".html\").render()\n        except TemplateDoesNotExist as e:\n            raise ValidationError(f\"Template {e} not found.\")\n        try:\n            get_template(self.cleaned_data[\"view_filename\"] + \".html\").render(\n                {\"base_html\": self.cleaned_data.get(\"base_filename\", \"base\") + \".html\"}\n            )\n        except TemplateDoesNotExist as e:\n            raise ValidationError(f\"Template {e} not found.\")\n\n\nclass ThemeAdmin(admin.ModelAdmin):\n    save_as = True\n    save_as_continue = True\n    form = ThemeForm\n\n    def has_module_permission(self, request):\n        return False\n\n\nclass CssClassAdmin(admin.ModelAdmin):\n    save_as = True\n    save_as_continue = True\n\n    def has_module_permission(self, request):\n        return False\n\n\nadmin_site.register(ControlItem, ControlItemAdmin)\nadmin_site.register(Chart, ChartAdmin)\nadmin_site.register(Pie, PieAdmin)\nadmin_site.register(Form, FormAdmin)\nadmin_site.register(SlidingPanelMenu, SlidingPanelMenuAdmin)\nadmin_site.register(Page, PageAdmin)\nadmin_site.register(GroupDisplayPermission, GroupDisplayPermissionAdmin)\nadmin_site.register(DisplayValueOption, DisplayValueOptionAdmin)\nadmin_site.register(ControlElementOption, ControlElementOptionAdmin)\nadmin_site.register(ControlPanel, ControlPanelAdmin)\nadmin_site.register(CustomHTMLPanel, CustomHTMLPanelAdmin)\nadmin_site.register(Widget, WidgetAdmin)\nadmin_site.register(View, ViewAdmin)\nadmin_site.register(ExternalView, ExternalViewAdmin)\nadmin_site.register(ProcessFlowDiagram, ProcessFlowDiagramAdmin)\nadmin_site.register(ProcessFlowDiagramItem, ProcessFlowDiagramItemAdmin)\nadmin_site.register(Theme, ThemeAdmin)\nadmin_site.register(CssClass, CssClassAdmin)\nadmin_site.register(TransformData, TransformDataAdmin)\nadmin_site.register(DisplayValueOptionTemplate, DisplayValueOptionTemplateAdmin)\n"
  },
  {
    "path": "pyscada/hmi/apps.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.apps import AppConfig\nfrom django.utils.translation import gettext_lazy as _\nfrom django.db.utils import ProgrammingError, OperationalError\nfrom django.conf import settings\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass PyScadaHMIConfig(AppConfig):\n    name = \"pyscada.hmi\"\n    verbose_name = _(\"PyScada HMI\")\n    default_auto_field = \"django.db.models.AutoField\"\n\n    def ready(self):\n        import pyscada.hmi.signals\n\n    def pyscada_app_init(self):\n        logger.debug(\"HMI init app\")\n        try:\n            from .models import TransformData\n\n            # create the control item transform data display value options\n            # min, max, mean difference, difference percent...\n            # TODO: do not get the whole historical data for first, difference, difference percent\n            TransformData.objects.update_or_create(\n                short_name=\"Min\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataMin\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataMin\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Max\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataMax\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataMax\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Total\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataTotal\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataTotal\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Difference\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataDifference\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataDifference\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"DifferencePercent\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataDifferencePercent\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataDifferencePercent\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Delta\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataDelta\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataDelta\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Mean\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataMean\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataMean\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"First\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataFirst\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataFirst\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Count\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataCount\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataCount\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"CountValue\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataCountValue\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataCountValue\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Range\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataRange\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataRange\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"Step\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataStep\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataStep\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"ChangeCount\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataChangeCount\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataChangeCount\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n            TransformData.objects.update_or_create(\n                short_name=\"DistinctCount\",\n                defaults={\n                    \"inline_model_name\": \"TransformDataDistinctCount\",\n                    \"js_function_name\": \"PyScadaControlItemDisplayValueTransformDataDistinctCount\",\n                    \"js_files\": \"pyscada/js/pyscada/TransformDataHmiPlugin.js\",\n                    \"need_historical_data\": True,\n                },\n            )\n        except (ProgrammingError, OperationalError) as e:\n            logger.debug(e)\n\n        try:\n            from .models import DisplayValueOptionTemplate\n\n            STATIC_URL = (\n                str(settings.STATIC_URL)\n                if hasattr(settings, \"STATIC_URL\")\n                else \"/static/\"\n            )\n\n            # create the circular gauge for control item display value option\n            DisplayValueOptionTemplate.objects.update_or_create(\n                label=\"Circular gauge\",\n                defaults={\n                    \"template_name\": \"circular_gauge.html\",\n                    \"js_files\": \"pyscada/js/jquery/jquery.tablesorter.min.js,\"\n                    + \"pyscada/js/jquery/parser-input-select.js,\"\n                    + \"pyscada/js/flot/lib/jquery.mousewheel.js,\"\n                    + \"pyscada/js/flot/source/jquery.canvaswrapper.js,\"\n                    + \"pyscada/js/flot/source/jquery.colorhelpers.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.saturated.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.browser.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.drawSeries.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.errorbars.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.uiConstants.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.logaxis.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.symbol.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.flatdata.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.navigate.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.fillbetween.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.stack.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.touchNavigate.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.hover.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.touch.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.time.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.axislabels.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.selection.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.composeImages.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.legend.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.pie.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.crosshair.js,\"\n                    + \"pyscada/js/flot/source/jquery.flot.gauge.js,\"\n                    + \"pyscada/js/jquery.flot.axisvalues.js\",\n                },\n            )\n        except (ProgrammingError, OperationalError) as e:\n            logger.debug(e)\n"
  },
  {
    "path": "pyscada/hmi/migrations/0001_initial.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0001_initial\"),\n        (\"auth\", \"0001_initial\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Chart\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"x_axis_label\",\n                    models.CharField(default=\"\", max_length=400, blank=True),\n                ),\n                (\"x_axis_ticks\", models.PositiveSmallIntegerField(default=6)),\n                (\n                    \"y_axis_label\",\n                    models.CharField(default=\"\", max_length=400, blank=True),\n                ),\n                (\"y_axis_min\", models.FloatField(default=0)),\n                (\"y_axis_max\", models.FloatField(default=100)),\n                (\"variables\", models.ManyToManyField(to=\"pyscada.Variable\")),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"ChartSet\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\n                    \"distribution\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"side by side (1/2)\"),\n                            (1, \"side by side (2/3|1/3)\"),\n                            (2, \"side by side (1/3|2/3)\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"chart_1\",\n                    models.ForeignKey(\n                        related_name=\"chart_1\",\n                        verbose_name=\"left Chart\",\n                        blank=True,\n                        to=\"hmi.Chart\",\n                        null=True,\n                        on_delete=models.SET_NULL,\n                    ),\n                ),\n                (\n                    \"chart_2\",\n                    models.ForeignKey(\n                        related_name=\"chart_2\",\n                        verbose_name=\"right Chart\",\n                        blank=True,\n                        to=\"hmi.Chart\",\n                        null=True,\n                        on_delete=models.SET_NULL,\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Color\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"name\", models.SlugField(max_length=80, verbose_name=\"variable name\")),\n                (\"R\", models.PositiveSmallIntegerField(default=0)),\n                (\"G\", models.PositiveSmallIntegerField(default=0)),\n                (\"B\", models.PositiveSmallIntegerField(default=0)),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"ControlItem\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"label\", models.CharField(default=\"\", max_length=400)),\n                (\"position\", models.PositiveSmallIntegerField(default=0)),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"label blue\"),\n                            (1, \"label light blue\"),\n                            (2, \"label ok\"),\n                            (3, \"label warning\"),\n                            (4, \"label alarm\"),\n                            (7, \"label alarm inverted\"),\n                            (5, \"Control Element\"),\n                            (6, \"Display Value\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.Variable\",\n                        null=True,\n                    ),\n                ),\n            ],\n            options={\n                \"ordering\": [\"position\"],\n            },\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"ControlPanel\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\"items\", models.ManyToManyField(to=\"hmi.ControlItem\", blank=True)),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"CustomHTMLPanel\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400, blank=True)),\n                (\"html\", models.TextField()),\n                (\"variables\", models.ManyToManyField(to=\"pyscada.Variable\")),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"GroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"charts\", models.ManyToManyField(to=\"hmi.Chart\", blank=True)),\n                (\n                    \"control_items\",\n                    models.ManyToManyField(to=\"hmi.ControlItem\", blank=True),\n                ),\n                (\n                    \"custom_html_panels\",\n                    models.ManyToManyField(to=\"hmi.CustomHTMLPanel\", blank=True),\n                ),\n                (\n                    \"hmi_group\",\n                    models.OneToOneField(to=\"auth.Group\", on_delete=models.SET_NULL),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"HMIVariable\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\n                    \"short_name\",\n                    models.CharField(\n                        default=\"\", max_length=80, verbose_name=\"variable short name\"\n                    ),\n                ),\n                (\n                    \"chart_line_thickness\",\n                    models.PositiveSmallIntegerField(default=3, choices=[(3, \"3Px\")]),\n                ),\n                (\n                    \"chart_line_color\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        default=None,\n                        to=\"hmi.Color\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"hmi_variable\",\n                    models.OneToOneField(\n                        to=\"pyscada.Variable\", on_delete=models.SET_NULL\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Page\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\"link_title\", models.SlugField(default=\"\", max_length=80)),\n                (\"position\", models.PositiveSmallIntegerField(default=0)),\n            ],\n            options={\n                \"ordering\": [\"position\"],\n            },\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"SlidingPanelMenu\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"position\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[(0, \"Control Menu\"), (1, \"left\"), (2, \"right\")],\n                    ),\n                ),\n                (\"visable\", models.BooleanField(default=True)),\n                (\n                    \"control_panel\",\n                    models.ForeignKey(\n                        default=None,\n                        blank=True,\n                        to=\"hmi.ControlPanel\",\n                        null=True,\n                        on_delete=models.SET_NULL,\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"View\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"description\",\n                    models.TextField(default=\"\", null=True, verbose_name=\"Description\"),\n                ),\n                (\"link_title\", models.SlugField(default=\"\", max_length=80)),\n                (\n                    \"logo\",\n                    models.ImageField(\n                        upload_to=\"img/\", verbose_name=\"Overview Picture\", blank=True\n                    ),\n                ),\n                (\"visable\", models.BooleanField(default=True)),\n                (\"position\", models.PositiveSmallIntegerField(default=0)),\n                (\"pages\", models.ManyToManyField(to=\"hmi.Page\")),\n                (\n                    \"sliding_panel_menus\",\n                    models.ManyToManyField(to=\"hmi.SlidingPanelMenu\", blank=True),\n                ),\n            ],\n            options={\n                \"ordering\": [\"position\"],\n            },\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Widget\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400, blank=True)),\n                (\n                    \"row\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"1. row\"),\n                            (1, \"2. row\"),\n                            (2, \"3. row\"),\n                            (3, \"4. row\"),\n                            (4, \"5. row\"),\n                            (5, \"6. row\"),\n                            (6, \"7. row\"),\n                            (7, \"8. row\"),\n                            (8, \"9. row\"),\n                            (9, \"10. row\"),\n                            (10, \"11. row\"),\n                            (11, \"12. row\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"col\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"1. col\"),\n                            (1, \"2. col\"),\n                            (2, \"3. col\"),\n                            (3, \"4. col\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"size\",\n                    models.PositiveSmallIntegerField(\n                        default=4,\n                        choices=[\n                            (4, \"page width\"),\n                            (3, \"3/4 page width\"),\n                            (2, \"1/2 page width\"),\n                            (1, \"1/4 page width\"),\n                        ],\n                    ),\n                ),\n                (\"visable\", models.BooleanField(default=True)),\n                (\n                    \"chart\",\n                    models.ForeignKey(\n                        default=None,\n                        blank=True,\n                        to=\"hmi.Chart\",\n                        null=True,\n                        on_delete=models.SET_NULL,\n                    ),\n                ),\n                (\n                    \"chart_set\",\n                    models.ForeignKey(\n                        default=None,\n                        blank=True,\n                        to=\"hmi.ChartSet\",\n                        null=True,\n                        on_delete=models.SET_NULL,\n                    ),\n                ),\n                (\n                    \"control_panel\",\n                    models.ForeignKey(\n                        default=None,\n                        blank=True,\n                        to=\"hmi.ControlPanel\",\n                        null=True,\n                        on_delete=models.SET_NULL,\n                    ),\n                ),\n                (\n                    \"custom_html_panel\",\n                    models.ForeignKey(\n                        default=None,\n                        blank=True,\n                        to=\"hmi.CustomHTMLPanel\",\n                        null=True,\n                        on_delete=models.SET_NULL,\n                    ),\n                ),\n                (\n                    \"page\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        default=None,\n                        to=\"hmi.Page\",\n                        null=True,\n                    ),\n                ),\n            ],\n            options={\n                \"ordering\": [\"row\", \"col\"],\n            },\n            bases=(models.Model,),\n        ),\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"pages\",\n            field=models.ManyToManyField(to=\"hmi.Page\", blank=True),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"sliding_panel_menus\",\n            field=models.ManyToManyField(to=\"hmi.SlidingPanelMenu\", blank=True),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"views\",\n            field=models.ManyToManyField(to=\"hmi.View\", blank=True),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"widgets\",\n            field=models.ManyToManyField(to=\"hmi.Widget\", blank=True),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0002_auto_20151016_1932.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0002_event_hysteresis\"),\n        (\"hmi\", \"0001_initial\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ProcessFlowDiagram\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"title\", models.CharField(default=\"\", max_length=400, blank=True)),\n                (\n                    \"background_image\",\n                    models.ImageField(\n                        upload_to=\"img/\", verbose_name=\"background image\", blank=True\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"ProcessFlowDiagramItem\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"label\", models.CharField(default=\"\", max_length=400, blank=True)),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"label blue\"),\n                            (1, \"label light blue\"),\n                            (2, \"label ok\"),\n                            (3, \"label warning\"),\n                            (4, \"label alarm\"),\n                            (7, \"label alarm inverted\"),\n                            (5, \"Control Element\"),\n                            (6, \"Display Value\"),\n                        ],\n                    ),\n                ),\n                (\"top\", models.PositiveIntegerField(default=0)),\n                (\"left\", models.PositiveIntegerField(default=0)),\n                (\"width\", models.PositiveIntegerField(default=0)),\n                (\"height\", models.PositiveIntegerField(default=0)),\n                (\"visible\", models.BooleanField(default=True)),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        default=None, to=\"pyscada.Variable\", on_delete=models.SET_NULL\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.AddField(\n            model_name=\"processflowdiagram\",\n            name=\"process_flow_diagram_items\",\n            field=models.ManyToManyField(to=\"hmi.ProcessFlowDiagramItem\", blank=True),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"process_flow_diagram\",\n            field=models.ManyToManyField(to=\"hmi.ProcessFlowDiagram\", blank=True),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"widget\",\n            name=\"process_flow_diagram\",\n            field=models.ForeignKey(\n                default=None,\n                blank=True,\n                to=\"hmi.ProcessFlowDiagram\",\n                null=True,\n                on_delete=models.SET_NULL,\n            ),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0003_auto_20151130_1456.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0002_auto_20151016_1932\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                to=\"pyscada.Variable\", default=1, null=True, on_delete=models.SET_NULL\n            ),\n        ),\n        # migrations.AlterField(\n        #     model_name='hmivariable',\n        #     name='chart_line_color',\n        #     field=models.ForeignKey(on_delete=models.SET(1), default=1, to='hmi.Color', null=True),\n        # ),\n        # migrations.AlterField(\n        #     model_name='widget',\n        #     name='page',\n        #     field=models.ForeignKey(default=1, to='hmi.Page'),\n        #     preserve_default=False,\n        # ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0004_auto_20151130_1502.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0003_auto_20151130_1456\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                to=\"pyscada.Variable\", null=True, on_delete=models.SET_NULL\n            ),\n        ),\n        # migrations.AlterField(\n        #     model_name='hmivariable',\n        #     name='chart_line_color',\n        #     field=models.ForeignKey(on_delete=models.SET(1), default=1, to='hmi.Color', null=True),\n        # ),\n        # migrations.AlterField(\n        #     model_name='widget',\n        #     name='page',\n        #     field=models.ForeignKey(default=1, to='hmi.Page'),\n        #     preserve_default=False,\n        # ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0005_auto_20160111_1822.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0004_auto_20151130_1502\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"processflowdiagramitem\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                default=None,\n                blank=True,\n                to=\"pyscada.Variable\",\n                null=True,\n                on_delete=models.SET_NULL,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0006_auto_20160111_1848.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0005_auto_20160111_1822\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"hmivariable\",\n            name=\"chart_line_color\",\n            field=models.ForeignKey(\n                default=None,\n                blank=True,\n                to=\"hmi.Color\",\n                null=True,\n                on_delete=models.SET_NULL,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"widget\",\n            name=\"page\",\n            field=models.ForeignKey(\n                default=None,\n                blank=True,\n                to=\"hmi.Page\",\n                null=True,\n                on_delete=models.SET_NULL,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0007_auto_20160518_0848.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0006_auto_20160111_1848\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"chartset\",\n            name=\"chart_1\",\n        ),\n        migrations.RemoveField(\n            model_name=\"chartset\",\n            name=\"chart_2\",\n        ),\n        migrations.RemoveField(\n            model_name=\"hmivariable\",\n            name=\"chart_line_color\",\n        ),\n        migrations.RemoveField(\n            model_name=\"hmivariable\",\n            name=\"hmi_variable\",\n        ),\n        migrations.RemoveField(\n            model_name=\"widget\",\n            name=\"chart_set\",\n        ),\n        migrations.DeleteModel(\n            name=\"ChartSet\",\n        ),\n        migrations.DeleteModel(\n            name=\"Color\",\n        ),\n        migrations.DeleteModel(\n            name=\"HMIVariable\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0008_auto_20180620_0716.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.4 on 2018-06-20 07:16\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0007_auto_20160518_0848\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"processflowdiagramitem\",\n            name=\"height\",\n            field=models.PositiveIntegerField(blank=True, default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"processflowdiagramitem\",\n            name=\"left\",\n            field=models.PositiveIntegerField(blank=True, default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"processflowdiagramitem\",\n            name=\"top\",\n            field=models.PositiveIntegerField(blank=True, default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"processflowdiagramitem\",\n            name=\"width\",\n            field=models.PositiveIntegerField(blank=True, default=0),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0009_controlitem_property_name.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.13 on 2018-06-29 10:15\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0008_auto_20180620_0716\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"property_name\",\n            field=models.CharField(blank=True, default=\"\", max_length=255),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0010_auto_20180705_1341.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.13 on 2018-07-05 13:41\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0045_auto_20180705_1341\"),\n        (\"hmi\", \"0009_controlitem_property_name\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"property_name\",\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0011_auto_20180710_1549.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.13 on 2018-07-10 15:49\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0010_auto_20180705_1341\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0012_auto_20180912_1302.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.11 on 2018-09-12 13:02\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0011_auto_20180710_1549\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"WidgetContent\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"content_model\", models.CharField(max_length=400)),\n                (\"content_pk\", models.PositiveIntegerField()),\n            ],\n        ),\n        migrations.RenameField(\n            model_name=\"slidingpanelmenu\",\n            old_name=\"visable\",\n            new_name=\"visible\",\n        ),\n        migrations.RenameField(\n            model_name=\"view\",\n            old_name=\"visable\",\n            new_name=\"visible\",\n        ),\n        migrations.RenameField(\n            model_name=\"widget\",\n            old_name=\"visable\",\n            new_name=\"visible\",\n        ),\n        migrations.AddField(\n            model_name=\"widget\",\n            name=\"content\",\n            field=models.ForeignKey(\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.WidgetContent\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0013_widget_update_20180912_1315.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.11 on 2018-09-12 13:02\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\ndef forwards_func(apps, schema_editor):\n    # We get the model from the versioned app registry;\n    # if we directly import it, it'll be the wrong version\n    Chart = apps.get_model(\"hmi\", \"Chart\")\n    ProcessFlowDiagram = apps.get_model(\"hmi\", \"ProcessFlowDiagram\")\n    CustomHTMLPanel = apps.get_model(\"hmi\", \"CustomHTMLPanel\")\n    ControlPanel = apps.get_model(\"hmi\", \"ControlPanel\")\n    Widget = apps.get_model(\"hmi\", \"Widget\")\n    WidgetContent = apps.get_model(\"hmi\", \"WidgetContent\")\n\n    for c in Chart.objects.using(schema_editor.connection.alias).all():\n        wc = WidgetContent(content_pk=c.pk, content_model=\"pyscada.hmi.models.Chart\")\n        wc.save()\n\n    for c in ProcessFlowDiagram.objects.using(schema_editor.connection.alias).all():\n        wc = WidgetContent(\n            content_pk=c.pk, content_model=\"pyscada.hmi.models.ProcessFlowDiagram\"\n        )\n        wc.save()\n\n    for c in CustomHTMLPanel.objects.using(schema_editor.connection.alias).all():\n        wc = WidgetContent(\n            content_pk=c.pk, content_model=\"pyscada.hmi.models.CustomHTMLPanel\"\n        )\n        wc.save()\n\n    for c in ControlPanel.objects.using(schema_editor.connection.alias).all():\n        wc = WidgetContent(\n            content_pk=c.pk, content_model=\"pyscada.hmi.models.ControlPanel\"\n        )\n        wc.save()\n\n    for w in Widget.objects.using(schema_editor.connection.alias).all():\n        if w.chart is not None:\n            content = (\n                WidgetContent.objects.using(schema_editor.connection.alias)\n                .filter(content_pk=w.chart.pk, content_model=\"pyscada.hmi.models.Chart\")\n                .first()\n            )\n            if content is None:\n                continue\n        elif w.control_panel is not None:\n            content = (\n                WidgetContent.objects.using(schema_editor.connection.alias)\n                .filter(\n                    content_pk=w.control_panel.pk,\n                    content_model=\"pyscada.hmi.models.ControlPanel\",\n                )\n                .first()\n            )\n            if content is None:\n                continue\n        elif w.custom_html_panel is not None:\n            content = (\n                WidgetContent.objects.using(schema_editor.connection.alias)\n                .filter(\n                    content_pk=w.custom_html_panel.pk,\n                    content_model=\"pyscada.hmi.models.CustomHTMLPanel\",\n                )\n                .first()\n            )\n            if content is None:\n                continue\n        elif w.process_flow_diagram is not None:\n            content = (\n                WidgetContent.objects.using(schema_editor.connection.alias)\n                .filter(\n                    content_pk=w.process_flow_diagram.pk,\n                    content_model=\"pyscada.hmi.models.ProcessFlowDiagram\",\n                )\n                .first()\n            )\n            if content is None:\n                continue\n        else:\n            continue\n        w.content = content\n        w.save()\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0012_auto_20180912_1302\"),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards_func, reverse_code=migrations.RunPython.noop),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0014_auto_20180912_1340.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.11 on 2018-09-12 13:40\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0013_widget_update_20180912_1315\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"widget\",\n            name=\"chart\",\n        ),\n        migrations.RemoveField(\n            model_name=\"widget\",\n            name=\"control_panel\",\n        ),\n        migrations.RemoveField(\n            model_name=\"widget\",\n            name=\"custom_html_panel\",\n        ),\n        migrations.RemoveField(\n            model_name=\"widget\",\n            name=\"process_flow_diagram\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0015_auto_20180913_1608.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-09-13 16:08\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0047_variableproperty_unit\"),\n        (\"hmi\", \"0014_auto_20180912_1340\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Form\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\"button\", models.CharField(default=\"Ok\", max_length=50)),\n                (\n                    \"control_items\",\n                    models.ManyToManyField(\n                        related_name=\"control_items_form\", to=\"hmi.ControlItem\"\n                    ),\n                ),\n                (\n                    \"hidden_control_items_to_true\",\n                    models.ManyToManyField(\n                        related_name=\"hidden_control_items_form\", to=\"hmi.ControlItem\"\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"XYChart\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"x_axis_label\",\n                    models.CharField(blank=True, default=\"\", max_length=400),\n                ),\n                (\n                    \"x_axis_linlog\",\n                    models.BooleanField(\n                        default=False, help_text=\"False->Lin / True->Log\"\n                    ),\n                ),\n                (\n                    \"y_axis_label\",\n                    models.CharField(blank=True, default=\"\", max_length=400),\n                ),\n                (\n                    \"variables\",\n                    models.ManyToManyField(\n                        related_name=\"variables_xy_chart\", to=\"pyscada.Variable\"\n                    ),\n                ),\n                (\n                    \"x_axis_var\",\n                    models.ForeignKey(\n                        default=None,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"x_axis_var\",\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n            ],\n            options={\n                \"abstract\": False,\n            },\n        ),\n        migrations.AddField(\n            model_name=\"view\",\n            name=\"show_timeline\",\n            field=models.BooleanField(default=True),\n        ),\n        migrations.AddField(\n            model_name=\"controlpanel\",\n            name=\"forms\",\n            field=models.ManyToManyField(blank=True, to=\"hmi.Form\"),\n        ),\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"xy_charts\",\n            field=models.ManyToManyField(blank=True, to=\"hmi.XYChart\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0016_auto_20181004_0831.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-10-04 08:31\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0015_auto_20180913_1608\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"xychart\",\n            name=\"y_axis_plotpoints\",\n            field=models.BooleanField(default=False, help_text=\"Show the plots points\"),\n        ),\n        migrations.AddField(\n            model_name=\"xychart\",\n            name=\"y_axis_uniquescale\",\n            field=models.BooleanField(\n                default=True, help_text=\"To have a unique scale for all the y axis\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"control_items\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"control_items_form\", to=\"hmi.ControlItem\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"hidden_control_items_to_true\",\n            field=models.ManyToManyField(\n                blank=True,\n                related_name=\"hidden_control_items_form\",\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0017_groupdisplaypermission_forms.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-10-26 06:30\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0016_auto_20181004_0831\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"forms\",\n            field=models.ManyToManyField(blank=True, to=\"hmi.Form\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0018_auto_20181205_0937.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-12-05 09:37\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0017_groupdisplaypermission_forms\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"DropDown\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\"empty\", models.BooleanField(default=False)),\n                (\"empty_value\", models.CharField(default=\"------\", max_length=30)),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"DropDownItem\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"dropdown\",\n            name=\"items\",\n            field=models.ManyToManyField(to=\"hmi.DropDownItem\"),\n        ),\n        migrations.AddField(\n            model_name=\"controlpanel\",\n            name=\"dropdowns\",\n            field=models.ManyToManyField(blank=True, to=\"hmi.DropDown\"),\n        ),\n        migrations.AddField(\n            model_name=\"form\",\n            name=\"dropdowns\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"dropdowns_form\", to=\"hmi.DropDown\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"dropdowns\",\n            field=models.ManyToManyField(blank=True, to=\"hmi.DropDown\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0019_auto_20181205_1058.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-12-05 10:58\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0050_merge_20181130_1143\"),\n        (\"hmi\", \"0018_auto_20181205_0937\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"dropdown\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"dropdown\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0020_pie.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.18 on 2019-05-15 12:32\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0054_auto_20190411_0749\"),\n        (\"hmi\", \"0019_auto_20181205_1058\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Pie\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"radius\",\n                    models.CharField(\n                        default=\"auto\",\n                        help_text=\"auto or between 0 and 1 or value in pixel\",\n                        max_length=10,\n                    ),\n                ),\n                (\n                    \"innerRadius\",\n                    models.PositiveSmallIntegerField(\n                        default=0, help_text=\"between 0 and 1 or value in pixel\"\n                    ),\n                ),\n                (\"variables\", models.ManyToManyField(to=\"pyscada.Variable\")),\n            ],\n            options={\n                \"abstract\": False,\n            },\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0021_auto_20190528_0924.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.18 on 2019-05-28 09:24\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0054_auto_20190411_0749\"),\n        (\"hmi\", \"0020_pie\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"customhtmlpanel\",\n            name=\"variable_properties\",\n            field=models.ManyToManyField(blank=True, to=\"pyscada.VariableProperty\"),\n        ),\n        migrations.AlterField(\n            model_name=\"customhtmlpanel\",\n            name=\"variables\",\n            field=models.ManyToManyField(blank=True, to=\"pyscada.Variable\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0022_auto_20191004_0912.py",
    "content": "# Generated by Django 2.2.6 on 2019-10-04 09:12\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0021_auto_20190528_0924\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"dropdown\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"dropdown\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"control_items\",\n            field=models.ManyToManyField(\n                blank=True,\n                limit_choices_to={\"type\": \"5\"},\n                related_name=\"control_items_form\",\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"hidden_control_items_to_true\",\n            field=models.ManyToManyField(\n                blank=True,\n                limit_choices_to={\"type\": \"5\"},\n                related_name=\"hidden_control_items_form\",\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"hmi_group\",\n            field=models.OneToOneField(\n                null=True, on_delete=django.db.models.deletion.SET_NULL, to=\"auth.Group\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"widget\",\n            name=\"content\",\n            field=models.ForeignKey(\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.WidgetContent\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"xychart\",\n            name=\"x_axis_var\",\n            field=models.ForeignKey(\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"x_axis_var\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0023_dropdownitem_value.py",
    "content": "# Generated by Django 2.2.7 on 2019-11-13 16:41\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0022_auto_20191004_0912\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"dropdownitem\",\n            name=\"value\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0024_auto_20191212_1516.py",
    "content": "# Generated by Django 2.2.8 on 2019-12-12 15:16\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0023_dropdownitem_value\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0025_widgetcontent_content_str.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-07 07:31\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0024_auto_20191212_1516\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"widgetcontent\",\n            name=\"content_str\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0026_auto_20200915_1333.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-15 13:33\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0061_devicereadtask\"),\n        (\"hmi\", \"0025_widgetcontent_content_str\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"color_1\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"color_1\",\n                to=\"pyscada.Color\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"color_2\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"color_2\",\n                to=\"pyscada.Color\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"color_3\",\n            field=models.ForeignKey(\n                blank=True,\n                help_text=\"Only needed for 3 colors\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"color_3\",\n                to=\"pyscada.Color\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"color_level_1\",\n            field=models.PositiveSmallIntegerField(default=0),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"color_level_2\",\n            field=models.PositiveSmallIntegerField(\n                default=50, help_text=\"Only needed for 3 colors and gradient\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"display_type_color\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"Value only\"), (1, \"Color only\"), (2, \"Value and color\")],\n                default=0,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"display_value_color\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"No color\"),\n                    (1, \"2 color\"),\n                    (2, \"3 colors\"),\n                    (3, \"color gradient\"),\n                ],\n                default=0,\n                help_text=\"For boolean no level needed and will use color 1 and 2\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"display_value_transformation\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"None\"),\n                    (1, \"Timestamp to local date\"),\n                    (2, \"Dictionary\"),\n                ],\n                default=0,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"display_value_transformation_parameter\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"type\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"Control Element\"), (1, \"Display Value\")], default=0\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0027_auto_20200915_1407.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-15 14:07\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0026_auto_20200915_1333\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"controlitem\",\n            old_name=\"display_value_color\",\n            new_name=\"display_value_color_type\",\n        ),\n        migrations.RenameField(\n            model_name=\"controlitem\",\n            old_name=\"display_type_color\",\n            new_name=\"display_value_mode\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0028_auto_20200915_1540.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-15 15:40\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0027_auto_20200915_1407\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"controlitem\",\n            name=\"display_value_transformation_parameter\",\n            field=models.CharField(blank=True, default=\"\", max_length=400, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0029_auto_20200916_0720.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-16 07:20\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0061_devicereadtask\"),\n        (\"hmi\", \"0028_auto_20200915_1540\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"color_1\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"color_2\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"color_3\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"color_level_1\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"color_level_2\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"display_value_color_type\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"display_value_mode\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"display_value_transformation\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlitem\",\n            name=\"display_value_transformation_parameter\",\n        ),\n        migrations.CreateModel(\n            name=\"DisplayValueOption\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"name\", models.CharField(max_length=400)),\n                (\n                    \"display_value_color_type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[\n                            (0, \"No color\"),\n                            (1, \"2 color\"),\n                            (2, \"3 colors\"),\n                            (3, \"color gradient\"),\n                        ],\n                        default=0,\n                        help_text=\"For boolean no level needed and will use color 1 and 2\",\n                    ),\n                ),\n                (\"color_level_1\", models.PositiveSmallIntegerField(default=0)),\n                (\n                    \"color_level_1_type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"color 1 =< level 1\"), (1, \"color 1 < level 1\")],\n                        default=0,\n                        help_text=\"Only needed for 2 or 3 colors\",\n                    ),\n                ),\n                (\n                    \"color_level_2\",\n                    models.PositiveSmallIntegerField(\n                        default=50, help_text=\"Only needed for 3 colors and gradient\"\n                    ),\n                ),\n                (\n                    \"color_level_2_type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"color 2 =< level 2\"), (1, \"color 2 < level 2\")],\n                        default=0,\n                        help_text=\"Only needed for 3 colors\",\n                    ),\n                ),\n                (\n                    \"display_value_mode\",\n                    models.PositiveSmallIntegerField(\n                        choices=[\n                            (0, \"Value only\"),\n                            (1, \"Color only\"),\n                            (2, \"Value and color\"),\n                        ],\n                        default=0,\n                    ),\n                ),\n                (\n                    \"display_value_transformation\",\n                    models.PositiveSmallIntegerField(\n                        choices=[\n                            (0, \"None\"),\n                            (1, \"Timestamp to local date\"),\n                            (2, \"Dictionary\"),\n                        ],\n                        default=0,\n                    ),\n                ),\n                (\n                    \"display_value_transformation_parameter\",\n                    models.CharField(blank=True, default=\"\", max_length=400, null=True),\n                ),\n                (\n                    \"color_1\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"color_1\",\n                        to=\"pyscada.Color\",\n                    ),\n                ),\n                (\n                    \"color_2\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"color_2\",\n                        to=\"pyscada.Color\",\n                    ),\n                ),\n                (\n                    \"color_3\",\n                    models.ForeignKey(\n                        blank=True,\n                        help_text=\"Only needed for 3 colors\",\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"color_3\",\n                        to=\"pyscada.Color\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"display_value_options\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.DisplayValueOption\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0030_auto_20200918_0842.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-18 08:42\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0029_auto_20200916_0720\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"displayvalueoption\",\n            name=\"display_value_transformation\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"None\"),\n                    (1, \"Timestamp to local date\"),\n                    (2, \"Timestamp to local time\"),\n                    (3, \"Timestamp to local date and time\"),\n                    (4, \"Dictionary\"),\n                ],\n                default=0,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0031_auto_20200918_1206.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-18 12:06\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0030_auto_20200918_0842\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"processflowdiagramitem\",\n            name=\"label\",\n        ),\n        migrations.RemoveField(\n            model_name=\"processflowdiagramitem\",\n            name=\"type\",\n        ),\n        migrations.RemoveField(\n            model_name=\"processflowdiagramitem\",\n            name=\"variable\",\n        ),\n        migrations.AddField(\n            model_name=\"processflowdiagramitem\",\n            name=\"control_item\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"displayvalueoption\",\n            name=\"color_1\",\n            field=models.ForeignKey(\n                blank=True,\n                default=\"\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"color_1\",\n                to=\"pyscada.Color\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0032_auto_20200918_1408.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-18 14:08\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0031_auto_20200918_1206\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"displayvalueoption\",\n            name=\"color_level_1\",\n            field=models.FloatField(default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"displayvalueoption\",\n            name=\"color_level_2\",\n            field=models.FloatField(\n                default=50, help_text=\"Only needed for 3 colors and gradient\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0033_auto_20200918_1439.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-18 14:39\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0032_auto_20200918_1408\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"control_items\",\n            field=models.ManyToManyField(\n                blank=True,\n                limit_choices_to={\"type\": \"1\"},\n                related_name=\"control_items_form\",\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0034_auto_20200918_1445.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-18 14:45\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0033_auto_20200918_1439\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"hidden_control_items_to_true\",\n            field=models.ManyToManyField(\n                blank=True,\n                limit_choices_to={\"type\": \"1\"},\n                related_name=\"hidden_control_items_form\",\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0035_auto_20200918_1517.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-18 15:17\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0034_auto_20200918_1445\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"control_items\",\n            field=models.ManyToManyField(\n                blank=True,\n                limit_choices_to={\"type\": \"0\"},\n                related_name=\"control_items_form\",\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"form\",\n            name=\"hidden_control_items_to_true\",\n            field=models.ManyToManyField(\n                blank=True,\n                limit_choices_to={\"type\": \"0\"},\n                related_name=\"hidden_control_items_form\",\n                to=\"hmi.ControlItem\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0036_auto_20200923_0850.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-23 08:50\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0035_auto_20200918_1517\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"xychart\",\n            old_name=\"y_axis_plotpoints\",\n            new_name=\"show_plot_points\",\n        ),\n        migrations.AddField(\n            model_name=\"chart\",\n            name=\"show_plot_lines\",\n            field=models.PositiveSmallIntegerField(\n                default=2, help_text=\"Show the plot lines\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"chart\",\n            name=\"show_plot_points\",\n            field=models.BooleanField(default=False, help_text=\"Show the plots points\"),\n        ),\n        migrations.AddField(\n            model_name=\"chart\",\n            name=\"y_axis_uniquescale\",\n            field=models.BooleanField(\n                default=True, help_text=\"To have a unique scale for all the y axis\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"xychart\",\n            name=\"show_plot_lines\",\n            field=models.PositiveSmallIntegerField(\n                default=1, help_text=\"Show the plot lines\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0037_auto_20200923_0852.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-23 08:52\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0036_auto_20200923_0850\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"chart\",\n            name=\"show_plot_lines\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"No\"), (1, \"Yes\"), (2, \"Yes as steps\")],\n                default=2,\n                help_text=\"Show the plot lines\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"xychart\",\n            name=\"show_plot_lines\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"No\"), (1, \"Yes\"), (2, \"Yes as steps\")],\n                default=1,\n                help_text=\"Show the plot lines\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0038_auto_20200929_1410.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-29 14:10\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0037_auto_20200923_0852\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"dropdown\",\n            name=\"items\",\n        ),\n        migrations.AddField(\n            model_name=\"dropdownitem\",\n            name=\"dropdown\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.DropDown\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0039_auto_20201002_0928.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-02 09:28\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0038_auto_20200929_1410\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"displayvalueoption\",\n            old_name=\"display_value_color_type\",\n            new_name=\"color_type\",\n        ),\n        migrations.RenameField(\n            model_name=\"displayvalueoption\",\n            old_name=\"display_value_mode\",\n            new_name=\"mode\",\n        ),\n        migrations.RenameField(\n            model_name=\"dropdown\",\n            old_name=\"title\",\n            new_name=\"label\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"display_value_transformation\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"display_value_transformation_parameter\",\n        ),\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"timestamp_conversion\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"None\"),\n                    (1, \"Timestamp to local date\"),\n                    (2, \"Timestamp to local time\"),\n                    (3, \"Timestamp to local date and time\"),\n                ],\n                default=0,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"displayvalueoption\",\n            name=\"color_1\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"color_1\",\n                to=\"pyscada.Color\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"dropdown\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"dropdown\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.DeleteModel(\n            name=\"DropDownItem\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0040_dictionary_dictionaryitem.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-02 09:30\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0039_auto_20201002_0928\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Dictionary\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"name\", models.CharField(default=\"\", max_length=400)),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"DictionaryItem\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"label\", models.CharField(default=\"\", max_length=400)),\n                (\"value\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"dictionary\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"hmi.Dictionary\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0041_auto_20201002_0934.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-02 09:34\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0040_dictionary_dictionaryitem\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"dictionary\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.Dictionary\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"dropdown\",\n            name=\"dictionary\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.Dictionary\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0042_auto_20201201_1335.py",
    "content": "# Generated by Django 2.2.8 on 2020-12-01 13:35\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0078_auto_20201123_1906\"),\n        (\"hmi\", \"0041_auto_20201002_0934\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"chart\",\n            name=\"x_axis_linlog\",\n            field=models.BooleanField(\n                default=False, help_text=\"False->Lin / True->Log\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"chart\",\n            name=\"x_axis_var\",\n            field=models.ForeignKey(\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"x_axis_var\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0043_auto_20201201_1411.py",
    "content": "# Generated by Django 2.2.8 on 2020-12-01 14:11\n\nfrom django.db import migrations\nfrom pyscada.hmi.models import Chart as ChartModel\nfrom time import time\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ncharts_dict = {}\n\n\ndef add_vars_move_wc(apps, schema_editor):\n    XYChart = apps.get_model(\"hmi.XYChart\")\n    Chart = apps.get_model(\"hmi.Chart\")\n    WidgetContent = apps.get_model(\"hmi.WidgetContent\")\n    Widget = apps.get_model(\"hmi.Widget\")\n    Variable = apps.get_model(\"pyscada.Variable\")\n    for xy in charts_dict:\n        item = XYChart.objects.get(id=xy)\n        c = Chart.objects.get(id=charts_dict[xy])\n        for v in item.variables.all():\n            c.variables.add(Variable.objects.get(id=v.id))\n        c.save()\n        try:\n\n            def fullname(o):\n                return o.__module__ + \".\" + o.__class__.__name__\n\n            instance = ChartModel.objects.get(id=c.id)\n            c = WidgetContent.objects.update_or_create(\n                content_pk=instance.pk,\n                content_model=fullname(instance),\n                defaults={\"content_str\": instance.__str__()},\n            )\n\n            wcxy = WidgetContent.objects.get(\n                content_pk=item.id, content_model__contains=\".XYChart\"\n            )\n            wc = WidgetContent.objects.get(\n                content_pk=c.id, content_model__contains=\".Chart\"\n            )\n            Widget.objects.filter(content=wcxy).update(content=wc)\n            if wcxy is not None:\n                wcxy.delete()\n        except Exception as e:\n            logger.info(e)\n\n\ndef move_xy_chart(apps, schema_editor):\n    XYChart = apps.get_model(\"hmi.XYChart\")\n    Chart = apps.get_model(\"hmi.Chart\")\n    Variable = apps.get_model(\"pyscada.Variable\")\n\n    xy_chart_set = XYChart.objects.all()\n    count = 0\n    timeout = time() + 60 * 5\n    for item in xy_chart_set:\n        variables_list = []\n        for v in item.variables.all():\n            variables_list.append(Variable.objects.get(id=v.id))\n        c = Chart(\n            title=item.title,\n            x_axis_label=item.x_axis_label,\n            x_axis_var=Variable.objects.get(id=item.x_axis_var.id),\n            x_axis_linlog=item.x_axis_linlog,\n            y_axis_label=item.y_axis_label,\n            show_plot_points=item.show_plot_points,\n            show_plot_lines=item.show_plot_lines,\n            y_axis_uniquescale=item.y_axis_uniquescale,\n        )\n        c.save()\n        charts_dict[item.id] = c.id\n\n        if time() > timeout:\n            break\n\n        count += 1\n\n    logger.info(\"wrote %d lines in total\\n\" % count)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0078_auto_20201123_1906\"),\n        (\"hmi\", \"0042_auto_20201201_1335\"),\n    ]\n\n    operations = [\n        migrations.RunPython(move_xy_chart, reverse_code=migrations.RunPython.noop),\n        migrations.RunPython(add_vars_move_wc, reverse_code=migrations.RunPython.noop),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"xy_charts\",\n        ),\n        migrations.DeleteModel(\n            name=\"XYChart\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0044_auto_20201201_1539.py",
    "content": "# Generated by Django 2.2.8 on 2020-12-01 15:39\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0043_auto_20201201_1411\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"chart\",\n            name=\"x_axis_var\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"x_axis_var\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0045_auto_20201201_2100.py",
    "content": "# Generated by Django 2.2.17 on 2020-12-01 21:00\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0078_auto_20201123_1906\"),\n        (\"hmi\", \"0044_auto_20201201_1539\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ChartAxis\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"label\", models.CharField(blank=True, default=\"\", max_length=400)),\n                (\"min\", models.FloatField(default=0)),\n                (\"max\", models.FloatField(default=100)),\n                (\n                    \"show_plot_points\",\n                    models.BooleanField(\n                        default=False, help_text=\"Show the plots points\"\n                    ),\n                ),\n                (\n                    \"show_plot_lines\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"No\"), (1, \"Yes\"), (2, \"Yes as steps\")],\n                        default=2,\n                        help_text=\"Show the plot lines\",\n                    ),\n                ),\n                (\n                    \"stack\",\n                    models.BooleanField(\n                        default=False, help_text=\"Stack all variables of this axis\"\n                    ),\n                ),\n                (\n                    \"chart\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE, to=\"hmi.Chart\"\n                    ),\n                ),\n                (\"variables\", models.ManyToManyField(to=\"pyscada.Variable\")),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0046_auto_20201201_2109.py",
    "content": "# Generated by Django 2.2.17 on 2020-12-01 21:00\n\nfrom django.db import migrations\n\nfrom time import time\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ncharts_dict = {}\n\n\ndef move_chart_vars(apps, schema_editor):\n    chart_model = apps.get_model(\"hmi.Chart\")\n    chart_axis_model = apps.get_model(\"hmi.ChartAxis\")\n    for chart in charts_dict:\n        item = chart_model.objects.get(id=chart)\n        axis = chart_axis_model.objects.get(id=charts_dict[chart])\n        axis.variables.set(item.variables.all())\n\n\ndef move_chart_axis(apps, schema_editor):\n    chart_model = apps.get_model(\"hmi.Chart\")\n    chart_axis_model = apps.get_model(\"hmi.ChartAxis\")\n    chart_set = chart_model.objects.all()\n    count = 0\n    timeout = time() + 60 * 5\n    for item in chart_set:\n        axis = chart_axis_model(\n            label=item.y_axis_label,\n            min=item.y_axis_min,\n            max=item.y_axis_max,\n            show_plot_points=item.show_plot_points,\n            show_plot_lines=item.show_plot_lines,\n            stack=False,\n            chart=chart_model.objects.get(id=item.id),\n        )\n        axis.save()\n        charts_dict[item.id] = axis.id\n\n        if time() > timeout:\n            break\n\n        count += 1\n\n    logger.info(\"wrote %d lines in total\\n\" % count)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0045_auto_20201201_2100\"),\n    ]\n\n    operations = [\n        migrations.RunPython(move_chart_axis, reverse_code=migrations.RunPython.noop),\n        migrations.RunPython(move_chart_vars, reverse_code=migrations.RunPython.noop),\n        migrations.RemoveField(\n            model_name=\"chart\",\n            name=\"show_plot_lines\",\n        ),\n        migrations.RemoveField(\n            model_name=\"chart\",\n            name=\"show_plot_points\",\n        ),\n        migrations.RemoveField(\n            model_name=\"chart\",\n            name=\"variables\",\n        ),\n        migrations.RemoveField(\n            model_name=\"chart\",\n            name=\"y_axis_label\",\n        ),\n        migrations.RemoveField(\n            model_name=\"chart\",\n            name=\"y_axis_max\",\n        ),\n        migrations.RemoveField(\n            model_name=\"chart\",\n            name=\"y_axis_min\",\n        ),\n        migrations.RemoveField(\n            model_name=\"chart\",\n            name=\"y_axis_uniquescale\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0047_auto_20201202_1445.py",
    "content": "# Generated by Django 2.2.8 on 2020-12-02 14:45\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0046_auto_20201201_2109\"),\n    ]\n\n    operations = [\n        migrations.AlterModelOptions(\n            name=\"chartaxis\",\n            options={\"verbose_name\": \"Y Axis\", \"verbose_name_plural\": \"Y Axis\"},\n        ),\n        migrations.AddField(\n            model_name=\"chartaxis\",\n            name=\"position\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"left\"), (1, \"right\")], default=0\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0048_chartaxis_fill.py",
    "content": "# Generated by Django 2.2.8 on 2020-12-02 16:21\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0047_auto_20201202_1445\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"chartaxis\",\n            name=\"fill\",\n            field=models.BooleanField(\n                default=False, help_text=\"Fill all variables of this axis\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0049_auto_20201202_2037.py",
    "content": "# Generated by Django 2.2.17 on 2020-12-02 20:37\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0048_chartaxis_fill\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"chartaxis\",\n            name=\"max\",\n            field=models.FloatField(blank=True, null=True),\n        ),\n        migrations.AlterField(\n            model_name=\"chartaxis\",\n            name=\"min\",\n            field=models.FloatField(blank=True, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0050_auto_20201203_2101.py",
    "content": "# Generated by Django 2.2.17 on 2020-12-03 21:01\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0049_auto_20201202_2037\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ControlElementOption\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"name\", models.CharField(max_length=400)),\n                (\n                    \"placeholder\",\n                    models.CharField(default=\"Enter a value\", max_length=30),\n                ),\n                (\"empty_dictionary\", models.BooleanField(default=False)),\n                (\n                    \"dictionary\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.Dictionary\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"controlitem\",\n            name=\"control_element_options\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.ControlElementOption\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0051_auto_20201204_0901.py",
    "content": "# Generated by Django 2.2.17 on 2020-12-03 21:01\n\nfrom django.db import migrations\n\nfrom time import time\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndd_dict = {}\ncontrol_element_dict = {}\n\n\ndef move_dropdown_in_control_panel(apps, schema_editor):\n    control_item_model = apps.get_model(\"hmi.ControlItem\")\n    control_panel_model = apps.get_model(\"hmi.ControlPanel\")\n    control_panel_model_set = control_panel_model.objects.all()\n    form_model = apps.get_model(\"hmi.Form\")\n    form_model_set = form_model.objects.all()\n    count = 0\n    timeout = time() + 60 * 5\n    for item in control_panel_model_set:\n        for dd in item.dropdowns.all():\n            item.items.add(\n                control_item_model.objects.get(id=control_element_dict[dd.id])\n            )\n\n            if time() > timeout:\n                break\n\n            count += 1\n\n    for item in form_model_set:\n        for dd in item.dropdowns.all():\n            item.control_items.add(\n                control_item_model.objects.get(id=control_element_dict[dd.id])\n            )\n\n            if time() > timeout:\n                break\n\n            count += 1\n\n    logger.info(\"move %d dropdown in total\\n\" % count)\n\n\ndef move_dropdown(apps, schema_editor):\n    dropdown = apps.get_model(\"hmi.DropDown\")\n    control_item_model = apps.get_model(\"hmi.ControlItem\")\n    control_element_option_model = apps.get_model(\"hmi.ControlElementOption\")\n    dropdown_set = dropdown.objects.all()\n    count = 0\n    timeout = time() + 60 * 5\n    for item in dropdown_set:\n        control_item = control_item_model(\n            label=item.label,\n            position=0,\n            type=0,\n            variable=item.variable,\n            variable_property=item.variable_property,\n            display_value_options=None,\n            control_element_options=control_element_option_model.objects.get(\n                id=dd_dict[item.id]\n            ),\n        )\n        control_item.save()\n        control_element_dict[item.id] = control_item.id\n\n        if time() > timeout:\n            break\n\n        count += 1\n\n    logger.info(\"move %d dropdown in total\\n\" % count)\n\n\ndef create_control_element_option(apps, schema_editor):\n    dropdown = apps.get_model(\"hmi.DropDown\")\n    control_element_option_model = apps.get_model(\"hmi.ControlElementOption\")\n    dropdown_set = dropdown.objects.all()\n    count = 0\n    timeout = time() + 60 * 5\n    for item in dropdown_set:\n        control_element_option = control_element_option_model(\n            name=item.label,\n            placeholder=item.empty_value,\n            dictionary=item.dictionary,\n            empty_dictionary=item.empty,\n        )\n        control_element_option.save()\n        dd_dict[item.id] = control_element_option.id\n\n        if time() > timeout:\n            break\n\n        count += 1\n\n    logger.info(\"create %d control element option in total\\n\" % count)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0050_auto_20201203_2101\"),\n    ]\n\n    operations = [\n        migrations.RunPython(\n            create_control_element_option, reverse_code=migrations.RunPython.noop\n        ),\n        migrations.RunPython(move_dropdown, reverse_code=migrations.RunPython.noop),\n        migrations.RunPython(\n            move_dropdown_in_control_panel, reverse_code=migrations.RunPython.noop\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0052_auto_20201204_0949.py",
    "content": "# Generated by Django 2.2.8 on 2020-12-04 09:49\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0051_auto_20201204_0901\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"controlpanel\",\n            name=\"dropdowns\",\n        ),\n        migrations.RemoveField(\n            model_name=\"form\",\n            name=\"dropdowns\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"dropdowns\",\n        ),\n        migrations.DeleteModel(\n            name=\"DropDown\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0053_auto_20211118_1438.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-18 14:38\n\nimport django.core.validators\nfrom django.db import migrations, models\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef radius_auto(apps, schema_editor):\n    # We can't import the Person model directly as it may be a newer\n    # version than this migration expects. We use the historical version.\n    Pie = apps.get_model(\"hmi\", \"Pie\")\n    count = 0\n    for item in Pie.objects.using(schema_editor.connection.alias).all():\n        try:\n            int(item.radius)\n        except ValueError:\n            item.radius = 100\n            item.save()\n            count += 1\n\n    logger.info(\"changed %d Pies\\n\" % count)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0091_auto_20211118_1019\"),\n        (\"hmi\", \"0052_auto_20201204_0949\"),\n    ]\n\n    operations = [\n        migrations.RunPython(radius_auto, reverse_code=migrations.RunPython.noop),\n        migrations.AddField(\n            model_name=\"pie\",\n            name=\"variable_properties\",\n            field=models.ManyToManyField(blank=True, to=\"pyscada.VariableProperty\"),\n        ),\n        migrations.AlterField(\n            model_name=\"pie\",\n            name=\"innerRadius\",\n            field=models.PositiveSmallIntegerField(\n                default=0,\n                validators=[\n                    django.core.validators.MaxValueValidator(100),\n                    django.core.validators.MinValueValidator(0),\n                ],\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"pie\",\n            name=\"radius\",\n            field=models.PositiveSmallIntegerField(\n                default=100,\n                validators=[\n                    django.core.validators.MaxValueValidator(100),\n                    django.core.validators.MinValueValidator(1),\n                ],\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"pie\",\n            name=\"variables\",\n            field=models.ManyToManyField(blank=True, to=\"pyscada.Variable\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0054_displayvalueoption_type.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-18 16:35\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0053_auto_20211118_1438\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"type\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"Classic (Div)\"),\n                    (1, \"Horizontal gauge\"),\n                    (2, \"Vertical gauge\"),\n                    (3, \"Circular gauge\"),\n                ],\n                default=0,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0055_auto_20211125_1405.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-25 14:05\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0094_move_dictionaries\"),\n        (\"hmi\", \"0054_displayvalueoption_type\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"dictionaryitem\",\n            name=\"dictionary\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlelementoption\",\n            name=\"dictionary\",\n        ),\n        migrations.RemoveField(\n            model_name=\"controlelementoption\",\n            name=\"empty_dictionary\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"dictionary\",\n        ),\n        migrations.AddField(\n            model_name=\"controlelementoption\",\n            name=\"dropdown\",\n            field=models.BooleanField(\n                default=False,\n                help_text=\"Show control item as dropdown. The variable must have a dictionary\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"controlelementoption\",\n            name=\"empty_dropdown_value\",\n            field=models.BooleanField(\n                default=False,\n                help_text=\"If true, show placeholder as default unelectable text\",\n            ),\n        ),\n        migrations.DeleteModel(\n            name=\"Dictionary\",\n        ),\n        migrations.DeleteModel(\n            name=\"DictionaryItem\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0056_auto_20211210_1608.py",
    "content": "# Generated by Django 2.2.8 on 2021-12-10 16:08\n\nfrom django.db import migrations, models\nfrom django.conf import settings\nfrom PIL import Image\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef add_height_width(apps, schema_editor):\n    # We can't import the Person model directly as it may be a newer\n    # version than this migration expects. We use the historical version.\n    ProcessFlowDiagram = apps.get_model(\"hmi\", \"ProcessFlowDiagram\")\n    count = 0\n    for item in ProcessFlowDiagram.objects.using(schema_editor.connection.alias).all():\n        if str(item.url_height) == \"100\" and str(item.url_width) == \"100\":\n            if hasattr(settings, \"MEDIA_ROOT\"):\n                file_path = str(settings.MEDIA_ROOT) + str(item.background_image.name)\n                try:\n                    img = Image.open(file_path)\n                    item.url_height = int(img.height)\n                    item.url_width = int(img.width)\n                    item.save()\n                    count += 1\n                except Exception as e:\n                    logger.info(e)\n\n    logger.info(\"changed %d ProcessFlowDiagram\\n\" % count)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0055_auto_20211125_1405\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"processflowdiagram\",\n            name=\"url_height\",\n            field=models.PositiveIntegerField(default=\"100\", editable=False),\n        ),\n        migrations.AddField(\n            model_name=\"processflowdiagram\",\n            name=\"url_width\",\n            field=models.PositiveIntegerField(default=\"100\", editable=False),\n        ),\n        migrations.AlterField(\n            model_name=\"processflowdiagram\",\n            name=\"background_image\",\n            field=models.ImageField(\n                blank=True,\n                height_field=\"url_height\",\n                upload_to=\"img/\",\n                verbose_name=\"background image\",\n                width_field=\"url_width\",\n            ),\n        ),\n        migrations.RunPython(add_height_width, reverse_code=migrations.RunPython.noop),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0057_auto_20211214_1157.py",
    "content": "# Generated by Django 2.2.8 on 2021-12-14 11:57\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0056_auto_20211210_1608\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"processflowdiagram\",\n            name=\"type\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"HTML\"), (1, \"SVG\")],\n                default=0,\n                help_text=\"HTML is not responsive and can display control element<br>SVG is responsive and cannot display control element\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"processflowdiagramitem\",\n            name=\"font_size\",\n            field=models.PositiveSmallIntegerField(default=14),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0058_auto_20220523_1639.py",
    "content": "# Generated by Django 3.2 on 2022-05-23 16:39\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0057_auto_20211214_1157\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Theme\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"name\", models.CharField(max_length=400)),\n                (\n                    \"base_filename\",\n                    models.CharField(\n                        default=\"base\",\n                        help_text=\"Enter the filename without '.html'\",\n                        max_length=400,\n                    ),\n                ),\n                (\n                    \"view_filename\",\n                    models.CharField(\n                        default=\"view\",\n                        help_text=\"Enter the filename without '.html'\",\n                        max_length=400,\n                    ),\n                ),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"view\",\n            name=\"theme\",\n            field=models.ForeignKey(\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.theme\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0059_alter_view_theme.py",
    "content": "# Generated by Django 3.2.13 on 2022-05-24 19:48\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0058_auto_20220523_1639\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"view\",\n            name=\"theme\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.theme\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0060_chartaxis_show_bars.py",
    "content": "# Generated by Django 3.2 on 2022-05-25 16:34\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0059_alter_view_theme\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"chartaxis\",\n            name=\"show_bars\",\n            field=models.BooleanField(default=False, help_text=\"Show bars\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0061_auto_20220610_1459.py",
    "content": "# Generated by Django 3.2 on 2022-06-10 14:59\n\nfrom django.db import migrations, models\nimport pyscada.hmi.models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0060_chartaxis_show_bars\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"theme\",\n            name=\"base_filename\",\n            field=models.CharField(\n                default=\"base\",\n                help_text=\"Enter the filename without '.html'\",\n                max_length=400,\n                # validators=[pyscada.hmi.models.validate_tempalte],\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"theme\",\n            name=\"view_filename\",\n            field=models.CharField(\n                default=\"view\",\n                help_text=\"Enter the filename without '.html'\",\n                max_length=400,\n                # validators=[pyscada.hmi.models.validate_tempalte],\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0062_auto_20220616_1523.py",
    "content": "# Generated by Django 3.2 on 2022-06-16 15:23\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0061_auto_20220610_1459\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"charts\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"old_charts\", to=\"hmi.Chart\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"control_items\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"old_control_items\", to=\"hmi.ControlItem\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"custom_html_panels\",\n            field=models.ManyToManyField(\n                blank=True,\n                related_name=\"old_custom_html_panels\",\n                to=\"hmi.CustomHTMLPanel\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"forms\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"old_forms\", to=\"hmi.Form\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"pages\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"old_pages\", to=\"hmi.Page\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"process_flow_diagram\",\n            field=models.ManyToManyField(\n                blank=True,\n                related_name=\"old_process_flow_diagram\",\n                to=\"hmi.ProcessFlowDiagram\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"sliding_panel_menus\",\n            field=models.ManyToManyField(\n                blank=True,\n                related_name=\"old_sliding_panel_menus\",\n                to=\"hmi.SlidingPanelMenu\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"views\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"old_views\", to=\"hmi.View\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"widgets\",\n            field=models.ManyToManyField(\n                blank=True, related_name=\"old_widgets\", to=\"hmi.Widget\"\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"WidgetGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n                (\n                    \"widgets\",\n                    models.ManyToManyField(\n                        blank=True,\n                        related_name=\"groupdisplaypermission\",\n                        to=\"hmi.Widget\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"ViewGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n                (\n                    \"views\",\n                    models.ManyToManyField(\n                        blank=True, related_name=\"groupdisplaypermission\", to=\"hmi.View\"\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"SlidingPanelMenuGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n                (\n                    \"sliding_panel_menus\",\n                    models.ManyToManyField(\n                        blank=True,\n                        related_name=\"groupdisplaypermission\",\n                        to=\"hmi.SlidingPanelMenu\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"ProcessFlowDiagramGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n                (\n                    \"process_flow_diagram\",\n                    models.ManyToManyField(\n                        blank=True,\n                        related_name=\"groupdisplaypermission\",\n                        to=\"hmi.ProcessFlowDiagram\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"PieGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n                (\n                    \"pies\",\n                    models.ManyToManyField(\n                        blank=True, related_name=\"groupdisplaypermission\", to=\"hmi.Pie\"\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"PageGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n                (\n                    \"pages\",\n                    models.ManyToManyField(\n                        blank=True, related_name=\"groupdisplaypermission\", to=\"hmi.Page\"\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"FormGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"forms\",\n                    models.ManyToManyField(\n                        blank=True, related_name=\"groupdisplaypermission\", to=\"hmi.Form\"\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"CustomHTMLPanelGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"custom_html_panels\",\n                    models.ManyToManyField(\n                        blank=True,\n                        related_name=\"groupdisplaypermission\",\n                        to=\"hmi.CustomHTMLPanel\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"ControlItemGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"control_items\",\n                    models.ManyToManyField(\n                        blank=True,\n                        related_name=\"groupdisplaypermission\",\n                        to=\"hmi.ControlItem\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"ChartGroupDisplayPermission\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"allow\"), (1, \"exclude\")],\n                        default=0,\n                        help_text=\"If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.\",\n                    ),\n                ),\n                (\n                    \"charts\",\n                    models.ManyToManyField(\n                        blank=True,\n                        related_name=\"groupdisplaypermission\",\n                        to=\"hmi.Chart\",\n                    ),\n                ),\n                (\n                    \"group_display_permission\",\n                    models.OneToOneField(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"hmi.groupdisplaypermission\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0063_move_group_display_permissions.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-25 12:56\n\nfrom django.db import migrations, models\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ncharts_dict = {}\n\n\ndef move_group_display_permissions(apps, schema_editor):\n    HMIGroupDisplayPermissions = apps.get_model(\"hmi\", \"GroupDisplayPermission\")\n    HMIPagesGroupDisplayPermissions = apps.get_model(\n        \"hmi\", \"PageGroupDisplayPermission\"\n    )\n    HMISlidingPanelMenuGroupDisplayPermission = apps.get_model(\n        \"hmi\", \"SlidingPanelMenuGroupDisplayPermission\"\n    )\n    HMIChartGroupDisplayPermission = apps.get_model(\n        \"hmi\", \"ChartGroupDisplayPermission\"\n    )\n    HMIControlItemGroupDisplayPermission = apps.get_model(\n        \"hmi\", \"ControlItemGroupDisplayPermission\"\n    )\n    HMIFormGroupDisplayPermission = apps.get_model(\"hmi\", \"FormGroupDisplayPermission\")\n    HMIWidgetGroupDisplayPermission = apps.get_model(\n        \"hmi\", \"WidgetGroupDisplayPermission\"\n    )\n    HMICustomHTMLPanelGroupDisplayPermission = apps.get_model(\n        \"hmi\", \"CustomHTMLPanelGroupDisplayPermission\"\n    )\n    HMIViewGroupDisplayPermission = apps.get_model(\"hmi\", \"ViewGroupDisplayPermission\")\n    HMIProcessFlowDiagramGroupDisplayPermission = apps.get_model(\n        \"hmi\", \"ProcessFlowDiagramGroupDisplayPermission\"\n    )\n\n    db_alias = schema_editor.connection.alias\n\n    hmi_group_display_permissions_set = HMIGroupDisplayPermissions.objects.all()\n    count = 0\n    pages_dict = []\n    for item in hmi_group_display_permissions_set:\n        if (\n            item.pages.count()\n            and HMIPagesGroupDisplayPermissions.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMIPagesGroupDisplayPermissions(\n                    group_display_permission=item,\n                )\n            ]\n            HMIPagesGroupDisplayPermissions.objects.using(db_alias).bulk_create(i)\n            HMIPagesGroupDisplayPermissions.objects.using(db_alias).get(\n                group_display_permission=item\n            ).pages.set(item.pages.all())\n            count += 1\n        if (\n            item.sliding_panel_menus.count()\n            and HMISlidingPanelMenuGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMISlidingPanelMenuGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMISlidingPanelMenuGroupDisplayPermission.objects.using(\n                db_alias\n            ).bulk_create(i)\n            HMISlidingPanelMenuGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).sliding_panel_menus.set(item.sliding_panel_menus.all())\n            count += 1\n        if (\n            item.charts.count()\n            and HMIChartGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMIChartGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMIChartGroupDisplayPermission.objects.using(db_alias).bulk_create(i)\n            HMIChartGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).charts.set(item.charts.all())\n            count += 1\n        if (\n            item.control_items.count()\n            and HMIControlItemGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMIControlItemGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMIControlItemGroupDisplayPermission.objects.using(db_alias).bulk_create(i)\n            HMIControlItemGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).control_items.set(item.control_items.all())\n            count += 1\n        if (\n            item.forms.count()\n            and HMIFormGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMIFormGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMIFormGroupDisplayPermission.objects.using(db_alias).bulk_create(i)\n            HMIFormGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).forms.set(item.forms.all())\n            count += 1\n        if (\n            item.widgets.count()\n            and HMIWidgetGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMIWidgetGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMIWidgetGroupDisplayPermission.objects.using(db_alias).bulk_create(i)\n            HMIWidgetGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).widgets.set(item.widgets.all())\n            count += 1\n        if (\n            item.custom_html_panels.count()\n            and HMICustomHTMLPanelGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMICustomHTMLPanelGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMICustomHTMLPanelGroupDisplayPermission.objects.using(\n                db_alias\n            ).bulk_create(i)\n            HMICustomHTMLPanelGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).custom_html_panels.set(item.custom_html_panels.all())\n            count += 1\n        if (\n            item.views.count()\n            and HMIViewGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMIViewGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMIViewGroupDisplayPermission.objects.using(db_alias).bulk_create(i)\n            HMIViewGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).views.set(item.views.all())\n            count += 1\n        if (\n            item.process_flow_diagram.count()\n            and HMIProcessFlowDiagramGroupDisplayPermission.objects.using(db_alias)\n            .filter(group_display_permission=item)\n            .count()\n            == 0\n        ):\n            i = [\n                HMIProcessFlowDiagramGroupDisplayPermission(\n                    group_display_permission=item,\n                )\n            ]\n            HMIProcessFlowDiagramGroupDisplayPermission.objects.using(\n                db_alias\n            ).bulk_create(i)\n            HMIProcessFlowDiagramGroupDisplayPermission.objects.using(db_alias).get(\n                group_display_permission=item\n            ).process_flow_diagram.set(item.process_flow_diagram.all())\n            count += 1\n    logger.info(\"moved %d Groups\\n\" % count)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0062_auto_20220616_1523\"),\n    ]\n\n    operations = [\n        migrations.RunPython(\n            move_group_display_permissions, reverse_code=migrations.RunPython.noop\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0064_auto_20220617_1333.py",
    "content": "# Generated by Django 3.2 on 2022-06-17 13:33\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"auth\", \"0012_alter_user_first_name_max_length\"),\n        (\"hmi\", \"0063_move_group_display_permissions\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"chartgroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"controlitemgroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"customhtmlpanelgroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"formgroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"hmi_group\",\n            field=models.OneToOneField(\n                null=True, on_delete=django.db.models.deletion.CASCADE, to=\"auth.group\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"pagegroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"piegroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"processflowdiagramgroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"slidingpanelmenugroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"viewgroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"widgetgroupdisplaypermission\",\n            name=\"group_display_permission\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.groupdisplaypermission\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0065_auto_20220620_0854.py",
    "content": "# Generated by Django 3.2 on 2022-06-20 08:54\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0064_auto_20220617_1333\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"charts\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"control_items\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"custom_html_panels\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"forms\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"pages\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"process_flow_diagram\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"sliding_panel_menus\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"views\",\n        ),\n        migrations.RemoveField(\n            model_name=\"groupdisplaypermission\",\n            name=\"widgets\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0066_auto_20221205_1435.py",
    "content": "# Generated by Django 3.2.13 on 2022-12-05 14:35\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0065_auto_20220620_0854\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"CssClass\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"title\", models.CharField(default=\"\", max_length=400)),\n                (\"css_class\", models.SlugField(default=\"\", max_length=80)),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"widget\",\n            name=\"extra_css_class\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"hmi.cssclass\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0067_alter_cssclass_options.py",
    "content": "# Generated by Django 3.2 on 2023-01-13 09:33\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0066_auto_20221205_1435\"),\n    ]\n\n    operations = [\n        migrations.AlterModelOptions(\n            name=\"cssclass\",\n            options={\"verbose_name_plural\": \"Css Classes\"},\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0068_alter_displayvalueoption_timestamp_conversion.py",
    "content": "# Generated by Django 3.2 on 2023-02-02 14:50\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0067_alter_cssclass_options\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"displayvalueoption\",\n            name=\"timestamp_conversion\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"None\"),\n                    (1, \"Timestamp in milliseconds to local date\"),\n                    (2, \"Timestamp in milliseconds to local time\"),\n                    (3, \"Timestamp in milliseconds to local date and time\"),\n                    (4, \"Timestamp in seconds to local date\"),\n                    (5, \"Timestamp in seconds to local time\"),\n                    (6, \"Timestamp in seconds to local date and time\"),\n                ],\n                default=0,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0069_displayvalueoption_color_and_more.py",
    "content": "# Generated by Django 4.2rc1 on 2023-03-30 07:48\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0100_device_instrument_handler\"),\n        (\"hmi\", \"0068_alter_displayvalueoption_timestamp_conversion\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"color\",\n            field=models.ForeignKey(\n                blank=True,\n                help_text=\"Default color if no level defined, can be null.<br>Color < or =< first level, if a level is defined.\",\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.color\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"color_only\",\n            field=models.BooleanField(\n                default=False, help_text=\"If true, will not display the value.\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"gradient\",\n            field=models.BooleanField(\n                default=False, help_text=\"Need 1 color option to be defined.\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"gradient_higher_level\",\n            field=models.FloatField(\n                default=0, help_text=\"Color defined above will be used for this level.\"\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"DisplayValueColorOption\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"color_level\", models.FloatField()),\n                (\n                    \"color_level_type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"color =< level\"), (1, \"color < level\")], default=0\n                    ),\n                ),\n                (\n                    \"color\",\n                    models.ForeignKey(\n                        blank=True,\n                        help_text=\"Let blank for no color below the selected level.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.color\",\n                    ),\n                ),\n                (\n                    \"display_value_option\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"hmi.displayvalueoption\",\n                    ),\n                ),\n            ],\n            options={\n                \"ordering\": [\"color_level\", \"-color_level_type\"],\n            },\n        ),\n        migrations.AddConstraint(\n            model_name=\"displayvaluecoloroption\",\n            constraint=models.UniqueConstraint(\n                fields=(\"display_value_option\", \"color_level\", \"color_level_type\"),\n                name=\"unique_display_value_color_option\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0070_move_displayvalueoptions.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-25 12:56\n\nfrom django.db import migrations, models\nfrom pyscada.hmi.models import DisplayValueColorOption, DisplayValueOption\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ncharts_dict = {}\n\n\ndef move_dvo(apps, schema_editor):\n    dvo = apps.get_model(\"hmi\", \"DisplayValueOption\")\n    dvco = apps.get_model(\"hmi\", \"DisplayValueColorOption\")\n    db_alias = schema_editor.connection.alias\n\n    dvo_set = dvo.objects.all()\n    count = 0\n    countc = 0\n    dictc = []\n    dict = []\n    for item in dvo_set:\n        if item.mode == 1:\n            item.color_only = True\n        if item.color_type == 1:\n            item.color = item.color_1\n            if (\n                dvco.objects.filter(\n                    display_value_option=item,\n                    color_level=item.color_level_1,\n                    color_level_type=item.color_level_1_type,\n                    color=item.color_2,\n                ).count()\n                == 0\n            ):\n                dictc.append(\n                    dvco(\n                        display_value_option=item,\n                        color_level=item.color_level_1,\n                        color_level_type=item.color_level_1_type,\n                        color=item.color_2,\n                    )\n                )\n            countc += 1\n        elif item.color_type == 2:\n            item.color = item.color_1\n            if (\n                dvco.objects.filter(\n                    display_value_option=item,\n                    color_level=item.color_level_1,\n                    color_level_type=item.color_level_1_type,\n                    color=item.color_2,\n                ).count()\n                == 0\n            ):\n                dictc.append(\n                    dvco(\n                        display_value_option=item,\n                        color_level=item.color_level_1,\n                        color_level_type=item.color_level_1_type,\n                        color=item.color_2,\n                    )\n                )\n            if (\n                dvco.objects.filter(\n                    display_value_option=item,\n                    color_level=item.color_level_2,\n                    color_level_type=item.color_level_2_type,\n                    color=item.color_3,\n                ).count()\n                == 0\n            ):\n                dictc.append(\n                    dvco(\n                        display_value_option=item,\n                        color_level=item.color_level_2,\n                        color_level_type=item.color_level_2_type,\n                        color=item.color_3,\n                    )\n                )\n            countc += 2\n\n        elif item.color_type == 3:\n            item.color = item.color_1\n            if (\n                dvco.objects.filter(\n                    display_value_option=item,\n                    color_level=item.color_level_1,\n                    color_level_type=item.color_level_1_type,\n                    color=item.color_2,\n                ).count()\n                == 0\n            ):\n                dictc.append(\n                    dvco(\n                        display_value_option=item,\n                        color_level=item.color_level_1,\n                        color_level_type=item.color_level_1_type,\n                        color=item.color_2,\n                    )\n                )\n            item.gradient = True\n            item.gradient_higher_level = item.color_level_2\n            countc += 1\n\n        dict.append(item)\n        count += 1\n\n    logger.info(\"update %d DisplayValueOption\\n\" % count)\n    logger.info(\"create %d DisplayValueColorOption\\n\" % countc)\n    DisplayValueColorOption.objects.using(db_alias).bulk_create(dictc)\n    DisplayValueOption.objects.using(db_alias).bulk_update(\n        dict, [\"color_only\", \"color\", \"gradient\", \"gradient_higher_level\"]\n    )\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0100_device_instrument_handler\"),\n        (\"hmi\", \"0069_displayvalueoption_color_and_more\"),\n    ]\n\n    operations = [\n        migrations.RunPython(move_dvo, reverse_code=migrations.RunPython.noop),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0071_remove_displayvalueoption_color_1_and_more.py",
    "content": "# Generated by Django 4.2rc1 on 2023-03-30 10:23\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0070_move_displayvalueoptions\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_1\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_2\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_3\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_level_1\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_level_1_type\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_level_2\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_level_2_type\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"color_type\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"mode\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0072_alter_groupdisplaypermission_hmi_group.py",
    "content": "# Generated by Django 4.2 on 2023-04-06 14:40\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nfrom django.db.models.fields.related import OneToOneRel\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef create_empty_group_display_permission(apps, schema_editor):\n    gdp = apps.get_model(\"hmi\", \"GroupDisplayPermission\")\n\n    g = gdp.objects.get_or_create(hmi_group=None)\n    items = [\n        field\n        for field in gdp._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    for item in items:\n        item.related_model.objects.get_or_create(group_display_permission=g[0], type=1)\n\n    if g[1]:\n        logger.info(\"Empty GroupDisplayPermission Created\\n\")\n\n\ndef delete_empty_group_display_permission(apps, schema_editor):\n    gdp = apps.get_model(\"hmi\", \"GroupDisplayPermission\")\n\n    g = gdp.objects.filter(hmi_group=None).delete()\n\n    if g[1]:\n        logger.info(\"Empty GroupDisplayPermission Deleted\\n\")\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"auth\", \"0012_alter_user_first_name_max_length\"),\n        (\"hmi\", \"0071_remove_displayvalueoption_color_1_and_more\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"groupdisplaypermission\",\n            name=\"hmi_group\",\n            field=models.OneToOneField(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"auth.group\",\n            ),\n        ),\n        migrations.RunPython(\n            create_empty_group_display_permission, delete_empty_group_display_permission\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0073_alter_processflowdiagramitem_control_item.py",
    "content": "# Generated by Django 4.2.1 on 2023-06-05 07:57\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0072_alter_groupdisplaypermission_hmi_group\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"processflowdiagramitem\",\n            name=\"control_item\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.controlitem\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0074_alter_cssclass_css_class.py",
    "content": "# Generated by Django 4.2.1 on 2023-06-15 09:56\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0073_alter_processflowdiagramitem_control_item\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"cssclass\",\n            name=\"css_class\",\n            field=models.CharField(default=\"\", max_length=250),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0075_alter_processflowdiagram_url_height_and_more.py",
    "content": "# Generated by Django 4.2.1 on 2023-06-16 07:35\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0074_alter_cssclass_css_class\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"processflowdiagram\",\n            name=\"url_height\",\n            field=models.PositiveIntegerField(default=\"100\", editable=False, null=True),\n        ),\n        migrations.AlterField(\n            model_name=\"processflowdiagram\",\n            name=\"url_width\",\n            field=models.PositiveIntegerField(default=\"100\", editable=False, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0076_displayvalueoptiontemplate_transformdata_and_more.py",
    "content": "# Generated by Django 4.2.5 on 2023-11-16 16:05\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nimport pyscada.hmi.models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0075_alter_processflowdiagram_url_height_and_more\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"DisplayValueOptionTemplate\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"label\", models.CharField(max_length=40, unique=True)),\n                (\n                    \"template_name\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"The template to use for the control item. Must ends with '.html'.\",\n                        max_length=100,\n                        validators=[pyscada.hmi.models.validate_html],\n                    ),\n                ),\n                (\n                    \"js_files\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n                        max_length=400,\n                    ),\n                ),\n                (\n                    \"css_files\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n                        max_length=100,\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"TransformData\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"inline_model_name\", models.CharField(max_length=100)),\n                (\"short_name\", models.CharField(max_length=20)),\n                (\"js_function_name\", models.CharField(max_length=100)),\n                (\n                    \"js_files\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n                        max_length=100,\n                    ),\n                ),\n                (\n                    \"css_files\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n                        max_length=100,\n                    ),\n                ),\n                (\n                    \"need_historical_data\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"If true, will query the data corresponding of the date range picker.\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.RenameField(\n            model_name=\"displayvalueoption\",\n            old_name=\"name\",\n            new_name=\"title\",\n        ),\n        migrations.RemoveField(\n            model_name=\"displayvalueoption\",\n            name=\"type\",\n        ),\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"template\",\n            field=models.ForeignKey(\n                blank=True,\n                help_text=\"Select a custom template to use for this control item display value option.\",\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.displayvalueoptiontemplate\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"transform_data\",\n            field=models.ForeignKey(\n                blank=True,\n                help_text=\"Select a function to transform and manipulate data before displaying it.\",\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"hmi.transformdata\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0077_transformdatacountvalue.py",
    "content": "# Generated by Django 4.2.5 on 2023-11-22 10:58\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0076_displayvalueoptiontemplate_transformdata_and_more\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"TransformDataCountValue\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"value\", models.FloatField()),\n                (\n                    \"display_value_option\",\n                    models.OneToOneField(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"hmi.displayvalueoption\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0078_alter_theme_base_filename_alter_theme_view_filename.py",
    "content": "# Generated by Django 4.2.5 on 2024-02-22 14:37\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0077_transformdatacountvalue\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"theme\",\n            name=\"base_filename\",\n            field=models.CharField(\n                default=\"base\",\n                help_text=\"Enter the filename without '.html'\",\n                max_length=400,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"theme\",\n            name=\"view_filename\",\n            field=models.CharField(\n                default=\"view\",\n                help_text=\"Enter the filename without '.html'\",\n                max_length=400,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0079_displayvalueoption_from_timestamp_offset.py",
    "content": "# Generated by Django 5.0.3 on 2024-07-01 12:34\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0078_alter_theme_base_filename_alter_theme_view_filename\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"displayvalueoption\",\n            name=\"from_timestamp_offset\",\n            field=models.PositiveSmallIntegerField(\n                blank=True,\n                default=None,\n                help_text=\"Manage the value to be displayed if there is no data within the specified time interval.<br>If the field is empty, the last known data before the specified time interval will be displayed.<br>Set a value to add an offset in milliseconds before the start of the specified time interval.\",\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0080_view_default_time_delta.py",
    "content": "# Generated by Django 5.0.3 on 2024-07-02 07:53\n\nimport datetime\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"hmi\", \"0079_displayvalueoption_from_timestamp_offset\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"view\",\n            name=\"default_time_delta\",\n            field=models.DurationField(default=datetime.timedelta(seconds=7200)),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0081_groupdisplaypermission_unauthenticated_users.py",
    "content": "# Generated by Django 5.2.9 on 2026-01-13 08:55\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nfrom django.db.models.fields.related import OneToOneRel\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef create_unauthenticated_users_group_display_permission(apps, schema_editor):\n    gdp = apps.get_model(\"hmi\", \"GroupDisplayPermission\")\n\n    g = gdp.objects.get_or_create(hmi_group=None, unauthenticated_users=True)\n\n    if g[1]:\n        logger.info(\"Unauthenticated users GroupDisplayPermission Created\\n\")\n\n\ndef delete_unauthenticated_users_group_display_permission(apps, schema_editor):\n    gdp = apps.get_model(\"hmi\", \"GroupDisplayPermission\")\n\n    g = gdp.objects.filter(hmi_group=None, unauthenticated_users=True).delete()\n\n    if g[1]:\n        logger.info(\"Unauthenticated users GroupDisplayPermission Deleted\\n\")\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        (\"hmi\", \"0080_view_default_time_delta\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"groupdisplaypermission\",\n            name=\"unauthenticated_users\",\n            field=models.BooleanField(default=False),\n        ),\n        migrations.RunPython(\n            create_unauthenticated_users_group_display_permission, delete_unauthenticated_users_group_display_permission\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0082_externalview.py",
    "content": "# Generated by Django 5.2.10 on 2026-02-04 11:10\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('hmi', '0081_groupdisplaypermission_unauthenticated_users'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ExternalView',\n            fields=[\n                ('id', models.AutoField(primary_key=True, serialize=False)),\n                ('title', models.CharField(default='', max_length=400)),\n                ('description', models.TextField(default='', null=True, verbose_name='Description')),\n                ('url', models.URLField()),\n                ('logo', models.ImageField(blank=True, upload_to='img/', verbose_name='Overview Picture')),\n                ('visible', models.BooleanField(default=True)),\n                ('position', models.PositiveSmallIntegerField(default=0)),\n            ],\n            options={\n                'ordering': ['position'],\n            },\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/0083_externalviewgroupdisplaypermission.py",
    "content": "# Generated by Django 5.2.10 on 2026-02-04 17:01\n\nimport django.db.models.deletion\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('hmi', '0082_externalview'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ExternalViewGroupDisplayPermission',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('type', models.PositiveSmallIntegerField(choices=[(0, 'allow'), (1, 'exclude')], default=0, help_text='If allow: only selected items can be seen by the group.<br>If exclude: allows all items except the selected ones.')),\n                ('external_view', models.ManyToManyField(blank=True, related_name='groupdisplaypermission', to='hmi.externalview')),\n                ('group_display_permission', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='hmi.groupdisplaypermission')),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/hmi/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/hmi/models.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.models import Device, Variable, VariableProperty, Color\nfrom pyscada.utils import (\n    _get_objects_for_html as get_objects_for_html,\n    get_group_display_permission_list,\n)\n\nfrom django.template.exceptions import TemplateDoesNotExist, TemplateSyntaxError\nfrom django.db import models\nfrom django.contrib.auth.models import Group\nfrom django.template.loader import get_template\nfrom django.core.validators import MinValueValidator, MaxValueValidator\nfrom django.core.exceptions import ValidationError\nfrom django.utils.translation import gettext_lazy as _\nfrom django.db.models.query import QuerySet\nfrom django.db.utils import ProgrammingError\nfrom django.conf import settings\nfrom django.forms.models import BaseInlineFormSet\n\nfrom asgiref.sync import sync_to_async\nfrom datetime import timedelta\nfrom six import text_type\nimport traceback\nfrom uuid import uuid4\nimport logging\nimport json\n\nlogger = logging.getLogger(__name__)\n\n\ndef _delete_widget_content(sender, instance, **kwargs):\n    \"\"\"\n    delete the widget content instance when a WidgetContentModel is deleted\n    \"\"\"\n    if not issubclass(sender, WidgetContentModel):\n        return\n\n    # delete WidgetContent Entry\n    wcs = WidgetContent.objects.filter(\n        content_pk=instance.pk,\n        content_model=(\"%s\" % instance.__class__)\n        .replace(\"<class '\", \"\")\n        .replace(\"'>\", \"\"),\n    )\n    for wc in wcs:\n        logger.debug(\"delete wc %r\" % wc)\n        wc.delete()\n\n\ndef _create_widget_content(sender, instance, created=False, **kwargs):\n    \"\"\"\n    create a widget content instance when a WidgetContentModel is deleted\n    \"\"\"\n    if not issubclass(sender, WidgetContentModel):\n        return\n\n    # create a WidgetContent Entry\n    if created:\n        instance.create_widget_content_entry()\n    else:\n        instance.update_widget_content_entry()\n    return\n\n\n# raise a ValidationError if value not endswith .html or if template not found\ndef validate_html(value):\n    if not value.endswith(\".html\"):\n        raise ValidationError(\n            _(\"%(value)s should ends with '.html'\"),\n            params={\"value\": value},\n        )\n    try:\n        get_template(value)\n    except TemplateDoesNotExist:\n        raise ValidationError(\n            _(\"%(value)s template does not exist.\"),\n            params={\"value\": value},\n        )\n\n\n# return a list of files from a coma separated string\n# if :// not in the file name and the filename is not starting with /, add the static url\ndef get_js_or_css_set_from_str(self, field):\n    result = list()\n    if not hasattr(self, field):\n        logger.warning(f\"{field} not in {self}\")\n        return result\n    files = getattr(self, field)\n    for file in files.split(\",\"):\n        if file == \"\":\n            continue\n        if not file.startswith(\"/\") and \"://\" not in file:\n            STATIC_URL = (\n                str(settings.STATIC_URL)\n                if hasattr(settings, \"STATIC_URL\")\n                else \"/static/\"\n            )\n            result.append(STATIC_URL + file)\n        else:\n            result.append(file)\n    return result\n\n\nclass WidgetContentModel(models.Model):\n    @classmethod\n    def __init_subclass__(cls, **kwargs):\n        super(WidgetContentModel, cls).__init_subclass__(**kwargs)\n        models.signals.post_save.connect(_create_widget_content, sender=cls)\n        models.signals.pre_delete.connect(_delete_widget_content, sender=cls)\n\n    def gen_html(self, **kwargs):\n        \"\"\"\n\n        :return: main panel html and sidebar html as\n        \"\"\"\n        logger.info(f\"gen_html function of {self} model needs to be overwritten\")\n        return None, None, {}\n\n    def _get_objects_for_html(\n        self, list_to_append=None, obj=None, exclude_model_names=None\n    ):\n        if obj is None:\n            obj = self\n        return get_objects_for_html(\n            list_to_append=list_to_append,\n            obj=obj,\n            exclude_model_names=exclude_model_names,\n        )\n\n    def add_custom_fields_list(self, opts):\n        return opts\n\n    def add_exclude_fields_list(self, opts):\n        return opts\n\n    def create_widget_content_entry(self):\n        def fullname(o):\n            return o.__module__ + \".\" + o.__class__.__name__\n\n        wc = WidgetContent(\n            content_pk=self.pk, content_model=fullname(self), content_str=self.__str__()\n        )\n        wc.save()\n\n    def update_widget_content_entry(self):\n        def fullname(o):\n            return o.__module__ + \".\" + o.__class__.__name__\n\n        self.delete_duplicates()\n        wc = WidgetContent.objects.get(content_pk=self.pk, content_model=fullname(self))\n        wc.content_str = self.__str__()\n        wc.save()\n\n    def get_widget_content_entry(self):\n        def fullname(o):\n            return o.__module__ + \".\" + o.__class__.__name__\n\n        try:\n            return WidgetContent.objects.get(\n                content_pk=self.pk,\n                content_model=fullname(self),\n                content_str=self.__str__(),\n            )\n        except WidgetContent.DoesNotExist:\n            logger.warning(f\"Widget content not found for {self}\")\n            return None\n\n    def delete_duplicates(self):\n        for i in WidgetContent.objects.all():\n            c = WidgetContent.objects.filter(\n                content_pk=i.content_pk, content_model=i.content_model\n            ).count()\n            if c > 1:\n                logger.debug(\n                    \"%s WidgetContent for %s ( %s )\"\n                    % (c, i.content_model, i.content_pk)\n                )\n                for j in range(0, c - 1):\n                    WidgetContent.objects.filter(\n                        content_pk=i.content_pk, content_model=i.content_model\n                    )[j].delete()\n\n    def check_visible_object(self, visible_models_lists):\n        visible_model_list_str = f\"visible_{self._meta.object_name.lower()}_list\"\n        if visible_model_list_str in visible_models_lists:\n            visible_list = visible_models_lists[visible_model_list_str]\n        else:\n            return True\n        if type(visible_list) == QuerySet and self.pk in visible_list:\n            return True\n        return False\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        logger.info(f\"data_objects function of {self} model should be overwritten\")\n        return {}\n\n    class Meta:\n        abstract = True\n\n\nclass Theme(models.Model):\n    name = models.CharField(max_length=400)\n    base_filename = models.CharField(\n        max_length=400,\n        default=\"base\",\n        help_text=\"Enter the filename without '.html'\",\n    )\n    view_filename = models.CharField(\n        max_length=400,\n        default=\"view\",\n        help_text=\"Enter the filename without '.html'\",\n    )\n\n    def __str__(self):\n        return self.name\n\n    def check_all_themes(self):\n        # Delete theme with missing template file\n        for theme in Theme.objects.all():\n            try:\n                get_template(theme.base_filename + \".html\")\n                get_template(theme.view_filename + \".html\")\n            except TemplateDoesNotExist as e:\n                logger.info(f\"Template {e} not found. {self} will be delete.\")\n                theme.delete()\n            else:\n                try:\n                    get_template(theme.view_filename + \".html\").render(\n                        {\"base_html\": theme.base_filename + \".html\"}\n                    )\n                except TemplateDoesNotExist as e:\n                    logger.info(\n                        f\"Template {e} used in the view as base_html not found. {self} will be delete.\"\n                    )\n                    theme.delete()\n                except TemplateSyntaxError as e:\n                    logger.info(e)\n                except AttributeError:\n                    pass\n\n\nclass ControlElementOption(models.Model):\n    name = models.CharField(max_length=400)\n    placeholder = models.CharField(max_length=30, default=\"Enter a value\")\n    dropdown = models.BooleanField(\n        default=False,\n        help_text=\"Show control item as dropdown. The variable must have a dictionary\",\n    )\n    empty_dropdown_value = models.BooleanField(\n        default=False,\n        help_text=\"If true, show placeholder as \" \"default unelectable text\",\n    )\n\n    def __str__(self):\n        return self.name\n\n    def get_js(self):\n        files = list()\n        return files\n\n    def get_css(self):\n        files = list()\n        return files\n\n    def get_daterangepicker(self):\n        return False\n\n    def get_timeline(self):\n        return False\n\n\nclass TransformData(models.Model):\n    inline_model_name = models.CharField(max_length=100)\n    short_name = models.CharField(max_length=20)\n    js_function_name = models.CharField(max_length=100)\n    js_files = models.TextField(\n        max_length=100,\n        blank=True,\n        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n    )\n    css_files = models.TextField(\n        max_length=100,\n        blank=True,\n        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n    )\n    need_historical_data = models.BooleanField(\n        default=False,\n        help_text=\"If true, will query the data corresponding of the date range picker.\",\n    )\n\n    def __str__(self):\n        return self.short_name\n\n    def get_js(self):\n        return get_js_or_css_set_from_str(self, \"js_files\")\n\n    def get_css(self):\n        return get_js_or_css_set_from_str(self, \"css_files\")\n\n\nclass DisplayValueOptionTemplate(models.Model):\n    label = models.CharField(max_length=40, unique=True)\n    template_name = models.CharField(\n        max_length=100,\n        blank=True,\n        help_text=\"The template to use for the control item. Must ends with '.html'.\",\n        validators=[validate_html],\n    )\n    js_files = models.TextField(\n        max_length=400,\n        blank=True,\n        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n    )\n    css_files = models.TextField(\n        max_length=100,\n        blank=True,\n        help_text=\"for a file in static, start without /, like : pyscada/js/pyscada/file.js<br>for a local file not in static, start with /, like : /test/file.js<br>for a remote file, indicate the url<br>you can provide a coma separated list\",\n    )\n\n    def __str__(self):\n        return self.label\n\n    def get_js(self):\n        return get_js_or_css_set_from_str(self, \"js_files\")\n\n    def get_css(self):\n        return get_js_or_css_set_from_str(self, \"css_files\")\n\n    # return the template name or template_not_found.html if the template is not found\n    def get_template_name(self):\n        try:\n            validate_html(self.template_name)\n            return self.template_name\n        except ValidationError as e:\n            logger.warning(e)\n            return \"template_not_found.html\"\n\n\nclass DisplayValueOption(models.Model):\n    title = models.CharField(max_length=400)\n    template = models.ForeignKey(\n        DisplayValueOptionTemplate,\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"Select a custom template to use for this control item display value option.\",\n    )\n\n    color = models.ForeignKey(\n        Color,\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"Default color if no level defined, can be null.<br>\"\n        \"Color < or =< first level, if a level is defined.\",\n    )\n    color_only = models.BooleanField(\n        default=False, help_text=\"If true, will not display the value.\"\n    )\n    gradient = models.BooleanField(\n        default=False, help_text=\"Need 1 color option to be defined.\"\n    )\n    gradient_higher_level = models.FloatField(\n        default=0, help_text=\"Color defined above will be used for this level.\"\n    )\n\n    timestamp_conversion_choices = (\n        (0, \"None\"),\n        (1, \"Timestamp in milliseconds to local date\"),\n        (2, \"Timestamp in milliseconds to local time\"),\n        (3, \"Timestamp in milliseconds to local date and time\"),\n        (4, \"Timestamp in seconds to local date\"),\n        (5, \"Timestamp in seconds to local time\"),\n        (6, \"Timestamp in seconds to local date and time\"),\n    )\n    timestamp_conversion = models.PositiveSmallIntegerField(\n        default=0, choices=timestamp_conversion_choices\n    )\n\n    transform_data = models.ForeignKey(\n        TransformData,\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"Select a function to transform and manipulate data before displaying it.\",\n    )\n\n    from_timestamp_offset = models.PositiveSmallIntegerField(\n        default=None,\n        blank=True,\n        null=True,\n        help_text=\"Manage the value to be displayed if there is no data within the specified time interval.<br>\"\n        \"If the field is empty, the last known data before the specified time interval will be displayed.<br>\"\n        \"Set a value to add an offset in milliseconds before the start of the specified time interval.\",\n    )\n\n    def __str__(self):\n        return self.title\n\n    def _get_objects_for_html(\n        self, list_to_append=None, obj=None, exclude_model_names=None\n    ):\n        list_to_append = get_objects_for_html(list_to_append, self, exclude_model_names)\n        for item in self.displayvaluecoloroption_set.all():\n            list_to_append = get_objects_for_html(\n                list_to_append, item, [\"display_value_option\"]\n            )\n        return list_to_append\n\n    def get_js(self):\n        files = list()\n        if self.transform_data is not None:\n            js_files = self.transform_data.get_js()\n            if type(js_files) == list:\n                files += js_files\n            elif type(js_files) == str:\n                files.append(js_files)\n        if self.template is not None:\n            js_files = self.template.get_js()\n            if type(js_files) == list:\n                files += js_files\n            elif type(js_files) == str:\n                files.append(js_files)\n        return files\n\n    def get_css(self):\n        files = list()\n        if self.transform_data is not None:\n            css_files = self.transform_data.get_css()\n            if type(css_files) == list:\n                files += css_files\n            elif type(css_files) == str:\n                files.append(css_files)\n        if self.template is not None:\n            css_files = self.template.get_css()\n            if type(css_files) == list:\n                files += css_files\n            elif type(css_files) == str:\n                files.append(css_files)\n        return files\n\n    def get_daterangepicker(self):\n        if self.transform_data is not None:\n            return self.transform_data.need_historical_data\n\n    def get_timeline(self):\n        if self.transform_data is not None:\n            return self.transform_data.need_historical_data\n\n\nclass DisplayValueColorOption(models.Model):\n    display_value_option = models.ForeignKey(\n        DisplayValueOption, on_delete=models.CASCADE\n    )\n    color_level = models.FloatField()\n    color_level_type_choices = (\n        (0, \"color =< level\"),\n        (1, \"color < level\"),\n    )\n    color_level_type = models.PositiveSmallIntegerField(\n        default=0, choices=color_level_type_choices\n    )\n    color = models.ForeignKey(\n        Color,\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"Let blank for no color below the selected level.\",\n    )\n\n    class Meta:\n        constraints = [\n            models.UniqueConstraint(\n                fields=[\"display_value_option\", \"color_level\", \"color_level_type\"],\n                name=\"unique_display_value_color_option\",\n            )\n        ]\n        ordering = [\"color_level\", \"-color_level_type\"]\n\n\nclass TransformDataCountValue(models.Model):\n    display_value_option = models.OneToOneField(\n        DisplayValueOption, on_delete=models.CASCADE\n    )\n    value = models.FloatField()  # the value to count\n\n    class FormSet(BaseInlineFormSet):\n        def clean(self):\n            super().clean()\n            # get the formset model name, here TransformDataCountValue\n            class_name = self.model.__name__\n            # check if a transform data has been selected in the admin and if a transform data exist with this id\n            if (\n                self.data[\"transform_data\"] != \"\"\n                and TransformData.objects.get(id=self.data[\"transform_data\"])\n                is not None\n            ):\n                # get the selected transform data inline model name\n                transform_data_name = TransformData.objects.get(\n                    id=self.data[\"transform_data\"]\n                ).inline_model_name\n                # if the selected transform data inline model name is this model, check if the value field has been filled in\n                # otherwhise raise a ValidationError\n                if (\n                    class_name == transform_data_name\n                    and self.data[transform_data_name.lower() + \"-0-value\"] == \"\"\n                    and self.data[\"transform_data\"] != \"\"\n                ):\n                    raise ValidationError(\"Value is required.\")\n\n\nclass ControlItem(models.Model):\n    id = models.AutoField(primary_key=True)\n    label = models.CharField(max_length=400, default=\"\")\n    position = models.PositiveSmallIntegerField(default=0)\n    type_choices = (\n        (0, \"Control Element\"),\n        (1, \"Display Value\"),\n    )\n    type = models.PositiveSmallIntegerField(default=0, choices=type_choices)\n    variable = models.ForeignKey(\n        Variable, null=True, blank=True, on_delete=models.CASCADE\n    )\n    variable_property = models.ForeignKey(\n        VariableProperty, null=True, blank=True, on_delete=models.CASCADE\n    )\n    display_value_options = models.ForeignKey(\n        DisplayValueOption, null=True, blank=True, on_delete=models.SET_NULL\n    )\n    control_element_options = models.ForeignKey(\n        ControlElementOption, null=True, blank=True, on_delete=models.SET_NULL\n    )\n\n    class Meta:\n        ordering = [\"position\"]\n\n    def __str__(self):\n        type_str = \"\"\n        for i in self.type_choices:\n            if i[0] == self.type:\n                type_str = i[1]\n\n        if self.variable_property:\n            return (\n                self.id.__str__()\n                + \"-\"\n                + type_str.replace(\" \", \"_\")\n                + \"-\"\n                + self.label.replace(\" \", \"_\")\n                + \"-\"\n                + self.variable_property.name.replace(\" \", \"_\")\n            )\n        elif self.variable:\n            return (\n                self.id.__str__()\n                + \"-\"\n                + type_str.replace(\" \", \"_\")\n                + \"-\"\n                + \"-\"\n                + self.label.replace(\" \", \"_\")\n                + \"-\"\n                + \"-\"\n                + self.variable.name.replace(\" \", \"_\")\n            )\n        else:\n            return \"Empty control item with id \" + self.id.__str__()\n\n    def web_id(self):\n        if self.variable_property:\n            return (\n                \"controlitem-\"\n                + self.id.__str__()\n                + \"-\"\n                + self.variable_property.id.__str__()\n            )\n        elif self.variable:\n            return \"controlitem-\" + self.id.__str__() + \"-\" + self.variable.id.__str__()\n\n    def web_class_str(self):\n        if self.variable_property:\n            return \"prop-%d\" % self.variable_property_id\n        elif self.variable:\n            return \"var-%d\" % self.variable_id\n\n    def active(self):\n        if self.variable_property:\n            return (\n                self.variable_property.variable.active\n                and self.variable_property.variable.device.active\n            )\n        elif self.variable:\n            return self.variable.active and self.variable.device.active\n        return False\n\n    def key(self):\n        if self.variable_property:\n            return self.variable_property_id\n        elif self.variable:\n            return self.variable_id\n\n    def name(self):\n        if self.variable_property:\n            return self.variable_property.name\n        elif self.variable:\n            return self.variable.name\n\n    def item_type(self):\n        if self.variable_property:\n            return \"variable_property\"\n        elif self.variable:\n            return \"variable\"\n\n    def unit(self):\n        if self.variable_property:\n            if self.variable_property.unit is not None:\n                return self.variable_property.unit.unit\n            else:\n                return \"\"\n        elif self.variable:\n            return self.variable.unit.unit\n\n    def min(self):\n        if self.variable_property:\n            return self.variable_property.value_min\n        elif self.variable:\n            return self.variable.value_min\n\n    def max(self):\n        if self.variable_property:\n            return self.variable_property.value_max\n        elif self.variable:\n            return self.variable.value_max\n\n    def value(self):\n        if self.variable_property:\n            return self.variable_property.value()\n        elif self.variable:\n            self.variable.query_prev_value()\n            return self.variable.prev_value\n\n    def value_class(self):\n        if self.variable_property:\n            return self.variable_property.value_class\n        elif self.variable:\n            return self.variable.value_class\n\n    def min_type(self):\n        if self.variable_property:\n            return self.variable_property.min_type\n        elif self.variable:\n            return self.variable.min_type\n\n    def max_type(self):\n        if self.variable_property:\n            return self.variable_property.max_type\n        elif self.variable:\n            return self.variable.max_type\n\n    def device(self):\n        if self.variable_property:\n            return self.variable_property.variable.device\n        elif self.variable:\n            return self.variable.device\n\n    def threshold_values(self):\n        tv = dict()\n        if (\n            self.display_value_options is not None\n            and self.display_value_options.color is not None\n        ):\n            if len(self.display_value_options.displayvaluecoloroption_set.all()) == 0:\n                tv[\"max\"] = self.display_value_options.color.color_code()\n            else:\n                prev_color = self.display_value_options.color\n                for (\n                    dvco\n                ) in self.display_value_options.displayvaluecoloroption_set.all():\n                    tv[dvco.color_level] = prev_color.color_code()\n                    prev_color = dvco.color\n                tv[\"max\"] = prev_color.color_code()\n        return json.dumps(tv)\n\n    def gauge_params(self):\n        d = dict()\n        d[\"min\"] = self.min()\n        d[\"max\"] = self.max()\n        d[\"threshold_values\"] = self.threshold_values()\n        return json.dumps(d)\n\n    def dictionary(self):\n        if self.variable_property:\n            return self.variable_property.dictionary\n        elif self.variable:\n            return self.variable.dictionary\n\n    def _get_objects_for_html(\n        self, list_to_append=None, obj=None, exclude_model_names=None\n    ):\n        list_to_append = get_objects_for_html(list_to_append, self, exclude_model_names)\n        return list_to_append\n\n    def get_js(self):\n        files = list()\n        if self.type == 1 and self.display_value_options is not None:\n            files += self.display_value_options.get_js()\n        if self.type == 0 and self.control_element_options is not None:\n            files += self.control_element_options.get_js()\n        return files\n\n    def get_css(self):\n        files = list()\n        if self.type == 1 and self.display_value_options is not None:\n            files += self.display_value_options.get_css()\n        if self.type == 0 and self.control_element_options is not None:\n            files += self.control_element_options.get_css()\n        return files\n\n    def get_daterangepicker(self):\n        if self.type == 0 and self.control_element_options is not None:\n            return self.control_element_options.get_daterangepicker()\n        elif self.type == 1 and self.display_value_options is not None:\n            return self.display_value_options.get_daterangepicker()\n        return False\n\n    def get_timeline(self):\n        if self.type == 0 and self.control_element_options is not None:\n            return self.control_element_options.get_timeline()\n        elif self.type == 1 and self.display_value_options is not None:\n            return self.display_value_options.get_timeline()\n        return False\n\n    def readable(self):\n        if self.variable_property:\n            return self.variable_property.variable.readable\n        elif self.variable:\n            return self.variable.readable\n\n\nclass Chart(WidgetContentModel):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    x_axis_label = models.CharField(max_length=400, default=\"\", blank=True)\n    x_axis_var = models.ForeignKey(\n        Variable,\n        default=None,\n        related_name=\"x_axis_var\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n    )\n    x_axis_ticks = models.PositiveSmallIntegerField(default=6)\n    x_axis_linlog = models.BooleanField(\n        default=False, help_text=\"False->Lin / True->Log\"\n    )\n\n    def __str__(self):\n        return text_type(str(self.id) + \": \" + self.title)\n\n    def visible(self):\n        return True\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        objects = {}\n        for axe in self.chartaxis_set.all():\n            for item in axe.variables.all():\n                if \"variable\" not in objects:\n                    objects[\"variable\"] = []\n                objects[\"variable\"].append(item.pk)\n        return objects\n\n    def gen_html(self, **kwargs):\n        \"\"\"\n\n        :return: main panel html and sidebar html as\n        \"\"\"\n        widget_pk = kwargs[\"widget_pk\"] if \"widget_pk\" in kwargs else 0\n        widget_extra_css_class = (\n            kwargs[\"widget_extra_css_class\"]\n            if \"widget_extra_css_class\" in kwargs\n            else \"\"\n        )\n        main_template = get_template(\"chart.html\")\n        sidebar_template = get_template(\"chart_legend.html\")\n        main_content = None\n        sidebar_content = None\n        if \"visible_objects_lists\" in kwargs and self.check_visible_object(\n            kwargs[\"visible_objects_lists\"]\n        ):\n            main_content = main_template.render(\n                dict(\n                    chart=self,\n                    widget_pk=widget_pk,\n                    widget_extra_css_class=widget_extra_css_class,\n                )\n            )\n            sidebar_content = sidebar_template.render(\n                dict(\n                    chart=self,\n                    widget_pk=widget_pk,\n                    widget_extra_css_class=widget_extra_css_class,\n                )\n            )\n        opts = dict()\n        opts[\"show_daterangepicker\"] = True\n        opts[\"show_timeline\"] = True\n        opts[\"flot\"] = True\n        # opts[\"object_config_list\"] = set()\n        # opts[\"object_config_list\"].update(self._get_objects_for_html())\n        return main_content, sidebar_content, opts\n\n    def _get_objects_for_html(\n        self, list_to_append=None, obj=None, exclude_model_names=None\n    ):\n        list_to_append = super()._get_objects_for_html(\n            list_to_append, obj, exclude_model_names\n        )\n        if obj is None:\n            for axis in self.chartaxis_set.all():\n                list_to_append = super()._get_objects_for_html(\n                    list_to_append, axis, [\"chart\"]\n                )\n\n        return list_to_append\n\n\nclass ChartAxis(models.Model):\n    label = models.CharField(max_length=400, default=\"\", blank=True)\n    position_choices = (\n        (0, \"left\"),\n        (1, \"right\"),\n    )\n    position = models.PositiveSmallIntegerField(default=0, choices=position_choices)\n    min = models.FloatField(blank=True, null=True)\n    max = models.FloatField(blank=True, null=True)\n    show_bars = models.BooleanField(default=False, help_text=\"Show bars\")\n    show_plot_points = models.BooleanField(\n        default=False, help_text=\"Show the plots points\"\n    )\n    show_plot_lines_choices = (\n        (0, \"No\"),\n        (1, \"Yes\"),\n        (2, \"Yes as steps\"),\n    )\n    show_plot_lines = models.PositiveSmallIntegerField(\n        default=2, help_text=\"Show the plot lines\", choices=show_plot_lines_choices\n    )\n    stack = models.BooleanField(\n        default=False, help_text=\"Stack all variables of this axis\"\n    )\n    fill = models.BooleanField(\n        default=False, help_text=\"Fill all variables of this axis\"\n    )\n    variables = models.ManyToManyField(Variable)\n    chart = models.ForeignKey(Chart, on_delete=models.CASCADE)\n\n    class Meta:\n        verbose_name = \"Y Axis\"\n        verbose_name_plural = \"Y Axis\"\n\n\nclass Pie(WidgetContentModel):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    radius = models.PositiveSmallIntegerField(\n        default=100, validators=[MaxValueValidator(100), MinValueValidator(1)]\n    )\n    innerRadius = models.PositiveSmallIntegerField(\n        default=0, validators=[MaxValueValidator(100), MinValueValidator(0)]\n    )\n    variables = models.ManyToManyField(Variable, blank=True)\n    variable_properties = models.ManyToManyField(VariableProperty, blank=True)\n\n    def __str__(self):\n        return text_type(str(self.id) + \": \" + self.title)\n\n    def visible(self):\n        return True\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        objects = {}\n        for item in self.variables.all():\n            if \"variable\" not in objects:\n                objects[\"variable\"] = []\n            objects[\"variable\"].append(item.pk)\n        for item in self.variable_properties.all():\n            if \"variable_property\" not in objects:\n                objects[\"variable_property\"] = []\n            objects[\"variable_property\"].append(item.pk)\n        return objects\n\n    def gen_html(self, **kwargs):\n        \"\"\"\n        :return: main panel html and sidebar html as\n        \"\"\"\n        widget_pk = kwargs[\"widget_pk\"] if \"widget_pk\" in kwargs else 0\n        widget_extra_css_class = (\n            kwargs[\"widget_extra_css_class\"]\n            if \"widget_extra_css_class\" in kwargs\n            else \"\"\n        )\n        main_template = get_template(\"pie.html\")\n        sidebar_template = get_template(\"chart_legend.html\")\n        main_content = None\n        sidebar_content = None\n        if \"visible_objects_lists\" in kwargs and self.check_visible_object(\n            kwargs[\"visible_objects_lists\"]\n        ):\n            main_content = main_template.render(\n                dict(\n                    pie=self,\n                    widget_pk=widget_pk,\n                    widget_extra_css_class=widget_extra_css_class,\n                )\n            )\n            sidebar_content = sidebar_template.render(\n                dict(\n                    chart=self,\n                    pie=1,\n                    widget_pk=widget_pk,\n                    widget_extra_css_class=widget_extra_css_class,\n                )\n            )\n        opts = dict()\n        opts[\"flot\"] = True\n        opts[\"topbar\"] = True\n        return main_content, sidebar_content, opts\n\n\nclass Form(models.Model):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    button = models.CharField(max_length=50, default=\"Ok\")\n    control_items = models.ManyToManyField(\n        ControlItem,\n        related_name=\"control_items_form\",\n        limit_choices_to={\"type\": \"0\"},\n        blank=True,\n    )\n    hidden_control_items_to_true = models.ManyToManyField(\n        ControlItem,\n        related_name=\"hidden_control_items_form\",\n        limit_choices_to={\"type\": \"0\"},\n        blank=True,\n    )\n\n    def __str__(self):\n        return text_type(str(self.id) + \": \" + self.title)\n\n    def visible(self):\n        return True\n\n    def web_id(self):\n        return \"form-\" + self.id.__str__()\n\n    def control_items_list(self):\n        return [item.pk for item in self.control_items]\n\n    def hidden_control_items_to_true_list(self):\n        return [item.pk for item in self.hidden_control_items_to_true]\n\n    def get_js(self):\n        files = list()\n        for item in self.control_items.all():\n            files += item.get_js()\n        for item in self.hidden_control_items_to_true.all():\n            files += item.get_js()\n        return files\n\n    def get_css(self):\n        files = list()\n        for item in self.control_items.all():\n            files += item.get_css()\n        for item in self.hidden_control_items_to_true.all():\n            files += item.get_css()\n        return files\n\n    def get_daterangepicker(self):\n        get_daterangepicker = False\n        for item in self.control_items.all():\n            get_daterangepicker = get_daterangepicker or item.get_daterangepicker()\n        for item in self.hidden_control_items_to_true.all():\n            get_daterangepicker = get_daterangepicker or item.get_daterangepicker()\n        return get_daterangepicker\n\n    def get_timeline(self):\n        get_timeline = False\n        for item in self.control_items.all():\n            get_timeline = get_timeline or item.get_timeline()\n        for item in self.hidden_control_items_to_true.all():\n            get_timeline = get_timeline or item.get_timeline()\n        return get_timeline\n\n\nclass Page(models.Model):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    link_title = models.SlugField(max_length=80, default=\"\")\n    position = models.PositiveSmallIntegerField(default=0)\n\n    class Meta:\n        ordering = [\"position\"]\n\n    def __str__(self):\n        return self.link_title.replace(\" \", \"_\")\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        objects = {}\n        groups = user.groups.all() if user is not None else []\n        authenticated = True if user is not None else False\n        for w in get_group_display_permission_list(\n            self.widget_set.filter(visible=True), groups, authenticated\n        ):\n            wdo = w.data_objects(user)\n            for o in wdo.keys():\n                if o not in objects:\n                    objects[o] = []\n                objects[o] = list(set(objects[o] + wdo.get(o, [])))\n        return objects\n\n\nclass ControlPanel(WidgetContentModel):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    items = models.ManyToManyField(ControlItem, blank=True)\n    forms = models.ManyToManyField(Form, blank=True)\n\n    def __str__(self):\n        return str(self.id) + \": \" + self.title\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        objects = {}\n        groups = user.groups.all() if user is not None else []\n        authenticated = True if user is not None else False\n        for ci in get_group_display_permission_list(\n            self.items.all(), groups, authenticated\n        ):\n            if ci.item_type() not in objects:\n                objects[ci.item_type()] = []\n            objects[ci.item_type()].append(ci.key())\n            if ci.type == 0:\n                # accessible in writing\n                if f\"{ci.item_type()}_write\" not in objects:\n                    objects[f\"{ci.item_type()}_write\"] = []\n                objects[f\"{ci.item_type()}_write\"].append(ci.key())\n        for form in get_group_display_permission_list(\n            self.forms.all(), groups, authenticated\n        ):\n            for ci in get_group_display_permission_list(\n                form.control_items.all(), groups, authenticated\n            ):\n                if ci.item_type() not in objects:\n                    objects[ci.item_type()] = []\n                objects[ci.item_type()].append(ci.key())\n                if ci.type == 0:\n                    # accessible in writing\n                    if f\"{ci.item_type()}_write\" not in objects:\n                        objects[f\"{ci.item_type()}_write\"] = []\n                    objects[f\"{ci.item_type()}_write\"].append(ci.key())\n            for ci in get_group_display_permission_list(\n                form.hidden_control_items_to_true.all(), groups, authenticated\n            ):\n                if ci.item_type() not in objects:\n                    objects[ci.item_type()] = []\n                objects[ci.item_type()].append(ci.key())\n                if ci.type == 0:\n                    # accessible in writing\n                    if f\"{ci.item_type()}_write\" not in objects:\n                        objects[f\"{ci.item_type()}_write\"] = []\n                    objects[f\"{ci.item_type()}_write\"].append(ci.key())\n        return objects\n\n    def gen_html(self, **kwargs):\n        \"\"\"\n\n        :return: main panel html and sidebar html as\n        \"\"\"\n        widget_pk = kwargs[\"widget_pk\"] if \"widget_pk\" in kwargs else 0\n        widget_extra_css_class = (\n            kwargs[\"widget_extra_css_class\"]\n            if \"widget_extra_css_class\" in kwargs\n            else \"\"\n        )\n        main_template = get_template(\"control_panel.html\")\n        main_content = None\n        if \"visible_objects_lists\" in kwargs and self.check_visible_object(\n            kwargs[\"visible_objects_lists\"]\n        ):\n            visible_element_list = (\n                kwargs[\"visible_objects_lists\"][\"visible_controlitem_list\"]\n                if \"visible_controlitem_list\" in kwargs[\"visible_objects_lists\"]\n                else []\n            )\n            visible_form_list = (\n                kwargs[\"visible_objects_lists\"][\"visible_form_list\"]\n                if \"visible_form_list\" in kwargs[\"visible_objects_lists\"]\n                else []\n            )\n            main_content = main_template.render(\n                dict(\n                    control_panel=self,\n                    visible_control_element_list=visible_element_list,\n                    visible_form_list=visible_form_list,\n                    uuid=uuid4().hex,\n                    widget_pk=widget_pk,\n                    widget_extra_css_class=widget_extra_css_class,\n                )\n            )\n        sidebar_content = None\n        opts = dict()\n        opts[\"flot\"] = False\n        opts[\"javascript_files_list\"] = list()\n        opts[\"css_files_list\"] = list()\n        opts[\"show_daterangepicker\"] = False\n        opts[\"show_timeline\"] = False\n        for item in self.items.all():\n            opts[\"javascript_files_list\"] += item.get_js()\n            opts[\"css_files_list\"] += item.get_css()\n            opts[\"show_daterangepicker\"] = (\n                opts[\"show_daterangepicker\"] or item.get_daterangepicker()\n            )\n            opts[\"show_timeline\"] = opts[\"show_timeline\"] or item.get_timeline()\n        for form in self.forms.all():\n            opts[\"javascript_files_list\"] += form.get_js()\n            opts[\"css_files_list\"] += form.get_css()\n            opts[\"show_daterangepicker\"] = (\n                opts[\"show_daterangepicker\"] or form.get_daterangepicker()\n            )\n            opts[\"show_timeline\"] = opts[\"show_timeline\"] or form.get_timeline()\n        # opts[\"object_config_list\"] = set()\n        # opts[\"object_config_list\"].update(self._get_objects_for_html())\n        # opts = self.add_custom_fields_list(opts)\n        return main_content, sidebar_content, opts\n\n    def add_custom_fields_list(self, opts):\n        if type(opts) == dict:\n            if \"custom_fields_list\" not in opts:\n                opts[\"custom_fields_list\"] = dict()\n            opts[\"custom_fields_list\"][\"variable\"] = [\n                {\"name\": \"refresh-requested-timestamp\", \"value\": \"\"},\n                {\"name\": \"value-timestamp\", \"value\": \"\"},\n            ]\n            opts[\"custom_fields_list\"][\"variableproperty\"] = [\n                {\"name\": \"refresh-requested-timestamp\", \"value\": \"\"},\n                {\"name\": \"value-timestamp\", \"value\": \"\"},\n            ]\n        return opts\n\n\nclass CustomHTMLPanel(WidgetContentModel):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\", blank=True)\n    html = models.TextField()\n    variables = models.ManyToManyField(Variable, blank=True)\n    variable_properties = models.ManyToManyField(VariableProperty, blank=True)\n\n    def __str__(self):\n        return str(self.id) + \": \" + self.title\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        objects = {}\n        for variable in self.variables.all():\n            if \"variable\" not in objects:\n                objects[\"variable\"] = []\n            objects[\"variable\"].append(variable.pk)\n        for variable_property in self.variable_properties.all():\n            if \"variable_property\" not in objects:\n                objects[\"variable_property\"] = []\n            objects[\"variable_property\"].append(variable_property.pk)\n        return objects\n\n    def gen_html(self, **kwargs):\n        \"\"\"\n\n        :return: main panel html and sidebar html as\n        \"\"\"\n        widget_pk = kwargs[\"widget_pk\"] if \"widget_pk\" in kwargs else 0\n        widget_extra_css_class = (\n            kwargs[\"widget_extra_css_class\"]\n            if \"widget_extra_css_class\" in kwargs\n            else \"\"\n        )\n        main_template = get_template(\"custom_html_panel.html\")\n        main_content = None\n        if \"visible_objects_lists\" in kwargs and self.check_visible_object(\n            kwargs[\"visible_objects_lists\"]\n        ):\n            main_content = main_template.render(\n                dict(\n                    custom_html_panel=self,\n                    widget_pk=widget_pk,\n                    widget_extra_css_class=widget_extra_css_class,\n                )\n            )\n        sidebar_content = None\n        opts = dict()\n        # opts[\"object_config_list\"] = set()\n        # opts[\"object_config_list\"].update(self._get_objects_for_html())\n        return main_content, sidebar_content, opts\n\n\nclass ProcessFlowDiagramItem(models.Model):\n    id = models.AutoField(primary_key=True)\n    control_item = models.ForeignKey(\n        ControlItem, default=None, blank=True, null=True, on_delete=models.CASCADE\n    )\n    top = models.PositiveIntegerField(blank=True, default=0)\n    left = models.PositiveIntegerField(blank=True, default=0)\n    font_size = models.PositiveSmallIntegerField(default=14)\n    width = models.PositiveIntegerField(blank=True, default=0)\n    height = models.PositiveIntegerField(blank=True, default=0)\n    visible = models.BooleanField(default=True)\n\n    def __str__(self):\n        if self.control_item:\n            if self.control_item.label != \"\":\n                return str(self.id) + \": \" + self.control_item.label\n            else:\n                return str(self.id) + \": \" + self.control_item.name\n        else:\n            return str(self.id)\n\n\nclass ProcessFlowDiagram(WidgetContentModel):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\", blank=True)\n    background_image = models.ImageField(\n        upload_to=\"img/\",\n        height_field=\"url_height\",\n        width_field=\"url_width\",\n        verbose_name=\"background image\",\n        blank=True,\n    )\n    type_choices = (\n        (0, \"HTML\"),\n        (1, \"SVG\"),\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=type_choices,\n        help_text=\"HTML is not responsive and can display control element<br>\"\n        \"SVG is responsive and cannot display control element\",\n    )\n    process_flow_diagram_items = models.ManyToManyField(\n        ProcessFlowDiagramItem, blank=True\n    )\n    url_height = models.PositiveIntegerField(editable=False, default=\"100\", null=True)\n    url_width = models.PositiveIntegerField(editable=False, default=\"100\", null=True)\n\n    def __str__(self):\n        if self.title:\n            return str(self.id) + \": \" + self.title\n        else:\n            return str(self.id) + \": \" + self.background_image.name\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        objects = {}\n        groups = user.groups.all() if user is not None else []\n        authenticated = True if user is not None else False\n        for pfdi in self.process_flow_diagram_items.all():\n            if (\n                pfdi.control_item is not None\n                and pfdi.visible\n                and pfdi.control_item\n                in get_group_display_permission_list(\n                    ControlItem.objects.all(), groups, authenticated\n                )\n            ):\n                if pfdi.control_item.item_type() not in objects:\n                    objects[pfdi.control_item.item_type()] = []\n                objects[pfdi.control_item.item_type()].append(pfdi.control_item.key())\n                if pfdi.control_item.type == 0:\n                    # accessible in writing\n                    if f\"{pfdi.control_item.item_type()}_write\" not in objects:\n                        objects[f\"{pfdi.control_item.item_type()}_write\"] = []\n                    objects[f\"{pfdi.control_item.item_type()}_write\"].append(\n                        pfdi.control_item.key()\n                    )\n        return objects\n\n    def gen_html(self, **kwargs):\n        \"\"\"\n\n        :return: main panel html and sidebar html as\n        \"\"\"\n        main_template = get_template(\"process_flow_diagram.html\")\n        try:\n            widget_pk = kwargs[\"widget_pk\"] if \"widget_pk\" in kwargs else 0\n            widget_extra_css_class = (\n                kwargs[\"widget_extra_css_class\"]\n                if \"widget_extra_css_class\" in kwargs\n                else \"\"\n            )\n            main_content = None\n            if \"visible_objects_lists\" in kwargs and self.check_visible_object(\n                kwargs[\"visible_objects_lists\"]\n            ):\n                main_content = main_template.render(\n                    dict(\n                        process_flow_diagram=self,\n                        height_width_ratio=100\n                        * float(self.url_height)\n                        / float(self.url_width),\n                        uuid=uuid4().hex,\n                        widget_pk=widget_pk,\n                        widget_extra_css_class=widget_extra_css_class,\n                    )\n                )\n        except ValueError:\n            logger.info(f\"ProcessFlowDiagram {self} has no background image defined\")\n        except FileNotFoundError as e:\n            logger.info(f\"ProcessFlowDiagram {self} : {e}\")\n        sidebar_content = None\n        opts = dict()\n        # opts[\"object_config_list\"] = set()\n        # opts[\"object_config_list\"].update(self._get_objects_for_html())\n\n        return main_content, sidebar_content, opts\n\n\nclass SlidingPanelMenu(models.Model):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    position_choices = ((0, \"Control Menu\"), (1, \"left\"), (2, \"right\"))\n    position = models.PositiveSmallIntegerField(default=0, choices=position_choices)\n    control_panel = models.ForeignKey(\n        ControlPanel, blank=True, null=True, default=None, on_delete=models.SET_NULL\n    )\n    visible = models.BooleanField(default=True)\n\n    def __str__(self):\n        return self.title\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        if self.control_panel is not None:\n            return self.control_panel.data_objects(user)\n        return {}\n\n\nclass WidgetContent(models.Model):\n    content_model = models.CharField(max_length=400)\n    content_pk = models.PositiveIntegerField()\n    content_str = models.CharField(default=\"\", max_length=400)\n\n    def create_panel_html(self, **kwargs):\n        \"\"\"\n        return main_content, sidebar_content, optional list\n        \"\"\"\n        content_model = self._import_content_model()\n        try:\n            if content_model is not None:\n                return content_model.gen_html(**kwargs)\n            else:\n                logger.info(\n                    f\"WidgetContent content_model of {self.content_str} is None\"\n                )\n                return \"\", \"\", \"\"\n        except:\n            logger.error(f\"{content_model} unhandled exception\", exc_info=True)\n            # todo del self\n            return \"\", \"\", \"\"\n\n    def get_hidden_config2(self, **kwargs):\n        \"\"\"\n        return main_content, sidebar_content, optional list\n        \"\"\"\n        content_model = self._import_content_model()\n        opts = dict()\n        opts[\"object_config_list\"] = set()\n        try:\n            if content_model is not None:\n                opts[\"object_config_list\"].update(content_model._get_objects_for_html())\n                opts = content_model.add_custom_fields_list(opts)\n                opts = content_model.add_exclude_fields_list(opts)\n            else:\n                logger.info(\n                    f\"WidgetContent content_model of {self.content_str} is None\"\n                )\n        except:\n            logger.error(f\"{content_model} unhandled exception\", exc_info=True)\n            # todo del self\n\n        return opts\n\n    def _import_content_model(self):\n        content_class_str = self.content_model\n        class_name = content_class_str.split(\".\")[-1]\n        class_path = content_class_str.replace(\".\" + class_name, \"\")\n        try:\n            mod = __import__(class_path, fromlist=[class_name.__str__()])\n            content_class = getattr(mod, class_name.__str__())\n            if isinstance(content_class, models.base.ModelBase):\n                return content_class.objects.get(pk=self.content_pk)\n        except ModuleNotFoundError:\n            logger.info(\n                f\"{class_name} of {class_path} not found. A module is not installed ?\"\n            )\n        except ProgrammingError as e:\n            logger.info(\n                f\"{e} A module is not installed ?\"\n            )\n        except:\n            logger.error(f\"{class_path} unhandled exception\", exc_info=True)\n        return None\n\n    def __str__(self):\n        return \"%s [%d] %s\" % (\n            self.content_model.split(\".\")[-1],\n            self.content_pk,\n            self.content_str,\n        )  # todo add more infos\n\n\nclass CssClass(models.Model):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    css_class = models.CharField(max_length=250, default=\"\")\n\n    def __str__(self):\n        return self.title\n\n    class Meta:\n        verbose_name_plural = \"Css Classes\"\n\n\nclass Widget(models.Model):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\", blank=True)\n    page = models.ForeignKey(\n        Page, null=True, default=None, blank=True, on_delete=models.SET_NULL\n    )\n    row_choices = (\n        (0, \"1. row\"),\n        (1, \"2. row\"),\n        (2, \"3. row\"),\n        (3, \"4. row\"),\n        (4, \"5. row\"),\n        (5, \"6. row\"),\n        (6, \"7. row\"),\n        (7, \"8. row\"),\n        (8, \"9. row\"),\n        (9, \"10. row\"),\n        (10, \"11. row\"),\n        (11, \"12. row\"),\n    )\n    row = models.PositiveSmallIntegerField(default=0, choices=row_choices)\n    col_choices = ((0, \"1. col\"), (1, \"2. col\"), (2, \"3. col\"), (3, \"4. col\"))\n    col = models.PositiveSmallIntegerField(default=0, choices=col_choices)\n    size_choices = (\n        (4, \"page width\"),\n        (3, \"3/4 page width\"),\n        (2, \"1/2 page width\"),\n        (1, \"1/4 page width\"),\n    )\n    size = models.PositiveSmallIntegerField(default=4, choices=size_choices)\n    visible = models.BooleanField(default=True)\n    content = models.ForeignKey(\n        WidgetContent, null=True, default=None, on_delete=models.SET_NULL\n    )\n    extra_css_class = models.ForeignKey(\n        CssClass, null=True, default=None, blank=True, on_delete=models.SET_NULL\n    )\n\n    class Meta:\n        ordering = [\"row\", \"col\"]\n\n    def __str__(self):\n        if self.title is not None and self.page:\n            return str(self.id) + \": \" + self.page.title + \", \" + self.title\n        else:\n            return str(self.id) + \": \" + \"None, None\"\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        if self.content is not None:\n            content_model = self.content._import_content_model()\n            groups = user.groups.all() if user is not None else []\n            authenticated = True if user is not None else False\n            if (\n                content_model is not None\n                and hasattr(content_model, \"data_objects\")\n                and (\n                    not hasattr(content_model, \"groupdisplaypermission\")\n                    or content_model\n                    in get_group_display_permission_list(\n                        content_model.__class__.objects.all(), groups, authenticated\n                    )\n                )\n            ):\n                return content_model.data_objects(user)\n        return {}\n\n    def css_class(self):\n        widget_size = \"col-xs-12 col-sm-12 col-md-12 col-lg-12\"\n        widgets = Widget.objects.filter(\n            visible=True, page=self.page, row=self.row, content__isnull=False\n        )\n        if self.size == 3:\n            if self.col in [1, 2, 3] and len(widgets.filter(col=0)) == 0:\n                # no widget on same row and column 0: offset 3 on lg\n                widget_size = \"col-xs-12 col-sm-12 col-md-12 col-lg-9 col-lg-offset-3\"\n            else:\n                widget_size = \"col-xs-12 col-sm-12 col-md-12 col-lg-9\"\n        elif self.size == 2:\n            if self.col == 1 and len(widgets.filter(col=0)) == 0:\n                widget_size = \"col-xs-12 col-sm-12 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3\"\n            elif self.col in [2, 3] and len(widgets.filter(col__in=[0, 1])) == 0:\n                widget_size = \"col-xs-12 col-sm-12 col-md-6 col-md-offset-6 col-lg-6 col-lg-offset-6\"\n            else:\n                widget_size = \"col-xs-12 col-sm-12 col-md-6 col-lg-6\"\n        elif self.size == 1:\n            if self.col == 1 and len(widgets.filter(col=0)) == 0:\n                widget_size = \"col-xs-12 col-sm-6 col-md-6 col-lg-3 col-lg-offset-3\"\n            elif self.col == 2 and len(widgets.filter(col__in=[0, 1])) == 0:\n                widget_size = \"col-xs-12 col-sm-6 col-sm-offset-6 col-md-6 col-md-offset-6 col-lg-3 col-lg-offset-6\"\n            elif self.col == 3 and len(widgets.filter(col__in=[0, 1, 2])) == 0:\n                widget_size = \"col-xs-12 col-sm-6 col-sm-offset-6 col-md-6 col-md-offset-6 col-lg-3 col-lg-offset-9\"\n            else:\n                widget_size = \"col-xs-12 col-sm-6 col-md-6 col-lg-3\"\n        return (\n            \"widget_row_\"\n            + str(self.row)\n            + \" widget_col_\"\n            + str(self.col)\n            + \" \"\n            + widget_size\n        )\n\n\nclass View(models.Model):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    description = models.TextField(default=\"\", verbose_name=\"Description\", null=True)\n    link_title = models.SlugField(max_length=80, default=\"\")\n    pages = models.ManyToManyField(Page)\n    sliding_panel_menus = models.ManyToManyField(SlidingPanelMenu, blank=True)\n    logo = models.ImageField(\n        upload_to=\"img/\", verbose_name=\"Overview Picture\", blank=True\n    )\n    visible = models.BooleanField(default=True)\n    position = models.PositiveSmallIntegerField(default=0)\n    show_timeline = models.BooleanField(default=True)\n    theme = models.ForeignKey(\n        Theme, blank=True, null=True, default=None, on_delete=models.SET_NULL\n    )\n    default_time_delta = models.DurationField(default=timedelta(hours=2))\n\n    def __str__(self):\n        return self.title\n\n    def data_objects(self, user):\n        # used to get all objects which need to retrive data\n        objects = {}\n        if self.visible:\n            groups = user.groups.all() if user is not None else []\n            authenticated = True if user is not None else False\n            for w in get_group_display_permission_list(\n                self.sliding_panel_menus.all(), groups, authenticated\n            ):\n                wdo = w.data_objects(user)\n                for o in wdo.keys():\n                    if o not in objects:\n                        objects[o] = []\n                    objects[o] = list(set(objects[o] + wdo.get(o, [])))\n            for w in get_group_display_permission_list(\n                self.pages.all(), groups, authenticated\n            ):\n                wdo = w.data_objects(user)\n                for o in wdo.keys():\n                    if o not in objects:\n                        objects[o] = []\n                    objects[o] = list(set(objects[o] + wdo.get(o, [])))\n        return objects\n\n    class Meta:\n        ordering = [\"position\"]\n\nclass ExternalView(models.Model):\n    id = models.AutoField(primary_key=True)\n    title = models.CharField(max_length=400, default=\"\")\n    description = models.TextField(default=\"\", verbose_name=\"Description\", null=True)\n    url = models.URLField()\n    logo = models.ImageField(\n        upload_to=\"img/\", verbose_name=\"Overview Picture\", blank=True\n    )\n    visible = models.BooleanField(default=True)\n    position = models.PositiveSmallIntegerField(default=0)\n\n    def __str__(self):\n        return self.title\n\n    class Meta:\n        ordering = [\"position\"]\n\n\nclass GroupDisplayPermission(models.Model):\n    hmi_group = models.OneToOneField(\n        Group, blank=True, null=True, on_delete=models.CASCADE\n    )\n    unauthenticated_users = models.BooleanField(default=False)\n    type_choices = (\n        (0, \"allow\"),\n        (1, \"exclude\"),\n    )\n\n    def __str__(self):\n        if self.hmi_group is not None:\n            return self.hmi_group.name\n        elif not self.unauthenticated_users:\n            return \"Users without any group\"\n        else:\n            return \"Unauthenticated users\"\n\n\nclass PieGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    pies = models.ManyToManyField(\n        Pie, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = Pie\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass PageGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    pages = models.ManyToManyField(\n        Page, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = Page\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass SlidingPanelMenuGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    sliding_panel_menus = models.ManyToManyField(\n        SlidingPanelMenu, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = SlidingPanelMenu\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass ChartGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    charts = models.ManyToManyField(\n        Chart, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = Chart\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass ControlItemGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    control_items = models.ManyToManyField(\n        ControlItem, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = ControlItem\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass FormGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    forms = models.ManyToManyField(\n        Form, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = Form\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass WidgetGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    widgets = models.ManyToManyField(\n        Widget, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = Widget\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass CustomHTMLPanelGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    custom_html_panels = models.ManyToManyField(\n        CustomHTMLPanel, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = CustomHTMLPanel\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass ViewGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    views = models.ManyToManyField(\n        View, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = View\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\nclass ExternalViewGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    external_view = models.ManyToManyField(\n        ExternalView, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = ExternalView\n\n    def __str__(self):\n        return str(self.group_display_permission)\n\n\nclass ProcessFlowDiagramGroupDisplayPermission(models.Model):\n    group_display_permission = models.OneToOneField(\n        GroupDisplayPermission, null=True, on_delete=models.CASCADE\n    )\n    type = models.PositiveSmallIntegerField(\n        default=0,\n        choices=GroupDisplayPermission.type_choices,\n        help_text=\"If allow: only selected items can be seen by the group.\"\n        \"<br>If exclude: allows all items except the selected ones.\",\n    )\n    process_flow_diagram = models.ManyToManyField(\n        ProcessFlowDiagram, blank=True, related_name=\"groupdisplaypermission\"\n    )\n    m2m_related_model = ProcessFlowDiagram\n\n    def __str__(self):\n        return str(self.group_display_permission)\n"
  },
  {
    "path": "pyscada/hmi/signals.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.dispatch import receiver\nfrom django.db.models.signals import post_save, pre_delete\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n# moved to hmi.models.WidgetContentModel.__init_subclass__\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/bootstrap/bootstrap-theme.css",
    "content": "/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n  text-shadow: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n}\n.btn-default {\n  background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n  background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));\n  background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #dbdbdb;\n  text-shadow: 0 1px 0 #fff;\n  border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n  background-color: #e0e0e0;\n  background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n  background-color: #e0e0e0;\n  border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n  background-color: #e0e0e0;\n  background-image: none;\n}\n.btn-primary {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n  background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));\n  background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n  background-color: #265a88;\n  background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n  background-color: #265a88;\n  border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n  background-color: #265a88;\n  background-image: none;\n}\n.btn-success {\n  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n  background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));\n  background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n  background-color: #419641;\n  background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n  background-color: #419641;\n  border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n  background-color: #419641;\n  background-image: none;\n}\n.btn-info {\n  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n  background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));\n  background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n  background-color: #2aabd2;\n  background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n  background-color: #2aabd2;\n  border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n  background-color: #2aabd2;\n  background-image: none;\n}\n.btn-warning {\n  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n  background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));\n  background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n  background-color: #eb9316;\n  background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n  background-color: #eb9316;\n  border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n  background-color: #eb9316;\n  background-image: none;\n}\n.btn-danger {\n  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n  background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));\n  background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n  background-color: #c12e2a;\n  background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n  background-color: #c12e2a;\n  border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n  background-color: #c12e2a;\n  background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n  background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n  background-repeat: repeat-x;\n  background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n  background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n  background-repeat: repeat-x;\n  background-color: #2e6da4;\n}\n.navbar-default {\n  background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n  background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8));\n  background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n  background-repeat: repeat-x;\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n  background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n  background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));\n  background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n  background-repeat: repeat-x;\n  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n  box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n  background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n  background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));\n  background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n  background-repeat: repeat-x;\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n  background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n  background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));\n  background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n  background-repeat: repeat-x;\n  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n  box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  border-radius: 0;\n}\n@media (max-width: 767px) {\n  .navbar .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n    background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n    background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n    background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n    background-repeat: repeat-x;\n  }\n}\n.alert {\n  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n  background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));\n  background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #b2dba1;\n}\n.alert-info {\n  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n  background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));\n  background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #9acfea;\n}\n.alert-warning {\n  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n  background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));\n  background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #f5e79e;\n}\n.alert-danger {\n  background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n  background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));\n  background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #dca7a7;\n}\n.progress {\n  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n  background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));\n  background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n  background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));\n  background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-success {\n  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n  background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));\n  background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-info {\n  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n  background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));\n  background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-warning {\n  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n  background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));\n  background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-danger {\n  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n  background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));\n  background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  text-shadow: 0 -1px 0 #286090;\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n  background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));\n  background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n  text-shadow: none;\n}\n.panel {\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n  background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-primary > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n  background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-success > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n  background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));\n  background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-info > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n  background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));\n  background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-warning > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n  background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));\n  background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-danger > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n  background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));\n  background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n  background-repeat: repeat-x;\n}\n.well {\n  background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n  background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));\n  background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #dcdcdc;\n  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/bootstrap/bootstrap.css",
    "content": "/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n  font-family: sans-serif;\n  -ms-text-size-adjust: 100%;\n  -webkit-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n  display: inline-block;\n  vertical-align: baseline;\n}\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n[hidden],\ntemplate {\n  display: none;\n}\na {\n  background-color: transparent;\n}\na:active,\na:hover {\n  outline: 0;\n}\nabbr[title] {\n  border-bottom: none;\n  text-decoration: underline;\n  -webkit-text-decoration: underline dotted;\n  -moz-text-decoration: underline dotted;\n  text-decoration: underline dotted;\n}\nb,\nstrong {\n  font-weight: bold;\n}\ndfn {\n  font-style: italic;\n}\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\nmark {\n  background: #ff0;\n  color: #000;\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\nsup {\n  top: -0.5em;\n}\nsub {\n  bottom: -0.25em;\n}\nimg {\n  border: 0;\n}\nsvg:not(:root) {\n  overflow: hidden;\n}\nfigure {\n  margin: 1em 40px;\n}\nhr {\n  -webkit-box-sizing: content-box;\n  -moz-box-sizing: content-box;\n  box-sizing: content-box;\n  height: 0;\n}\npre {\n  overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  color: inherit;\n  font: inherit;\n  margin: 0;\n}\nbutton {\n  overflow: visible;\n}\nbutton,\nselect {\n  text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button;\n  cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  border: 0;\n  padding: 0;\n}\ninput {\n  line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  -webkit-box-sizing: border-box;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n  padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-appearance: textfield;\n  -webkit-box-sizing: content-box;\n  -moz-box-sizing: content-box;\n  box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\nfieldset {\n  border: 1px solid #c0c0c0;\n  margin: 0 2px;\n  padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n  border: 0;\n  padding: 0;\n}\ntextarea {\n  overflow: auto;\n}\noptgroup {\n  font-weight: bold;\n}\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\ntd,\nth {\n  padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n  *,\n  *:before,\n  *:after {\n    color: #000 !important;\n    text-shadow: none !important;\n    background: transparent !important;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n  a,\n  a:visited {\n    text-decoration: underline;\n  }\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n  abbr[title]:after {\n    content: \" (\" attr(title) \")\";\n  }\n  a[href^=\"#\"]:after,\n  a[href^=\"javascript:\"]:after {\n    content: \"\";\n  }\n  pre,\n  blockquote {\n    border: 1px solid #999;\n    page-break-inside: avoid;\n  }\n  thead {\n    display: table-header-group;\n  }\n  tr,\n  img {\n    page-break-inside: avoid;\n  }\n  img {\n    max-width: 100% !important;\n  }\n  p,\n  h2,\n  h3 {\n    orphans: 3;\n    widows: 3;\n  }\n  h2,\n  h3 {\n    page-break-after: avoid;\n  }\n  .navbar {\n    display: none;\n  }\n  .btn > .caret,\n  .dropup > .btn > .caret {\n    border-top-color: #000 !important;\n  }\n  .label {\n    border: 1px solid #000;\n  }\n  .table {\n    border-collapse: collapse !important;\n  }\n  .table td,\n  .table th {\n    background-color: #fff !important;\n  }\n  .table-bordered th,\n  .table-bordered td {\n    border: 1px solid #ddd !important;\n  }\n}\n@font-face {\n  font-family: \"Glyphicons Halflings\";\n  src: url(\"../fonts/glyphicons-halflings-regular.eot\");\n  src: url(\"../fonts/glyphicons-halflings-regular.eot?#iefix\") format(\"embedded-opentype\"), url(\"../fonts/glyphicons-halflings-regular.woff2\") format(\"woff2\"), url(\"../fonts/glyphicons-halflings-regular.woff\") format(\"woff\"), url(\"../fonts/glyphicons-halflings-regular.ttf\") format(\"truetype\"), url(\"../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular\") format(\"svg\");\n}\n.glyphicon {\n  position: relative;\n  top: 1px;\n  display: inline-block;\n  font-family: \"Glyphicons Halflings\";\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n  content: \"\\002a\";\n}\n.glyphicon-plus:before {\n  content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n  content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n  content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n  content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n  content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n  content: \"\\270f\";\n}\n.glyphicon-glass:before {\n  content: \"\\e001\";\n}\n.glyphicon-music:before {\n  content: \"\\e002\";\n}\n.glyphicon-search:before {\n  content: \"\\e003\";\n}\n.glyphicon-heart:before {\n  content: \"\\e005\";\n}\n.glyphicon-star:before {\n  content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n  content: \"\\e007\";\n}\n.glyphicon-user:before {\n  content: \"\\e008\";\n}\n.glyphicon-film:before {\n  content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n  content: \"\\e010\";\n}\n.glyphicon-th:before {\n  content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n  content: \"\\e012\";\n}\n.glyphicon-ok:before {\n  content: \"\\e013\";\n}\n.glyphicon-remove:before {\n  content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n  content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n  content: \"\\e016\";\n}\n.glyphicon-off:before {\n  content: \"\\e017\";\n}\n.glyphicon-signal:before {\n  content: \"\\e018\";\n}\n.glyphicon-cog:before {\n  content: \"\\e019\";\n}\n.glyphicon-trash:before {\n  content: \"\\e020\";\n}\n.glyphicon-home:before {\n  content: \"\\e021\";\n}\n.glyphicon-file:before {\n  content: \"\\e022\";\n}\n.glyphicon-time:before {\n  content: \"\\e023\";\n}\n.glyphicon-road:before {\n  content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n  content: \"\\e025\";\n}\n.glyphicon-download:before {\n  content: \"\\e026\";\n}\n.glyphicon-upload:before {\n  content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n  content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n  content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n  content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n  content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n  content: \"\\e032\";\n}\n.glyphicon-lock:before {\n  content: \"\\e033\";\n}\n.glyphicon-flag:before {\n  content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n  content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n  content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n  content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n  content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n  content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n  content: \"\\e040\";\n}\n.glyphicon-tag:before {\n  content: \"\\e041\";\n}\n.glyphicon-tags:before {\n  content: \"\\e042\";\n}\n.glyphicon-book:before {\n  content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n  content: \"\\e044\";\n}\n.glyphicon-print:before {\n  content: \"\\e045\";\n}\n.glyphicon-camera:before {\n  content: \"\\e046\";\n}\n.glyphicon-font:before {\n  content: \"\\e047\";\n}\n.glyphicon-bold:before {\n  content: \"\\e048\";\n}\n.glyphicon-italic:before {\n  content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n  content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n  content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n  content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n  content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n  content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n  content: \"\\e055\";\n}\n.glyphicon-list:before {\n  content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n  content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n  content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n  content: \"\\e059\";\n}\n.glyphicon-picture:before {\n  content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n  content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n  content: \"\\e063\";\n}\n.glyphicon-tint:before {\n  content: \"\\e064\";\n}\n.glyphicon-edit:before {\n  content: \"\\e065\";\n}\n.glyphicon-share:before {\n  content: \"\\e066\";\n}\n.glyphicon-check:before {\n  content: \"\\e067\";\n}\n.glyphicon-move:before {\n  content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n  content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n  content: \"\\e070\";\n}\n.glyphicon-backward:before {\n  content: \"\\e071\";\n}\n.glyphicon-play:before {\n  content: \"\\e072\";\n}\n.glyphicon-pause:before {\n  content: \"\\e073\";\n}\n.glyphicon-stop:before {\n  content: \"\\e074\";\n}\n.glyphicon-forward:before {\n  content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n  content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n  content: \"\\e077\";\n}\n.glyphicon-eject:before {\n  content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n  content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n  content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n  content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n  content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n  content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n  content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n  content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n  content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n  content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n  content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n  content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n  content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n  content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n  content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n  content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n  content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n  content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n  content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n  content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n  content: \"\\e101\";\n}\n.glyphicon-gift:before {\n  content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n  content: \"\\e103\";\n}\n.glyphicon-fire:before {\n  content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n  content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n  content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n  content: \"\\e107\";\n}\n.glyphicon-plane:before {\n  content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n  content: \"\\e109\";\n}\n.glyphicon-random:before {\n  content: \"\\e110\";\n}\n.glyphicon-comment:before {\n  content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n  content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n  content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n  content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n  content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n  content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n  content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n  content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n  content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n  content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n  content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n  content: \"\\e122\";\n}\n.glyphicon-bell:before {\n  content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n  content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n  content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n  content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n  content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n  content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n  content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n  content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n  content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n  content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n  content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n  content: \"\\e134\";\n}\n.glyphicon-globe:before {\n  content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n  content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n  content: \"\\e137\";\n}\n.glyphicon-filter:before {\n  content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n  content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n  content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n  content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n  content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n  content: \"\\e143\";\n}\n.glyphicon-link:before {\n  content: \"\\e144\";\n}\n.glyphicon-phone:before {\n  content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n  content: \"\\e146\";\n}\n.glyphicon-usd:before {\n  content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n  content: \"\\e149\";\n}\n.glyphicon-sort:before {\n  content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n  content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n  content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n  content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n  content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n  content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n  content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n  content: \"\\e157\";\n}\n.glyphicon-expand:before {\n  content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n  content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n  content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n  content: \"\\e161\";\n}\n.glyphicon-flash:before {\n  content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n  content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n  content: \"\\e164\";\n}\n.glyphicon-record:before {\n  content: \"\\e165\";\n}\n.glyphicon-save:before {\n  content: \"\\e166\";\n}\n.glyphicon-open:before {\n  content: \"\\e167\";\n}\n.glyphicon-saved:before {\n  content: \"\\e168\";\n}\n.glyphicon-import:before {\n  content: \"\\e169\";\n}\n.glyphicon-export:before {\n  content: \"\\e170\";\n}\n.glyphicon-send:before {\n  content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n  content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n  content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n  content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n  content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n  content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n  content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n  content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n  content: \"\\e179\";\n}\n.glyphicon-header:before {\n  content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n  content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n  content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n  content: \"\\e183\";\n}\n.glyphicon-tower:before {\n  content: \"\\e184\";\n}\n.glyphicon-stats:before {\n  content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n  content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n  content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n  content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n  content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n  content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n  content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n  content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n  content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n  content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n  content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n  content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n  content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n  content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n  content: \"\\e200\";\n}\n.glyphicon-cd:before {\n  content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n  content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n  content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n  content: \"\\e204\";\n}\n.glyphicon-copy:before {\n  content: \"\\e205\";\n}\n.glyphicon-paste:before {\n  content: \"\\e206\";\n}\n.glyphicon-alert:before {\n  content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n  content: \"\\e210\";\n}\n.glyphicon-king:before {\n  content: \"\\e211\";\n}\n.glyphicon-queen:before {\n  content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n  content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n  content: \"\\e214\";\n}\n.glyphicon-knight:before {\n  content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n  content: \"\\e216\";\n}\n.glyphicon-tent:before {\n  content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n  content: \"\\e218\";\n}\n.glyphicon-bed:before {\n  content: \"\\e219\";\n}\n.glyphicon-apple:before {\n  content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n  content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n  content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n  content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n  content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n  content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n  content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n  content: \"\\e227\";\n}\n.glyphicon-btc:before {\n  content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n  content: \"\\e227\";\n}\n.glyphicon-yen:before {\n  content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n  content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n  content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n  content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n  content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n  content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n  content: \"\\e232\";\n}\n.glyphicon-education:before {\n  content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n  content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n  content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n  content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n  content: \"\\e237\";\n}\n.glyphicon-oil:before {\n  content: \"\\e238\";\n}\n.glyphicon-grain:before {\n  content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n  content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n  content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n  content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n  content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n  content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n  content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n  content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n  content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n  content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n  content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n  content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n  content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n  content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n  content: \"\\e253\";\n}\n.glyphicon-console:before {\n  content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n  content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n  content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n  content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n  content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n  content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n  content: \"\\e260\";\n}\n* {\n  -webkit-box-sizing: border-box;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n}\n*:before,\n*:after {\n  -webkit-box-sizing: border-box;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n}\nhtml {\n  font-size: 10px;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #333333;\n  background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\na {\n  color: #337ab7;\n  text-decoration: none;\n}\na:hover,\na:focus {\n  color: #23527c;\n  text-decoration: underline;\n}\na:focus {\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\nfigure {\n  margin: 0;\n}\nimg {\n  vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n}\n.img-rounded {\n  border-radius: 6px;\n}\n.img-thumbnail {\n  padding: 4px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: all 0.2s ease-in-out;\n  -o-transition: all 0.2s ease-in-out;\n  transition: all 0.2s ease-in-out;\n  display: inline-block;\n  max-width: 100%;\n  height: auto;\n}\n.img-circle {\n  border-radius: 50%;\n}\nhr {\n  margin-top: 20px;\n  margin-bottom: 20px;\n  border: 0;\n  border-top: 1px solid #eeeeee;\n}\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n  position: static;\n  width: auto;\n  height: auto;\n  margin: 0;\n  overflow: visible;\n  clip: auto;\n}\n[role=\"button\"] {\n  cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n  font-family: inherit;\n  font-weight: 500;\n  line-height: 1.1;\n  color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n  font-weight: 400;\n  line-height: 1;\n  color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n  margin-top: 20px;\n  margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n  font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n  font-size: 75%;\n}\nh1,\n.h1 {\n  font-size: 36px;\n}\nh2,\n.h2 {\n  font-size: 30px;\n}\nh3,\n.h3 {\n  font-size: 24px;\n}\nh4,\n.h4 {\n  font-size: 18px;\n}\nh5,\n.h5 {\n  font-size: 14px;\n}\nh6,\n.h6 {\n  font-size: 12px;\n}\np {\n  margin: 0 0 10px;\n}\n.lead {\n  margin-bottom: 20px;\n  font-size: 16px;\n  font-weight: 300;\n  line-height: 1.4;\n}\n@media (min-width: 768px) {\n  .lead {\n    font-size: 21px;\n  }\n}\nsmall,\n.small {\n  font-size: 85%;\n}\nmark,\n.mark {\n  padding: 0.2em;\n  background-color: #fcf8e3;\n}\n.text-left {\n  text-align: left;\n}\n.text-right {\n  text-align: right;\n}\n.text-center {\n  text-align: center;\n}\n.text-justify {\n  text-align: justify;\n}\n.text-nowrap {\n  white-space: nowrap;\n}\n.text-lowercase {\n  text-transform: lowercase;\n}\n.text-uppercase {\n  text-transform: uppercase;\n}\n.text-capitalize {\n  text-transform: capitalize;\n}\n.text-muted {\n  color: #777777;\n}\n.text-primary {\n  color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n  color: #286090;\n}\n.text-success {\n  color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n  color: #2b542c;\n}\n.text-info {\n  color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n  color: #245269;\n}\n.text-warning {\n  color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n  color: #66512c;\n}\n.text-danger {\n  color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n  color: #843534;\n}\n.bg-primary {\n  color: #fff;\n  background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n  background-color: #286090;\n}\n.bg-success {\n  background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n  background-color: #c1e2b3;\n}\n.bg-info {\n  background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n  background-color: #afd9ee;\n}\n.bg-warning {\n  background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n  background-color: #f7ecb5;\n}\n.bg-danger {\n  background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n  background-color: #e4b9b9;\n}\n.page-header {\n  padding-bottom: 9px;\n  margin: 40px 0 20px;\n  border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n  margin-bottom: 0;\n}\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n.list-inline {\n  padding-left: 0;\n  list-style: none;\n  margin-left: -5px;\n}\n.list-inline > li {\n  display: inline-block;\n  padding-right: 5px;\n  padding-left: 5px;\n}\ndl {\n  margin-top: 0;\n  margin-bottom: 20px;\n}\ndt,\ndd {\n  line-height: 1.42857143;\n}\ndt {\n  font-weight: 700;\n}\ndd {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .dl-horizontal dt {\n    float: left;\n    width: 160px;\n    clear: left;\n    text-align: right;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .dl-horizontal dd {\n    margin-left: 180px;\n  }\n}\nabbr[title],\nabbr[data-original-title] {\n  cursor: help;\n}\n.initialism {\n  font-size: 90%;\n  text-transform: uppercase;\n}\nblockquote {\n  padding: 10px 20px;\n  margin: 0 0 20px;\n  font-size: 17.5px;\n  border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n  margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n  display: block;\n  font-size: 80%;\n  line-height: 1.42857143;\n  color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n  content: \"\\2014 \\00A0\";\n}\n.blockquote-reverse,\nblockquote.pull-right {\n  padding-right: 15px;\n  padding-left: 0;\n  text-align: right;\n  border-right: 5px solid #eeeeee;\n  border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n  content: \"\";\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n  content: \"\\00A0 \\2014\";\n}\naddress {\n  margin-bottom: 20px;\n  font-style: normal;\n  line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #c7254e;\n  background-color: #f9f2f4;\n  border-radius: 4px;\n}\nkbd {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #fff;\n  background-color: #333;\n  border-radius: 3px;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n  padding: 0;\n  font-size: 100%;\n  font-weight: 700;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\npre {\n  display: block;\n  padding: 9.5px;\n  margin: 0 0 10px;\n  font-size: 13px;\n  line-height: 1.42857143;\n  color: #333333;\n  word-break: break-all;\n  word-wrap: break-word;\n  background-color: #f5f5f5;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\npre code {\n  padding: 0;\n  font-size: inherit;\n  color: inherit;\n  white-space: pre-wrap;\n  background-color: transparent;\n  border-radius: 0;\n}\n.pre-scrollable {\n  max-height: 340px;\n  overflow-y: scroll;\n}\n.container {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n@media (min-width: 768px) {\n  .container {\n    width: 750px;\n  }\n}\n@media (min-width: 992px) {\n  .container {\n    width: 970px;\n  }\n}\n@media (min-width: 1200px) {\n  .container {\n    width: 1170px;\n  }\n}\n.container-fluid {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n.row {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n.row-no-gutters {\n  margin-right: 0;\n  margin-left: 0;\n}\n.row-no-gutters [class*=\"col-\"] {\n  padding-right: 0;\n  padding-left: 0;\n}\n.col-xs-1,\n.col-sm-1,\n.col-md-1,\n.col-lg-1,\n.col-xs-2,\n.col-sm-2,\n.col-md-2,\n.col-lg-2,\n.col-xs-3,\n.col-sm-3,\n.col-md-3,\n.col-lg-3,\n.col-xs-4,\n.col-sm-4,\n.col-md-4,\n.col-lg-4,\n.col-xs-5,\n.col-sm-5,\n.col-md-5,\n.col-lg-5,\n.col-xs-6,\n.col-sm-6,\n.col-md-6,\n.col-lg-6,\n.col-xs-7,\n.col-sm-7,\n.col-md-7,\n.col-lg-7,\n.col-xs-8,\n.col-sm-8,\n.col-md-8,\n.col-lg-8,\n.col-xs-9,\n.col-sm-9,\n.col-md-9,\n.col-lg-9,\n.col-xs-10,\n.col-sm-10,\n.col-md-10,\n.col-lg-10,\n.col-xs-11,\n.col-sm-11,\n.col-md-11,\n.col-lg-11,\n.col-xs-12,\n.col-sm-12,\n.col-md-12,\n.col-lg-12 {\n  position: relative;\n  min-height: 1px;\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.col-xs-1,\n.col-xs-2,\n.col-xs-3,\n.col-xs-4,\n.col-xs-5,\n.col-xs-6,\n.col-xs-7,\n.col-xs-8,\n.col-xs-9,\n.col-xs-10,\n.col-xs-11,\n.col-xs-12 {\n  float: left;\n}\n.col-xs-12 {\n  width: 100%;\n}\n.col-xs-11 {\n  width: 91.66666667%;\n}\n.col-xs-10 {\n  width: 83.33333333%;\n}\n.col-xs-9 {\n  width: 75%;\n}\n.col-xs-8 {\n  width: 66.66666667%;\n}\n.col-xs-7 {\n  width: 58.33333333%;\n}\n.col-xs-6 {\n  width: 50%;\n}\n.col-xs-5 {\n  width: 41.66666667%;\n}\n.col-xs-4 {\n  width: 33.33333333%;\n}\n.col-xs-3 {\n  width: 25%;\n}\n.col-xs-2 {\n  width: 16.66666667%;\n}\n.col-xs-1 {\n  width: 8.33333333%;\n}\n.col-xs-pull-12 {\n  right: 100%;\n}\n.col-xs-pull-11 {\n  right: 91.66666667%;\n}\n.col-xs-pull-10 {\n  right: 83.33333333%;\n}\n.col-xs-pull-9 {\n  right: 75%;\n}\n.col-xs-pull-8 {\n  right: 66.66666667%;\n}\n.col-xs-pull-7 {\n  right: 58.33333333%;\n}\n.col-xs-pull-6 {\n  right: 50%;\n}\n.col-xs-pull-5 {\n  right: 41.66666667%;\n}\n.col-xs-pull-4 {\n  right: 33.33333333%;\n}\n.col-xs-pull-3 {\n  right: 25%;\n}\n.col-xs-pull-2 {\n  right: 16.66666667%;\n}\n.col-xs-pull-1 {\n  right: 8.33333333%;\n}\n.col-xs-pull-0 {\n  right: auto;\n}\n.col-xs-push-12 {\n  left: 100%;\n}\n.col-xs-push-11 {\n  left: 91.66666667%;\n}\n.col-xs-push-10 {\n  left: 83.33333333%;\n}\n.col-xs-push-9 {\n  left: 75%;\n}\n.col-xs-push-8 {\n  left: 66.66666667%;\n}\n.col-xs-push-7 {\n  left: 58.33333333%;\n}\n.col-xs-push-6 {\n  left: 50%;\n}\n.col-xs-push-5 {\n  left: 41.66666667%;\n}\n.col-xs-push-4 {\n  left: 33.33333333%;\n}\n.col-xs-push-3 {\n  left: 25%;\n}\n.col-xs-push-2 {\n  left: 16.66666667%;\n}\n.col-xs-push-1 {\n  left: 8.33333333%;\n}\n.col-xs-push-0 {\n  left: auto;\n}\n.col-xs-offset-12 {\n  margin-left: 100%;\n}\n.col-xs-offset-11 {\n  margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n  margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n  margin-left: 75%;\n}\n.col-xs-offset-8 {\n  margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n  margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n  margin-left: 50%;\n}\n.col-xs-offset-5 {\n  margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n  margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n  margin-left: 25%;\n}\n.col-xs-offset-2 {\n  margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n  margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n  margin-left: 0%;\n}\n@media (min-width: 768px) {\n  .col-sm-1,\n  .col-sm-2,\n  .col-sm-3,\n  .col-sm-4,\n  .col-sm-5,\n  .col-sm-6,\n  .col-sm-7,\n  .col-sm-8,\n  .col-sm-9,\n  .col-sm-10,\n  .col-sm-11,\n  .col-sm-12 {\n    float: left;\n  }\n  .col-sm-12 {\n    width: 100%;\n  }\n  .col-sm-11 {\n    width: 91.66666667%;\n  }\n  .col-sm-10 {\n    width: 83.33333333%;\n  }\n  .col-sm-9 {\n    width: 75%;\n  }\n  .col-sm-8 {\n    width: 66.66666667%;\n  }\n  .col-sm-7 {\n    width: 58.33333333%;\n  }\n  .col-sm-6 {\n    width: 50%;\n  }\n  .col-sm-5 {\n    width: 41.66666667%;\n  }\n  .col-sm-4 {\n    width: 33.33333333%;\n  }\n  .col-sm-3 {\n    width: 25%;\n  }\n  .col-sm-2 {\n    width: 16.66666667%;\n  }\n  .col-sm-1 {\n    width: 8.33333333%;\n  }\n  .col-sm-pull-12 {\n    right: 100%;\n  }\n  .col-sm-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-sm-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-sm-pull-9 {\n    right: 75%;\n  }\n  .col-sm-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-sm-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-sm-pull-6 {\n    right: 50%;\n  }\n  .col-sm-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-sm-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-sm-pull-3 {\n    right: 25%;\n  }\n  .col-sm-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-sm-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-sm-pull-0 {\n    right: auto;\n  }\n  .col-sm-push-12 {\n    left: 100%;\n  }\n  .col-sm-push-11 {\n    left: 91.66666667%;\n  }\n  .col-sm-push-10 {\n    left: 83.33333333%;\n  }\n  .col-sm-push-9 {\n    left: 75%;\n  }\n  .col-sm-push-8 {\n    left: 66.66666667%;\n  }\n  .col-sm-push-7 {\n    left: 58.33333333%;\n  }\n  .col-sm-push-6 {\n    left: 50%;\n  }\n  .col-sm-push-5 {\n    left: 41.66666667%;\n  }\n  .col-sm-push-4 {\n    left: 33.33333333%;\n  }\n  .col-sm-push-3 {\n    left: 25%;\n  }\n  .col-sm-push-2 {\n    left: 16.66666667%;\n  }\n  .col-sm-push-1 {\n    left: 8.33333333%;\n  }\n  .col-sm-push-0 {\n    left: auto;\n  }\n  .col-sm-offset-12 {\n    margin-left: 100%;\n  }\n  .col-sm-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-sm-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-sm-offset-9 {\n    margin-left: 75%;\n  }\n  .col-sm-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-sm-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-sm-offset-6 {\n    margin-left: 50%;\n  }\n  .col-sm-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-sm-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-sm-offset-3 {\n    margin-left: 25%;\n  }\n  .col-sm-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-sm-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-sm-offset-0 {\n    margin-left: 0%;\n  }\n}\n@media (min-width: 992px) {\n  .col-md-1,\n  .col-md-2,\n  .col-md-3,\n  .col-md-4,\n  .col-md-5,\n  .col-md-6,\n  .col-md-7,\n  .col-md-8,\n  .col-md-9,\n  .col-md-10,\n  .col-md-11,\n  .col-md-12 {\n    float: left;\n  }\n  .col-md-12 {\n    width: 100%;\n  }\n  .col-md-11 {\n    width: 91.66666667%;\n  }\n  .col-md-10 {\n    width: 83.33333333%;\n  }\n  .col-md-9 {\n    width: 75%;\n  }\n  .col-md-8 {\n    width: 66.66666667%;\n  }\n  .col-md-7 {\n    width: 58.33333333%;\n  }\n  .col-md-6 {\n    width: 50%;\n  }\n  .col-md-5 {\n    width: 41.66666667%;\n  }\n  .col-md-4 {\n    width: 33.33333333%;\n  }\n  .col-md-3 {\n    width: 25%;\n  }\n  .col-md-2 {\n    width: 16.66666667%;\n  }\n  .col-md-1 {\n    width: 8.33333333%;\n  }\n  .col-md-pull-12 {\n    right: 100%;\n  }\n  .col-md-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-md-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-md-pull-9 {\n    right: 75%;\n  }\n  .col-md-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-md-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-md-pull-6 {\n    right: 50%;\n  }\n  .col-md-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-md-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-md-pull-3 {\n    right: 25%;\n  }\n  .col-md-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-md-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-md-pull-0 {\n    right: auto;\n  }\n  .col-md-push-12 {\n    left: 100%;\n  }\n  .col-md-push-11 {\n    left: 91.66666667%;\n  }\n  .col-md-push-10 {\n    left: 83.33333333%;\n  }\n  .col-md-push-9 {\n    left: 75%;\n  }\n  .col-md-push-8 {\n    left: 66.66666667%;\n  }\n  .col-md-push-7 {\n    left: 58.33333333%;\n  }\n  .col-md-push-6 {\n    left: 50%;\n  }\n  .col-md-push-5 {\n    left: 41.66666667%;\n  }\n  .col-md-push-4 {\n    left: 33.33333333%;\n  }\n  .col-md-push-3 {\n    left: 25%;\n  }\n  .col-md-push-2 {\n    left: 16.66666667%;\n  }\n  .col-md-push-1 {\n    left: 8.33333333%;\n  }\n  .col-md-push-0 {\n    left: auto;\n  }\n  .col-md-offset-12 {\n    margin-left: 100%;\n  }\n  .col-md-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-md-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-md-offset-9 {\n    margin-left: 75%;\n  }\n  .col-md-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-md-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-md-offset-6 {\n    margin-left: 50%;\n  }\n  .col-md-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-md-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-md-offset-3 {\n    margin-left: 25%;\n  }\n  .col-md-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-md-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-md-offset-0 {\n    margin-left: 0%;\n  }\n}\n@media (min-width: 1200px) {\n  .col-lg-1,\n  .col-lg-2,\n  .col-lg-3,\n  .col-lg-4,\n  .col-lg-5,\n  .col-lg-6,\n  .col-lg-7,\n  .col-lg-8,\n  .col-lg-9,\n  .col-lg-10,\n  .col-lg-11,\n  .col-lg-12 {\n    float: left;\n  }\n  .col-lg-12 {\n    width: 100%;\n  }\n  .col-lg-11 {\n    width: 91.66666667%;\n  }\n  .col-lg-10 {\n    width: 83.33333333%;\n  }\n  .col-lg-9 {\n    width: 75%;\n  }\n  .col-lg-8 {\n    width: 66.66666667%;\n  }\n  .col-lg-7 {\n    width: 58.33333333%;\n  }\n  .col-lg-6 {\n    width: 50%;\n  }\n  .col-lg-5 {\n    width: 41.66666667%;\n  }\n  .col-lg-4 {\n    width: 33.33333333%;\n  }\n  .col-lg-3 {\n    width: 25%;\n  }\n  .col-lg-2 {\n    width: 16.66666667%;\n  }\n  .col-lg-1 {\n    width: 8.33333333%;\n  }\n  .col-lg-pull-12 {\n    right: 100%;\n  }\n  .col-lg-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-lg-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-lg-pull-9 {\n    right: 75%;\n  }\n  .col-lg-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-lg-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-lg-pull-6 {\n    right: 50%;\n  }\n  .col-lg-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-lg-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-lg-pull-3 {\n    right: 25%;\n  }\n  .col-lg-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-lg-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-lg-pull-0 {\n    right: auto;\n  }\n  .col-lg-push-12 {\n    left: 100%;\n  }\n  .col-lg-push-11 {\n    left: 91.66666667%;\n  }\n  .col-lg-push-10 {\n    left: 83.33333333%;\n  }\n  .col-lg-push-9 {\n    left: 75%;\n  }\n  .col-lg-push-8 {\n    left: 66.66666667%;\n  }\n  .col-lg-push-7 {\n    left: 58.33333333%;\n  }\n  .col-lg-push-6 {\n    left: 50%;\n  }\n  .col-lg-push-5 {\n    left: 41.66666667%;\n  }\n  .col-lg-push-4 {\n    left: 33.33333333%;\n  }\n  .col-lg-push-3 {\n    left: 25%;\n  }\n  .col-lg-push-2 {\n    left: 16.66666667%;\n  }\n  .col-lg-push-1 {\n    left: 8.33333333%;\n  }\n  .col-lg-push-0 {\n    left: auto;\n  }\n  .col-lg-offset-12 {\n    margin-left: 100%;\n  }\n  .col-lg-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-lg-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-lg-offset-9 {\n    margin-left: 75%;\n  }\n  .col-lg-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-lg-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-lg-offset-6 {\n    margin-left: 50%;\n  }\n  .col-lg-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-lg-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-lg-offset-3 {\n    margin-left: 25%;\n  }\n  .col-lg-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-lg-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-lg-offset-0 {\n    margin-left: 0%;\n  }\n}\ntable {\n  background-color: transparent;\n}\ntable col[class*=\"col-\"] {\n  position: static;\n  display: table-column;\n  float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n  position: static;\n  display: table-cell;\n  float: none;\n}\ncaption {\n  padding-top: 8px;\n  padding-bottom: 8px;\n  color: #777777;\n  text-align: left;\n}\nth {\n  text-align: left;\n}\n.table {\n  width: 100%;\n  max-width: 100%;\n  margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n  padding: 8px;\n  line-height: 1.42857143;\n  vertical-align: top;\n  border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n  vertical-align: bottom;\n  border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n  border-top: 0;\n}\n.table > tbody + tbody {\n  border-top: 2px solid #ddd;\n}\n.table .table {\n  background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n  padding: 5px;\n}\n.table-bordered {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n  border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n  background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n  background-color: #f5f5f5;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n  background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n  background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n  background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n  background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n  background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n  background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n  background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n  background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n  background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n  background-color: #ebcccc;\n}\n.table-responsive {\n  min-height: 0.01%;\n  overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n  .table-responsive {\n    width: 100%;\n    margin-bottom: 15px;\n    overflow-y: hidden;\n    -ms-overflow-style: -ms-autohiding-scrollbar;\n    border: 1px solid #ddd;\n  }\n  .table-responsive > .table {\n    margin-bottom: 0;\n  }\n  .table-responsive > .table > thead > tr > th,\n  .table-responsive > .table > tbody > tr > th,\n  .table-responsive > .table > tfoot > tr > th,\n  .table-responsive > .table > thead > tr > td,\n  .table-responsive > .table > tbody > tr > td,\n  .table-responsive > .table > tfoot > tr > td {\n    white-space: nowrap;\n  }\n  .table-responsive > .table-bordered {\n    border: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:first-child,\n  .table-responsive > .table-bordered > tbody > tr > th:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n  .table-responsive > .table-bordered > thead > tr > td:first-child,\n  .table-responsive > .table-bordered > tbody > tr > td:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n    border-left: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:last-child,\n  .table-responsive > .table-bordered > tbody > tr > th:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n  .table-responsive > .table-bordered > thead > tr > td:last-child,\n  .table-responsive > .table-bordered > tbody > tr > td:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n    border-right: 0;\n  }\n  .table-responsive > .table-bordered > tbody > tr:last-child > th,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n  .table-responsive > .table-bordered > tbody > tr:last-child > td,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n    border-bottom: 0;\n  }\n}\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 20px;\n  font-size: 21px;\n  line-height: inherit;\n  color: #333333;\n  border: 0;\n  border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n  display: inline-block;\n  max-width: 100%;\n  margin-bottom: 5px;\n  font-weight: 700;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: border-box;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9;\n  line-height: normal;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n  cursor: not-allowed;\n}\ninput[type=\"file\"] {\n  display: block;\n}\ninput[type=\"range\"] {\n  display: block;\n  width: 100%;\n}\nselect[multiple],\nselect[size] {\n  height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\noutput {\n  display: block;\n  padding-top: 7px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555555;\n}\n.form-control {\n  display: block;\n  width: 100%;\n  height: 34px;\n  padding: 6px 12px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555555;\n  background-color: #fff;\n  background-image: none;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n  -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n  -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n  transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n  border-color: #66afe9;\n  outline: 0;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n  color: #999;\n  opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n  color: #999;\n}\n.form-control::-webkit-input-placeholder {\n  color: #999;\n}\n.form-control::-ms-expand {\n  background-color: transparent;\n  border: 0;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n  background-color: #eeeeee;\n  opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n  cursor: not-allowed;\n}\ntextarea.form-control {\n  height: auto;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n  input[type=\"date\"].form-control,\n  input[type=\"time\"].form-control,\n  input[type=\"datetime-local\"].form-control,\n  input[type=\"month\"].form-control {\n    line-height: 34px;\n  }\n  input[type=\"date\"].input-sm,\n  input[type=\"time\"].input-sm,\n  input[type=\"datetime-local\"].input-sm,\n  input[type=\"month\"].input-sm,\n  .input-group-sm input[type=\"date\"],\n  .input-group-sm input[type=\"time\"],\n  .input-group-sm input[type=\"datetime-local\"],\n  .input-group-sm input[type=\"month\"] {\n    line-height: 30px;\n  }\n  input[type=\"date\"].input-lg,\n  input[type=\"time\"].input-lg,\n  input[type=\"datetime-local\"].input-lg,\n  input[type=\"month\"].input-lg,\n  .input-group-lg input[type=\"date\"],\n  .input-group-lg input[type=\"time\"],\n  .input-group-lg input[type=\"datetime-local\"],\n  .input-group-lg input[type=\"month\"] {\n    line-height: 46px;\n  }\n}\n.form-group {\n  margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n  position: relative;\n  display: block;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n  cursor: not-allowed;\n}\n.radio label,\n.checkbox label {\n  min-height: 20px;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: 400;\n  cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  position: absolute;\n  margin-top: 4px \\9;\n  margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n  position: relative;\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: 400;\n  vertical-align: middle;\n  cursor: pointer;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n  cursor: not-allowed;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px;\n}\n.form-control-static {\n  min-height: 34px;\n  padding-top: 7px;\n  padding-bottom: 7px;\n  margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-sm {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-sm {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n  height: auto;\n}\n.form-group-sm .form-control {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.form-group-sm select.form-control {\n  height: 30px;\n  line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n  height: auto;\n}\n.form-group-sm .form-control-static {\n  height: 30px;\n  min-height: 32px;\n  padding: 6px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n}\n.input-lg {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-lg {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n  height: auto;\n}\n.form-group-lg .form-control {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\n.form-group-lg select.form-control {\n  height: 46px;\n  line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n  height: auto;\n}\n.form-group-lg .form-control-static {\n  height: 46px;\n  min-height: 38px;\n  padding: 11px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n}\n.has-feedback {\n  position: relative;\n}\n.has-feedback .form-control {\n  padding-right: 42.5px;\n}\n.form-control-feedback {\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: 2;\n  display: block;\n  width: 34px;\n  height: 34px;\n  line-height: 34px;\n  text-align: center;\n  pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n  width: 46px;\n  height: 46px;\n  line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n  width: 30px;\n  height: 30px;\n  line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n  color: #3c763d;\n}\n.has-success .form-control {\n  border-color: #3c763d;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n  border-color: #2b542c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n  color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n  color: #8a6d3b;\n}\n.has-warning .form-control {\n  border-color: #8a6d3b;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n  border-color: #66512c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n  color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n  color: #a94442;\n}\n.has-error .form-control {\n  border-color: #a94442;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n  border-color: #843534;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #a94442;\n}\n.has-error .form-control-feedback {\n  color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n  top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n  top: 0;\n}\n.help-block {\n  display: block;\n  margin-top: 5px;\n  margin-bottom: 10px;\n  color: #737373;\n}\n@media (min-width: 768px) {\n  .form-inline .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .form-inline .form-control-static {\n    display: inline-block;\n  }\n  .form-inline .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .form-inline .input-group .input-group-addon,\n  .form-inline .input-group .input-group-btn,\n  .form-inline .input-group .form-control {\n    width: auto;\n  }\n  .form-inline .input-group > .form-control {\n    width: 100%;\n  }\n  .form-inline .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio,\n  .form-inline .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio label,\n  .form-inline .checkbox label {\n    padding-left: 0;\n  }\n  .form-inline .radio input[type=\"radio\"],\n  .form-inline .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .form-inline .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n  padding-top: 7px;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n  min-height: 27px;\n}\n.form-horizontal .form-group {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .control-label {\n    padding-top: 7px;\n    margin-bottom: 0;\n    text-align: right;\n  }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n  right: 15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-lg .control-label {\n    padding-top: 11px;\n    font-size: 18px;\n  }\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-sm .control-label {\n    padding-top: 6px;\n    font-size: 12px;\n  }\n}\n.btn {\n  display: inline-block;\n  margin-bottom: 0;\n  font-weight: normal;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  -ms-touch-action: manipulation;\n  touch-action: manipulation;\n  cursor: pointer;\n  background-image: none;\n  border: 1px solid transparent;\n  padding: 6px 12px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  border-radius: 4px;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n  color: #333;\n  text-decoration: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n  outline: 0;\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n  cursor: not-allowed;\n  filter: alpha(opacity=65);\n  opacity: 0.65;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n  pointer-events: none;\n}\n.btn-default {\n  color: #333;\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #8c8c8c;\n}\n.btn-default:hover {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  color: #333;\n  background-color: #e6e6e6;\n  background-image: none;\n  border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n  color: #333;\n  background-color: #d4d4d4;\n  border-color: #8c8c8c;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default .badge {\n  color: #fff;\n  background-color: #333;\n}\n.btn-primary {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n  color: #fff;\n  background-color: #286090;\n  border-color: #122b40;\n}\n.btn-primary:hover {\n  color: #fff;\n  background-color: #286090;\n  border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  color: #fff;\n  background-color: #286090;\n  background-image: none;\n  border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n  color: #fff;\n  background-color: #204d74;\n  border-color: #122b40;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.btn-success {\n  color: #fff;\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #255625;\n}\n.btn-success:hover {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  color: #fff;\n  background-color: #449d44;\n  background-image: none;\n  border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n  color: #fff;\n  background-color: #398439;\n  border-color: #255625;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success .badge {\n  color: #5cb85c;\n  background-color: #fff;\n}\n.btn-info {\n  color: #fff;\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #1b6d85;\n}\n.btn-info:hover {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  color: #fff;\n  background-color: #31b0d5;\n  background-image: none;\n  border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n  color: #fff;\n  background-color: #269abc;\n  border-color: #1b6d85;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info .badge {\n  color: #5bc0de;\n  background-color: #fff;\n}\n.btn-warning {\n  color: #fff;\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #985f0d;\n}\n.btn-warning:hover {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  color: #fff;\n  background-color: #ec971f;\n  background-image: none;\n  border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n  color: #fff;\n  background-color: #d58512;\n  border-color: #985f0d;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning .badge {\n  color: #f0ad4e;\n  background-color: #fff;\n}\n.btn-danger {\n  color: #fff;\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #761c19;\n}\n.btn-danger:hover {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  color: #fff;\n  background-color: #c9302c;\n  background-image: none;\n  border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n  color: #fff;\n  background-color: #ac2925;\n  border-color: #761c19;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger .badge {\n  color: #d9534f;\n  background-color: #fff;\n}\n.btn-link {\n  font-weight: 400;\n  color: #337ab7;\n  border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n  background-color: transparent;\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n  border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n  color: #23527c;\n  text-decoration: underline;\n  background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n  color: #777777;\n  text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n  padding: 1px 5px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-block {\n  display: block;\n  width: 100%;\n}\n.btn-block + .btn-block {\n  margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n  width: 100%;\n}\n.fade {\n  opacity: 0;\n  -webkit-transition: opacity 0.15s linear;\n  -o-transition: opacity 0.15s linear;\n  transition: opacity 0.15s linear;\n}\n.fade.in {\n  opacity: 1;\n}\n.collapse {\n  display: none;\n}\n.collapse.in {\n  display: block;\n}\ntr.collapse.in {\n  display: table-row;\n}\ntbody.collapse.in {\n  display: table-row-group;\n}\n.collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  -webkit-transition-property: height, visibility;\n  -o-transition-property: height, visibility;\n  transition-property: height, visibility;\n  -webkit-transition-duration: 0.35s;\n  -o-transition-duration: 0.35s;\n  transition-duration: 0.35s;\n  -webkit-transition-timing-function: ease;\n  -o-transition-timing-function: ease;\n  transition-timing-function: ease;\n}\n.caret {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 2px;\n  vertical-align: middle;\n  border-top: 4px dashed;\n  border-top: 4px solid \\9;\n  border-right: 4px solid transparent;\n  border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n  position: relative;\n}\n.dropdown-toggle:focus {\n  outline: 0;\n}\n.dropdown-menu {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: 1000;\n  display: none;\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0;\n  font-size: 14px;\n  text-align: left;\n  list-style: none;\n  background-color: #fff;\n  background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, 0.15);\n  border-radius: 4px;\n  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n}\n.dropdown-menu.pull-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu .divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n  display: block;\n  padding: 3px 20px;\n  clear: both;\n  font-weight: 400;\n  line-height: 1.42857143;\n  color: #333333;\n  white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  color: #262626;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  color: #fff;\n  text-decoration: none;\n  background-color: #337ab7;\n  outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n  background-image: none;\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n  display: block;\n}\n.open > a {\n  outline: 0;\n}\n.dropdown-menu-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu-left {\n  right: auto;\n  left: 0;\n}\n.dropdown-header {\n  display: block;\n  padding: 3px 20px;\n  font-size: 12px;\n  line-height: 1.42857143;\n  color: #777777;\n  white-space: nowrap;\n}\n.dropdown-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 990;\n}\n.pull-right > .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n  content: \"\";\n  border-top: 0;\n  border-bottom: 4px dashed;\n  border-bottom: 4px solid \\9;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n  top: auto;\n  bottom: 100%;\n  margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n  .navbar-right .dropdown-menu {\n    right: 0;\n    left: auto;\n  }\n  .navbar-right .dropdown-menu-left {\n    right: auto;\n    left: 0;\n  }\n}\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n  position: relative;\n  float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n  z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n  margin-left: -1px;\n}\n.btn-toolbar {\n  margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n  float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n  margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n  border-radius: 0;\n}\n.btn-group > .btn:first-child {\n  margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n  float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n  outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n  padding-right: 8px;\n  padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n  padding-right: 12px;\n  padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n  -webkit-box-shadow: none;\n  box-shadow: none;\n}\n.btn .caret {\n  margin-left: 0;\n}\n.btn-lg .caret {\n  border-width: 5px 5px 0;\n  border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n  border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n  display: block;\n  float: none;\n  width: 100%;\n  max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n  float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n  margin-top: -1px;\n  margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.btn-group-justified {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n  border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n  display: table-cell;\n  float: none;\n  width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n  width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n  left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n}\n.input-group {\n  position: relative;\n  display: table;\n  border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n  float: none;\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-group .form-control {\n  position: relative;\n  z-index: 2;\n  float: left;\n  width: 100%;\n  margin-bottom: 0;\n}\n.input-group .form-control:focus {\n  z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n  display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n  width: 1%;\n  white-space: nowrap;\n  vertical-align: middle;\n}\n.input-group-addon {\n  padding: 6px 12px;\n  font-size: 14px;\n  font-weight: 400;\n  line-height: 1;\n  color: #555555;\n  text-align: center;\n  background-color: #eeeeee;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\n.input-group-addon.input-sm {\n  padding: 5px 10px;\n  font-size: 12px;\n  border-radius: 3px;\n}\n.input-group-addon.input-lg {\n  padding: 10px 16px;\n  font-size: 18px;\n  border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n  margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n  border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n  border-left: 0;\n}\n.input-group-btn {\n  position: relative;\n  font-size: 0;\n  white-space: nowrap;\n}\n.input-group-btn > .btn {\n  position: relative;\n}\n.input-group-btn > .btn + .btn {\n  margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n  z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n  margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n  z-index: 2;\n  margin-left: -1px;\n}\n.nav {\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n.nav > li {\n  position: relative;\n  display: block;\n}\n.nav > li > a {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n  text-decoration: none;\n  background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n  color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n  color: #777777;\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n  background-color: #eeeeee;\n  border-color: #337ab7;\n}\n.nav .nav-divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.nav > li > a > img {\n  max-width: none;\n}\n.nav-tabs {\n  border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n  float: left;\n  margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n  margin-right: 2px;\n  line-height: 1.42857143;\n  border: 1px solid transparent;\n  border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n  border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n  color: #555555;\n  cursor: default;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n  width: 100%;\n  border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n  float: none;\n}\n.nav-tabs.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-tabs.nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs.nav-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs.nav-justified > .active > a,\n  .nav-tabs.nav-justified > .active > a:hover,\n  .nav-tabs.nav-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.nav-pills > li {\n  float: left;\n}\n.nav-pills > li > a {\n  border-radius: 4px;\n}\n.nav-pills > li + li {\n  margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n  color: #fff;\n  background-color: #337ab7;\n}\n.nav-stacked > li {\n  float: none;\n}\n.nav-stacked > li + li {\n  margin-top: 2px;\n  margin-left: 0;\n}\n.nav-justified {\n  width: 100%;\n}\n.nav-justified > li {\n  float: none;\n}\n.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs-justified {\n  border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs-justified > .active > a,\n  .nav-tabs-justified > .active > a:hover,\n  .nav-tabs-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.tab-content > .tab-pane {\n  display: none;\n}\n.tab-content > .active {\n  display: block;\n}\n.nav-tabs .dropdown-menu {\n  margin-top: -1px;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar {\n  position: relative;\n  min-height: 50px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n  .navbar {\n    border-radius: 4px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-header {\n    float: left;\n  }\n}\n.navbar-collapse {\n  padding-right: 15px;\n  padding-left: 15px;\n  overflow-x: visible;\n  border-top: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n  -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n  overflow-y: auto;\n}\n@media (min-width: 768px) {\n  .navbar-collapse {\n    width: auto;\n    border-top: 0;\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n  .navbar-collapse.collapse {\n    display: block !important;\n    height: auto !important;\n    padding-bottom: 0;\n    overflow: visible !important;\n  }\n  .navbar-collapse.in {\n    overflow-y: visible;\n  }\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-static-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    padding-right: 0;\n    padding-left: 0;\n  }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  position: fixed;\n  right: 0;\n  left: 0;\n  z-index: 1030;\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n  max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    max-height: 200px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-fixed-top,\n  .navbar-fixed-bottom {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top {\n  top: 0;\n  border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n  bottom: 0;\n  margin-bottom: 0;\n  border-width: 1px 0 0;\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .container > .navbar-header,\n  .container-fluid > .navbar-header,\n  .container > .navbar-collapse,\n  .container-fluid > .navbar-collapse {\n    margin-right: 0;\n    margin-left: 0;\n  }\n}\n.navbar-static-top {\n  z-index: 1000;\n  border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n  .navbar-static-top {\n    border-radius: 0;\n  }\n}\n.navbar-brand {\n  float: left;\n  height: 50px;\n  padding: 15px 15px;\n  font-size: 18px;\n  line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n  text-decoration: none;\n}\n.navbar-brand > img {\n  display: block;\n}\n@media (min-width: 768px) {\n  .navbar > .container .navbar-brand,\n  .navbar > .container-fluid .navbar-brand {\n    margin-left: -15px;\n  }\n}\n.navbar-toggle {\n  position: relative;\n  float: right;\n  padding: 9px 10px;\n  margin-right: 15px;\n  margin-top: 8px;\n  margin-bottom: 8px;\n  background-color: transparent;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.navbar-toggle:focus {\n  outline: 0;\n}\n.navbar-toggle .icon-bar {\n  display: block;\n  width: 22px;\n  height: 2px;\n  border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n  margin-top: 4px;\n}\n@media (min-width: 768px) {\n  .navbar-toggle {\n    display: none;\n  }\n}\n.navbar-nav {\n  margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n  padding-top: 10px;\n  padding-bottom: 10px;\n  line-height: 20px;\n}\n@media (max-width: 767px) {\n  .navbar-nav .open .dropdown-menu {\n    position: static;\n    float: none;\n    width: auto;\n    margin-top: 0;\n    background-color: transparent;\n    border: 0;\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n  .navbar-nav .open .dropdown-menu > li > a,\n  .navbar-nav .open .dropdown-menu .dropdown-header {\n    padding: 5px 15px 5px 25px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a {\n    line-height: 20px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-nav .open .dropdown-menu > li > a:focus {\n    background-image: none;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-nav {\n    float: left;\n    margin: 0;\n  }\n  .navbar-nav > li {\n    float: left;\n  }\n  .navbar-nav > li > a {\n    padding-top: 15px;\n    padding-bottom: 15px;\n  }\n}\n.navbar-form {\n  padding: 10px 15px;\n  margin-right: -15px;\n  margin-left: -15px;\n  border-top: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n  margin-top: 8px;\n  margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n  .navbar-form .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control-static {\n    display: inline-block;\n  }\n  .navbar-form .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .navbar-form .input-group .input-group-addon,\n  .navbar-form .input-group .input-group-btn,\n  .navbar-form .input-group .form-control {\n    width: auto;\n  }\n  .navbar-form .input-group > .form-control {\n    width: 100%;\n  }\n  .navbar-form .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio,\n  .navbar-form .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio label,\n  .navbar-form .checkbox label {\n    padding-left: 0;\n  }\n  .navbar-form .radio input[type=\"radio\"],\n  .navbar-form .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .navbar-form .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n@media (max-width: 767px) {\n  .navbar-form .form-group {\n    margin-bottom: 5px;\n  }\n  .navbar-form .form-group:last-child {\n    margin-bottom: 0;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-form {\n    width: auto;\n    padding-top: 0;\n    padding-bottom: 0;\n    margin-right: 0;\n    margin-left: 0;\n    border: 0;\n    -webkit-box-shadow: none;\n    box-shadow: none;\n  }\n}\n.navbar-nav > li > .dropdown-menu {\n  margin-top: 0;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n  margin-bottom: 0;\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.navbar-btn {\n  margin-top: 8px;\n  margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n  margin-top: 14px;\n  margin-bottom: 14px;\n}\n.navbar-text {\n  margin-top: 15px;\n  margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n  .navbar-text {\n    float: left;\n    margin-right: 15px;\n    margin-left: 15px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-left {\n    float: left !important;\n  }\n  .navbar-right {\n    float: right !important;\n    margin-right: -15px;\n  }\n  .navbar-right ~ .navbar-right {\n    margin-right: 0;\n  }\n}\n.navbar-default {\n  background-color: #f8f8f8;\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n  color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n  color: #5e5e5e;\n  background-color: transparent;\n}\n.navbar-default .navbar-text {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n  color: #333;\n  background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n  color: #ccc;\n  background-color: transparent;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n    color: #777;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #333;\n    background-color: transparent;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #555;\n    background-color: #e7e7e7;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #ccc;\n    background-color: transparent;\n  }\n}\n.navbar-default .navbar-toggle {\n  border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n  background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n  background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-link {\n  color: #777;\n}\n.navbar-default .navbar-link:hover {\n  color: #333;\n}\n.navbar-default .btn-link {\n  color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n  color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n  color: #ccc;\n}\n.navbar-inverse {\n  background-color: #222;\n  border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n  color: #444;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n@media (max-width: 767px) {\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n    border-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n    color: #9d9d9d;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #fff;\n    background-color: transparent;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #444;\n    background-color: transparent;\n  }\n}\n.navbar-inverse .navbar-toggle {\n  border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n  background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n  background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n  border-color: #101010;\n}\n.navbar-inverse .navbar-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n  color: #fff;\n}\n.navbar-inverse .btn-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n  color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n  color: #444;\n}\n.breadcrumb {\n  padding: 8px 15px;\n  margin-bottom: 20px;\n  list-style: none;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n}\n.breadcrumb > li {\n  display: inline-block;\n}\n.breadcrumb > li + li:before {\n  padding: 0 5px;\n  color: #ccc;\n  content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n  color: #777777;\n}\n.pagination {\n  display: inline-block;\n  padding-left: 0;\n  margin: 20px 0;\n  border-radius: 4px;\n}\n.pagination > li {\n  display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n  position: relative;\n  float: left;\n  padding: 6px 12px;\n  margin-left: -1px;\n  line-height: 1.42857143;\n  color: #337ab7;\n  text-decoration: none;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n  z-index: 2;\n  color: #23527c;\n  background-color: #eeeeee;\n  border-color: #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n  margin-left: 0;\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n  z-index: 3;\n  color: #fff;\n  cursor: default;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n  color: #777777;\n  cursor: not-allowed;\n  background-color: #fff;\n  border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n  border-top-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n  border-top-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n}\n.pager {\n  padding-left: 0;\n  margin: 20px 0;\n  text-align: center;\n  list-style: none;\n}\n.pager li {\n  display: inline;\n}\n.pager li > a,\n.pager li > span {\n  display: inline-block;\n  padding: 5px 14px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n  text-decoration: none;\n  background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n  float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n  float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n  color: #777777;\n  cursor: not-allowed;\n  background-color: #fff;\n}\n.label {\n  display: inline;\n  padding: 0.2em 0.6em 0.3em;\n  font-size: 75%;\n  font-weight: 700;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: 0.25em;\n}\na.label:hover,\na.label:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.label:empty {\n  display: none;\n}\n.btn .label {\n  position: relative;\n  top: -1px;\n}\n.label-default {\n  background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n  background-color: #5e5e5e;\n}\n.label-primary {\n  background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n  background-color: #286090;\n}\n.label-success {\n  background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n  background-color: #449d44;\n}\n.label-info {\n  background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n  background-color: #31b0d5;\n}\n.label-warning {\n  background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n  background-color: #ec971f;\n}\n.label-danger {\n  background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n  background-color: #c9302c;\n}\n.badge {\n  display: inline-block;\n  min-width: 10px;\n  padding: 3px 7px;\n  font-size: 12px;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  background-color: #777777;\n  border-radius: 10px;\n}\n.badge:empty {\n  display: none;\n}\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n  top: 0;\n  padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.list-group-item > .badge {\n  float: right;\n}\n.list-group-item > .badge + .badge {\n  margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n  margin-left: 3px;\n}\n.jumbotron {\n  padding-top: 30px;\n  padding-bottom: 30px;\n  margin-bottom: 30px;\n  color: inherit;\n  background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n  color: inherit;\n}\n.jumbotron p {\n  margin-bottom: 15px;\n  font-size: 21px;\n  font-weight: 200;\n}\n.jumbotron > hr {\n  border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n  padding-right: 15px;\n  padding-left: 15px;\n  border-radius: 6px;\n}\n.jumbotron .container {\n  max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n  .jumbotron {\n    padding-top: 48px;\n    padding-bottom: 48px;\n  }\n  .container .jumbotron,\n  .container-fluid .jumbotron {\n    padding-right: 60px;\n    padding-left: 60px;\n  }\n  .jumbotron h1,\n  .jumbotron .h1 {\n    font-size: 63px;\n  }\n}\n.thumbnail {\n  display: block;\n  padding: 4px;\n  margin-bottom: 20px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: border 0.2s ease-in-out;\n  -o-transition: border 0.2s ease-in-out;\n  transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n  margin-right: auto;\n  margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n  border-color: #337ab7;\n}\n.thumbnail .caption {\n  padding: 9px;\n  color: #333333;\n}\n.alert {\n  padding: 15px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.alert h4 {\n  margin-top: 0;\n  color: inherit;\n}\n.alert .alert-link {\n  font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n  margin-bottom: 0;\n}\n.alert > p + p {\n  margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n  padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n  position: relative;\n  top: -2px;\n  right: -21px;\n  color: inherit;\n}\n.alert-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.alert-success hr {\n  border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n  color: #2b542c;\n}\n.alert-info {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.alert-info hr {\n  border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n  color: #245269;\n}\n.alert-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.alert-warning hr {\n  border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n  color: #66512c;\n}\n.alert-danger {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.alert-danger hr {\n  border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n  color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@-o-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n.progress {\n  height: 20px;\n  margin-bottom: 20px;\n  overflow: hidden;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n  float: left;\n  width: 0%;\n  height: 100%;\n  font-size: 12px;\n  line-height: 20px;\n  color: #fff;\n  text-align: center;\n  background-color: #337ab7;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n  -webkit-transition: width 0.6s ease;\n  -o-transition: width 0.6s ease;\n  transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  -webkit-background-size: 40px 40px;\n  background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n  -webkit-animation: progress-bar-stripes 2s linear infinite;\n  -o-animation: progress-bar-stripes 2s linear infinite;\n  animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n  background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n  background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n  background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n  background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n  margin-top: 15px;\n}\n.media:first-child {\n  margin-top: 0;\n}\n.media,\n.media-body {\n  overflow: hidden;\n  zoom: 1;\n}\n.media-body {\n  width: 10000px;\n}\n.media-object {\n  display: block;\n}\n.media-object.img-thumbnail {\n  max-width: none;\n}\n.media-right,\n.media > .pull-right {\n  padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n  padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n  display: table-cell;\n  vertical-align: top;\n}\n.media-middle {\n  vertical-align: middle;\n}\n.media-bottom {\n  vertical-align: bottom;\n}\n.media-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.media-list {\n  padding-left: 0;\n  list-style: none;\n}\n.list-group {\n  padding-left: 0;\n  margin-bottom: 20px;\n}\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n  margin-bottom: -1px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n  margin-bottom: 0;\n  border-bottom-right-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n  color: #777777;\n  cursor: not-allowed;\n  background-color: #eeeeee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n  color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n  color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  z-index: 2;\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n  color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n  color: #c7ddef;\n}\na.list-group-item,\nbutton.list-group-item {\n  color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n  color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n  color: #555;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\nbutton.list-group-item {\n  width: 100%;\n  text-align: left;\n}\n.list-group-item-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n  color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n  color: #3c763d;\n  background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n  color: #fff;\n  background-color: #3c763d;\n  border-color: #3c763d;\n}\n.list-group-item-info {\n  color: #31708f;\n  background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n  color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n  color: #31708f;\n  background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n  color: #fff;\n  background-color: #31708f;\n  border-color: #31708f;\n}\n.list-group-item-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n  color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n  color: #8a6d3b;\n  background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n  color: #fff;\n  background-color: #8a6d3b;\n  border-color: #8a6d3b;\n}\n.list-group-item-danger {\n  color: #a94442;\n  background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n  color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n  color: #a94442;\n  background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n  color: #fff;\n  background-color: #a94442;\n  border-color: #a94442;\n}\n.list-group-item-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.list-group-item-text {\n  margin-bottom: 0;\n  line-height: 1.3;\n}\n.panel {\n  margin-bottom: 20px;\n  background-color: #fff;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n  padding: 15px;\n}\n.panel-heading {\n  padding: 10px 15px;\n  border-bottom: 1px solid transparent;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n  color: inherit;\n}\n.panel-title {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-size: 16px;\n  color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n  color: inherit;\n}\n.panel-footer {\n  padding: 10px 15px;\n  background-color: #f5f5f5;\n  border-top: 1px solid #ddd;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n  margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n  border-width: 1px 0;\n  border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n  border-top: 0;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n  border-bottom: 0;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n  border-top-width: 0;\n}\n.list-group + .panel-footer {\n  border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n  margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n  border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n  border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n  border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n  border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n  border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n  border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n  border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n  border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n  border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n  border-bottom: 0;\n}\n.panel > .table-responsive {\n  margin-bottom: 0;\n  border: 0;\n}\n.panel-group {\n  margin-bottom: 20px;\n}\n.panel-group .panel {\n  margin-bottom: 0;\n  border-radius: 4px;\n}\n.panel-group .panel + .panel {\n  margin-top: 5px;\n}\n.panel-group .panel-heading {\n  border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n  border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n  border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n  border-bottom: 1px solid #ddd;\n}\n.panel-default {\n  border-color: #ddd;\n}\n.panel-default > .panel-heading {\n  color: #333333;\n  background-color: #f5f5f5;\n  border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n  color: #f5f5f5;\n  background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ddd;\n}\n.panel-primary {\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #337ab7;\n}\n.panel-success {\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n  color: #dff0d8;\n  background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #d6e9c6;\n}\n.panel-info {\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n  color: #d9edf7;\n  background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #bce8f1;\n}\n.panel-warning {\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n  color: #fcf8e3;\n  background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #faebcc;\n}\n.panel-danger {\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n  color: #f2dede;\n  background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n  position: relative;\n  display: block;\n  height: 0;\n  padding: 0;\n  overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border: 0;\n}\n.embed-responsive-16by9 {\n  padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n  padding-bottom: 75%;\n}\n.well {\n  min-height: 20px;\n  padding: 19px;\n  margin-bottom: 20px;\n  background-color: #f5f5f5;\n  border: 1px solid #e3e3e3;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n  border-color: #ddd;\n  border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n  padding: 24px;\n  border-radius: 6px;\n}\n.well-sm {\n  padding: 9px;\n  border-radius: 3px;\n}\n.close {\n  float: right;\n  font-size: 21px;\n  font-weight: bold;\n  line-height: 1;\n  color: #000;\n  text-shadow: 0 1px 0 #fff;\n  filter: alpha(opacity=20);\n  opacity: 0.2;\n}\n.close:hover,\n.close:focus {\n  color: #000;\n  text-decoration: none;\n  cursor: pointer;\n  filter: alpha(opacity=50);\n  opacity: 0.5;\n}\nbutton.close {\n  padding: 0;\n  cursor: pointer;\n  background: transparent;\n  border: 0;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n}\n.modal-open {\n  overflow: hidden;\n}\n.modal {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1050;\n  display: none;\n  overflow: hidden;\n  -webkit-overflow-scrolling: touch;\n  outline: 0;\n}\n.modal.fade .modal-dialog {\n  -webkit-transform: translate(0, -25%);\n  -ms-transform: translate(0, -25%);\n  -o-transform: translate(0, -25%);\n  transform: translate(0, -25%);\n  -webkit-transition: -webkit-transform 0.3s ease-out;\n  -o-transition: -o-transform 0.3s ease-out;\n  transition: -webkit-transform 0.3s ease-out;\n  transition: transform 0.3s ease-out;\n  transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out, -o-transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n  -webkit-transform: translate(0, 0);\n  -ms-transform: translate(0, 0);\n  -o-transform: translate(0, 0);\n  transform: translate(0, 0);\n}\n.modal-open .modal {\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: 10px;\n}\n.modal-content {\n  position: relative;\n  background-color: #fff;\n  background-clip: padding-box;\n  border: 1px solid #999;\n  border: 1px solid rgba(0, 0, 0, 0.2);\n  border-radius: 6px;\n  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n  box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n  outline: 0;\n}\n.modal-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1040;\n  background-color: #000;\n}\n.modal-backdrop.fade {\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.modal-backdrop.in {\n  filter: alpha(opacity=50);\n  opacity: 0.5;\n}\n.modal-header {\n  padding: 15px;\n  border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n  margin-top: -2px;\n}\n.modal-title {\n  margin: 0;\n  line-height: 1.42857143;\n}\n.modal-body {\n  position: relative;\n  padding: 15px;\n}\n.modal-footer {\n  padding: 15px;\n  text-align: right;\n  border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n  margin-bottom: 0;\n  margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n  margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n  margin-left: 0;\n}\n.modal-scrollbar-measure {\n  position: absolute;\n  top: -9999px;\n  width: 50px;\n  height: 50px;\n  overflow: scroll;\n}\n@media (min-width: 768px) {\n  .modal-dialog {\n    width: 600px;\n    margin: 30px auto;\n  }\n  .modal-content {\n    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n  }\n  .modal-sm {\n    width: 300px;\n  }\n}\n@media (min-width: 992px) {\n  .modal-lg {\n    width: 900px;\n  }\n}\n.tooltip {\n  position: absolute;\n  z-index: 1070;\n  display: block;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1.42857143;\n  line-break: auto;\n  text-align: left;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  word-spacing: normal;\n  word-wrap: normal;\n  white-space: normal;\n  font-size: 12px;\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.tooltip.in {\n  filter: alpha(opacity=90);\n  opacity: 0.9;\n}\n.tooltip.top {\n  padding: 5px 0;\n  margin-top: -3px;\n}\n.tooltip.right {\n  padding: 0 5px;\n  margin-left: 3px;\n}\n.tooltip.bottom {\n  padding: 5px 0;\n  margin-top: 3px;\n}\n.tooltip.left {\n  padding: 0 5px;\n  margin-left: -3px;\n}\n.tooltip.top .tooltip-arrow {\n  bottom: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n  right: 5px;\n  bottom: 0;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n  bottom: 0;\n  left: 5px;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n  top: 50%;\n  left: 0;\n  margin-top: -5px;\n  border-width: 5px 5px 5px 0;\n  border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n  top: 50%;\n  right: 0;\n  margin-top: -5px;\n  border-width: 5px 0 5px 5px;\n  border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n  top: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n  top: 0;\n  right: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n  top: 0;\n  left: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip-inner {\n  max-width: 200px;\n  padding: 3px 8px;\n  color: #fff;\n  text-align: center;\n  background-color: #000;\n  border-radius: 4px;\n}\n.tooltip-arrow {\n  position: absolute;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.popover {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 1060;\n  display: none;\n  max-width: 276px;\n  padding: 1px;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1.42857143;\n  line-break: auto;\n  text-align: left;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  word-spacing: normal;\n  word-wrap: normal;\n  white-space: normal;\n  font-size: 14px;\n  background-color: #fff;\n  background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, 0.2);\n  border-radius: 6px;\n  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n  margin-top: -10px;\n}\n.popover.right {\n  margin-left: 10px;\n}\n.popover.bottom {\n  margin-top: 10px;\n}\n.popover.left {\n  margin-left: -10px;\n}\n.popover > .arrow {\n  border-width: 11px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n  position: absolute;\n  display: block;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.popover > .arrow:after {\n  content: \"\";\n  border-width: 10px;\n}\n.popover.top > .arrow {\n  bottom: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-color: #999999;\n  border-top-color: rgba(0, 0, 0, 0.25);\n  border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n  bottom: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-color: #fff;\n  border-bottom-width: 0;\n}\n.popover.right > .arrow {\n  top: 50%;\n  left: -11px;\n  margin-top: -11px;\n  border-right-color: #999999;\n  border-right-color: rgba(0, 0, 0, 0.25);\n  border-left-width: 0;\n}\n.popover.right > .arrow:after {\n  bottom: -10px;\n  left: 1px;\n  content: \" \";\n  border-right-color: #fff;\n  border-left-width: 0;\n}\n.popover.bottom > .arrow {\n  top: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-width: 0;\n  border-bottom-color: #999999;\n  border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n.popover.bottom > .arrow:after {\n  top: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-width: 0;\n  border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n  top: 50%;\n  right: -11px;\n  margin-top: -11px;\n  border-right-width: 0;\n  border-left-color: #999999;\n  border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n  right: 1px;\n  bottom: -10px;\n  content: \" \";\n  border-right-width: 0;\n  border-left-color: #fff;\n}\n.popover-title {\n  padding: 8px 14px;\n  margin: 0;\n  font-size: 14px;\n  background-color: #f7f7f7;\n  border-bottom: 1px solid #ebebeb;\n  border-radius: 5px 5px 0 0;\n}\n.popover-content {\n  padding: 9px 14px;\n}\n.carousel {\n  position: relative;\n}\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n.carousel-inner > .item {\n  position: relative;\n  display: none;\n  -webkit-transition: 0.6s ease-in-out left;\n  -o-transition: 0.6s ease-in-out left;\n  transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n  .carousel-inner > .item {\n    -webkit-transition: -webkit-transform 0.6s ease-in-out;\n    -o-transition: -o-transform 0.6s ease-in-out;\n    transition: -webkit-transform 0.6s ease-in-out;\n    transition: transform 0.6s ease-in-out;\n    transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out, -o-transform 0.6s ease-in-out;\n    -webkit-backface-visibility: hidden;\n    backface-visibility: hidden;\n    -webkit-perspective: 1000px;\n    perspective: 1000px;\n  }\n  .carousel-inner > .item.next,\n  .carousel-inner > .item.active.right {\n    -webkit-transform: translate3d(100%, 0, 0);\n    transform: translate3d(100%, 0, 0);\n    left: 0;\n  }\n  .carousel-inner > .item.prev,\n  .carousel-inner > .item.active.left {\n    -webkit-transform: translate3d(-100%, 0, 0);\n    transform: translate3d(-100%, 0, 0);\n    left: 0;\n  }\n  .carousel-inner > .item.next.left,\n  .carousel-inner > .item.prev.right,\n  .carousel-inner > .item.active {\n    -webkit-transform: translate3d(0, 0, 0);\n    transform: translate3d(0, 0, 0);\n    left: 0;\n  }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  display: block;\n}\n.carousel-inner > .active {\n  left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.carousel-inner > .next {\n  left: 100%;\n}\n.carousel-inner > .prev {\n  left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n  left: 0;\n}\n.carousel-inner > .active.left {\n  left: -100%;\n}\n.carousel-inner > .active.right {\n  left: 100%;\n}\n.carousel-control {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 15%;\n  font-size: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n  background-color: rgba(0, 0, 0, 0);\n  filter: alpha(opacity=50);\n  opacity: 0.5;\n}\n.carousel-control.left {\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n  background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001)));\n  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control.right {\n  right: 0;\n  left: auto;\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n  background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5)));\n  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n  color: #fff;\n  text-decoration: none;\n  outline: 0;\n  filter: alpha(opacity=90);\n  opacity: 0.9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n  position: absolute;\n  top: 50%;\n  z-index: 5;\n  display: inline-block;\n  margin-top: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n  left: 50%;\n  margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n  right: 50%;\n  margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n  width: 20px;\n  height: 20px;\n  font-family: serif;\n  line-height: 1;\n}\n.carousel-control .icon-prev:before {\n  content: \"\\2039\";\n}\n.carousel-control .icon-next:before {\n  content: \"\\203a\";\n}\n.carousel-indicators {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  z-index: 15;\n  width: 60%;\n  padding-left: 0;\n  margin-left: -30%;\n  text-align: center;\n  list-style: none;\n}\n.carousel-indicators li {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  margin: 1px;\n  text-indent: -999px;\n  cursor: pointer;\n  background-color: #000 \\9;\n  background-color: rgba(0, 0, 0, 0);\n  border: 1px solid #fff;\n  border-radius: 10px;\n}\n.carousel-indicators .active {\n  width: 12px;\n  height: 12px;\n  margin: 0;\n  background-color: #fff;\n}\n.carousel-caption {\n  position: absolute;\n  right: 15%;\n  bottom: 20px;\n  left: 15%;\n  z-index: 10;\n  padding-top: 20px;\n  padding-bottom: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n  text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-prev,\n  .carousel-control .icon-next {\n    width: 30px;\n    height: 30px;\n    margin-top: -10px;\n    font-size: 30px;\n  }\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .icon-prev {\n    margin-left: -10px;\n  }\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-next {\n    margin-right: -10px;\n  }\n  .carousel-caption {\n    right: 20%;\n    left: 20%;\n    padding-bottom: 30px;\n  }\n  .carousel-indicators {\n    bottom: 20px;\n  }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n  display: table;\n  content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n  clear: both;\n}\n.center-block {\n  display: block;\n  margin-right: auto;\n  margin-left: auto;\n}\n.pull-right {\n  float: right !important;\n}\n.pull-left {\n  float: left !important;\n}\n.hide {\n  display: none !important;\n}\n.show {\n  display: block !important;\n}\n.invisible {\n  visibility: hidden;\n}\n.text-hide {\n  font: 0/0 a;\n  color: transparent;\n  text-shadow: none;\n  background-color: transparent;\n  border: 0;\n}\n.hidden {\n  display: none !important;\n}\n.affix {\n  position: fixed;\n}\n@-ms-viewport {\n  width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n  display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n  display: none !important;\n}\n@media (max-width: 767px) {\n  .visible-xs {\n    display: block !important;\n  }\n  table.visible-xs {\n    display: table !important;\n  }\n  tr.visible-xs {\n    display: table-row !important;\n  }\n  th.visible-xs,\n  td.visible-xs {\n    display: table-cell !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-block {\n    display: block !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline {\n    display: inline !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm {\n    display: block !important;\n  }\n  table.visible-sm {\n    display: table !important;\n  }\n  tr.visible-sm {\n    display: table-row !important;\n  }\n  th.visible-sm,\n  td.visible-sm {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-block {\n    display: block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md {\n    display: block !important;\n  }\n  table.visible-md {\n    display: table !important;\n  }\n  tr.visible-md {\n    display: table-row !important;\n  }\n  th.visible-md,\n  td.visible-md {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-block {\n    display: block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg {\n    display: block !important;\n  }\n  table.visible-lg {\n    display: table !important;\n  }\n  tr.visible-lg {\n    display: table-row !important;\n  }\n  th.visible-lg,\n  td.visible-lg {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-block {\n    display: block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (max-width: 767px) {\n  .hidden-xs {\n    display: none !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .hidden-sm {\n    display: none !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .hidden-md {\n    display: none !important;\n  }\n}\n@media (min-width: 1200px) {\n  .hidden-lg {\n    display: none !important;\n  }\n}\n.visible-print {\n  display: none !important;\n}\n@media print {\n  .visible-print {\n    display: block !important;\n  }\n  table.visible-print {\n    display: table !important;\n  }\n  tr.visible-print {\n    display: table-row !important;\n  }\n  th.visible-print,\n  td.visible-print {\n    display: table-cell !important;\n  }\n}\n.visible-print-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-block {\n    display: block !important;\n  }\n}\n.visible-print-inline {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline {\n    display: inline !important;\n  }\n}\n.visible-print-inline-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline-block {\n    display: inline-block !important;\n  }\n}\n@media print {\n  .hidden-print {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap.css.map */"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/daterangepicker/daterangepicker.css",
    "content": ".daterangepicker {\n  position: absolute;\n  color: inherit;\n  background-color: #fff;\n  border-radius: 4px;\n  border: 1px solid #ddd;\n  width: 278px;\n  max-width: none;\n  padding: 0;\n  margin-top: 7px;\n  top: 100px;\n  left: 20px;\n  z-index: 3001;\n  display: none;\n  font-family: arial;\n  font-size: 15px;\n  line-height: 1em;\n}\n\n.daterangepicker:before, .daterangepicker:after {\n  position: absolute;\n  display: inline-block;\n  border-bottom-color: rgba(0, 0, 0, 0.2);\n  content: '';\n}\n\n.daterangepicker:before {\n  top: -7px;\n  border-right: 7px solid transparent;\n  border-left: 7px solid transparent;\n  border-bottom: 7px solid #ccc;\n}\n\n.daterangepicker:after {\n  top: -6px;\n  border-right: 6px solid transparent;\n  border-bottom: 6px solid #fff;\n  border-left: 6px solid transparent;\n}\n\n.daterangepicker.opensleft:before {\n  right: 9px;\n}\n\n.daterangepicker.opensleft:after {\n  right: 10px;\n}\n\n.daterangepicker.openscenter:before {\n  left: 0;\n  right: 0;\n  width: 0;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.daterangepicker.openscenter:after {\n  left: 0;\n  right: 0;\n  width: 0;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.daterangepicker.opensright:before {\n  left: 9px;\n}\n\n.daterangepicker.opensright:after {\n  left: 10px;\n}\n\n.daterangepicker.drop-up {\n  margin-top: -7px;\n}\n\n.daterangepicker.drop-up:before {\n  top: initial;\n  bottom: -7px;\n  border-bottom: initial;\n  border-top: 7px solid #ccc;\n}\n\n.daterangepicker.drop-up:after {\n  top: initial;\n  bottom: -6px;\n  border-bottom: initial;\n  border-top: 6px solid #fff;\n}\n\n.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar {\n  float: none;\n}\n\n.daterangepicker.single .drp-selected {\n  display: none;\n}\n\n.daterangepicker.show-calendar .drp-calendar {\n  display: block;\n}\n\n.daterangepicker.show-calendar .drp-buttons {\n  display: block;\n}\n\n.daterangepicker.auto-apply .drp-buttons {\n  display: none;\n}\n\n.daterangepicker .drp-calendar {\n  display: none;\n  max-width: 270px;\n}\n\n.daterangepicker .drp-calendar.left {\n  padding: 8px 0 8px 8px;\n}\n\n.daterangepicker .drp-calendar.right {\n  padding: 8px;\n}\n\n.daterangepicker .drp-calendar.single .calendar-table {\n  border: none;\n}\n\n.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span {\n  color: #fff;\n  border: solid black;\n  border-width: 0 2px 2px 0;\n  border-radius: 0;\n  display: inline-block;\n  padding: 3px;\n}\n\n.daterangepicker .calendar-table .next span {\n  transform: rotate(-45deg);\n  -webkit-transform: rotate(-45deg);\n}\n\n.daterangepicker .calendar-table .prev span {\n  transform: rotate(135deg);\n  -webkit-transform: rotate(135deg);\n}\n\n.daterangepicker .calendar-table th, .daterangepicker .calendar-table td {\n  white-space: nowrap;\n  text-align: center;\n  vertical-align: middle;\n  min-width: 32px;\n  width: 32px;\n  height: 24px;\n  line-height: 24px;\n  font-size: 12px;\n  border-radius: 4px;\n  border: 1px solid transparent;\n  white-space: nowrap;\n  cursor: pointer;\n}\n\n.daterangepicker .calendar-table {\n  border: 1px solid #fff;\n  border-radius: 4px;\n  background-color: #fff;\n}\n\n.daterangepicker .calendar-table table {\n  width: 100%;\n  margin: 0;\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n\n.daterangepicker td.available:hover, .daterangepicker th.available:hover {\n  background-color: #eee;\n  border-color: transparent;\n  color: inherit;\n}\n\n.daterangepicker td.week, .daterangepicker th.week {\n  font-size: 80%;\n  color: #ccc;\n}\n\n.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {\n  background-color: #fff;\n  border-color: transparent;\n  color: #999;\n}\n\n.daterangepicker td.in-range {\n  background-color: #ebf4f8;\n  border-color: transparent;\n  color: #000;\n  border-radius: 0;\n}\n\n.daterangepicker td.start-date {\n  border-radius: 4px 0 0 4px;\n}\n\n.daterangepicker td.end-date {\n  border-radius: 0 4px 4px 0;\n}\n\n.daterangepicker td.start-date.end-date {\n  border-radius: 4px;\n}\n\n.daterangepicker td.active, .daterangepicker td.active:hover {\n  background-color: #357ebd;\n  border-color: transparent;\n  color: #fff;\n}\n\n.daterangepicker th.month {\n  width: auto;\n}\n\n.daterangepicker td.disabled, .daterangepicker option.disabled {\n  color: #999;\n  cursor: not-allowed;\n  text-decoration: line-through;\n}\n\n.daterangepicker select.monthselect, .daterangepicker select.yearselect {\n  font-size: 12px;\n  padding: 1px;\n  height: auto;\n  margin: 0;\n  cursor: default;\n}\n\n.daterangepicker select.monthselect {\n  margin-right: 2%;\n  width: 56%;\n}\n\n.daterangepicker select.yearselect {\n  width: 40%;\n}\n\n.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {\n  width: 50px;\n  margin: 0 auto;\n  background: #eee;\n  border: 1px solid #eee;\n  padding: 2px;\n  outline: 0;\n  font-size: 12px;\n}\n\n.daterangepicker .calendar-time {\n  text-align: center;\n  margin: 4px auto 0 auto;\n  line-height: 30px;\n  position: relative;\n}\n\n.daterangepicker .calendar-time select.disabled {\n  color: #ccc;\n  cursor: not-allowed;\n}\n\n.daterangepicker .drp-buttons {\n  clear: both;\n  text-align: right;\n  padding: 8px;\n  border-top: 1px solid #ddd;\n  display: none;\n  line-height: 12px;\n  vertical-align: middle;\n}\n\n.daterangepicker .drp-selected {\n  display: inline-block;\n  font-size: 12px;\n  padding-right: 8px;\n}\n\n.daterangepicker .drp-buttons .btn {\n  margin-left: 8px;\n  font-size: 12px;\n  font-weight: bold;\n  padding: 4px 8px;\n}\n\n.daterangepicker.show-ranges.single.rtl .drp-calendar.left {\n  border-right: 1px solid #ddd;\n}\n\n.daterangepicker.show-ranges.single.ltr .drp-calendar.left {\n  border-left: 1px solid #ddd;\n}\n\n.daterangepicker.show-ranges.rtl .drp-calendar.right {\n  border-right: 1px solid #ddd;\n}\n\n.daterangepicker.show-ranges.ltr .drp-calendar.left {\n  border-left: 1px solid #ddd;\n}\n\n.daterangepicker .ranges {\n  float: none;\n  text-align: left;\n  margin: 0;\n}\n\n.daterangepicker.show-calendar .ranges {\n  margin-top: 8px;\n}\n\n.daterangepicker .ranges ul {\n  list-style: none;\n  margin: 0 auto;\n  padding: 0;\n  width: 100%;\n}\n\n.daterangepicker .ranges li {\n  font-size: 12px;\n  padding: 8px 12px;\n  cursor: pointer;\n}\n\n.daterangepicker .ranges li:hover {\n  background-color: #eee;\n}\n\n.daterangepicker .ranges li.active {\n  background-color: #08c;\n  color: #fff;\n}\n\n/*  Larger Screen Styling */\n@media (min-width: 564px) {\n  .daterangepicker {\n    width: auto;\n  }\n\n  .daterangepicker .ranges ul {\n    width: 140px;\n  }\n\n  .daterangepicker.single .ranges ul {\n    width: 100%;\n  }\n\n  .daterangepicker.single .drp-calendar.left {\n    clear: none;\n  }\n\n  .daterangepicker.single .ranges, .daterangepicker.single .drp-calendar {\n    float: left;\n  }\n\n  .daterangepicker {\n    direction: ltr;\n    text-align: left;\n  }\n\n  .daterangepicker .drp-calendar.left {\n    clear: left;\n    margin-right: 0;\n  }\n\n  .daterangepicker .drp-calendar.left .calendar-table {\n    border-right: none;\n    border-top-right-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n\n  .daterangepicker .drp-calendar.right {\n    margin-left: 0;\n  }\n\n  .daterangepicker .drp-calendar.right .calendar-table {\n    border-left: none;\n    border-top-left-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n\n  .daterangepicker .drp-calendar.left .calendar-table {\n    padding-right: 8px;\n  }\n\n  .daterangepicker .ranges, .daterangepicker .drp-calendar {\n    float: left;\n  }\n}\n\n@media (min-width: 730px) {\n  .daterangepicker .ranges {\n    width: auto;\n  }\n\n  .daterangepicker .ranges {\n    float: left;\n  }\n\n  .daterangepicker.rtl .ranges {\n    float: right;\n  }\n\n  .daterangepicker .drp-calendar.left {\n    clear: none !important;\n  }\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/fonts/roboto/LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/jquery-ui/AUTHORS.txt",
    "content": "Authors ordered by first contribution\nA list of current team members is available at http://jqueryui.com/about\n\nPaul Bakaus <paul.bakaus@gmail.com>\nRichard Worth <rdworth@gmail.com>\nYehuda Katz <wycats@gmail.com>\nSean Catchpole <sean@sunsean.com>\nJohn Resig <jeresig@gmail.com>\nTane Piper <piper.tane@gmail.com>\nDmitri Gaskin <dmitrig01@gmail.com>\nKlaus Hartl <klaus.hartl@gmail.com>\nStefan Petre <stefan.petre@gmail.com>\nGilles van den Hoven <gilles@webunity.nl>\nMicheil Bryan Smith <micheil@brandedcode.com>\nJörn Zaefferer <joern.zaefferer@gmail.com>\nMarc Grabanski <m@marcgrabanski.com>\nKeith Wood <kbwood@iinet.com.au>\nBrandon Aaron <brandon.aaron@gmail.com>\nScott González <scott.gonzalez@gmail.com>\nEduardo Lundgren <eduardolundgren@gmail.com>\nAaron Eisenberger <aaronchi@gmail.com>\nJoan Piedra <theneojp@gmail.com>\nBruno Basto <b.basto@gmail.com>\nRemy Sharp <remy@leftlogic.com>\nBohdan Ganicky <bohdan.ganicky@gmail.com>\nDavid Bolter <david.bolter@gmail.com>\nChi Cheng <cloudream@gmail.com>\nCa-Phun Ung <pazu2k@gmail.com>\nAriel Flesler <aflesler@gmail.com>\nMaggie Wachs <maggie@filamentgroup.com>\nScott Jehl <scottjehl@gmail.com>\nTodd Parker <todd@filamentgroup.com>\nAndrew Powell <andrew@shellscape.org>\nBrant Burnett <btburnett3@gmail.com>\nDouglas Neiner <doug@dougneiner.com>\nPaul Irish <paul.irish@gmail.com>\nRalph Whitbeck <ralph.whitbeck@gmail.com>\nThibault Duplessis <thibault.duplessis@gmail.com>\nDominique Vincent <dominique.vincent@toitl.com>\nJack Hsu <jack.hsu@gmail.com>\nAdam Sontag <ajpiano@ajpiano.com>\nCarl Fürstenberg <carl@excito.com>\nKevin Dalman <development@allpro.net>\nAlberto Fernández Capel <afcapel@gmail.com>\nJacek Jędrzejewski (http://jacek.jedrzejewski.name)\nTing Kuei <ting@kuei.com>\nSamuel Cormier-Iijima <sam@chide.it>\nJon Palmer <jonspalmer@gmail.com>\nBen Hollis <bhollis@amazon.com>\nJustin MacCarthy <Justin@Rubystars.biz>\nEyal Kobrigo <kobrigo@hotmail.com>\nTiago Freire <tiago.freire@gmail.com>\nDiego Tres <diegotres@gmail.com>\nHolger Rüprich <holger@rueprich.de>\nZiling Zhao <zilingzhao@gmail.com>\nMike Alsup <malsup@gmail.com>\nRobson Braga Araujo <robsonbraga@gmail.com>\nPierre-Henri Ausseil <ph.ausseil@gmail.com>\nChristopher McCulloh <cmcculloh@gmail.com>\nAndrew Newcomb <ext.github@preceptsoftware.co.uk>\nLim Chee Aun <cheeaun@gmail.com>\nJorge Barreiro <yortx.barry@gmail.com>\nDaniel Steigerwald <daniel@steigerwald.cz>\nJohn Firebaugh <john_firebaugh@bigfix.com>\nJohn Enters <github@darkdark.net>\nAndrey Kapitcyn <ru.m157y@gmail.com>\nDmitry Petrov <dpetroff@gmail.com>\nEric Hynds <eric@hynds.net>\nChairat Sunthornwiphat <pipo@sixhead.com>\nJosh Varner <josh.varner@gmail.com>\nStéphane Raimbault <stephane.raimbault@gmail.com>\nJay Merrifield <fracmak@gmail.com>\nJ. Ryan Stinnett <jryans@gmail.com>\nPeter Heiberg <peter@heiberg.se>\nAlex Dovenmuehle <adovenmuehle@gmail.com>\nJamie Gegerson <git@jamiegegerson.com>\nRaymond Schwartz <skeetergraphics@gmail.com>\nPhillip Barnes <philbar@gmail.com>\nKyle Wilkinson <kai@wikyd.org>\nKhaled AlHourani <me@khaledalhourani.com>\nMarian Rudzynski <mr@impaled.org>\nJean-Francois Remy <jeff@melix.org>\nDoug Blood <dougblood@gmail.com>\nFilippo Cavallarin <filippo.cavallarin@codseq.it>\nHeiko Henning <heiko@thehennings.ch>\nAliaksandr Rahalevich <saksmlz@gmail.com>\nMario Visic <mario@mariovisic.com>\nXavi Ramirez <xavi.rmz@gmail.com>\nMax Schnur <max.schnur@gmail.com>\nSaji Nediyanchath <saji89@gmail.com>\nCorey Frang <gnarf37@gmail.com>\nAaron Peterson <aaronp123@yahoo.com>\nIvan Peters <ivan@ivanpeters.com>\nMohamed Cherif Bouchelaghem <cherifbouchelaghem@yahoo.fr>\nMarcos Sousa <falecomigo@marcossousa.com>\nMichael DellaNoce <mdellanoce@mailtrust.com>\nGeorge Marshall <echosx@gmail.com>\nTobias Brunner <tobias@strongswan.org>\nMartin Solli <msolli@gmail.com>\nDavid Petersen <public@petersendidit.com>\nDan Heberden <danheberden@gmail.com>\nWilliam Kevin Manire <williamkmanire@gmail.com>\nGilmore Davidson <gilmoreorless@gmail.com>\nMichael Wu <michaelmwu@gmail.com>\nAdam Parod <mystic414@gmail.com>\nGuillaume Gautreau <guillaume+github@ghusse.com>\nMarcel Toele <EleotleCram@gmail.com>\nDan Streetman <ddstreet@ieee.org>\nMatt Hoskins <matt@nipltd.com>\nGiovanni Giacobbi <giovanni@giacobbi.net>\nKyle Florence <kyle.florence@gmail.com>\nPavol Hluchý <lopo@losys.sk>\nHans Hillen <hans.hillen@gmail.com>\nMark Johnson <virgofx@live.com>\nTrey Hunner <treyhunner@gmail.com>\nShane Whittet <whittet@gmail.com>\nEdward A Faulkner <ef@alum.mit.edu>\nAdam Baratz <adam@adambaratz.com>\nKato Kazuyoshi <kato.kazuyoshi@gmail.com>\nEike Send <eike.send@gmail.com>\nKris Borchers <kris.borchers@gmail.com>\nEddie Monge <eddie@eddiemonge.com>\nIsrael Tsadok <itsadok@gmail.com>\nCarson McDonald <carson@ioncannon.net>\nJason Davies <jason@jasondavies.com>\nGarrison Locke <gplocke@gmail.com>\nDavid Murdoch <david@davidmurdoch.com>\nBenjamin Scott Boyle <benjamins.boyle@gmail.com>\nJesse Baird <jebaird@gmail.com>\nJonathan Vingiano <jvingiano@gmail.com>\nDylan Just <dev@ephox.com>\nHiroshi Tomita <tomykaira@gmail.com>\nGlenn Goodrich <glenn.goodrich@gmail.com>\nTarafder Ashek-E-Elahi <mail.ashek@gmail.com>\nRyan Neufeld <ryan@neufeldmail.com>\nMarc Neuwirth <marc.neuwirth@gmail.com>\nPhilip Graham <philip.robert.graham@gmail.com>\nBenjamin Sterling <benjamin.sterling@kenzomedia.com>\nWesley Walser <waw325@gmail.com>\nKouhei Sutou <kou@clear-code.com>\nKarl Kirch <karlkrch@gmail.com>\nChris Kelly <ckdake@ckdake.com>\nJason Oster <jay@kodewerx.org>\nFelix Nagel <info@felixnagel.com>\nAlexander Polomoshnov <alex.polomoshnov@gmail.com>\nDavid Leal <dgleal@gmail.com>\nIgor Milla <igor.fsp.milla@gmail.com>\nDave Methvin <dave.methvin@gmail.com>\nFlorian Gutmann <f.gutmann@chronimo.com>\nMarwan Al Jubeh <marwan.aljubeh@gmail.com>\nMilan Broum <midlis@googlemail.com>\nSebastian Sauer <info@dynpages.de>\nGaëtan Muller <m.gaetan89@gmail.com>\nMichel Weimerskirch <michel@weimerskirch.net>\nWilliam Griffiths <william@ycymro.com>\nStojce Slavkovski <stojce@gmail.com>\nDavid Soms <david.soms@gmail.com>\nDavid De Sloovere <david.desloovere@outlook.com>\nMichael P. Jung <michael.jung@terreon.de>\nShannon Pekary <spekary@gmail.com>\nDan Wellman <danwellman@hotmail.com>\nMatthew Edward Hutton <meh@corefiling.co.uk>\nJames Khoury <james@jameskhoury.com>\nRob Loach <robloach@gmail.com>\nAlberto Monteiro <betimbrasil@gmail.com>\nAlex Rhea <alex.rhea@gmail.com>\nKrzysztof Rosiński <rozwell69@gmail.com>\nRyan Olton <oltonr@gmail.com>\nGenie <386@mail.com>\nRick Waldron <waldron.rick@gmail.com>\nIan Simpson <spoonlikesham@gmail.com>\nLev Kitsis <spam4lev@gmail.com>\nTJ VanToll <tj.vantoll@gmail.com>\nJustin Domnitz <jdomnitz@gmail.com>\nDouglas Cerna <douglascerna@yahoo.com>\nBert ter Heide <bertjh@hotmail.com>\nJasvir Nagra <jasvir@gmail.com>\nYuriy Khabarov <13real008@gmail.com>\nHarri Kilpiö <harri.kilpio@gmail.com>\nLado Lomidze <lado.lomidze@gmail.com>\nAmir E. Aharoni <amir.aharoni@mail.huji.ac.il>\nSimon Sattes <simon.sattes@gmail.com>\nJo Liss <joliss42@gmail.com>\nGuntupalli Karunakar <karunakarg@yahoo.com>\nShahyar Ghobadpour <shahyar@gmail.com>\nLukasz Lipinski <uzza17@gmail.com>\nTimo Tijhof <krinklemail@gmail.com>\nJason Moon <jmoon@socialcast.com>\nMartin Frost <martinf55@hotmail.com>\nEneko Illarramendi <eneko@illarra.com>\nEungJun Yi <semtlenori@gmail.com>\nCourtland Allen <courtlandallen@gmail.com>\nViktar Varvanovich <non4eg@gmail.com>\nDanny Trunk <dtrunk90@gmail.com>\nPavel Stetina <pavel.stetina@nangu.tv>\nMichael Stay <metaweta@gmail.com>\nSteven Roussey <sroussey@gmail.com>\nMichael Hollis <hollis21@gmail.com>\nLee Rowlands <lee.rowlands@previousnext.com.au>\nTimmy Willison <timmywillisn@gmail.com>\nKarl Swedberg <kswedberg@gmail.com>\nBaoju Yuan <the_guy_1987@hotmail.com>\nMaciej Mroziński <maciej.k.mrozinski@gmail.com>\nLuis Dalmolin <luis.nh@gmail.com>\nMark Aaron Shirley <maspwr@gmail.com>\nMartin Hoch <martin@fidion.de>\nJiayi Yang <tr870829@gmail.com>\nPhilipp Benjamin Köppchen <xgxtpbk@gws.ms>\nSindre Sorhus <sindresorhus@gmail.com>\nBernhard Sirlinger <bernhard.sirlinger@tele2.de>\nJared A. Scheel <jared@jaredscheel.com>\nRafael Xavier de Souza <rxaviers@gmail.com>\nJohn Chen <zhang.z.chen@intel.com>\nRobert Beuligmann <robertbeuligmann@gmail.com>\nDale Kocian <dale.kocian@gmail.com>\nMike Sherov <mike.sherov@gmail.com>\nAndrew Couch <andy@couchand.com>\nMarc-Andre Lafortune <github@marc-andre.ca>\nNate Eagle <nate.eagle@teamaol.com>\nDavid Souther <davidsouther@gmail.com>\nMathias Stenbom <mathias@stenbom.com>\nSergey Kartashov <ebishkek@yandex.ru>\nAvinash R <nashpapa@gmail.com>\nEthan Romba <ethanromba@gmail.com>\nCory Gackenheimer <cory.gack@gmail.com>\nJuan Pablo Kaniefsky <jpkaniefsky@gmail.com>\nRoman Salnikov <bardt.dz@gmail.com>\nAnika Henke <anika@selfthinker.org>\nSamuel Bovée <samycookie2000@yahoo.fr>\nFabrício Matté <ult_combo@hotmail.com>\nViktor Kojouharov <vkojouharov@gmail.com>\nPawel Maruszczyk (http://hrabstwo.net)\nPavel Selitskas <p.selitskas@gmail.com>\nBjørn Johansen <post@bjornjohansen.no>\nMatthieu Penant <thieum22@hotmail.com>\nDominic Barnes <dominic@dbarnes.info>\nDavid Sullivan <david.sullivan@gmail.com>\nThomas Jaggi <thomas@responsive.ch>\nVahid Sohrabloo <vahid4134@gmail.com>\nTravis Carden <travis.carden@gmail.com>\nBruno M. Custódio <bruno@brunomcustodio.com>\nNathanael Silverman <nathanael.silverman@gmail.com>\nChristian Wenz <christian@wenz.org>\nSteve Urmston <steve@urm.st>\nZaven Muradyan <megalivoithos@gmail.com>\nWoody Gilk <shadowhand@deviantart.com>\nZbigniew Motyka <zbigniew.motyka@gmail.com>\nSuhail Alkowaileet <xsoh.k7@gmail.com>\nToshi MARUYAMA <marutosijp2@yahoo.co.jp>\nDavid Hansen <hansede@gmail.com>\nBrian Grinstead <briangrinstead@gmail.com>\nChristian Klammer <christian314159@gmail.com>\nSteven Luscher <jquerycla@steveluscher.com>\nGan Eng Chin <engchin.gan@gmail.com>\nGabriel Schulhof <gabriel.schulhof@intel.com>\nAlexander Schmitz <arschmitz@gmail.com>\nVilhjálmur Skúlason <vis@dmm.is>\nSiebrand Mazeland <siebrand@kitano.nl>\nMohsen Ekhtiari <mohsenekhtiari@yahoo.com>\nPere Orga <gotrunks@gmail.com>\nJasper de Groot <mail@ugomobi.com>\nStephane Deschamps <stephane.deschamps@gmail.com>\nJyoti Deka <dekajp@gmail.com>\nAndrei Picus <office.nightcrawler@gmail.com>\nOndrej Novy <novy@ondrej.org>\nJacob McCutcheon <jacob.mccutcheon@gmail.com>\nMonika Piotrowicz <monika.piotrowicz@gmail.com>\nImants Horsts <imants.horsts@inbox.lv>\nEric Dahl <eric.c.dahl@gmail.com>\nDave Stein <dave@behance.com>\nDylan Barrell <dylan@barrell.com>\nDaniel DeGroff <djdegroff@gmail.com>\nMichael Wiencek <mwtuea@gmail.com>\nThomas Meyer <meyertee@gmail.com>\nRuslan Yakhyaev <ruslan@ruslan.io>\nBrian J. Dowling <bjd-dev@simplicity.net>\nBen Higgins <ben@extrahop.com>\nYermo Lamers <yml@yml.com>\nPatrick Stapleton <github@gdi2290.com>\nTrisha Crowley <trisha.crowley@gmail.com>\nUsman Akeju <akeju00+github@gmail.com>\nRodrigo Menezes <rod333@gmail.com>\nJacques Perrault <jacques_perrault@us.ibm.com>\nFrederik Elvhage <frederik.elvhage@googlemail.com>\nWill Holley <willholley@gmail.com>\nUri Gilad <antishok@gmail.com>\nRichard Gibson <richard.gibson@gmail.com>\nSimen Bekkhus <sbekkhus91@gmail.com>\nChen Eshchar <eshcharc@gmail.com>\nBruno Pérel <brunoperel@gmail.com>\nMohammed Alshehri <m@dralshehri.com>\nLisa Seacat DeLuca <ldeluca@us.ibm.com>\nAnne-Gaelle Colom <coloma@westminster.ac.uk>\nAdam Foster <slimfoster@gmail.com>\nLuke Page <luke.a.page@gmail.com>\nDaniel Owens <daniel@matchstickmixup.com>\nMichael Orchard <morchard@scottlogic.co.uk>\nMarcus Warren <marcus@envoke.com>\nNils Heuermann <nils@world-of-scripts.de>\nMarco Ziech <marco@ziech.net>\nPatricia Juarez <patrixd@gmail.com>\nBen Mosher <me@benmosher.com>\nAblay Keldibek <atomio.ak@gmail.com>\nThomas Applencourt <thomas.applencourt@irsamc.ups-tlse.fr>\nJiabao Wu <jiabao.foss@gmail.com>\nEric Lee Carraway <github@ericcarraway.com>\nVictor Homyakov <vkhomyackov@gmail.com>\nMyeongjin Lee <aranet100@gmail.com>\nLiran Sharir <lsharir@gmail.com>\nWeston Ruter <weston@xwp.co>\nMani Mishra <manimishra902@gmail.com>\nHannah Methvin <hannahmethvin@gmail.com>\nLeonardo Balter <leonardo.balter@gmail.com>\nBenjamin Albert <benjamin_a5@yahoo.com>\nMichał Gołębiowski <m.goleb@gmail.com>\nAlyosha Pushak <alyosha.pushak@gmail.com>\nFahad Ahmad <fahadahmad41@hotmail.com>\nMatt Brundage <github@mattbrundage.com>\nFrancesc Baeta <francesc.baeta@gmail.com>\nPiotr Baran <piotros@wp.pl>\nMukul Hase <mukulhase@gmail.com>\nKonstantin Dinev <kdinev@mail.bw.edu>\nRand Scullard <rand@randscullard.com>\nDan Strohl <dan@wjcg.net>\nMaksim Ryzhikov <rv.maksim@gmail.com>\nAmine HADDAD <haddad@allegorie.tv>\nAmanpreet Singh <apsdehal@gmail.com>\nAlexey Balchunas <bleshik@gmail.com>\nPeter Kehl <peter.kehl@gmail.com>\nPeter Dave Hello <hsu@peterdavehello.org>\nJohannes Schäfer <johnschaefer@gmx.de>\nVille Skyttä <ville.skytta@iki.fi>\nRyan Oriecuia <ryan.oriecuia@visioncritical.com>\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/jquery-ui/LICENSE.txt",
    "content": "Copyright jQuery Foundation and other contributors, https://jquery.org/\n\nThis software consists of voluntary contributions made by many\nindividuals. For exact contribution history, see the revision history\navailable at https://github.com/jquery/jquery-ui\n\nThe following license applies to all parts of this software except as\ndocumented below:\n\n====\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n====\n\nCopyright and related rights for sample code are waived via CC0. Sample\ncode is defined as all source code contained within the demos directory.\n\nCC0: http://creativecommons.org/publicdomain/zero/1.0/\n\n====\n\nAll files located in the node_modules and external directories are\nexternally maintained libraries used by this software which have their\nown licenses; we recommend you read them, as their terms may differ from\nthe terms above.\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/jquery-ui/jquery-ui.css",
    "content": "/*! jQuery UI - v1.12.1 - 2018-03-11\n* http://jqueryui.com\n* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css\n* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif\n* Copyright jQuery Foundation and other contributors; Licensed MIT */\n\n.ui-draggable-handle {\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n/* Layout helpers\n----------------------------------*/\n.ui-helper-hidden {\n\tdisplay: none;\n}\n.ui-helper-hidden-accessible {\n\tborder: 0;\n\tclip: rect(0 0 0 0);\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tpadding: 0;\n\tposition: absolute;\n\twidth: 1px;\n}\n.ui-helper-reset {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\toutline: 0;\n\tline-height: 1.3;\n\ttext-decoration: none;\n\tfont-size: 100%;\n\tlist-style: none;\n}\n.ui-helper-clearfix:before,\n.ui-helper-clearfix:after {\n\tcontent: \"\";\n\tdisplay: table;\n\tborder-collapse: collapse;\n}\n.ui-helper-clearfix:after {\n\tclear: both;\n}\n.ui-helper-zfix {\n\twidth: 100%;\n\theight: 100%;\n\ttop: 0;\n\tleft: 0;\n\tposition: absolute;\n\topacity: 0;\n\tfilter:Alpha(Opacity=0); /* support: IE8 */\n}\n\n.ui-front {\n\tz-index: 100;\n}\n\n\n/* Interaction Cues\n----------------------------------*/\n.ui-state-disabled {\n\tcursor: default !important;\n\tpointer-events: none;\n}\n\n\n/* Icons\n----------------------------------*/\n.ui-icon {\n\tdisplay: inline-block;\n\tvertical-align: middle;\n\tmargin-top: -.25em;\n\tposition: relative;\n\ttext-indent: -99999px;\n\toverflow: hidden;\n\tbackground-repeat: no-repeat;\n}\n\n.ui-widget-icon-block {\n\tleft: 50%;\n\tmargin-left: -8px;\n\tdisplay: block;\n}\n\n/* Misc visuals\n----------------------------------*/\n\n/* Overlays */\n.ui-widget-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n.ui-resizable {\n\tposition: relative;\n}\n.ui-resizable-handle {\n\tposition: absolute;\n\tfont-size: 0.1px;\n\tdisplay: block;\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-resizable-disabled .ui-resizable-handle,\n.ui-resizable-autohide .ui-resizable-handle {\n\tdisplay: none;\n}\n.ui-resizable-n {\n\tcursor: n-resize;\n\theight: 7px;\n\twidth: 100%;\n\ttop: -5px;\n\tleft: 0;\n}\n.ui-resizable-s {\n\tcursor: s-resize;\n\theight: 7px;\n\twidth: 100%;\n\tbottom: -5px;\n\tleft: 0;\n}\n.ui-resizable-e {\n\tcursor: e-resize;\n\twidth: 7px;\n\tright: -5px;\n\ttop: 0;\n\theight: 100%;\n}\n.ui-resizable-w {\n\tcursor: w-resize;\n\twidth: 7px;\n\tleft: -5px;\n\ttop: 0;\n\theight: 100%;\n}\n.ui-resizable-se {\n\tcursor: se-resize;\n\twidth: 12px;\n\theight: 12px;\n\tright: 1px;\n\tbottom: 1px;\n}\n.ui-resizable-sw {\n\tcursor: sw-resize;\n\twidth: 9px;\n\theight: 9px;\n\tleft: -5px;\n\tbottom: -5px;\n}\n.ui-resizable-nw {\n\tcursor: nw-resize;\n\twidth: 9px;\n\theight: 9px;\n\tleft: -5px;\n\ttop: -5px;\n}\n.ui-resizable-ne {\n\tcursor: ne-resize;\n\twidth: 9px;\n\theight: 9px;\n\tright: -5px;\n\ttop: -5px;\n}\n.ui-selectable {\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-selectable-helper {\n\tposition: absolute;\n\tz-index: 100;\n\tborder: 1px dotted black;\n}\n.ui-sortable-handle {\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-accordion .ui-accordion-header {\n\tdisplay: block;\n\tcursor: pointer;\n\tposition: relative;\n\tmargin: 2px 0 0 0;\n\tpadding: .5em .5em .5em .7em;\n\tfont-size: 100%;\n}\n.ui-accordion .ui-accordion-content {\n\tpadding: 1em 2.2em;\n\tborder-top: 0;\n\toverflow: auto;\n}\n.ui-autocomplete {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tcursor: default;\n}\n.ui-menu {\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n\tdisplay: block;\n\toutline: 0;\n}\n.ui-menu .ui-menu {\n\tposition: absolute;\n}\n.ui-menu .ui-menu-item {\n\tmargin: 0;\n\tcursor: pointer;\n\t/* support: IE10, see #8844 */\n\tlist-style-image: url(\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\");\n}\n.ui-menu .ui-menu-item-wrapper {\n\tposition: relative;\n\tpadding: 3px 1em 3px .4em;\n}\n.ui-menu .ui-menu-divider {\n\tmargin: 5px 0;\n\theight: 0;\n\tfont-size: 0;\n\tline-height: 0;\n\tborder-width: 1px 0 0 0;\n}\n.ui-menu .ui-state-focus,\n.ui-menu .ui-state-active {\n\tmargin: -1px;\n}\n\n/* icon support */\n.ui-menu-icons {\n\tposition: relative;\n}\n.ui-menu-icons .ui-menu-item-wrapper {\n\tpadding-left: 2em;\n}\n\n/* left-aligned */\n.ui-menu .ui-icon {\n\tposition: absolute;\n\ttop: 0;\n\tbottom: 0;\n\tleft: .2em;\n\tmargin: auto 0;\n}\n\n/* right-aligned */\n.ui-menu .ui-menu-icon {\n\tleft: auto;\n\tright: 0;\n}\n.ui-button {\n\tpadding: .4em 1em;\n\tdisplay: inline-block;\n\tposition: relative;\n\tline-height: normal;\n\tmargin-right: .1em;\n\tcursor: pointer;\n\tvertical-align: middle;\n\ttext-align: center;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n\n\t/* Support: IE <= 11 */\n\toverflow: visible;\n}\n\n.ui-button,\n.ui-button:link,\n.ui-button:visited,\n.ui-button:hover,\n.ui-button:active {\n\ttext-decoration: none;\n}\n\n/* to make room for the icon, a width needs to be set here */\n.ui-button-icon-only {\n\twidth: 2em;\n\tbox-sizing: border-box;\n\ttext-indent: -9999px;\n\twhite-space: nowrap;\n}\n\n/* no icon support for input elements */\ninput.ui-button.ui-button-icon-only {\n\ttext-indent: 0;\n}\n\n/* button icon element(s) */\n.ui-button-icon-only .ui-icon {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\tmargin-top: -8px;\n\tmargin-left: -8px;\n}\n\n.ui-button.ui-icon-notext .ui-icon {\n\tpadding: 0;\n\twidth: 2.1em;\n\theight: 2.1em;\n\ttext-indent: -9999px;\n\twhite-space: nowrap;\n\n}\n\ninput.ui-button.ui-icon-notext .ui-icon {\n\twidth: auto;\n\theight: auto;\n\ttext-indent: 0;\n\twhite-space: normal;\n\tpadding: .4em 1em;\n}\n\n/* workarounds */\n/* Support: Firefox 5 - 40 */\ninput.ui-button::-moz-focus-inner,\nbutton.ui-button::-moz-focus-inner {\n\tborder: 0;\n\tpadding: 0;\n}\n.ui-controlgroup {\n\tvertical-align: middle;\n\tdisplay: inline-block;\n}\n.ui-controlgroup > .ui-controlgroup-item {\n\tfloat: left;\n\tmargin-left: 0;\n\tmargin-right: 0;\n}\n.ui-controlgroup > .ui-controlgroup-item:focus,\n.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus {\n\tz-index: 9999;\n}\n.ui-controlgroup-vertical > .ui-controlgroup-item {\n\tdisplay: block;\n\tfloat: none;\n\twidth: 100%;\n\tmargin-top: 0;\n\tmargin-bottom: 0;\n\ttext-align: left;\n}\n.ui-controlgroup-vertical .ui-controlgroup-item {\n\tbox-sizing: border-box;\n}\n.ui-controlgroup .ui-controlgroup-label {\n\tpadding: .4em 1em;\n}\n.ui-controlgroup .ui-controlgroup-label span {\n\tfont-size: 80%;\n}\n.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item {\n\tborder-left: none;\n}\n.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item {\n\tborder-top: none;\n}\n.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content {\n\tborder-right: none;\n}\n.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content {\n\tborder-bottom: none;\n}\n\n/* Spinner specific style fixes */\n.ui-controlgroup-vertical .ui-spinner-input {\n\n\t/* Support: IE8 only, Android < 4.4 only */\n\twidth: 75%;\n\twidth: calc( 100% - 2.4em );\n}\n.ui-controlgroup-vertical .ui-spinner .ui-spinner-up {\n\tborder-top-style: solid;\n}\n\n.ui-checkboxradio-label .ui-icon-background {\n\tbox-shadow: inset 1px 1px 1px #ccc;\n\tborder-radius: .12em;\n\tborder: none;\n}\n.ui-checkboxradio-radio-label .ui-icon-background {\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 1em;\n\toverflow: visible;\n\tborder: none;\n}\n.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,\n.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon {\n\tbackground-image: none;\n\twidth: 8px;\n\theight: 8px;\n\tborder-width: 4px;\n\tborder-style: solid;\n}\n.ui-checkboxradio-disabled {\n\tpointer-events: none;\n}\n.ui-datepicker {\n\twidth: 17em;\n\tpadding: .2em .2em 0;\n\tdisplay: none;\n}\n.ui-datepicker .ui-datepicker-header {\n\tposition: relative;\n\tpadding: .2em 0;\n}\n.ui-datepicker .ui-datepicker-prev,\n.ui-datepicker .ui-datepicker-next {\n\tposition: absolute;\n\ttop: 2px;\n\twidth: 1.8em;\n\theight: 1.8em;\n}\n.ui-datepicker .ui-datepicker-prev-hover,\n.ui-datepicker .ui-datepicker-next-hover {\n\ttop: 1px;\n}\n.ui-datepicker .ui-datepicker-prev {\n\tleft: 2px;\n}\n.ui-datepicker .ui-datepicker-next {\n\tright: 2px;\n}\n.ui-datepicker .ui-datepicker-prev-hover {\n\tleft: 1px;\n}\n.ui-datepicker .ui-datepicker-next-hover {\n\tright: 1px;\n}\n.ui-datepicker .ui-datepicker-prev span,\n.ui-datepicker .ui-datepicker-next span {\n\tdisplay: block;\n\tposition: absolute;\n\tleft: 50%;\n\tmargin-left: -8px;\n\ttop: 50%;\n\tmargin-top: -8px;\n}\n.ui-datepicker .ui-datepicker-title {\n\tmargin: 0 2.3em;\n\tline-height: 1.8em;\n\ttext-align: center;\n}\n.ui-datepicker .ui-datepicker-title select {\n\tfont-size: 1em;\n\tmargin: 1px 0;\n}\n.ui-datepicker select.ui-datepicker-month,\n.ui-datepicker select.ui-datepicker-year {\n\twidth: 45%;\n}\n.ui-datepicker table {\n\twidth: 100%;\n\tfont-size: .9em;\n\tborder-collapse: collapse;\n\tmargin: 0 0 .4em;\n}\n.ui-datepicker th {\n\tpadding: .7em .3em;\n\ttext-align: center;\n\tfont-weight: bold;\n\tborder: 0;\n}\n.ui-datepicker td {\n\tborder: 0;\n\tpadding: 1px;\n}\n.ui-datepicker td span,\n.ui-datepicker td a {\n\tdisplay: block;\n\tpadding: .2em;\n\ttext-align: right;\n\ttext-decoration: none;\n}\n.ui-datepicker .ui-datepicker-buttonpane {\n\tbackground-image: none;\n\tmargin: .7em 0 0 0;\n\tpadding: 0 .2em;\n\tborder-left: 0;\n\tborder-right: 0;\n\tborder-bottom: 0;\n}\n.ui-datepicker .ui-datepicker-buttonpane button {\n\tfloat: right;\n\tmargin: .5em .2em .4em;\n\tcursor: pointer;\n\tpadding: .2em .6em .3em .6em;\n\twidth: auto;\n\toverflow: visible;\n}\n.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {\n\tfloat: left;\n}\n\n/* with multiple calendars */\n.ui-datepicker.ui-datepicker-multi {\n\twidth: auto;\n}\n.ui-datepicker-multi .ui-datepicker-group {\n\tfloat: left;\n}\n.ui-datepicker-multi .ui-datepicker-group table {\n\twidth: 95%;\n\tmargin: 0 auto .4em;\n}\n.ui-datepicker-multi-2 .ui-datepicker-group {\n\twidth: 50%;\n}\n.ui-datepicker-multi-3 .ui-datepicker-group {\n\twidth: 33.3%;\n}\n.ui-datepicker-multi-4 .ui-datepicker-group {\n\twidth: 25%;\n}\n.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,\n.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {\n\tborder-left-width: 0;\n}\n.ui-datepicker-multi .ui-datepicker-buttonpane {\n\tclear: left;\n}\n.ui-datepicker-row-break {\n\tclear: both;\n\twidth: 100%;\n\tfont-size: 0;\n}\n\n/* RTL support */\n.ui-datepicker-rtl {\n\tdirection: rtl;\n}\n.ui-datepicker-rtl .ui-datepicker-prev {\n\tright: 2px;\n\tleft: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-next {\n\tleft: 2px;\n\tright: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-prev:hover {\n\tright: 1px;\n\tleft: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-next:hover {\n\tleft: 1px;\n\tright: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-buttonpane {\n\tclear: right;\n}\n.ui-datepicker-rtl .ui-datepicker-buttonpane button {\n\tfloat: left;\n}\n.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,\n.ui-datepicker-rtl .ui-datepicker-group {\n\tfloat: right;\n}\n.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,\n.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {\n\tborder-right-width: 0;\n\tborder-left-width: 1px;\n}\n\n/* Icons */\n.ui-datepicker .ui-icon {\n\tdisplay: block;\n\ttext-indent: -99999px;\n\toverflow: hidden;\n\tbackground-repeat: no-repeat;\n\tleft: .5em;\n\ttop: .3em;\n}\n.ui-dialog {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tpadding: .2em;\n\toutline: 0;\n}\n.ui-dialog .ui-dialog-titlebar {\n\tpadding: .4em 1em;\n\tposition: relative;\n}\n.ui-dialog .ui-dialog-title {\n\tfloat: left;\n\tmargin: .1em 0;\n\twhite-space: nowrap;\n\twidth: 90%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n.ui-dialog .ui-dialog-titlebar-close {\n\tposition: absolute;\n\tright: .3em;\n\ttop: 50%;\n\twidth: 20px;\n\tmargin: -10px 0 0 0;\n\tpadding: 1px;\n\theight: 20px;\n}\n.ui-dialog .ui-dialog-content {\n\tposition: relative;\n\tborder: 0;\n\tpadding: .5em 1em;\n\tbackground: none;\n\toverflow: auto;\n}\n.ui-dialog .ui-dialog-buttonpane {\n\ttext-align: left;\n\tborder-width: 1px 0 0 0;\n\tbackground-image: none;\n\tmargin-top: .5em;\n\tpadding: .3em 1em .5em .4em;\n}\n.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {\n\tfloat: right;\n}\n.ui-dialog .ui-dialog-buttonpane button {\n\tmargin: .5em .4em .5em 0;\n\tcursor: pointer;\n}\n.ui-dialog .ui-resizable-n {\n\theight: 2px;\n\ttop: 0;\n}\n.ui-dialog .ui-resizable-e {\n\twidth: 2px;\n\tright: 0;\n}\n.ui-dialog .ui-resizable-s {\n\theight: 2px;\n\tbottom: 0;\n}\n.ui-dialog .ui-resizable-w {\n\twidth: 2px;\n\tleft: 0;\n}\n.ui-dialog .ui-resizable-se,\n.ui-dialog .ui-resizable-sw,\n.ui-dialog .ui-resizable-ne,\n.ui-dialog .ui-resizable-nw {\n\twidth: 7px;\n\theight: 7px;\n}\n.ui-dialog .ui-resizable-se {\n\tright: 0;\n\tbottom: 0;\n}\n.ui-dialog .ui-resizable-sw {\n\tleft: 0;\n\tbottom: 0;\n}\n.ui-dialog .ui-resizable-ne {\n\tright: 0;\n\ttop: 0;\n}\n.ui-dialog .ui-resizable-nw {\n\tleft: 0;\n\ttop: 0;\n}\n.ui-draggable .ui-dialog-titlebar {\n\tcursor: move;\n}\n.ui-progressbar {\n\theight: 2em;\n\ttext-align: left;\n\toverflow: hidden;\n}\n.ui-progressbar .ui-progressbar-value {\n\tmargin: -1px;\n\theight: 100%;\n}\n.ui-progressbar .ui-progressbar-overlay {\n\tbackground: url(\"data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==\");\n\theight: 100%;\n\tfilter: alpha(opacity=25); /* support: IE8 */\n\topacity: 0.25;\n}\n.ui-progressbar-indeterminate .ui-progressbar-value {\n\tbackground-image: none;\n}\n.ui-selectmenu-menu {\n\tpadding: 0;\n\tmargin: 0;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tdisplay: none;\n}\n.ui-selectmenu-menu .ui-menu {\n\toverflow: auto;\n\toverflow-x: hidden;\n\tpadding-bottom: 1px;\n}\n.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {\n\tfont-size: 1em;\n\tfont-weight: bold;\n\tline-height: 1.5;\n\tpadding: 2px 0.4em;\n\tmargin: 0.5em 0 0 0;\n\theight: auto;\n\tborder: 0;\n}\n.ui-selectmenu-open {\n\tdisplay: block;\n}\n.ui-selectmenu-text {\n\tdisplay: block;\n\tmargin-right: 20px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n.ui-selectmenu-button.ui-button {\n\ttext-align: left;\n\twhite-space: nowrap;\n\twidth: 14em;\n}\n.ui-selectmenu-icon.ui-icon {\n\tfloat: right;\n\tmargin-top: 0;\n}\n.ui-slider {\n\tposition: relative;\n\ttext-align: left;\n}\n.ui-slider .ui-slider-handle {\n\tposition: absolute;\n\tz-index: 2;\n\twidth: 1.2em;\n\theight: 1.2em;\n\tcursor: default;\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-slider .ui-slider-range {\n\tposition: absolute;\n\tz-index: 1;\n\tfont-size: .7em;\n\tdisplay: block;\n\tborder: 0;\n\tbackground-position: 0 0;\n}\n\n/* support: IE8 - See #6727 */\n.ui-slider.ui-state-disabled .ui-slider-handle,\n.ui-slider.ui-state-disabled .ui-slider-range {\n\tfilter: inherit;\n}\n\n.ui-slider-horizontal {\n\theight: .8em;\n}\n.ui-slider-horizontal .ui-slider-handle {\n\ttop: -.3em;\n\tmargin-left: -.6em;\n}\n.ui-slider-horizontal .ui-slider-range {\n\ttop: 0;\n\theight: 100%;\n}\n.ui-slider-horizontal .ui-slider-range-min {\n\tleft: 0;\n}\n.ui-slider-horizontal .ui-slider-range-max {\n\tright: 0;\n}\n\n.ui-slider-vertical {\n\twidth: .8em;\n\theight: 100px;\n}\n.ui-slider-vertical .ui-slider-handle {\n\tleft: -.3em;\n\tmargin-left: 0;\n\tmargin-bottom: -.6em;\n}\n.ui-slider-vertical .ui-slider-range {\n\tleft: 0;\n\twidth: 100%;\n}\n.ui-slider-vertical .ui-slider-range-min {\n\tbottom: 0;\n}\n.ui-slider-vertical .ui-slider-range-max {\n\ttop: 0;\n}\n.ui-spinner {\n\tposition: relative;\n\tdisplay: inline-block;\n\toverflow: hidden;\n\tpadding: 0;\n\tvertical-align: middle;\n}\n.ui-spinner-input {\n\tborder: none;\n\tbackground: none;\n\tcolor: inherit;\n\tpadding: .222em 0;\n\tmargin: .2em 0;\n\tvertical-align: middle;\n\tmargin-left: .4em;\n\tmargin-right: 2em;\n}\n.ui-spinner-button {\n\twidth: 1.6em;\n\theight: 50%;\n\tfont-size: .5em;\n\tpadding: 0;\n\tmargin: 0;\n\ttext-align: center;\n\tposition: absolute;\n\tcursor: default;\n\tdisplay: block;\n\toverflow: hidden;\n\tright: 0;\n}\n/* more specificity required here to override default borders */\n.ui-spinner a.ui-spinner-button {\n\tborder-top-style: none;\n\tborder-bottom-style: none;\n\tborder-right-style: none;\n}\n.ui-spinner-up {\n\ttop: 0;\n}\n.ui-spinner-down {\n\tbottom: 0;\n}\n.ui-tabs {\n\tposition: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as \"fixed\") */\n\tpadding: .2em;\n}\n.ui-tabs .ui-tabs-nav {\n\tmargin: 0;\n\tpadding: .2em .2em 0;\n}\n.ui-tabs .ui-tabs-nav li {\n\tlist-style: none;\n\tfloat: left;\n\tposition: relative;\n\ttop: 0;\n\tmargin: 1px .2em 0 0;\n\tborder-bottom-width: 0;\n\tpadding: 0;\n\twhite-space: nowrap;\n}\n.ui-tabs .ui-tabs-nav .ui-tabs-anchor {\n\tfloat: left;\n\tpadding: .5em 1em;\n\ttext-decoration: none;\n}\n.ui-tabs .ui-tabs-nav li.ui-tabs-active {\n\tmargin-bottom: -1px;\n\tpadding-bottom: 1px;\n}\n.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,\n.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,\n.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {\n\tcursor: text;\n}\n.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {\n\tcursor: pointer;\n}\n.ui-tabs .ui-tabs-panel {\n\tdisplay: block;\n\tborder-width: 0;\n\tpadding: 1em 1.4em;\n\tbackground: none;\n}\n.ui-tooltip {\n\tpadding: 8px;\n\tposition: absolute;\n\tz-index: 9999;\n\tmax-width: 300px;\n}\nbody .ui-tooltip {\n\tborder-width: 2px;\n}\n\n/* Component containers\n----------------------------------*/\n.ui-widget {\n\tfont-family: Arial,Helvetica,sans-serif;\n\tfont-size: 1em;\n}\n.ui-widget .ui-widget {\n\tfont-size: 1em;\n}\n.ui-widget input,\n.ui-widget select,\n.ui-widget textarea,\n.ui-widget button {\n\tfont-family: Arial,Helvetica,sans-serif;\n\tfont-size: 1em;\n}\n.ui-widget.ui-widget-content {\n\tborder: 1px solid #c5c5c5;\n}\n.ui-widget-content {\n\tborder: 1px solid #dddddd;\n\tbackground: #ffffff;\n\tcolor: #333333;\n}\n.ui-widget-content a {\n\tcolor: #333333;\n}\n.ui-widget-header {\n\tborder: 1px solid #dddddd;\n\tbackground: #e9e9e9;\n\tcolor: #333333;\n\tfont-weight: bold;\n}\n.ui-widget-header a {\n\tcolor: #333333;\n}\n\n/* Interaction states\n----------------------------------*/\n.ui-state-default,\n.ui-widget-content .ui-state-default,\n.ui-widget-header .ui-state-default,\n.ui-button,\n\n/* We use html here because we need a greater specificity to make sure disabled\nworks properly when clicked or hovered */\nhtml .ui-button.ui-state-disabled:hover,\nhtml .ui-button.ui-state-disabled:active {\n\tborder: 1px solid #c5c5c5;\n\tbackground: #f6f6f6;\n\tfont-weight: normal;\n\tcolor: #454545;\n}\n.ui-state-default a,\n.ui-state-default a:link,\n.ui-state-default a:visited,\na.ui-button,\na:link.ui-button,\na:visited.ui-button,\n.ui-button {\n\tcolor: #454545;\n\ttext-decoration: none;\n}\n.ui-state-hover,\n.ui-widget-content .ui-state-hover,\n.ui-widget-header .ui-state-hover,\n.ui-state-focus,\n.ui-widget-content .ui-state-focus,\n.ui-widget-header .ui-state-focus,\n.ui-button:hover,\n.ui-button:focus {\n\tborder: 1px solid #cccccc;\n\tbackground: #ededed;\n\tfont-weight: normal;\n\tcolor: #2b2b2b;\n}\n.ui-state-hover a,\n.ui-state-hover a:hover,\n.ui-state-hover a:link,\n.ui-state-hover a:visited,\n.ui-state-focus a,\n.ui-state-focus a:hover,\n.ui-state-focus a:link,\n.ui-state-focus a:visited,\na.ui-button:hover,\na.ui-button:focus {\n\tcolor: #2b2b2b;\n\ttext-decoration: none;\n}\n\n.ui-visual-focus {\n\tbox-shadow: 0 0 3px 1px rgb(94, 158, 214);\n}\n.ui-state-active,\n.ui-widget-content .ui-state-active,\n.ui-widget-header .ui-state-active,\na.ui-button:active,\n.ui-button:active,\n.ui-button.ui-state-active:hover {\n\tborder: 1px solid #003eff;\n\tbackground: #007fff;\n\tfont-weight: normal;\n\tcolor: #ffffff;\n}\n.ui-icon-background,\n.ui-state-active .ui-icon-background {\n\tborder: #003eff;\n\tbackground-color: #ffffff;\n}\n.ui-state-active a,\n.ui-state-active a:link,\n.ui-state-active a:visited {\n\tcolor: #ffffff;\n\ttext-decoration: none;\n}\n\n/* Interaction Cues\n----------------------------------*/\n.ui-state-highlight,\n.ui-widget-content .ui-state-highlight,\n.ui-widget-header .ui-state-highlight {\n\tborder: 1px solid #dad55e;\n\tbackground: #fffa90;\n\tcolor: #777620;\n}\n.ui-state-checked {\n\tborder: 1px solid #dad55e;\n\tbackground: #fffa90;\n}\n.ui-state-highlight a,\n.ui-widget-content .ui-state-highlight a,\n.ui-widget-header .ui-state-highlight a {\n\tcolor: #777620;\n}\n.ui-state-error,\n.ui-widget-content .ui-state-error,\n.ui-widget-header .ui-state-error {\n\tborder: 1px solid #f1a899;\n\tbackground: #fddfdf;\n\tcolor: #5f3f3f;\n}\n.ui-state-error a,\n.ui-widget-content .ui-state-error a,\n.ui-widget-header .ui-state-error a {\n\tcolor: #5f3f3f;\n}\n.ui-state-error-text,\n.ui-widget-content .ui-state-error-text,\n.ui-widget-header .ui-state-error-text {\n\tcolor: #5f3f3f;\n}\n.ui-priority-primary,\n.ui-widget-content .ui-priority-primary,\n.ui-widget-header .ui-priority-primary {\n\tfont-weight: bold;\n}\n.ui-priority-secondary,\n.ui-widget-content .ui-priority-secondary,\n.ui-widget-header .ui-priority-secondary {\n\topacity: .7;\n\tfilter:Alpha(Opacity=70); /* support: IE8 */\n\tfont-weight: normal;\n}\n.ui-state-disabled,\n.ui-widget-content .ui-state-disabled,\n.ui-widget-header .ui-state-disabled {\n\topacity: .35;\n\tfilter:Alpha(Opacity=35); /* support: IE8 */\n\tbackground-image: none;\n}\n.ui-state-disabled .ui-icon {\n\tfilter:Alpha(Opacity=35); /* support: IE8 - See #6059 */\n}\n\n/* Icons\n----------------------------------*/\n\n/* states and images */\n.ui-icon {\n\twidth: 16px;\n\theight: 16px;\n}\n.ui-icon,\n.ui-widget-content .ui-icon {\n\tbackground-image: url(\"images/ui-icons_444444_256x240.png\");\n}\n.ui-widget-header .ui-icon {\n\tbackground-image: url(\"images/ui-icons_444444_256x240.png\");\n}\n.ui-state-hover .ui-icon,\n.ui-state-focus .ui-icon,\n.ui-button:hover .ui-icon,\n.ui-button:focus .ui-icon {\n\tbackground-image: url(\"images/ui-icons_555555_256x240.png\");\n}\n.ui-state-active .ui-icon,\n.ui-button:active .ui-icon {\n\tbackground-image: url(\"images/ui-icons_ffffff_256x240.png\");\n}\n.ui-state-highlight .ui-icon,\n.ui-button .ui-state-highlight.ui-icon {\n\tbackground-image: url(\"images/ui-icons_777620_256x240.png\");\n}\n.ui-state-error .ui-icon,\n.ui-state-error-text .ui-icon {\n\tbackground-image: url(\"images/ui-icons_cc0000_256x240.png\");\n}\n.ui-button .ui-icon {\n\tbackground-image: url(\"images/ui-icons_777777_256x240.png\");\n}\n\n/* positioning */\n.ui-icon-blank { background-position: 16px 16px; }\n.ui-icon-caret-1-n { background-position: 0 0; }\n.ui-icon-caret-1-ne { background-position: -16px 0; }\n.ui-icon-caret-1-e { background-position: -32px 0; }\n.ui-icon-caret-1-se { background-position: -48px 0; }\n.ui-icon-caret-1-s { background-position: -65px 0; }\n.ui-icon-caret-1-sw { background-position: -80px 0; }\n.ui-icon-caret-1-w { background-position: -96px 0; }\n.ui-icon-caret-1-nw { background-position: -112px 0; }\n.ui-icon-caret-2-n-s { background-position: -128px 0; }\n.ui-icon-caret-2-e-w { background-position: -144px 0; }\n.ui-icon-triangle-1-n { background-position: 0 -16px; }\n.ui-icon-triangle-1-ne { background-position: -16px -16px; }\n.ui-icon-triangle-1-e { background-position: -32px -16px; }\n.ui-icon-triangle-1-se { background-position: -48px -16px; }\n.ui-icon-triangle-1-s { background-position: -65px -16px; }\n.ui-icon-triangle-1-sw { background-position: -80px -16px; }\n.ui-icon-triangle-1-w { background-position: -96px -16px; }\n.ui-icon-triangle-1-nw { background-position: -112px -16px; }\n.ui-icon-triangle-2-n-s { background-position: -128px -16px; }\n.ui-icon-triangle-2-e-w { background-position: -144px -16px; }\n.ui-icon-arrow-1-n { background-position: 0 -32px; }\n.ui-icon-arrow-1-ne { background-position: -16px -32px; }\n.ui-icon-arrow-1-e { background-position: -32px -32px; }\n.ui-icon-arrow-1-se { background-position: -48px -32px; }\n.ui-icon-arrow-1-s { background-position: -65px -32px; }\n.ui-icon-arrow-1-sw { background-position: -80px -32px; }\n.ui-icon-arrow-1-w { background-position: -96px -32px; }\n.ui-icon-arrow-1-nw { background-position: -112px -32px; }\n.ui-icon-arrow-2-n-s { background-position: -128px -32px; }\n.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }\n.ui-icon-arrow-2-e-w { background-position: -160px -32px; }\n.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }\n.ui-icon-arrowstop-1-n { background-position: -192px -32px; }\n.ui-icon-arrowstop-1-e { background-position: -208px -32px; }\n.ui-icon-arrowstop-1-s { background-position: -224px -32px; }\n.ui-icon-arrowstop-1-w { background-position: -240px -32px; }\n.ui-icon-arrowthick-1-n { background-position: 1px -48px; }\n.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }\n.ui-icon-arrowthick-1-e { background-position: -32px -48px; }\n.ui-icon-arrowthick-1-se { background-position: -48px -48px; }\n.ui-icon-arrowthick-1-s { background-position: -64px -48px; }\n.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }\n.ui-icon-arrowthick-1-w { background-position: -96px -48px; }\n.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }\n.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }\n.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }\n.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }\n.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }\n.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }\n.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }\n.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }\n.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }\n.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }\n.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }\n.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }\n.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }\n.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }\n.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }\n.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }\n.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }\n.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }\n.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }\n.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }\n.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }\n.ui-icon-arrow-4 { background-position: 0 -80px; }\n.ui-icon-arrow-4-diag { background-position: -16px -80px; }\n.ui-icon-extlink { background-position: -32px -80px; }\n.ui-icon-newwin { background-position: -48px -80px; }\n.ui-icon-refresh { background-position: -64px -80px; }\n.ui-icon-shuffle { background-position: -80px -80px; }\n.ui-icon-transfer-e-w { background-position: -96px -80px; }\n.ui-icon-transferthick-e-w { background-position: -112px -80px; }\n.ui-icon-folder-collapsed { background-position: 0 -96px; }\n.ui-icon-folder-open { background-position: -16px -96px; }\n.ui-icon-document { background-position: -32px -96px; }\n.ui-icon-document-b { background-position: -48px -96px; }\n.ui-icon-note { background-position: -64px -96px; }\n.ui-icon-mail-closed { background-position: -80px -96px; }\n.ui-icon-mail-open { background-position: -96px -96px; }\n.ui-icon-suitcase { background-position: -112px -96px; }\n.ui-icon-comment { background-position: -128px -96px; }\n.ui-icon-person { background-position: -144px -96px; }\n.ui-icon-print { background-position: -160px -96px; }\n.ui-icon-trash { background-position: -176px -96px; }\n.ui-icon-locked { background-position: -192px -96px; }\n.ui-icon-unlocked { background-position: -208px -96px; }\n.ui-icon-bookmark { background-position: -224px -96px; }\n.ui-icon-tag { background-position: -240px -96px; }\n.ui-icon-home { background-position: 0 -112px; }\n.ui-icon-flag { background-position: -16px -112px; }\n.ui-icon-calendar { background-position: -32px -112px; }\n.ui-icon-cart { background-position: -48px -112px; }\n.ui-icon-pencil { background-position: -64px -112px; }\n.ui-icon-clock { background-position: -80px -112px; }\n.ui-icon-disk { background-position: -96px -112px; }\n.ui-icon-calculator { background-position: -112px -112px; }\n.ui-icon-zoomin { background-position: -128px -112px; }\n.ui-icon-zoomout { background-position: -144px -112px; }\n.ui-icon-search { background-position: -160px -112px; }\n.ui-icon-wrench { background-position: -176px -112px; }\n.ui-icon-gear { background-position: -192px -112px; }\n.ui-icon-heart { background-position: -208px -112px; }\n.ui-icon-star { background-position: -224px -112px; }\n.ui-icon-link { background-position: -240px -112px; }\n.ui-icon-cancel { background-position: 0 -128px; }\n.ui-icon-plus { background-position: -16px -128px; }\n.ui-icon-plusthick { background-position: -32px -128px; }\n.ui-icon-minus { background-position: -48px -128px; }\n.ui-icon-minusthick { background-position: -64px -128px; }\n.ui-icon-close { background-position: -80px -128px; }\n.ui-icon-closethick { background-position: -96px -128px; }\n.ui-icon-key { background-position: -112px -128px; }\n.ui-icon-lightbulb { background-position: -128px -128px; }\n.ui-icon-scissors { background-position: -144px -128px; }\n.ui-icon-clipboard { background-position: -160px -128px; }\n.ui-icon-copy { background-position: -176px -128px; }\n.ui-icon-contact { background-position: -192px -128px; }\n.ui-icon-image { background-position: -208px -128px; }\n.ui-icon-video { background-position: -224px -128px; }\n.ui-icon-script { background-position: -240px -128px; }\n.ui-icon-alert { background-position: 0 -144px; }\n.ui-icon-info { background-position: -16px -144px; }\n.ui-icon-notice { background-position: -32px -144px; }\n.ui-icon-help { background-position: -48px -144px; }\n.ui-icon-check { background-position: -64px -144px; }\n.ui-icon-bullet { background-position: -80px -144px; }\n.ui-icon-radio-on { background-position: -96px -144px; }\n.ui-icon-radio-off { background-position: -112px -144px; }\n.ui-icon-pin-w { background-position: -128px -144px; }\n.ui-icon-pin-s { background-position: -144px -144px; }\n.ui-icon-play { background-position: 0 -160px; }\n.ui-icon-pause { background-position: -16px -160px; }\n.ui-icon-seek-next { background-position: -32px -160px; }\n.ui-icon-seek-prev { background-position: -48px -160px; }\n.ui-icon-seek-end { background-position: -64px -160px; }\n.ui-icon-seek-start { background-position: -80px -160px; }\n/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */\n.ui-icon-seek-first { background-position: -80px -160px; }\n.ui-icon-stop { background-position: -96px -160px; }\n.ui-icon-eject { background-position: -112px -160px; }\n.ui-icon-volume-off { background-position: -128px -160px; }\n.ui-icon-volume-on { background-position: -144px -160px; }\n.ui-icon-power { background-position: 0 -176px; }\n.ui-icon-signal-diag { background-position: -16px -176px; }\n.ui-icon-signal { background-position: -32px -176px; }\n.ui-icon-battery-0 { background-position: -48px -176px; }\n.ui-icon-battery-1 { background-position: -64px -176px; }\n.ui-icon-battery-2 { background-position: -80px -176px; }\n.ui-icon-battery-3 { background-position: -96px -176px; }\n.ui-icon-circle-plus { background-position: 0 -192px; }\n.ui-icon-circle-minus { background-position: -16px -192px; }\n.ui-icon-circle-close { background-position: -32px -192px; }\n.ui-icon-circle-triangle-e { background-position: -48px -192px; }\n.ui-icon-circle-triangle-s { background-position: -64px -192px; }\n.ui-icon-circle-triangle-w { background-position: -80px -192px; }\n.ui-icon-circle-triangle-n { background-position: -96px -192px; }\n.ui-icon-circle-arrow-e { background-position: -112px -192px; }\n.ui-icon-circle-arrow-s { background-position: -128px -192px; }\n.ui-icon-circle-arrow-w { background-position: -144px -192px; }\n.ui-icon-circle-arrow-n { background-position: -160px -192px; }\n.ui-icon-circle-zoomin { background-position: -176px -192px; }\n.ui-icon-circle-zoomout { background-position: -192px -192px; }\n.ui-icon-circle-check { background-position: -208px -192px; }\n.ui-icon-circlesmall-plus { background-position: 0 -208px; }\n.ui-icon-circlesmall-minus { background-position: -16px -208px; }\n.ui-icon-circlesmall-close { background-position: -32px -208px; }\n.ui-icon-squaresmall-plus { background-position: -48px -208px; }\n.ui-icon-squaresmall-minus { background-position: -64px -208px; }\n.ui-icon-squaresmall-close { background-position: -80px -208px; }\n.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }\n.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }\n.ui-icon-grip-solid-vertical { background-position: -32px -224px; }\n.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }\n.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }\n.ui-icon-grip-diagonal-se { background-position: -80px -224px; }\n\n\n/* Misc visuals\n----------------------------------*/\n\n/* Corner radius */\n.ui-corner-all,\n.ui-corner-top,\n.ui-corner-left,\n.ui-corner-tl {\n\tborder-top-left-radius: 3px;\n}\n.ui-corner-all,\n.ui-corner-top,\n.ui-corner-right,\n.ui-corner-tr {\n\tborder-top-right-radius: 3px;\n}\n.ui-corner-all,\n.ui-corner-bottom,\n.ui-corner-left,\n.ui-corner-bl {\n\tborder-bottom-left-radius: 3px;\n}\n.ui-corner-all,\n.ui-corner-bottom,\n.ui-corner-right,\n.ui-corner-br {\n\tborder-bottom-right-radius: 3px;\n}\n\n/* Overlays */\n.ui-widget-overlay {\n\tbackground: #aaaaaa;\n\topacity: .3;\n\tfilter: Alpha(Opacity=30); /* support: IE8 */\n}\n.ui-widget-shadow {\n\t-webkit-box-shadow: 0px 0px 5px #666666;\n\tbox-shadow: 0px 0px 5px #666666;\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/jquery-ui/jquery-ui.structure.css",
    "content": "/*!\n * jQuery UI CSS Framework 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * http://api.jqueryui.com/category/theming/\n */\n.ui-draggable-handle {\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n/* Layout helpers\n----------------------------------*/\n.ui-helper-hidden {\n\tdisplay: none;\n}\n.ui-helper-hidden-accessible {\n\tborder: 0;\n\tclip: rect(0 0 0 0);\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tpadding: 0;\n\tposition: absolute;\n\twidth: 1px;\n}\n.ui-helper-reset {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\toutline: 0;\n\tline-height: 1.3;\n\ttext-decoration: none;\n\tfont-size: 100%;\n\tlist-style: none;\n}\n.ui-helper-clearfix:before,\n.ui-helper-clearfix:after {\n\tcontent: \"\";\n\tdisplay: table;\n\tborder-collapse: collapse;\n}\n.ui-helper-clearfix:after {\n\tclear: both;\n}\n.ui-helper-zfix {\n\twidth: 100%;\n\theight: 100%;\n\ttop: 0;\n\tleft: 0;\n\tposition: absolute;\n\topacity: 0;\n\tfilter:Alpha(Opacity=0); /* support: IE8 */\n}\n\n.ui-front {\n\tz-index: 100;\n}\n\n\n/* Interaction Cues\n----------------------------------*/\n.ui-state-disabled {\n\tcursor: default !important;\n\tpointer-events: none;\n}\n\n\n/* Icons\n----------------------------------*/\n.ui-icon {\n\tdisplay: inline-block;\n\tvertical-align: middle;\n\tmargin-top: -.25em;\n\tposition: relative;\n\ttext-indent: -99999px;\n\toverflow: hidden;\n\tbackground-repeat: no-repeat;\n}\n\n.ui-widget-icon-block {\n\tleft: 50%;\n\tmargin-left: -8px;\n\tdisplay: block;\n}\n\n/* Misc visuals\n----------------------------------*/\n\n/* Overlays */\n.ui-widget-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n.ui-resizable {\n\tposition: relative;\n}\n.ui-resizable-handle {\n\tposition: absolute;\n\tfont-size: 0.1px;\n\tdisplay: block;\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-resizable-disabled .ui-resizable-handle,\n.ui-resizable-autohide .ui-resizable-handle {\n\tdisplay: none;\n}\n.ui-resizable-n {\n\tcursor: n-resize;\n\theight: 7px;\n\twidth: 100%;\n\ttop: -5px;\n\tleft: 0;\n}\n.ui-resizable-s {\n\tcursor: s-resize;\n\theight: 7px;\n\twidth: 100%;\n\tbottom: -5px;\n\tleft: 0;\n}\n.ui-resizable-e {\n\tcursor: e-resize;\n\twidth: 7px;\n\tright: -5px;\n\ttop: 0;\n\theight: 100%;\n}\n.ui-resizable-w {\n\tcursor: w-resize;\n\twidth: 7px;\n\tleft: -5px;\n\ttop: 0;\n\theight: 100%;\n}\n.ui-resizable-se {\n\tcursor: se-resize;\n\twidth: 12px;\n\theight: 12px;\n\tright: 1px;\n\tbottom: 1px;\n}\n.ui-resizable-sw {\n\tcursor: sw-resize;\n\twidth: 9px;\n\theight: 9px;\n\tleft: -5px;\n\tbottom: -5px;\n}\n.ui-resizable-nw {\n\tcursor: nw-resize;\n\twidth: 9px;\n\theight: 9px;\n\tleft: -5px;\n\ttop: -5px;\n}\n.ui-resizable-ne {\n\tcursor: ne-resize;\n\twidth: 9px;\n\theight: 9px;\n\tright: -5px;\n\ttop: -5px;\n}\n.ui-selectable {\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-selectable-helper {\n\tposition: absolute;\n\tz-index: 100;\n\tborder: 1px dotted black;\n}\n.ui-sortable-handle {\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-accordion .ui-accordion-header {\n\tdisplay: block;\n\tcursor: pointer;\n\tposition: relative;\n\tmargin: 2px 0 0 0;\n\tpadding: .5em .5em .5em .7em;\n\tfont-size: 100%;\n}\n.ui-accordion .ui-accordion-content {\n\tpadding: 1em 2.2em;\n\tborder-top: 0;\n\toverflow: auto;\n}\n.ui-autocomplete {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tcursor: default;\n}\n.ui-menu {\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n\tdisplay: block;\n\toutline: 0;\n}\n.ui-menu .ui-menu {\n\tposition: absolute;\n}\n.ui-menu .ui-menu-item {\n\tmargin: 0;\n\tcursor: pointer;\n\t/* support: IE10, see #8844 */\n\tlist-style-image: url(\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\");\n}\n.ui-menu .ui-menu-item-wrapper {\n\tposition: relative;\n\tpadding: 3px 1em 3px .4em;\n}\n.ui-menu .ui-menu-divider {\n\tmargin: 5px 0;\n\theight: 0;\n\tfont-size: 0;\n\tline-height: 0;\n\tborder-width: 1px 0 0 0;\n}\n.ui-menu .ui-state-focus,\n.ui-menu .ui-state-active {\n\tmargin: -1px;\n}\n\n/* icon support */\n.ui-menu-icons {\n\tposition: relative;\n}\n.ui-menu-icons .ui-menu-item-wrapper {\n\tpadding-left: 2em;\n}\n\n/* left-aligned */\n.ui-menu .ui-icon {\n\tposition: absolute;\n\ttop: 0;\n\tbottom: 0;\n\tleft: .2em;\n\tmargin: auto 0;\n}\n\n/* right-aligned */\n.ui-menu .ui-menu-icon {\n\tleft: auto;\n\tright: 0;\n}\n.ui-button {\n\tpadding: .4em 1em;\n\tdisplay: inline-block;\n\tposition: relative;\n\tline-height: normal;\n\tmargin-right: .1em;\n\tcursor: pointer;\n\tvertical-align: middle;\n\ttext-align: center;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n\n\t/* Support: IE <= 11 */\n\toverflow: visible;\n}\n\n.ui-button,\n.ui-button:link,\n.ui-button:visited,\n.ui-button:hover,\n.ui-button:active {\n\ttext-decoration: none;\n}\n\n/* to make room for the icon, a width needs to be set here */\n.ui-button-icon-only {\n\twidth: 2em;\n\tbox-sizing: border-box;\n\ttext-indent: -9999px;\n\twhite-space: nowrap;\n}\n\n/* no icon support for input elements */\ninput.ui-button.ui-button-icon-only {\n\ttext-indent: 0;\n}\n\n/* button icon element(s) */\n.ui-button-icon-only .ui-icon {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\tmargin-top: -8px;\n\tmargin-left: -8px;\n}\n\n.ui-button.ui-icon-notext .ui-icon {\n\tpadding: 0;\n\twidth: 2.1em;\n\theight: 2.1em;\n\ttext-indent: -9999px;\n\twhite-space: nowrap;\n\n}\n\ninput.ui-button.ui-icon-notext .ui-icon {\n\twidth: auto;\n\theight: auto;\n\ttext-indent: 0;\n\twhite-space: normal;\n\tpadding: .4em 1em;\n}\n\n/* workarounds */\n/* Support: Firefox 5 - 40 */\ninput.ui-button::-moz-focus-inner,\nbutton.ui-button::-moz-focus-inner {\n\tborder: 0;\n\tpadding: 0;\n}\n.ui-controlgroup {\n\tvertical-align: middle;\n\tdisplay: inline-block;\n}\n.ui-controlgroup > .ui-controlgroup-item {\n\tfloat: left;\n\tmargin-left: 0;\n\tmargin-right: 0;\n}\n.ui-controlgroup > .ui-controlgroup-item:focus,\n.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus {\n\tz-index: 9999;\n}\n.ui-controlgroup-vertical > .ui-controlgroup-item {\n\tdisplay: block;\n\tfloat: none;\n\twidth: 100%;\n\tmargin-top: 0;\n\tmargin-bottom: 0;\n\ttext-align: left;\n}\n.ui-controlgroup-vertical .ui-controlgroup-item {\n\tbox-sizing: border-box;\n}\n.ui-controlgroup .ui-controlgroup-label {\n\tpadding: .4em 1em;\n}\n.ui-controlgroup .ui-controlgroup-label span {\n\tfont-size: 80%;\n}\n.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item {\n\tborder-left: none;\n}\n.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item {\n\tborder-top: none;\n}\n.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content {\n\tborder-right: none;\n}\n.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content {\n\tborder-bottom: none;\n}\n\n/* Spinner specific style fixes */\n.ui-controlgroup-vertical .ui-spinner-input {\n\n\t/* Support: IE8 only, Android < 4.4 only */\n\twidth: 75%;\n\twidth: calc( 100% - 2.4em );\n}\n.ui-controlgroup-vertical .ui-spinner .ui-spinner-up {\n\tborder-top-style: solid;\n}\n\n.ui-checkboxradio-label .ui-icon-background {\n\tbox-shadow: inset 1px 1px 1px #ccc;\n\tborder-radius: .12em;\n\tborder: none;\n}\n.ui-checkboxradio-radio-label .ui-icon-background {\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 1em;\n\toverflow: visible;\n\tborder: none;\n}\n.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,\n.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon {\n\tbackground-image: none;\n\twidth: 8px;\n\theight: 8px;\n\tborder-width: 4px;\n\tborder-style: solid;\n}\n.ui-checkboxradio-disabled {\n\tpointer-events: none;\n}\n.ui-datepicker {\n\twidth: 17em;\n\tpadding: .2em .2em 0;\n\tdisplay: none;\n}\n.ui-datepicker .ui-datepicker-header {\n\tposition: relative;\n\tpadding: .2em 0;\n}\n.ui-datepicker .ui-datepicker-prev,\n.ui-datepicker .ui-datepicker-next {\n\tposition: absolute;\n\ttop: 2px;\n\twidth: 1.8em;\n\theight: 1.8em;\n}\n.ui-datepicker .ui-datepicker-prev-hover,\n.ui-datepicker .ui-datepicker-next-hover {\n\ttop: 1px;\n}\n.ui-datepicker .ui-datepicker-prev {\n\tleft: 2px;\n}\n.ui-datepicker .ui-datepicker-next {\n\tright: 2px;\n}\n.ui-datepicker .ui-datepicker-prev-hover {\n\tleft: 1px;\n}\n.ui-datepicker .ui-datepicker-next-hover {\n\tright: 1px;\n}\n.ui-datepicker .ui-datepicker-prev span,\n.ui-datepicker .ui-datepicker-next span {\n\tdisplay: block;\n\tposition: absolute;\n\tleft: 50%;\n\tmargin-left: -8px;\n\ttop: 50%;\n\tmargin-top: -8px;\n}\n.ui-datepicker .ui-datepicker-title {\n\tmargin: 0 2.3em;\n\tline-height: 1.8em;\n\ttext-align: center;\n}\n.ui-datepicker .ui-datepicker-title select {\n\tfont-size: 1em;\n\tmargin: 1px 0;\n}\n.ui-datepicker select.ui-datepicker-month,\n.ui-datepicker select.ui-datepicker-year {\n\twidth: 45%;\n}\n.ui-datepicker table {\n\twidth: 100%;\n\tfont-size: .9em;\n\tborder-collapse: collapse;\n\tmargin: 0 0 .4em;\n}\n.ui-datepicker th {\n\tpadding: .7em .3em;\n\ttext-align: center;\n\tfont-weight: bold;\n\tborder: 0;\n}\n.ui-datepicker td {\n\tborder: 0;\n\tpadding: 1px;\n}\n.ui-datepicker td span,\n.ui-datepicker td a {\n\tdisplay: block;\n\tpadding: .2em;\n\ttext-align: right;\n\ttext-decoration: none;\n}\n.ui-datepicker .ui-datepicker-buttonpane {\n\tbackground-image: none;\n\tmargin: .7em 0 0 0;\n\tpadding: 0 .2em;\n\tborder-left: 0;\n\tborder-right: 0;\n\tborder-bottom: 0;\n}\n.ui-datepicker .ui-datepicker-buttonpane button {\n\tfloat: right;\n\tmargin: .5em .2em .4em;\n\tcursor: pointer;\n\tpadding: .2em .6em .3em .6em;\n\twidth: auto;\n\toverflow: visible;\n}\n.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {\n\tfloat: left;\n}\n\n/* with multiple calendars */\n.ui-datepicker.ui-datepicker-multi {\n\twidth: auto;\n}\n.ui-datepicker-multi .ui-datepicker-group {\n\tfloat: left;\n}\n.ui-datepicker-multi .ui-datepicker-group table {\n\twidth: 95%;\n\tmargin: 0 auto .4em;\n}\n.ui-datepicker-multi-2 .ui-datepicker-group {\n\twidth: 50%;\n}\n.ui-datepicker-multi-3 .ui-datepicker-group {\n\twidth: 33.3%;\n}\n.ui-datepicker-multi-4 .ui-datepicker-group {\n\twidth: 25%;\n}\n.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,\n.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {\n\tborder-left-width: 0;\n}\n.ui-datepicker-multi .ui-datepicker-buttonpane {\n\tclear: left;\n}\n.ui-datepicker-row-break {\n\tclear: both;\n\twidth: 100%;\n\tfont-size: 0;\n}\n\n/* RTL support */\n.ui-datepicker-rtl {\n\tdirection: rtl;\n}\n.ui-datepicker-rtl .ui-datepicker-prev {\n\tright: 2px;\n\tleft: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-next {\n\tleft: 2px;\n\tright: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-prev:hover {\n\tright: 1px;\n\tleft: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-next:hover {\n\tleft: 1px;\n\tright: auto;\n}\n.ui-datepicker-rtl .ui-datepicker-buttonpane {\n\tclear: right;\n}\n.ui-datepicker-rtl .ui-datepicker-buttonpane button {\n\tfloat: left;\n}\n.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,\n.ui-datepicker-rtl .ui-datepicker-group {\n\tfloat: right;\n}\n.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,\n.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {\n\tborder-right-width: 0;\n\tborder-left-width: 1px;\n}\n\n/* Icons */\n.ui-datepicker .ui-icon {\n\tdisplay: block;\n\ttext-indent: -99999px;\n\toverflow: hidden;\n\tbackground-repeat: no-repeat;\n\tleft: .5em;\n\ttop: .3em;\n}\n.ui-dialog {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tpadding: .2em;\n\toutline: 0;\n}\n.ui-dialog .ui-dialog-titlebar {\n\tpadding: .4em 1em;\n\tposition: relative;\n}\n.ui-dialog .ui-dialog-title {\n\tfloat: left;\n\tmargin: .1em 0;\n\twhite-space: nowrap;\n\twidth: 90%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n.ui-dialog .ui-dialog-titlebar-close {\n\tposition: absolute;\n\tright: .3em;\n\ttop: 50%;\n\twidth: 20px;\n\tmargin: -10px 0 0 0;\n\tpadding: 1px;\n\theight: 20px;\n}\n.ui-dialog .ui-dialog-content {\n\tposition: relative;\n\tborder: 0;\n\tpadding: .5em 1em;\n\tbackground: none;\n\toverflow: auto;\n}\n.ui-dialog .ui-dialog-buttonpane {\n\ttext-align: left;\n\tborder-width: 1px 0 0 0;\n\tbackground-image: none;\n\tmargin-top: .5em;\n\tpadding: .3em 1em .5em .4em;\n}\n.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {\n\tfloat: right;\n}\n.ui-dialog .ui-dialog-buttonpane button {\n\tmargin: .5em .4em .5em 0;\n\tcursor: pointer;\n}\n.ui-dialog .ui-resizable-n {\n\theight: 2px;\n\ttop: 0;\n}\n.ui-dialog .ui-resizable-e {\n\twidth: 2px;\n\tright: 0;\n}\n.ui-dialog .ui-resizable-s {\n\theight: 2px;\n\tbottom: 0;\n}\n.ui-dialog .ui-resizable-w {\n\twidth: 2px;\n\tleft: 0;\n}\n.ui-dialog .ui-resizable-se,\n.ui-dialog .ui-resizable-sw,\n.ui-dialog .ui-resizable-ne,\n.ui-dialog .ui-resizable-nw {\n\twidth: 7px;\n\theight: 7px;\n}\n.ui-dialog .ui-resizable-se {\n\tright: 0;\n\tbottom: 0;\n}\n.ui-dialog .ui-resizable-sw {\n\tleft: 0;\n\tbottom: 0;\n}\n.ui-dialog .ui-resizable-ne {\n\tright: 0;\n\ttop: 0;\n}\n.ui-dialog .ui-resizable-nw {\n\tleft: 0;\n\ttop: 0;\n}\n.ui-draggable .ui-dialog-titlebar {\n\tcursor: move;\n}\n.ui-progressbar {\n\theight: 2em;\n\ttext-align: left;\n\toverflow: hidden;\n}\n.ui-progressbar .ui-progressbar-value {\n\tmargin: -1px;\n\theight: 100%;\n}\n.ui-progressbar .ui-progressbar-overlay {\n\tbackground: url(\"data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==\");\n\theight: 100%;\n\tfilter: alpha(opacity=25); /* support: IE8 */\n\topacity: 0.25;\n}\n.ui-progressbar-indeterminate .ui-progressbar-value {\n\tbackground-image: none;\n}\n.ui-selectmenu-menu {\n\tpadding: 0;\n\tmargin: 0;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tdisplay: none;\n}\n.ui-selectmenu-menu .ui-menu {\n\toverflow: auto;\n\toverflow-x: hidden;\n\tpadding-bottom: 1px;\n}\n.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {\n\tfont-size: 1em;\n\tfont-weight: bold;\n\tline-height: 1.5;\n\tpadding: 2px 0.4em;\n\tmargin: 0.5em 0 0 0;\n\theight: auto;\n\tborder: 0;\n}\n.ui-selectmenu-open {\n\tdisplay: block;\n}\n.ui-selectmenu-text {\n\tdisplay: block;\n\tmargin-right: 20px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n.ui-selectmenu-button.ui-button {\n\ttext-align: left;\n\twhite-space: nowrap;\n\twidth: 14em;\n}\n.ui-selectmenu-icon.ui-icon {\n\tfloat: right;\n\tmargin-top: 0;\n}\n.ui-slider {\n\tposition: relative;\n\ttext-align: left;\n}\n.ui-slider .ui-slider-handle {\n\tposition: absolute;\n\tz-index: 2;\n\twidth: 1.2em;\n\theight: 1.2em;\n\tcursor: default;\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.ui-slider .ui-slider-range {\n\tposition: absolute;\n\tz-index: 1;\n\tfont-size: .7em;\n\tdisplay: block;\n\tborder: 0;\n\tbackground-position: 0 0;\n}\n\n/* support: IE8 - See #6727 */\n.ui-slider.ui-state-disabled .ui-slider-handle,\n.ui-slider.ui-state-disabled .ui-slider-range {\n\tfilter: inherit;\n}\n\n.ui-slider-horizontal {\n\theight: .8em;\n}\n.ui-slider-horizontal .ui-slider-handle {\n\ttop: -.3em;\n\tmargin-left: -.6em;\n}\n.ui-slider-horizontal .ui-slider-range {\n\ttop: 0;\n\theight: 100%;\n}\n.ui-slider-horizontal .ui-slider-range-min {\n\tleft: 0;\n}\n.ui-slider-horizontal .ui-slider-range-max {\n\tright: 0;\n}\n\n.ui-slider-vertical {\n\twidth: .8em;\n\theight: 100px;\n}\n.ui-slider-vertical .ui-slider-handle {\n\tleft: -.3em;\n\tmargin-left: 0;\n\tmargin-bottom: -.6em;\n}\n.ui-slider-vertical .ui-slider-range {\n\tleft: 0;\n\twidth: 100%;\n}\n.ui-slider-vertical .ui-slider-range-min {\n\tbottom: 0;\n}\n.ui-slider-vertical .ui-slider-range-max {\n\ttop: 0;\n}\n.ui-spinner {\n\tposition: relative;\n\tdisplay: inline-block;\n\toverflow: hidden;\n\tpadding: 0;\n\tvertical-align: middle;\n}\n.ui-spinner-input {\n\tborder: none;\n\tbackground: none;\n\tcolor: inherit;\n\tpadding: .222em 0;\n\tmargin: .2em 0;\n\tvertical-align: middle;\n\tmargin-left: .4em;\n\tmargin-right: 2em;\n}\n.ui-spinner-button {\n\twidth: 1.6em;\n\theight: 50%;\n\tfont-size: .5em;\n\tpadding: 0;\n\tmargin: 0;\n\ttext-align: center;\n\tposition: absolute;\n\tcursor: default;\n\tdisplay: block;\n\toverflow: hidden;\n\tright: 0;\n}\n/* more specificity required here to override default borders */\n.ui-spinner a.ui-spinner-button {\n\tborder-top-style: none;\n\tborder-bottom-style: none;\n\tborder-right-style: none;\n}\n.ui-spinner-up {\n\ttop: 0;\n}\n.ui-spinner-down {\n\tbottom: 0;\n}\n.ui-tabs {\n\tposition: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as \"fixed\") */\n\tpadding: .2em;\n}\n.ui-tabs .ui-tabs-nav {\n\tmargin: 0;\n\tpadding: .2em .2em 0;\n}\n.ui-tabs .ui-tabs-nav li {\n\tlist-style: none;\n\tfloat: left;\n\tposition: relative;\n\ttop: 0;\n\tmargin: 1px .2em 0 0;\n\tborder-bottom-width: 0;\n\tpadding: 0;\n\twhite-space: nowrap;\n}\n.ui-tabs .ui-tabs-nav .ui-tabs-anchor {\n\tfloat: left;\n\tpadding: .5em 1em;\n\ttext-decoration: none;\n}\n.ui-tabs .ui-tabs-nav li.ui-tabs-active {\n\tmargin-bottom: -1px;\n\tpadding-bottom: 1px;\n}\n.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,\n.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,\n.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {\n\tcursor: text;\n}\n.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {\n\tcursor: pointer;\n}\n.ui-tabs .ui-tabs-panel {\n\tdisplay: block;\n\tborder-width: 0;\n\tpadding: 1em 1.4em;\n\tbackground: none;\n}\n.ui-tooltip {\n\tpadding: 8px;\n\tposition: absolute;\n\tz-index: 9999;\n\tmax-width: 300px;\n}\nbody .ui-tooltip {\n\tborder-width: 2px;\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/jquery-ui/jquery-ui.theme.css",
    "content": "/*!\n * jQuery UI CSS Framework 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * http://api.jqueryui.com/category/theming/\n *\n * To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif\n */\n\n\n/* Component containers\n----------------------------------*/\n.ui-widget {\n\tfont-family: Arial,Helvetica,sans-serif;\n\tfont-size: 1em;\n}\n.ui-widget .ui-widget {\n\tfont-size: 1em;\n}\n.ui-widget input,\n.ui-widget select,\n.ui-widget textarea,\n.ui-widget button {\n\tfont-family: Arial,Helvetica,sans-serif;\n\tfont-size: 1em;\n}\n.ui-widget.ui-widget-content {\n\tborder: 1px solid #c5c5c5;\n}\n.ui-widget-content {\n\tborder: 1px solid #dddddd;\n\tbackground: #ffffff;\n\tcolor: #333333;\n}\n.ui-widget-content a {\n\tcolor: #333333;\n}\n.ui-widget-header {\n\tborder: 1px solid #dddddd;\n\tbackground: #e9e9e9;\n\tcolor: #333333;\n\tfont-weight: bold;\n}\n.ui-widget-header a {\n\tcolor: #333333;\n}\n\n/* Interaction states\n----------------------------------*/\n.ui-state-default,\n.ui-widget-content .ui-state-default,\n.ui-widget-header .ui-state-default,\n.ui-button,\n\n/* We use html here because we need a greater specificity to make sure disabled\nworks properly when clicked or hovered */\nhtml .ui-button.ui-state-disabled:hover,\nhtml .ui-button.ui-state-disabled:active {\n\tborder: 1px solid #c5c5c5;\n\tbackground: #f6f6f6;\n\tfont-weight: normal;\n\tcolor: #454545;\n}\n.ui-state-default a,\n.ui-state-default a:link,\n.ui-state-default a:visited,\na.ui-button,\na:link.ui-button,\na:visited.ui-button,\n.ui-button {\n\tcolor: #454545;\n\ttext-decoration: none;\n}\n.ui-state-hover,\n.ui-widget-content .ui-state-hover,\n.ui-widget-header .ui-state-hover,\n.ui-state-focus,\n.ui-widget-content .ui-state-focus,\n.ui-widget-header .ui-state-focus,\n.ui-button:hover,\n.ui-button:focus {\n\tborder: 1px solid #cccccc;\n\tbackground: #ededed;\n\tfont-weight: normal;\n\tcolor: #2b2b2b;\n}\n.ui-state-hover a,\n.ui-state-hover a:hover,\n.ui-state-hover a:link,\n.ui-state-hover a:visited,\n.ui-state-focus a,\n.ui-state-focus a:hover,\n.ui-state-focus a:link,\n.ui-state-focus a:visited,\na.ui-button:hover,\na.ui-button:focus {\n\tcolor: #2b2b2b;\n\ttext-decoration: none;\n}\n\n.ui-visual-focus {\n\tbox-shadow: 0 0 3px 1px rgb(94, 158, 214);\n}\n.ui-state-active,\n.ui-widget-content .ui-state-active,\n.ui-widget-header .ui-state-active,\na.ui-button:active,\n.ui-button:active,\n.ui-button.ui-state-active:hover {\n\tborder: 1px solid #003eff;\n\tbackground: #007fff;\n\tfont-weight: normal;\n\tcolor: #ffffff;\n}\n.ui-icon-background,\n.ui-state-active .ui-icon-background {\n\tborder: #003eff;\n\tbackground-color: #ffffff;\n}\n.ui-state-active a,\n.ui-state-active a:link,\n.ui-state-active a:visited {\n\tcolor: #ffffff;\n\ttext-decoration: none;\n}\n\n/* Interaction Cues\n----------------------------------*/\n.ui-state-highlight,\n.ui-widget-content .ui-state-highlight,\n.ui-widget-header .ui-state-highlight {\n\tborder: 1px solid #dad55e;\n\tbackground: #fffa90;\n\tcolor: #777620;\n}\n.ui-state-checked {\n\tborder: 1px solid #dad55e;\n\tbackground: #fffa90;\n}\n.ui-state-highlight a,\n.ui-widget-content .ui-state-highlight a,\n.ui-widget-header .ui-state-highlight a {\n\tcolor: #777620;\n}\n.ui-state-error,\n.ui-widget-content .ui-state-error,\n.ui-widget-header .ui-state-error {\n\tborder: 1px solid #f1a899;\n\tbackground: #fddfdf;\n\tcolor: #5f3f3f;\n}\n.ui-state-error a,\n.ui-widget-content .ui-state-error a,\n.ui-widget-header .ui-state-error a {\n\tcolor: #5f3f3f;\n}\n.ui-state-error-text,\n.ui-widget-content .ui-state-error-text,\n.ui-widget-header .ui-state-error-text {\n\tcolor: #5f3f3f;\n}\n.ui-priority-primary,\n.ui-widget-content .ui-priority-primary,\n.ui-widget-header .ui-priority-primary {\n\tfont-weight: bold;\n}\n.ui-priority-secondary,\n.ui-widget-content .ui-priority-secondary,\n.ui-widget-header .ui-priority-secondary {\n\topacity: .7;\n\tfilter:Alpha(Opacity=70); /* support: IE8 */\n\tfont-weight: normal;\n}\n.ui-state-disabled,\n.ui-widget-content .ui-state-disabled,\n.ui-widget-header .ui-state-disabled {\n\topacity: .35;\n\tfilter:Alpha(Opacity=35); /* support: IE8 */\n\tbackground-image: none;\n}\n.ui-state-disabled .ui-icon {\n\tfilter:Alpha(Opacity=35); /* support: IE8 - See #6059 */\n}\n\n/* Icons\n----------------------------------*/\n\n/* states and images */\n.ui-icon {\n\twidth: 16px;\n\theight: 16px;\n}\n.ui-icon,\n.ui-widget-content .ui-icon {\n\tbackground-image: url(\"images/ui-icons_444444_256x240.png\");\n}\n.ui-widget-header .ui-icon {\n\tbackground-image: url(\"images/ui-icons_444444_256x240.png\");\n}\n.ui-state-hover .ui-icon,\n.ui-state-focus .ui-icon,\n.ui-button:hover .ui-icon,\n.ui-button:focus .ui-icon {\n\tbackground-image: url(\"images/ui-icons_555555_256x240.png\");\n}\n.ui-state-active .ui-icon,\n.ui-button:active .ui-icon {\n\tbackground-image: url(\"images/ui-icons_ffffff_256x240.png\");\n}\n.ui-state-highlight .ui-icon,\n.ui-button .ui-state-highlight.ui-icon {\n\tbackground-image: url(\"images/ui-icons_777620_256x240.png\");\n}\n.ui-state-error .ui-icon,\n.ui-state-error-text .ui-icon {\n\tbackground-image: url(\"images/ui-icons_cc0000_256x240.png\");\n}\n.ui-button .ui-icon {\n\tbackground-image: url(\"images/ui-icons_777777_256x240.png\");\n}\n\n/* positioning */\n.ui-icon-blank { background-position: 16px 16px; }\n.ui-icon-caret-1-n { background-position: 0 0; }\n.ui-icon-caret-1-ne { background-position: -16px 0; }\n.ui-icon-caret-1-e { background-position: -32px 0; }\n.ui-icon-caret-1-se { background-position: -48px 0; }\n.ui-icon-caret-1-s { background-position: -65px 0; }\n.ui-icon-caret-1-sw { background-position: -80px 0; }\n.ui-icon-caret-1-w { background-position: -96px 0; }\n.ui-icon-caret-1-nw { background-position: -112px 0; }\n.ui-icon-caret-2-n-s { background-position: -128px 0; }\n.ui-icon-caret-2-e-w { background-position: -144px 0; }\n.ui-icon-triangle-1-n { background-position: 0 -16px; }\n.ui-icon-triangle-1-ne { background-position: -16px -16px; }\n.ui-icon-triangle-1-e { background-position: -32px -16px; }\n.ui-icon-triangle-1-se { background-position: -48px -16px; }\n.ui-icon-triangle-1-s { background-position: -65px -16px; }\n.ui-icon-triangle-1-sw { background-position: -80px -16px; }\n.ui-icon-triangle-1-w { background-position: -96px -16px; }\n.ui-icon-triangle-1-nw { background-position: -112px -16px; }\n.ui-icon-triangle-2-n-s { background-position: -128px -16px; }\n.ui-icon-triangle-2-e-w { background-position: -144px -16px; }\n.ui-icon-arrow-1-n { background-position: 0 -32px; }\n.ui-icon-arrow-1-ne { background-position: -16px -32px; }\n.ui-icon-arrow-1-e { background-position: -32px -32px; }\n.ui-icon-arrow-1-se { background-position: -48px -32px; }\n.ui-icon-arrow-1-s { background-position: -65px -32px; }\n.ui-icon-arrow-1-sw { background-position: -80px -32px; }\n.ui-icon-arrow-1-w { background-position: -96px -32px; }\n.ui-icon-arrow-1-nw { background-position: -112px -32px; }\n.ui-icon-arrow-2-n-s { background-position: -128px -32px; }\n.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }\n.ui-icon-arrow-2-e-w { background-position: -160px -32px; }\n.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }\n.ui-icon-arrowstop-1-n { background-position: -192px -32px; }\n.ui-icon-arrowstop-1-e { background-position: -208px -32px; }\n.ui-icon-arrowstop-1-s { background-position: -224px -32px; }\n.ui-icon-arrowstop-1-w { background-position: -240px -32px; }\n.ui-icon-arrowthick-1-n { background-position: 1px -48px; }\n.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }\n.ui-icon-arrowthick-1-e { background-position: -32px -48px; }\n.ui-icon-arrowthick-1-se { background-position: -48px -48px; }\n.ui-icon-arrowthick-1-s { background-position: -64px -48px; }\n.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }\n.ui-icon-arrowthick-1-w { background-position: -96px -48px; }\n.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }\n.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }\n.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }\n.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }\n.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }\n.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }\n.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }\n.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }\n.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }\n.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }\n.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }\n.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }\n.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }\n.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }\n.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }\n.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }\n.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }\n.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }\n.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }\n.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }\n.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }\n.ui-icon-arrow-4 { background-position: 0 -80px; }\n.ui-icon-arrow-4-diag { background-position: -16px -80px; }\n.ui-icon-extlink { background-position: -32px -80px; }\n.ui-icon-newwin { background-position: -48px -80px; }\n.ui-icon-refresh { background-position: -64px -80px; }\n.ui-icon-shuffle { background-position: -80px -80px; }\n.ui-icon-transfer-e-w { background-position: -96px -80px; }\n.ui-icon-transferthick-e-w { background-position: -112px -80px; }\n.ui-icon-folder-collapsed { background-position: 0 -96px; }\n.ui-icon-folder-open { background-position: -16px -96px; }\n.ui-icon-document { background-position: -32px -96px; }\n.ui-icon-document-b { background-position: -48px -96px; }\n.ui-icon-note { background-position: -64px -96px; }\n.ui-icon-mail-closed { background-position: -80px -96px; }\n.ui-icon-mail-open { background-position: -96px -96px; }\n.ui-icon-suitcase { background-position: -112px -96px; }\n.ui-icon-comment { background-position: -128px -96px; }\n.ui-icon-person { background-position: -144px -96px; }\n.ui-icon-print { background-position: -160px -96px; }\n.ui-icon-trash { background-position: -176px -96px; }\n.ui-icon-locked { background-position: -192px -96px; }\n.ui-icon-unlocked { background-position: -208px -96px; }\n.ui-icon-bookmark { background-position: -224px -96px; }\n.ui-icon-tag { background-position: -240px -96px; }\n.ui-icon-home { background-position: 0 -112px; }\n.ui-icon-flag { background-position: -16px -112px; }\n.ui-icon-calendar { background-position: -32px -112px; }\n.ui-icon-cart { background-position: -48px -112px; }\n.ui-icon-pencil { background-position: -64px -112px; }\n.ui-icon-clock { background-position: -80px -112px; }\n.ui-icon-disk { background-position: -96px -112px; }\n.ui-icon-calculator { background-position: -112px -112px; }\n.ui-icon-zoomin { background-position: -128px -112px; }\n.ui-icon-zoomout { background-position: -144px -112px; }\n.ui-icon-search { background-position: -160px -112px; }\n.ui-icon-wrench { background-position: -176px -112px; }\n.ui-icon-gear { background-position: -192px -112px; }\n.ui-icon-heart { background-position: -208px -112px; }\n.ui-icon-star { background-position: -224px -112px; }\n.ui-icon-link { background-position: -240px -112px; }\n.ui-icon-cancel { background-position: 0 -128px; }\n.ui-icon-plus { background-position: -16px -128px; }\n.ui-icon-plusthick { background-position: -32px -128px; }\n.ui-icon-minus { background-position: -48px -128px; }\n.ui-icon-minusthick { background-position: -64px -128px; }\n.ui-icon-close { background-position: -80px -128px; }\n.ui-icon-closethick { background-position: -96px -128px; }\n.ui-icon-key { background-position: -112px -128px; }\n.ui-icon-lightbulb { background-position: -128px -128px; }\n.ui-icon-scissors { background-position: -144px -128px; }\n.ui-icon-clipboard { background-position: -160px -128px; }\n.ui-icon-copy { background-position: -176px -128px; }\n.ui-icon-contact { background-position: -192px -128px; }\n.ui-icon-image { background-position: -208px -128px; }\n.ui-icon-video { background-position: -224px -128px; }\n.ui-icon-script { background-position: -240px -128px; }\n.ui-icon-alert { background-position: 0 -144px; }\n.ui-icon-info { background-position: -16px -144px; }\n.ui-icon-notice { background-position: -32px -144px; }\n.ui-icon-help { background-position: -48px -144px; }\n.ui-icon-check { background-position: -64px -144px; }\n.ui-icon-bullet { background-position: -80px -144px; }\n.ui-icon-radio-on { background-position: -96px -144px; }\n.ui-icon-radio-off { background-position: -112px -144px; }\n.ui-icon-pin-w { background-position: -128px -144px; }\n.ui-icon-pin-s { background-position: -144px -144px; }\n.ui-icon-play { background-position: 0 -160px; }\n.ui-icon-pause { background-position: -16px -160px; }\n.ui-icon-seek-next { background-position: -32px -160px; }\n.ui-icon-seek-prev { background-position: -48px -160px; }\n.ui-icon-seek-end { background-position: -64px -160px; }\n.ui-icon-seek-start { background-position: -80px -160px; }\n/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */\n.ui-icon-seek-first { background-position: -80px -160px; }\n.ui-icon-stop { background-position: -96px -160px; }\n.ui-icon-eject { background-position: -112px -160px; }\n.ui-icon-volume-off { background-position: -128px -160px; }\n.ui-icon-volume-on { background-position: -144px -160px; }\n.ui-icon-power { background-position: 0 -176px; }\n.ui-icon-signal-diag { background-position: -16px -176px; }\n.ui-icon-signal { background-position: -32px -176px; }\n.ui-icon-battery-0 { background-position: -48px -176px; }\n.ui-icon-battery-1 { background-position: -64px -176px; }\n.ui-icon-battery-2 { background-position: -80px -176px; }\n.ui-icon-battery-3 { background-position: -96px -176px; }\n.ui-icon-circle-plus { background-position: 0 -192px; }\n.ui-icon-circle-minus { background-position: -16px -192px; }\n.ui-icon-circle-close { background-position: -32px -192px; }\n.ui-icon-circle-triangle-e { background-position: -48px -192px; }\n.ui-icon-circle-triangle-s { background-position: -64px -192px; }\n.ui-icon-circle-triangle-w { background-position: -80px -192px; }\n.ui-icon-circle-triangle-n { background-position: -96px -192px; }\n.ui-icon-circle-arrow-e { background-position: -112px -192px; }\n.ui-icon-circle-arrow-s { background-position: -128px -192px; }\n.ui-icon-circle-arrow-w { background-position: -144px -192px; }\n.ui-icon-circle-arrow-n { background-position: -160px -192px; }\n.ui-icon-circle-zoomin { background-position: -176px -192px; }\n.ui-icon-circle-zoomout { background-position: -192px -192px; }\n.ui-icon-circle-check { background-position: -208px -192px; }\n.ui-icon-circlesmall-plus { background-position: 0 -208px; }\n.ui-icon-circlesmall-minus { background-position: -16px -208px; }\n.ui-icon-circlesmall-close { background-position: -32px -208px; }\n.ui-icon-squaresmall-plus { background-position: -48px -208px; }\n.ui-icon-squaresmall-minus { background-position: -64px -208px; }\n.ui-icon-squaresmall-close { background-position: -80px -208px; }\n.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }\n.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }\n.ui-icon-grip-solid-vertical { background-position: -32px -224px; }\n.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }\n.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }\n.ui-icon-grip-diagonal-se { background-position: -80px -224px; }\n\n\n/* Misc visuals\n----------------------------------*/\n\n/* Corner radius */\n.ui-corner-all,\n.ui-corner-top,\n.ui-corner-left,\n.ui-corner-tl {\n\tborder-top-left-radius: 3px;\n}\n.ui-corner-all,\n.ui-corner-top,\n.ui-corner-right,\n.ui-corner-tr {\n\tborder-top-right-radius: 3px;\n}\n.ui-corner-all,\n.ui-corner-bottom,\n.ui-corner-left,\n.ui-corner-bl {\n\tborder-bottom-left-radius: 3px;\n}\n.ui-corner-all,\n.ui-corner-bottom,\n.ui-corner-right,\n.ui-corner-br {\n\tborder-bottom-right-radius: 3px;\n}\n\n/* Overlays */\n.ui-widget-overlay {\n\tbackground: #aaaaaa;\n\topacity: .3;\n\tfilter: Alpha(Opacity=30); /* support: IE8 */\n}\n.ui-widget-shadow {\n\t-webkit-box-shadow: 0px 0px 5px #666666;\n\tbox-shadow: 0px 0px 5px #666666;\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/jquery-ui/package.json",
    "content": "{\n\t\"name\": \"jquery-ui\",\n\t\"title\": \"jQuery UI\",\n\t\"description\": \"A curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library.\",\n\t\"version\": \"1.12.1\",\n\t\"homepage\": \"http://jqueryui.com\",\n\t\"author\": {\n\t\t\"name\": \"jQuery Foundation and other contributors\",\n\t\t\"url\": \"https://github.com/jquery/jquery-ui/blob/1.12.1/AUTHORS.txt\"\n\t},\n\t\"main\": \"ui/widget.js\",\n\t\"maintainers\": [\n\t\t{\n\t\t\t\"name\": \"Scott González\",\n\t\t\t\"email\": \"scott.gonzalez@gmail.com\",\n\t\t\t\"url\": \"http://scottgonzalez.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Jörn Zaefferer\",\n\t\t\t\"email\": \"joern.zaefferer@gmail.com\",\n\t\t\t\"url\": \"http://bassistance.de\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Mike Sherov\",\n\t\t\t\"email\": \"mike.sherov@gmail.com\",\n\t\t\t\"url\": \"http://mike.sherov.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"TJ VanToll\",\n\t\t\t\"email\": \"tj.vantoll@gmail.com\",\n\t\t\t\"url\": \"http://tjvantoll.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Felix Nagel\",\n\t\t\t\"email\": \"info@felixnagel.com\",\n\t\t\t\"url\": \"http://www.felixnagel.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Alex Schmitz\",\n\t\t\t\"email\": \"arschmitz@gmail.com\",\n\t\t\t\"url\": \"https://github.com/arschmitz\"\n\t\t}\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git://github.com/jquery/jquery-ui.git\"\n\t},\n\t\"bugs\": \"https://bugs.jqueryui.com/\",\n\t\"license\": \"MIT\",\n\t\"scripts\": {\n\t\t\"test\": \"grunt\"\n\t},\n\t\"dependencies\": {},\n\t\"devDependencies\": {\n\t\t\"commitplease\": \"2.3.0\",\n\t\t\"grunt\": \"0.4.5\",\n\t\t\"grunt-bowercopy\": \"1.2.4\",\n\t\t\"grunt-cli\": \"0.1.13\",\n\t\t\"grunt-compare-size\": \"0.4.0\",\n\t\t\"grunt-contrib-concat\": \"0.5.1\",\n\t\t\"grunt-contrib-csslint\": \"0.5.0\",\n\t\t\"grunt-contrib-jshint\": \"0.12.0\",\n\t\t\"grunt-contrib-qunit\": \"1.0.1\",\n\t\t\"grunt-contrib-requirejs\": \"0.4.4\",\n\t\t\"grunt-contrib-uglify\": \"0.11.1\",\n\t\t\"grunt-git-authors\": \"3.1.0\",\n\t\t\"grunt-html\": \"6.0.0\",\n\t\t\"grunt-jscs\": \"2.1.0\",\n\t\t\"load-grunt-tasks\": \"3.4.0\",\n\t\t\"rimraf\": \"2.5.1\",\n\t\t\"testswarm\": \"1.1.0\"\n\t},\n\t\"keywords\": []\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/css/pyscada/pyscada-theme.css",
    "content": "/* Generated by Font Squirrel (http://www.fontsquirrel.com) on October 15, 2015 */\n\n\n\n@font-face {\n\tfont-family: 'robotoregular';\n\tsrc: url('../fonts/roboto/roboto-regular-webfont.eot');\n\tsrc: url(../fonts/roboto/'roboto-regular-webfont.eot?#iefix') format('embedded-opentype'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.woff2') format('woff2'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.woff') format('woff'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.ttf') format('truetype'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.svg#robotoregular') format('svg');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n@font-face {\n\tfont-family: 'roboto';\n\tsrc: url('../fonts/roboto/roboto-regular-webfont.eot');\n\tsrc: url(../fonts/roboto/'roboto-regular-webfont.eot?#iefix') format('embedded-opentype'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.woff2') format('woff2'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.woff') format('woff'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.ttf') format('truetype'),\n\t\turl('../fonts/roboto/roboto-regular-webfont.svg#robotoregular') format('svg');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n@font-face {\n\tfont-family: 'robotolight';\n\tsrc: url('../fonts/roboto/roboto-light-webfont.eot');\n\tsrc: url('../fonts/roboto/roboto-light-webfont.eot?#iefix') format('embedded-opentype'),\n\t\turl('../fonts/roboto/roboto-light-webfont.woff2') format('woff2'),\n\t\turl('../fonts/roboto/roboto-light-webfont.woff') format('woff'),\n\t\turl('../fonts/roboto/roboto-light-webfont.ttf') format('truetype'),\n\t\turl('../fonts/roboto/roboto-light-webfont.svg#robotolight') format('svg');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n\nhtml {\n\tfont-family: robotoregular, sans-serif;\n}\nbody {\n\tfont-family: robotoregular, Arial, sans-serif;\n\tpadding-top: 70px;\n}\nhtml,body {\n\theight:100%;\n}\n\n\n.row {\n    margin-bottom: 15px;\n}\n\n.btn {\n\tborder-radius: 0px;\n\t1px solid #ddd;\n}\n\n.btn-default {\n\tbackground-image:none;\n}\n\n.btn-default, .btn-primary, .btn-success, .btn-info, .btn-warning, .btn-danger {\n\ttext-shadow:none;\n\t-webkit-box-shadow:none;\n\tbox-shadow:none;\n}\n\n.input-group-addon {\n\tborder-radius:0px;\n}\n\n.navbar-inverse {\n\tbackground-image: none;\n\tbackground-color: #222;\n\tfilter:none;\n}\n\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n\tcolor: #fff;\n\tbackground-image:none;\n\tbox-shadow:none;\n\twebkit-box-shadow:none;\n\tbackground-color: #000;\n}\n.navbar-inverse .navbar-brand, .navbar-inverse .navbar-nav>li>a {\n\ttext-shadow: none;\n}\n.navbar-brand, .navbar-nav>li>a {\n\ttext-shadow: none;\n}\n.navbar-right .dropdown-menu {\n    position: fixed;\n    top: auto;\n    min-width: 25vw;\n    max-width: 95vw;\n}\n.theme-dropdown .dropdown-menu {\n\tdisplay: block;\n\tposition: static;\n\tmargin-bottom: 20px;\n}\n\n.theme-showcase > p > .btn {\n\tmargin: 5px 0;\n}\n\n.label {\n\tborder-radius:0px;\n}\n\n.control-panel .label {\n\tmargin-bottom: 5px;\n\tmargin-right: 5px;\n\twidth: 220px;\n\tpadding: 10px 12px;\n\tdisplay: inline-block;\n\tfont-size: 14px;\n\tfont-weight: normal;\n\tline-height: 1;\n\tcolor: #fff;\n}\n\n.sub-page{\n\tpadding-top:15px;\n\tpadding-left: 10px;\n}\n\n.SAC-Main {\n\tposition:relative;\n\ttop:0; left:0;\n}\n\n.SAC-Value {\n\tposition:absolute;\n\tfont-size: 12px;\n}\n.SAC-IMG {\n\tposition:absolute;\n}\n\n.status-panel tbody > tr > td {\n\tborder-top: 0px;\n\tpadding: 2px;\n}\n\n.status-panel span.label,\n.control-pabel span.label {\n\tfloat: left;\n\twidth: 100px;\n\n}\n.status-panel li,\n.control-pabel li {\n\tlist-style: none;\n\n}\n.legend-sidebar{\n\t/*min-width:240px;*/\n\tposition: relative;\n\tmin-height: 1px;\n\tpadding-right: 15px;\n\tpadding-left:10px;\n\tfloat: left;\n\tfont: 12px/1em \"proxima-nova\", Helvetica, Arial, sans-serif;\n}\n.chart-legend {\n\tfont-size: 10px;\n\n}\n\n.container {\n\tmax-width: 2550px !important;\n\tmargin-right: 0px !important;\n\tmargin-left: 0px !important;\n\twidth:100% !important;\n}\n\n#wrap {\n\tmin-height: 100%;\n}\n\n#content {\n\tpadding-bottom:50px;\n}\n\n.footer\n{\n\tposition: relative;\n\tmargin-top: -30px;\n\theight: 30px;\n\tclear: both;\n\tborder-top: 1px solid #ddd;\n\tpadding-top: 6px;\n\tpadding-left: 20px;\n}\n\n.xylegend-sidebar {\n\tmin-width:168px;\n\tposition: relative;\n\tmin-height: 1px;\n\tpadding-right: 15px;\n\tfloat: left;\n\tfont: 12px/1em \"proxima-nova\", Helvetica, Arial, sans-serif;\n}\n\n.xy-chart-container {\n\tposition: relative;\n\tbox-sizing: border-box;\n\tpadding: 10px 5px 10px 7px;\n\tborder: 1px solid #ddd;\n\tbackground: #fff;\n\t/*background: linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -o-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -ms-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -moz-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbox-shadow: 0 3px 10px rgba(0,0,0,0.15);\n\t-o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);*/\n\tfont: 12px/1em \"proxima-nova\", Helvetica, Arial, sans-serif;\n}\n\n.pie-container {\n\tposition: relative;\n\tbox-sizing: border-box;\n\tpadding: 10px 5px 10px 7px;\n\tborder: 1px solid #ddd;\n\tbackground: #fff;\n\t/*background: linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -o-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -ms-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -moz-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbox-shadow: 0 3px 10px rgba(0,0,0,0.15);\n\t-o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);*/\n\tfont: 12px/1em \"proxima-nova\", Helvetica, Arial, sans-serif;\n}\n\n.main-chart-area{\n\tposition: relative;\n\tmin-height: 1px;\n\tfloat: left;\n\theight:450px;\n\twidth:100%;\n}\n.half-size-chart{\n\tfloat: left;\n\tposition: relative;\n\tmin-height: 1px;\n\twidth: 49.5%;\n\theight: 100%;\n}\n.third-size-chart{\n\tfloat: left;\n\tposition: relative;\n\tmin-height: 1px;\n\twidth: 33%;\n\theight: 100%;\n}\n.two-third-size-chart{\n\tfloat: left;\n\tposition: relative;\n\tmin-height: 1px;\n\twidth: 65%;\n\theight: 100%;\n}\n\n.clear-sep{\n\tclear:both;\n\tpadding-top:15px;\n}\n.chart-container {\n\tposition: relative;\n\tbox-sizing: border-box;\n\tpadding: 10px 5px 10px 7px;\n\tborder: 1px solid #ddd;\n\tbackground: #fff;\n\t/*background: linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -o-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -ms-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -moz-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbackground: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);\n\tbox-shadow: 0 3px 10px rgba(0,0,0,0.15);\n\t-o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);\n\t-webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);*/\n\tfont: 12px/1em \"proxima-nova\", Helvetica, Arial, sans-serif;\n}\n\n.chart-placeholder {\n\twidth: 100%;\n\theight: 100%;\n\tfont-size: 14px;\n\tline-height: 1.2em;\n}\n\n.axisLabel {\n\tposition: absolute;\n\ttext-align: center;\n\tfont-size: 12px;\n}\n\n.chartTitle {\n\tposition: absolute;\n\ttext-align: center;\n\tfont-size: 12px;\n\ttop:5px;\n\tleft:50%;\n\n}\n\n.legend-sidebar .legend {\n\tmax-height: 450px;\n\toverflow-y: auto;\n}\n\n.legendTitle {\n\tfont-size: 12px;\n}\n\n.xaxisLabel {\n    bottom: 3px;\n    left: 50%;\n}\n\n.yaxisLabel {\n\ttop: 50%;\n\tleft: 5px;\n\ttransform: rotate(-90deg);\n\t-o-transform: rotate(-90deg);\n\t-ms-transform: rotate(-90deg);\n\t-moz-transform: rotate(-90deg);\n\t-webkit-transform:  rotate(-90deg);\n\ttransform-origin: 0 0;\n\t-o-transform-origin: 0 0;\n\t-ms-transform-origin: 0 0;\n\t-moz-transform-origin: 0 0;\n\t-webkit-transform-origin: 0 0;\n}\n\n.ie7 .yaxisLabel, .ie8 .yaxisLabel {\n\ttop: 40%;\n\tfont-size: 36px;\n\tfilter: progid:DXImageTransform.Microsoft.Matrix(M11=0.0, M12=0.33, M21=-0.33, M22=0.0,sizingMethod='auto expand');\n}\n.btn-minimize{\n\tfloat:right;\n}\n\n.chart-btn-bar {\n\tposition: absolute;\n\ttop:2px;\n\tright:2px;\n}\n.chart-btn-bar .btn{\n\tpadding:0px 12px;\n}\n\n.chart-zoom-bar {\n\tposition: absolute;\n\ttop:2px;\n\tleft:2px;\n}\n.chart-zoom-bar input{\n\tpadding:0px 12px;\n\tvertical-align: middle;\n\tmargin: 1px;\n}\n\n.legendSeries {\n\tborder: 1px solid #ddd;\n\tpadding:1px;\n\tfont-size: 11px;\n\t/*line-height: 0.5em;*/\n}\n.legendLabel {\n\tpadding-right:5px;\n}\n.legendValue {\n\tborder-left: 1px solid #ddd;\n}\n.legendSeries  input[type=\"checkbox\"]{\n\tmargin: 0px 0 0;\n}\n\n\n\n.side-menu {\n\tbackground: #fff;\n\tposition:fixed;\n\tborder: 1px solid #ddd;\n\tpadding:3px;\n\tz-index:100;\n\tmin-width:210px;\n\tmargin-bottom:0px;\n}\n.side-menu.panel {\n\tpadding:0px;\n}\n\n.side-menu span.label {\n\twidth: 100%;\n\tmargin-top:3px;\n}\n.side-menu ul li,\n.control-panel ul li,\n.dropdown-menu ul li {\n\tlist-style: none;\n}\n.side-menu.left {\n\tbottom:36px;\n\tleft:-200px;\n\ttext-align:right;\n\tfloat: right;\n}\n.side-menu.right {\n\tbottom:36px;\n\tright:-200px;\n\ttext-align:left;\n\tfloat: left;\n}\n.side-menu ul,\n.control-panel ul,\n.dropdown-menu ul {\n\tpadding-left:0px;\n}\n\n.control-panel .input-group {\n\tfloat:left;\n\tposition:relative;\n\twidth: 100%;\n    display: inline-block;\n}\n\n.control-panel .input-group-addon.input-group-addon-label {\n\twidth:50%;\n\ttext-align:left;\n\ttext-overflow:ellipsis;\n\toverflow:hidden;\n\tdisplay:inline-block;\n}\n\n.control-panel .input-group-addon {\n\tline-height:unset;\n}\n\n.control-panel .btn {\n\tmargin-bottom:5px;\n\tmargin-right:5px;\n\twidth :100%;\n\ttext-overflow:ellipsis;\n\toverflow:hidden;\n\tdisplay:inline-block;\n}\n\n.control-panel .input-group-btn .btn {\n\tmargin:0px;\n\twidth :auto;\n}\n\n\n.side-menu.left span.label {\n\ttext-align:right;\n}\n\n.side-menu.right span.label {\n\ttext-align:left;\n}\n\n.chart_hspace {\n\tfloat: left;\n\tposition: relative;\n\tmin-height: 1px;\n\twidth: 1%;\n\theight: 100%;\n}\n\n\n.widget {\n\n}\n\n.widget-body {\n\tpadding-top:10px;\n\tpadding-bottom:10px;\n}\n\n.widget-body::after {\n\tclear: both;\n}\n\n.widget-body::before ,\n.widget-body::after {\n\tdisplay: table;\n\tcontent: \" \";\n}\n.input-group.set_value {\n\twidth: 100%;\n\tmargin-bottom:5px;\n\tmargin-right:5px;\n\tfont-size:0;\n\tdisplay:inline-block;\n}\n.input-group.set_value .help-block {\n  width: 100%;\n  display: inline-block;\n  font-size: 14px;\n  caption-side: bottom;\n  margin: 0px;\n}\n.form-signin {\n\tmax-width: 330px;\n\tpadding: 15px;\n\tmargin: 0 auto;\n}\n.form-signin .form-signin-heading,\n.form-signin .checkbox {\n\tmargin-bottom: 10px;\n}\n.form-signin .checkbox {\n\tfont-weight: normal;\n}\n.form-signin .form-control {\n\tposition: relative;\n\theight: auto;\n\t-webkit-box-sizing: border-box;\n\t\t-moz-box-sizing: border-box;\n\t\t\tbox-sizing: border-box;\n\tpadding: 10px;\n\tfont-size: 16px;\n}\n.form-signin .form-control:focus {\n\tz-index: 2;\n}\n.form-signin input[type=\"email\"] {\n\tmargin-bottom: -1px;\n\tborder-bottom-right-radius: 0;\n\tborder-bottom-left-radius: 0;\n}\n.form-signin input[type=\"password\"] {\n\tmargin-bottom: 10px;\n\tborder-top-left-radius: 0;\n\tborder-top-right-radius: 0;\n}\n\n#tooltip {\n    position: absolute;\n    display: none;\n    border: 1px solid #fdd;\n    padding: 2px;\n    background-color: #fee;\n    opacity: 0.80;\n}\n\n/*form {\n    border-color: #ddd;\n    border-width: 1px;\n    border-radius: 4px 4px 0 0;\n    -webkit-box-shadow: none;\n    box-shadow: none;\n    border-style: solid;\n}*/\n\n.legendLayer .background {\n    fill: rgba(255, 255, 255, 0.85);\n    stroke: rgba(0, 0, 0, 0.85);\n    stroke-width: 1;\n}\n\n.widget .panel-body {\n  border-style: dashed;\n}\n\n.control-panel .form {\n  display: inline-block;\n  border-style: dotted;\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/MIT-LICENSE.txt",
    "content": "Copyright 2013 Klaus Hartl\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/admin/display_inline_datasource.js",
    "content": "function updateTransformDataInlines() {\n  document.querySelectorAll(\"div[id$='-group'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = true;});  // hide all datasource inlines\n  if (document.querySelector(\"#id_datasource_model\") != null) {\n    var v = document.querySelector(\"#id_datasource_model\").selectedOptions[0].dataset.inlineDatasourceModelName;  // get the selected datasource model name\n    document.querySelectorAll(\"[id='\" + v.toLowerCase() + \"-group'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = false;});  // show the correct transform data inline\n\n    document.querySelector(\"#id_datasource_model\").onchange = function(e) {\n        document.querySelectorAll(\"div[id$='-group'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = true;});  // hide all datasource inlines\n        var v = e.target.selectedOptions[0].dataset.inlineDatasourceModelName;  // get the selected transform data name\n        document.querySelectorAll(\"[id='\" + v.toLowerCase() + \"-group'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = false;});  // show the correct transform data inline\n    };\n  };\n};\n\n  document.addEventListener(\"DOMContentLoaded\", function () {updateTransformDataInlines();});"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/admin/display_inline_protocols_device.js",
    "content": "document.addEventListener(\"DOMContentLoaded\", function () {\n    display_inline_protocols_device();\n    document.querySelector(\"#id_protocol\").onchange = function() {\n      display_inline_protocols_device();\n    };\n});\n\nfunction display_inline_protocols_device() {\n    document.querySelectorAll(\".js-inline-admin-formset.inline-group\").forEach((e) => {\n        if (!e.id.includes(\"devicehandlerparameter_set\")) {\n            e.style.display = \"none\";\n        }\n    });\n    v = document.querySelector(\"#id_protocol\").options[document.querySelector(\"#id_protocol\").selectedIndex].text;\n    document.querySelectorAll(\"[id^='\" + v + \"'].js-inline-admin-formset.inline-group\").forEach((e) => {\n        e.style.display = \"\";\n    });\n};"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/admin/display_inline_protocols_variable.js",
    "content": "document.addEventListener(\"DOMContentLoaded\", function () {\n    display_inline_protocols_variable();\n    document.querySelector(\"#id_device\").onchange = function() {\n      display_inline_protocols_variable();\n    };\n});\n\nfunction display_inline_protocols_variable() {\n    document.querySelectorAll(\".js-inline-admin-formset.inline-group\").forEach((e) => {\n        if (!e.id.includes(\"variablehandlerparameter_set\")) {\n            e.style.display = \"none\";\n        }\n    });\n    v = document.querySelector(\"#id_device\").options[document.querySelector(\"#id_device\").selectedIndex].text.split(\"-\")[0];\n    document.querySelectorAll(\"[id^='\" + v + \"'].js-inline-admin-formset.inline-group\").forEach((e) => {\n        e.style.display = \"\";\n    });\n};"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/admin/display_inline_transform_data_display_value_option.js",
    "content": "function updateTransformDataInlines() {\n  document.querySelectorAll(\"div[id^='transformdata'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = true;});  // hide all transform data inlines\n  var v = document.querySelector(\"#id_transform_data\").selectedOptions[0].innerHTML;  // get the selected transform data name\n  document.querySelectorAll(\"[id='transformdata\" + v.toLowerCase() + \"-group'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = false;});  // show the correct transform data inline\n    document.querySelector(\"#id_transform_data\").onchange = function(e) {\n      document.querySelectorAll(\"div[id^='transformdata'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = true;});  // hide all transform data inlines\n      var v = e.target.selectedOptions[0].innerHTML;  // get the selected transform data name\n      document.querySelectorAll(\"[id='transformdata\" + v.toLowerCase() + \"-group'].js-inline-admin-formset.inline-group\").forEach((e) => {e.hidden = false;});  // show the correct transform data inline\n    };\n  };\n  document.addEventListener(\"DOMContentLoaded\", function () {updateTransformDataInlines();});\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/admin/handler_content_as_pre.js",
    "content": "function handlerContentAsPre() {\n  document.querySelectorAll(\".form-row.field-content .flex-container .readonly\").forEach((e) => {e.style.whiteSpace = \"pre\"; e.style.fontFamily = \"monospace\";});\n};\n\ndocument.addEventListener(\"DOMContentLoaded\", function () {handlerContentAsPre();});\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/admin/hideshow.js",
    "content": "// modified from https://github.com/scientifichackers/django-hideshow\n\n(function () {\n    Set.prototype.diff = function (other) {\n        let ret = new Set(this);\n        for (let elem of other) {\n            ret.delete(elem);\n        }\n        return ret;\n    };\n\n    document.addEventListener(\"DOMContentLoaded\", function () {\n        let nodes = document.querySelectorAll(\"[--hideshow-fields]\");\n\n        for (let node of nodes) {\n            let onChange;\n\n            let hideFields = new Set(csvParse(node.getAttribute(\"--hideshow-fields\")));\n\n            switch (node.type) {\n                case \"select-one\":\n                    onChange = function () {\n                        let value = node.value;\n\n                        let showOnSelected;\n                        if (value) {\n                            showOnSelected = csvParse(\n                                node.getAttribute(`--show-on-${value}`)\n                            );\n                        }\n\n                        let toShow = showOnSelected || [];\n                        let toHide = hideFields.diff(toShow);\n\n                        getFormRows(toShow, node).map(show);\n                        getFormRows(toHide, node).map(hide);\n                    };\n\n                    break;\n\n                case \"checkbox\":\n                    let showOnChecked = csvParse(node.getAttribute(\"--show-on-checked\"));\n\n                    let toShow = showOnChecked || [];\n                    let toHide = hideFields.diff(toShow);\n\n                    let showRows = getFormRows(toShow, node);\n                    let hideRows = getFormRows(toHide, node);\n\n                    onChange = function () {\n                        let checked = node.checked;\n                        hideRows.map(checked ? hide : show);\n                        showRows.map(checked ? show : hide);\n                    };\n\n                    break;\n            }\n\n            if (onChange) {\n                onChange();\n                node.addEventListener(\"change\", onChange);\n            }\n        }\n    });\n\n    function csvParse(str) {\n        if (!str) {\n            return [];\n        }\n        let arr = str.split(\",\");\n        for (let i = 0; i < arr.length; i++) {\n            arr[i] = arr[i].trim();\n        }\n        return arr;\n    }\n\n    function getFormRows(fields, node) {\n        return Array.from(_getFormRows(fields, node));\n    }\n\n    function* _getFormRows(fields, node) {\n        if (!fields) {\n            return;\n        }\n        for (let name of fields) {\n            let formRow = fieldNameToFormRow(name, node);\n            if (!formRow) continue;\n            yield formRow;\n        }\n    }\n\n    function fieldNameToFormRow(fieldName, node) {\n        let cls = \"field-\" + fieldName;\n        let formRow = node.parentNode.parentNode.parentNode.parentNode.getElementsByClassName(cls)[0];\n        return formRow;\n    }\n\n    function hide(node) {\n        node.style.display = \"none\";\n    }\n\n    function show(node) {\n        node.style.display = \"\";\n    }\n})();"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/affix.js",
    "content": "/* ========================================================================\n * Bootstrap: affix.js v3.3.4\n * http://getbootstrap.com/javascript/#affix\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // AFFIX CLASS DEFINITION\n  // ======================\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, Affix.DEFAULTS, options)\n\n    this.$target = $(this.options.target)\n      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))\n\n    this.$element     = $(element)\n    this.affixed      = null\n    this.unpin        = null\n    this.pinnedOffset = null\n\n    this.checkPosition()\n  }\n\n  Affix.VERSION  = '3.3.4'\n\n  Affix.RESET    = 'affix affix-top affix-bottom'\n\n  Affix.DEFAULTS = {\n    offset: 0,\n    target: window\n  }\n\n  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {\n    var scrollTop    = this.$target.scrollTop()\n    var position     = this.$element.offset()\n    var targetHeight = this.$target.height()\n\n    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false\n\n    if (this.affixed == 'bottom') {\n      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'\n      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'\n    }\n\n    var initializing   = this.affixed == null\n    var colliderTop    = initializing ? scrollTop : position.top\n    var colliderHeight = initializing ? targetHeight : height\n\n    if (offsetTop != null && scrollTop <= offsetTop) return 'top'\n    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'\n\n    return false\n  }\n\n  Affix.prototype.getPinnedOffset = function () {\n    if (this.pinnedOffset) return this.pinnedOffset\n    this.$element.removeClass(Affix.RESET).addClass('affix')\n    var scrollTop = this.$target.scrollTop()\n    var position  = this.$element.offset()\n    return (this.pinnedOffset = position.top - scrollTop)\n  }\n\n  Affix.prototype.checkPositionWithEventLoop = function () {\n    setTimeout($.proxy(this.checkPosition, this), 1)\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var height       = this.$element.height()\n    var offset       = this.options.offset\n    var offsetTop    = offset.top\n    var offsetBottom = offset.bottom\n    var scrollHeight = $(document.body).height()\n\n    if (typeof offset != 'object')         offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)\n\n    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)\n\n    if (this.affixed != affix) {\n      if (this.unpin != null) this.$element.css('top', '')\n\n      var affixType = 'affix' + (affix ? '-' + affix : '')\n      var e         = $.Event(affixType + '.bs.affix')\n\n      this.$element.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      this.affixed = affix\n      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null\n\n      this.$element\n        .removeClass(Affix.RESET)\n        .addClass(affixType)\n        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')\n    }\n\n    if (affix == 'bottom') {\n      this.$element.offset({\n        top: scrollHeight - height - offsetBottom\n      })\n    }\n  }\n\n\n  // AFFIX PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.affix')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.affix\n\n  $.fn.affix             = Plugin\n  $.fn.affix.Constructor = Affix\n\n\n  // AFFIX NO CONFLICT\n  // =================\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  // AFFIX DATA-API\n  // ==============\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n      var data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom\n      if (data.offsetTop    != null) data.offset.top    = data.offsetTop\n\n      Plugin.call($spy, data)\n    })\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/alert.js",
    "content": "/* ========================================================================\n * Bootstrap: alert.js v3.3.4\n * http://getbootstrap.com/javascript/#alerts\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // ALERT CLASS DEFINITION\n  // ======================\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n  var Alert   = function (el) {\n    $(el).on('click', dismiss, this.close)\n  }\n\n  Alert.VERSION = '3.3.4'\n\n  Alert.TRANSITION_DURATION = 150\n\n  Alert.prototype.close = function (e) {\n    var $this    = $(this)\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = $(selector)\n\n    if (e) e.preventDefault()\n\n    if (!$parent.length) {\n      $parent = $this.closest('.alert')\n    }\n\n    $parent.trigger(e = $.Event('close.bs.alert'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      // detach from parent, fire event then clean up data\n      $parent.detach().trigger('closed.bs.alert').remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent\n        .one('bsTransitionEnd', removeElement)\n        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :\n      removeElement()\n  }\n\n\n  // ALERT PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.alert')\n\n      if (!data) $this.data('bs.alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.alert\n\n  $.fn.alert             = Plugin\n  $.fn.alert.Constructor = Alert\n\n\n  // ALERT NO CONFLICT\n  // =================\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  // ALERT DATA-API\n  // ==============\n\n  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/bootstrap.js",
    "content": "/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under the MIT license\n */\n\nif (typeof jQuery === 'undefined') {\n  throw new Error('Bootstrap\\'s JavaScript requires jQuery')\n}\n\n+function ($) {\n  'use strict';\n  var version = $.fn.jquery.split(' ')[0].split('.')\n  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {\n    throw new Error('Bootstrap\\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')\n  }\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: transition.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#transitions\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CSS TRANSITION SUPPORT (Shoutout: https://modernizr.com/)\n  // ============================================================\n\n  function transitionEnd() {\n    var el = document.createElement('bootstrap')\n\n    var transEndEventNames = {\n      WebkitTransition : 'webkitTransitionEnd',\n      MozTransition    : 'transitionend',\n      OTransition      : 'oTransitionEnd otransitionend',\n      transition       : 'transitionend'\n    }\n\n    for (var name in transEndEventNames) {\n      if (el.style[name] !== undefined) {\n        return { end: transEndEventNames[name] }\n      }\n    }\n\n    return false // explicit for ie8 (  ._.)\n  }\n\n  // https://blog.alexmaccaw.com/css-transitions\n  $.fn.emulateTransitionEnd = function (duration) {\n    var called = false\n    var $el = this\n    $(this).one('bsTransitionEnd', function () { called = true })\n    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n    setTimeout(callback, duration)\n    return this\n  }\n\n  $(function () {\n    $.support.transition = transitionEnd()\n\n    if (!$.support.transition) return\n\n    $.event.special.bsTransitionEnd = {\n      bindType: $.support.transition.end,\n      delegateType: $.support.transition.end,\n      handle: function (e) {\n        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)\n      }\n    }\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: alert.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#alerts\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // ALERT CLASS DEFINITION\n  // ======================\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n  var Alert   = function (el) {\n    $(el).on('click', dismiss, this.close)\n  }\n\n  Alert.VERSION = '3.4.1'\n\n  Alert.TRANSITION_DURATION = 150\n\n  Alert.prototype.close = function (e) {\n    var $this    = $(this)\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    selector    = selector === '#' ? [] : selector\n    var $parent = $(document).find(selector)\n\n    if (e) e.preventDefault()\n\n    if (!$parent.length) {\n      $parent = $this.closest('.alert')\n    }\n\n    $parent.trigger(e = $.Event('close.bs.alert'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      // detach from parent, fire event then clean up data\n      $parent.detach().trigger('closed.bs.alert').remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent\n        .one('bsTransitionEnd', removeElement)\n        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :\n      removeElement()\n  }\n\n\n  // ALERT PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.alert')\n\n      if (!data) $this.data('bs.alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.alert\n\n  $.fn.alert             = Plugin\n  $.fn.alert.Constructor = Alert\n\n\n  // ALERT NO CONFLICT\n  // =================\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  // ALERT DATA-API\n  // ==============\n\n  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: button.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#buttons\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // BUTTON PUBLIC CLASS DEFINITION\n  // ==============================\n\n  var Button = function (element, options) {\n    this.$element  = $(element)\n    this.options   = $.extend({}, Button.DEFAULTS, options)\n    this.isLoading = false\n  }\n\n  Button.VERSION  = '3.4.1'\n\n  Button.DEFAULTS = {\n    loadingText: 'loading...'\n  }\n\n  Button.prototype.setState = function (state) {\n    var d    = 'disabled'\n    var $el  = this.$element\n    var val  = $el.is('input') ? 'val' : 'html'\n    var data = $el.data()\n\n    state += 'Text'\n\n    if (data.resetText == null) $el.data('resetText', $el[val]())\n\n    // push to event loop to allow forms to submit\n    setTimeout($.proxy(function () {\n      $el[val](data[state] == null ? this.options[state] : data[state])\n\n      if (state == 'loadingText') {\n        this.isLoading = true\n        $el.addClass(d).attr(d, d).prop(d, true)\n      } else if (this.isLoading) {\n        this.isLoading = false\n        $el.removeClass(d).removeAttr(d).prop(d, false)\n      }\n    }, this), 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var changed = true\n    var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n    if ($parent.length) {\n      var $input = this.$element.find('input')\n      if ($input.prop('type') == 'radio') {\n        if ($input.prop('checked')) changed = false\n        $parent.find('.active').removeClass('active')\n        this.$element.addClass('active')\n      } else if ($input.prop('type') == 'checkbox') {\n        if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false\n        this.$element.toggleClass('active')\n      }\n      $input.prop('checked', this.$element.hasClass('active'))\n      if (changed) $input.trigger('change')\n    } else {\n      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))\n      this.$element.toggleClass('active')\n    }\n  }\n\n\n  // BUTTON PLUGIN DEFINITION\n  // ========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.button')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  var old = $.fn.button\n\n  $.fn.button             = Plugin\n  $.fn.button.Constructor = Button\n\n\n  // BUTTON NO CONFLICT\n  // ==================\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  // BUTTON DATA-API\n  // ===============\n\n  $(document)\n    .on('click.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      var $btn = $(e.target).closest('.btn')\n      Plugin.call($btn, 'toggle')\n      if (!($(e.target).is('input[type=\"radio\"], input[type=\"checkbox\"]'))) {\n        // Prevent double click on radios, and the double selections (so cancellation) on checkboxes\n        e.preventDefault()\n        // The target component still receive the focus\n        if ($btn.is('input,button')) $btn.trigger('focus')\n        else $btn.find('input:visible,button:visible').first().trigger('focus')\n      }\n    })\n    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))\n    })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: carousel.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#carousel\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CAROUSEL CLASS DEFINITION\n  // =========================\n\n  var Carousel = function (element, options) {\n    this.$element    = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options     = options\n    this.paused      = null\n    this.sliding     = null\n    this.interval    = null\n    this.$active     = null\n    this.$items      = null\n\n    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))\n\n    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element\n      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))\n      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))\n  }\n\n  Carousel.VERSION  = '3.4.1'\n\n  Carousel.TRANSITION_DURATION = 600\n\n  Carousel.DEFAULTS = {\n    interval: 5000,\n    pause: 'hover',\n    wrap: true,\n    keyboard: true\n  }\n\n  Carousel.prototype.keydown = function (e) {\n    if (/input|textarea/i.test(e.target.tagName)) return\n    switch (e.which) {\n      case 37: this.prev(); break\n      case 39: this.next(); break\n      default: return\n    }\n\n    e.preventDefault()\n  }\n\n  Carousel.prototype.cycle = function (e) {\n    e || (this.paused = false)\n\n    this.interval && clearInterval(this.interval)\n\n    this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n    return this\n  }\n\n  Carousel.prototype.getItemIndex = function (item) {\n    this.$items = item.parent().children('.item')\n    return this.$items.index(item || this.$active)\n  }\n\n  Carousel.prototype.getItemForDirection = function (direction, active) {\n    var activeIndex = this.getItemIndex(active)\n    var willWrap = (direction == 'prev' && activeIndex === 0)\n                || (direction == 'next' && activeIndex == (this.$items.length - 1))\n    if (willWrap && !this.options.wrap) return active\n    var delta = direction == 'prev' ? -1 : 1\n    var itemIndex = (activeIndex + delta) % this.$items.length\n    return this.$items.eq(itemIndex)\n  }\n\n  Carousel.prototype.to = function (pos) {\n    var that        = this\n    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))\n\n    if (pos > (this.$items.length - 1) || pos < 0) return\n\n    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, \"slid\"\n    if (activeIndex == pos) return this.pause().cycle()\n\n    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))\n  }\n\n  Carousel.prototype.pause = function (e) {\n    e || (this.paused = true)\n\n    if (this.$element.find('.next, .prev').length && $.support.transition) {\n      this.$element.trigger($.support.transition.end)\n      this.cycle(true)\n    }\n\n    this.interval = clearInterval(this.interval)\n\n    return this\n  }\n\n  Carousel.prototype.next = function () {\n    if (this.sliding) return\n    return this.slide('next')\n  }\n\n  Carousel.prototype.prev = function () {\n    if (this.sliding) return\n    return this.slide('prev')\n  }\n\n  Carousel.prototype.slide = function (type, next) {\n    var $active   = this.$element.find('.item.active')\n    var $next     = next || this.getItemForDirection(type, $active)\n    var isCycling = this.interval\n    var direction = type == 'next' ? 'left' : 'right'\n    var that      = this\n\n    if ($next.hasClass('active')) return (this.sliding = false)\n\n    var relatedTarget = $next[0]\n    var slideEvent = $.Event('slide.bs.carousel', {\n      relatedTarget: relatedTarget,\n      direction: direction\n    })\n    this.$element.trigger(slideEvent)\n    if (slideEvent.isDefaultPrevented()) return\n\n    this.sliding = true\n\n    isCycling && this.pause()\n\n    if (this.$indicators.length) {\n      this.$indicators.find('.active').removeClass('active')\n      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])\n      $nextIndicator && $nextIndicator.addClass('active')\n    }\n\n    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, \"slid\"\n    if ($.support.transition && this.$element.hasClass('slide')) {\n      $next.addClass(type)\n      if (typeof $next === 'object' && $next.length) {\n        $next[0].offsetWidth // force reflow\n      }\n      $active.addClass(direction)\n      $next.addClass(direction)\n      $active\n        .one('bsTransitionEnd', function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () {\n            that.$element.trigger(slidEvent)\n          }, 0)\n        })\n        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)\n    } else {\n      $active.removeClass('active')\n      $next.addClass('active')\n      this.sliding = false\n      this.$element.trigger(slidEvent)\n    }\n\n    isCycling && this.cycle()\n\n    return this\n  }\n\n\n  // CAROUSEL PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.carousel')\n      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n      var action  = typeof option == 'string' ? option : options.slide\n\n      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  var old = $.fn.carousel\n\n  $.fn.carousel             = Plugin\n  $.fn.carousel.Constructor = Carousel\n\n\n  // CAROUSEL NO CONFLICT\n  // ====================\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n\n  // CAROUSEL DATA-API\n  // =================\n\n  var clickHandler = function (e) {\n    var $this   = $(this)\n    var href    = $this.attr('href')\n    if (href) {\n      href = href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n    }\n\n    var target  = $this.attr('data-target') || href\n    var $target = $(document).find(target)\n\n    if (!$target.hasClass('carousel')) return\n\n    var options = $.extend({}, $target.data(), $this.data())\n    var slideIndex = $this.attr('data-slide-to')\n    if (slideIndex) options.interval = false\n\n    Plugin.call($target, options)\n\n    if (slideIndex) {\n      $target.data('bs.carousel').to(slideIndex)\n    }\n\n    e.preventDefault()\n  }\n\n  $(document)\n    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)\n    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)\n\n  $(window).on('load', function () {\n    $('[data-ride=\"carousel\"]').each(function () {\n      var $carousel = $(this)\n      Plugin.call($carousel, $carousel.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: collapse.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#collapse\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n/* jshint latedef: false */\n\n+function ($) {\n  'use strict';\n\n  // COLLAPSE PUBLIC CLASS DEFINITION\n  // ================================\n\n  var Collapse = function (element, options) {\n    this.$element      = $(element)\n    this.options       = $.extend({}, Collapse.DEFAULTS, options)\n    this.$trigger      = $('[data-toggle=\"collapse\"][href=\"#' + element.id + '\"],' +\n                           '[data-toggle=\"collapse\"][data-target=\"#' + element.id + '\"]')\n    this.transitioning = null\n\n    if (this.options.parent) {\n      this.$parent = this.getParent()\n    } else {\n      this.addAriaAndCollapsedClass(this.$element, this.$trigger)\n    }\n\n    if (this.options.toggle) this.toggle()\n  }\n\n  Collapse.VERSION  = '3.4.1'\n\n  Collapse.TRANSITION_DURATION = 350\n\n  Collapse.DEFAULTS = {\n    toggle: true\n  }\n\n  Collapse.prototype.dimension = function () {\n    var hasWidth = this.$element.hasClass('width')\n    return hasWidth ? 'width' : 'height'\n  }\n\n  Collapse.prototype.show = function () {\n    if (this.transitioning || this.$element.hasClass('in')) return\n\n    var activesData\n    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')\n\n    if (actives && actives.length) {\n      activesData = actives.data('bs.collapse')\n      if (activesData && activesData.transitioning) return\n    }\n\n    var startEvent = $.Event('show.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    if (actives && actives.length) {\n      Plugin.call(actives, 'hide')\n      activesData || actives.data('bs.collapse', null)\n    }\n\n    var dimension = this.dimension()\n\n    this.$element\n      .removeClass('collapse')\n      .addClass('collapsing')[dimension](0)\n      .attr('aria-expanded', true)\n\n    this.$trigger\n      .removeClass('collapsed')\n      .attr('aria-expanded', true)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse in')[dimension]('')\n      this.transitioning = 0\n      this.$element\n        .trigger('shown.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n    this.$element\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])\n  }\n\n  Collapse.prototype.hide = function () {\n    if (this.transitioning || !this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('hide.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var dimension = this.dimension()\n\n    this.$element[dimension](this.$element[dimension]())[0].offsetHeight\n\n    this.$element\n      .addClass('collapsing')\n      .removeClass('collapse in')\n      .attr('aria-expanded', false)\n\n    this.$trigger\n      .addClass('collapsed')\n      .attr('aria-expanded', false)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.transitioning = 0\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse')\n        .trigger('hidden.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    this.$element\n      [dimension](0)\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)\n  }\n\n  Collapse.prototype.toggle = function () {\n    this[this.$element.hasClass('in') ? 'hide' : 'show']()\n  }\n\n  Collapse.prototype.getParent = function () {\n    return $(document).find(this.options.parent)\n      .find('[data-toggle=\"collapse\"][data-parent=\"' + this.options.parent + '\"]')\n      .each($.proxy(function (i, element) {\n        var $element = $(element)\n        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)\n      }, this))\n      .end()\n  }\n\n  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {\n    var isOpen = $element.hasClass('in')\n\n    $element.attr('aria-expanded', isOpen)\n    $trigger\n      .toggleClass('collapsed', !isOpen)\n      .attr('aria-expanded', isOpen)\n  }\n\n  function getTargetFromTrigger($trigger) {\n    var href\n    var target = $trigger.attr('data-target')\n      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n\n    return $(document).find(target)\n  }\n\n\n  // COLLAPSE PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.collapse')\n      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false\n      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.collapse\n\n  $.fn.collapse             = Plugin\n  $.fn.collapse.Constructor = Collapse\n\n\n  // COLLAPSE NO CONFLICT\n  // ====================\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  // COLLAPSE DATA-API\n  // =================\n\n  $(document).on('click.bs.collapse.data-api', '[data-toggle=\"collapse\"]', function (e) {\n    var $this   = $(this)\n\n    if (!$this.attr('data-target')) e.preventDefault()\n\n    var $target = getTargetFromTrigger($this)\n    var data    = $target.data('bs.collapse')\n    var option  = data ? 'toggle' : $this.data()\n\n    Plugin.call($target, option)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: dropdown.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#dropdowns\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // DROPDOWN CLASS DEFINITION\n  // =========================\n\n  var backdrop = '.dropdown-backdrop'\n  var toggle   = '[data-toggle=\"dropdown\"]'\n  var Dropdown = function (element) {\n    $(element).on('click.bs.dropdown', this.toggle)\n  }\n\n  Dropdown.VERSION = '3.4.1'\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = selector !== '#' ? $(document).find(selector) : null\n\n    return $parent && $parent.length ? $parent : $this.parent()\n  }\n\n  function clearMenus(e) {\n    if (e && e.which === 3) return\n    $(backdrop).remove()\n    $(toggle).each(function () {\n      var $this         = $(this)\n      var $parent       = getParent($this)\n      var relatedTarget = { relatedTarget: this }\n\n      if (!$parent.hasClass('open')) return\n\n      if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return\n\n      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this.attr('aria-expanded', 'false')\n      $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))\n    })\n  }\n\n  Dropdown.prototype.toggle = function (e) {\n    var $this = $(this)\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    clearMenus()\n\n    if (!isActive) {\n      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n        // if mobile we use a backdrop because click events don't delegate\n        $(document.createElement('div'))\n          .addClass('dropdown-backdrop')\n          .insertAfter($(this))\n          .on('click', clearMenus)\n      }\n\n      var relatedTarget = { relatedTarget: this }\n      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this\n        .trigger('focus')\n        .attr('aria-expanded', 'true')\n\n      $parent\n        .toggleClass('open')\n        .trigger($.Event('shown.bs.dropdown', relatedTarget))\n    }\n\n    return false\n  }\n\n  Dropdown.prototype.keydown = function (e) {\n    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return\n\n    var $this = $(this)\n\n    e.preventDefault()\n    e.stopPropagation()\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    if (!isActive && e.which != 27 || isActive && e.which == 27) {\n      if (e.which == 27) $parent.find(toggle).trigger('focus')\n      return $this.trigger('click')\n    }\n\n    var desc = ' li:not(.disabled):visible a'\n    var $items = $parent.find('.dropdown-menu' + desc)\n\n    if (!$items.length) return\n\n    var index = $items.index(e.target)\n\n    if (e.which == 38 && index > 0)                 index--         // up\n    if (e.which == 40 && index < $items.length - 1) index++         // down\n    if (!~index)                                    index = 0\n\n    $items.eq(index).trigger('focus')\n  }\n\n\n  // DROPDOWN PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.dropdown')\n\n      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown             = Plugin\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  // DROPDOWN NO CONFLICT\n  // ====================\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  // APPLY TO STANDARD DROPDOWN ELEMENTS\n  // ===================================\n\n  $(document)\n    .on('click.bs.dropdown.data-api', clearMenus)\n    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: modal.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#modals\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // MODAL CLASS DEFINITION\n  // ======================\n\n  var Modal = function (element, options) {\n    this.options = options\n    this.$body = $(document.body)\n    this.$element = $(element)\n    this.$dialog = this.$element.find('.modal-dialog')\n    this.$backdrop = null\n    this.isShown = null\n    this.originalBodyPad = null\n    this.scrollbarWidth = 0\n    this.ignoreBackdropClick = false\n    this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom'\n\n    if (this.options.remote) {\n      this.$element\n        .find('.modal-content')\n        .load(this.options.remote, $.proxy(function () {\n          this.$element.trigger('loaded.bs.modal')\n        }, this))\n    }\n  }\n\n  Modal.VERSION = '3.4.1'\n\n  Modal.TRANSITION_DURATION = 300\n  Modal.BACKDROP_TRANSITION_DURATION = 150\n\n  Modal.DEFAULTS = {\n    backdrop: true,\n    keyboard: true,\n    show: true\n  }\n\n  Modal.prototype.toggle = function (_relatedTarget) {\n    return this.isShown ? this.hide() : this.show(_relatedTarget)\n  }\n\n  Modal.prototype.show = function (_relatedTarget) {\n    var that = this\n    var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n    this.$element.trigger(e)\n\n    if (this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = true\n\n    this.checkScrollbar()\n    this.setScrollbar()\n    this.$body.addClass('modal-open')\n\n    this.escape()\n    this.resize()\n\n    this.$element.on('click.dismiss.bs.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n    this.$dialog.on('mousedown.dismiss.bs.modal', function () {\n      that.$element.one('mouseup.dismiss.bs.modal', function (e) {\n        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true\n      })\n    })\n\n    this.backdrop(function () {\n      var transition = $.support.transition && that.$element.hasClass('fade')\n\n      if (!that.$element.parent().length) {\n        that.$element.appendTo(that.$body) // don't move modals dom position\n      }\n\n      that.$element\n        .show()\n        .scrollTop(0)\n\n      that.adjustDialog()\n\n      if (transition) {\n        that.$element[0].offsetWidth // force reflow\n      }\n\n      that.$element.addClass('in')\n\n      that.enforceFocus()\n\n      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n      transition ?\n        that.$dialog // wait for modal to slide in\n          .one('bsTransitionEnd', function () {\n            that.$element.trigger('focus').trigger(e)\n          })\n          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n        that.$element.trigger('focus').trigger(e)\n    })\n  }\n\n  Modal.prototype.hide = function (e) {\n    if (e) e.preventDefault()\n\n    e = $.Event('hide.bs.modal')\n\n    this.$element.trigger(e)\n\n    if (!this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = false\n\n    this.escape()\n    this.resize()\n\n    $(document).off('focusin.bs.modal')\n\n    this.$element\n      .removeClass('in')\n      .off('click.dismiss.bs.modal')\n      .off('mouseup.dismiss.bs.modal')\n\n    this.$dialog.off('mousedown.dismiss.bs.modal')\n\n    $.support.transition && this.$element.hasClass('fade') ?\n      this.$element\n        .one('bsTransitionEnd', $.proxy(this.hideModal, this))\n        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n      this.hideModal()\n  }\n\n  Modal.prototype.enforceFocus = function () {\n    $(document)\n      .off('focusin.bs.modal') // guard against infinite focus loop\n      .on('focusin.bs.modal', $.proxy(function (e) {\n        if (document !== e.target &&\n          this.$element[0] !== e.target &&\n          !this.$element.has(e.target).length) {\n          this.$element.trigger('focus')\n        }\n      }, this))\n  }\n\n  Modal.prototype.escape = function () {\n    if (this.isShown && this.options.keyboard) {\n      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {\n        e.which == 27 && this.hide()\n      }, this))\n    } else if (!this.isShown) {\n      this.$element.off('keydown.dismiss.bs.modal')\n    }\n  }\n\n  Modal.prototype.resize = function () {\n    if (this.isShown) {\n      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))\n    } else {\n      $(window).off('resize.bs.modal')\n    }\n  }\n\n  Modal.prototype.hideModal = function () {\n    var that = this\n    this.$element.hide()\n    this.backdrop(function () {\n      that.$body.removeClass('modal-open')\n      that.resetAdjustments()\n      that.resetScrollbar()\n      that.$element.trigger('hidden.bs.modal')\n    })\n  }\n\n  Modal.prototype.removeBackdrop = function () {\n    this.$backdrop && this.$backdrop.remove()\n    this.$backdrop = null\n  }\n\n  Modal.prototype.backdrop = function (callback) {\n    var that = this\n    var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n    if (this.isShown && this.options.backdrop) {\n      var doAnimate = $.support.transition && animate\n\n      this.$backdrop = $(document.createElement('div'))\n        .addClass('modal-backdrop ' + animate)\n        .appendTo(this.$body)\n\n      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {\n        if (this.ignoreBackdropClick) {\n          this.ignoreBackdropClick = false\n          return\n        }\n        if (e.target !== e.currentTarget) return\n        this.options.backdrop == 'static'\n          ? this.$element[0].focus()\n          : this.hide()\n      }, this))\n\n      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n      this.$backdrop.addClass('in')\n\n      if (!callback) return\n\n      doAnimate ?\n        this.$backdrop\n          .one('bsTransitionEnd', callback)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callback()\n\n    } else if (!this.isShown && this.$backdrop) {\n      this.$backdrop.removeClass('in')\n\n      var callbackRemove = function () {\n        that.removeBackdrop()\n        callback && callback()\n      }\n      $.support.transition && this.$element.hasClass('fade') ?\n        this.$backdrop\n          .one('bsTransitionEnd', callbackRemove)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callbackRemove()\n\n    } else if (callback) {\n      callback()\n    }\n  }\n\n  // these following methods are used to handle overflowing modals\n\n  Modal.prototype.handleUpdate = function () {\n    this.adjustDialog()\n  }\n\n  Modal.prototype.adjustDialog = function () {\n    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight\n\n    this.$element.css({\n      paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',\n      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''\n    })\n  }\n\n  Modal.prototype.resetAdjustments = function () {\n    this.$element.css({\n      paddingLeft: '',\n      paddingRight: ''\n    })\n  }\n\n  Modal.prototype.checkScrollbar = function () {\n    var fullWindowWidth = window.innerWidth\n    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8\n      var documentElementRect = document.documentElement.getBoundingClientRect()\n      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)\n    }\n    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth\n    this.scrollbarWidth = this.measureScrollbar()\n  }\n\n  Modal.prototype.setScrollbar = function () {\n    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)\n    this.originalBodyPad = document.body.style.paddingRight || ''\n    var scrollbarWidth = this.scrollbarWidth\n    if (this.bodyIsOverflowing) {\n      this.$body.css('padding-right', bodyPad + scrollbarWidth)\n      $(this.fixedContent).each(function (index, element) {\n        var actualPadding = element.style.paddingRight\n        var calculatedPadding = $(element).css('padding-right')\n        $(element)\n          .data('padding-right', actualPadding)\n          .css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px')\n      })\n    }\n  }\n\n  Modal.prototype.resetScrollbar = function () {\n    this.$body.css('padding-right', this.originalBodyPad)\n    $(this.fixedContent).each(function (index, element) {\n      var padding = $(element).data('padding-right')\n      $(element).removeData('padding-right')\n      element.style.paddingRight = padding ? padding : ''\n    })\n  }\n\n  Modal.prototype.measureScrollbar = function () { // thx walsh\n    var scrollDiv = document.createElement('div')\n    scrollDiv.className = 'modal-scrollbar-measure'\n    this.$body.append(scrollDiv)\n    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n    this.$body[0].removeChild(scrollDiv)\n    return scrollbarWidth\n  }\n\n\n  // MODAL PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option, _relatedTarget) {\n    return this.each(function () {\n      var $this = $(this)\n      var data = $this.data('bs.modal')\n      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option](_relatedTarget)\n      else if (options.show) data.show(_relatedTarget)\n    })\n  }\n\n  var old = $.fn.modal\n\n  $.fn.modal = Plugin\n  $.fn.modal.Constructor = Modal\n\n\n  // MODAL NO CONFLICT\n  // =================\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  // MODAL DATA-API\n  // ==============\n\n  $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this = $(this)\n    var href = $this.attr('href')\n    var target = $this.attr('data-target') ||\n      (href && href.replace(/.*(?=#[^\\s]+$)/, '')) // strip for ie7\n\n    var $target = $(document).find(target)\n    var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    if ($this.is('a')) e.preventDefault()\n\n    $target.one('show.bs.modal', function (showEvent) {\n      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown\n      $target.one('hidden.bs.modal', function () {\n        $this.is(':visible') && $this.trigger('focus')\n      })\n    })\n    Plugin.call($target, option, this)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tooltip.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n+function ($) {\n  'use strict';\n\n  var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']\n\n  var uriAttrs = [\n    'background',\n    'cite',\n    'href',\n    'itemtype',\n    'longdesc',\n    'poster',\n    'src',\n    'xlink:href'\n  ]\n\n  var ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\n  var DefaultWhitelist = {\n    // Global attributes allowed on any supplied element below.\n    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n    a: ['target', 'href', 'title', 'rel'],\n    area: [],\n    b: [],\n    br: [],\n    col: [],\n    code: [],\n    div: [],\n    em: [],\n    hr: [],\n    h1: [],\n    h2: [],\n    h3: [],\n    h4: [],\n    h5: [],\n    h6: [],\n    i: [],\n    img: ['src', 'alt', 'title', 'width', 'height'],\n    li: [],\n    ol: [],\n    p: [],\n    pre: [],\n    s: [],\n    small: [],\n    span: [],\n    sub: [],\n    sup: [],\n    strong: [],\n    u: [],\n    ul: []\n  }\n\n  /**\n   * A pattern that recognizes a commonly useful subset of URLs that are safe.\n   *\n   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n   */\n  var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi\n\n  /**\n   * A pattern that matches safe data URLs. Only matches image, video and audio types.\n   *\n   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n   */\n  var DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i\n\n  function allowedAttribute(attr, allowedAttributeList) {\n    var attrName = attr.nodeName.toLowerCase()\n\n    if ($.inArray(attrName, allowedAttributeList) !== -1) {\n      if ($.inArray(attrName, uriAttrs) !== -1) {\n        return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))\n      }\n\n      return true\n    }\n\n    var regExp = $(allowedAttributeList).filter(function (index, value) {\n      return value instanceof RegExp\n    })\n\n    // Check if a regular expression validates the attribute.\n    for (var i = 0, l = regExp.length; i < l; i++) {\n      if (attrName.match(regExp[i])) {\n        return true\n      }\n    }\n\n    return false\n  }\n\n  function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {\n    if (unsafeHtml.length === 0) {\n      return unsafeHtml\n    }\n\n    if (sanitizeFn && typeof sanitizeFn === 'function') {\n      return sanitizeFn(unsafeHtml)\n    }\n\n    // IE 8 and below don't support createHTMLDocument\n    if (!document.implementation || !document.implementation.createHTMLDocument) {\n      return unsafeHtml\n    }\n\n    var createdDocument = document.implementation.createHTMLDocument('sanitization')\n    createdDocument.body.innerHTML = unsafeHtml\n\n    var whitelistKeys = $.map(whiteList, function (el, i) { return i })\n    var elements = $(createdDocument.body).find('*')\n\n    for (var i = 0, len = elements.length; i < len; i++) {\n      var el = elements[i]\n      var elName = el.nodeName.toLowerCase()\n\n      if ($.inArray(elName, whitelistKeys) === -1) {\n        el.parentNode.removeChild(el)\n\n        continue\n      }\n\n      var attributeList = $.map(el.attributes, function (el) { return el })\n      var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])\n\n      for (var j = 0, len2 = attributeList.length; j < len2; j++) {\n        if (!allowedAttribute(attributeList[j], whitelistedAttributes)) {\n          el.removeAttribute(attributeList[j].nodeName)\n        }\n      }\n    }\n\n    return createdDocument.body.innerHTML\n  }\n\n  // TOOLTIP PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Tooltip = function (element, options) {\n    this.type       = null\n    this.options    = null\n    this.enabled    = null\n    this.timeout    = null\n    this.hoverState = null\n    this.$element   = null\n    this.inState    = null\n\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.VERSION  = '3.4.1'\n\n  Tooltip.TRANSITION_DURATION = 150\n\n  Tooltip.DEFAULTS = {\n    animation: true,\n    placement: 'top',\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>',\n    trigger: 'hover focus',\n    title: '',\n    delay: 0,\n    html: false,\n    container: false,\n    viewport: {\n      selector: 'body',\n      padding: 0\n    },\n    sanitize : true,\n    sanitizeFn : null,\n    whiteList : DefaultWhitelist\n  }\n\n  Tooltip.prototype.init = function (type, element, options) {\n    this.enabled   = true\n    this.type      = type\n    this.$element  = $(element)\n    this.options   = this.getOptions(options)\n    this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))\n    this.inState   = { click: false, hover: false, focus: false }\n\n    if (this.$element[0] instanceof document.constructor && !this.options.selector) {\n      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')\n    }\n\n    var triggers = this.options.trigger.split(' ')\n\n    for (var i = triggers.length; i--;) {\n      var trigger = triggers[i]\n\n      if (trigger == 'click') {\n        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n      } else if (trigger != 'manual') {\n        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'\n        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'\n\n        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n      }\n    }\n\n    this.options.selector ?\n      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n      this.fixTitle()\n  }\n\n  Tooltip.prototype.getDefaults = function () {\n    return Tooltip.DEFAULTS\n  }\n\n  Tooltip.prototype.getOptions = function (options) {\n    var dataAttributes = this.$element.data()\n\n    for (var dataAttr in dataAttributes) {\n      if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {\n        delete dataAttributes[dataAttr]\n      }\n    }\n\n    options = $.extend({}, this.getDefaults(), dataAttributes, options)\n\n    if (options.delay && typeof options.delay == 'number') {\n      options.delay = {\n        show: options.delay,\n        hide: options.delay\n      }\n    }\n\n    if (options.sanitize) {\n      options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn)\n    }\n\n    return options\n  }\n\n  Tooltip.prototype.getDelegateOptions = function () {\n    var options  = {}\n    var defaults = this.getDefaults()\n\n    this._options && $.each(this._options, function (key, value) {\n      if (defaults[key] != value) options[key] = value\n    })\n\n    return options\n  }\n\n  Tooltip.prototype.enter = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    if (obj instanceof $.Event) {\n      self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true\n    }\n\n    if (self.tip().hasClass('in') || self.hoverState == 'in') {\n      self.hoverState = 'in'\n      return\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'in'\n\n    if (!self.options.delay || !self.options.delay.show) return self.show()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'in') self.show()\n    }, self.options.delay.show)\n  }\n\n  Tooltip.prototype.isInStateTrue = function () {\n    for (var key in this.inState) {\n      if (this.inState[key]) return true\n    }\n\n    return false\n  }\n\n  Tooltip.prototype.leave = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    if (obj instanceof $.Event) {\n      self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false\n    }\n\n    if (self.isInStateTrue()) return\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'out'\n\n    if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'out') self.hide()\n    }, self.options.delay.hide)\n  }\n\n  Tooltip.prototype.show = function () {\n    var e = $.Event('show.bs.' + this.type)\n\n    if (this.hasContent() && this.enabled) {\n      this.$element.trigger(e)\n\n      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])\n      if (e.isDefaultPrevented() || !inDom) return\n      var that = this\n\n      var $tip = this.tip()\n\n      var tipId = this.getUID(this.type)\n\n      this.setContent()\n      $tip.attr('id', tipId)\n      this.$element.attr('aria-describedby', tipId)\n\n      if (this.options.animation) $tip.addClass('fade')\n\n      var placement = typeof this.options.placement == 'function' ?\n        this.options.placement.call(this, $tip[0], this.$element[0]) :\n        this.options.placement\n\n      var autoToken = /\\s?auto?\\s?/i\n      var autoPlace = autoToken.test(placement)\n      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n      $tip\n        .detach()\n        .css({ top: 0, left: 0, display: 'block' })\n        .addClass(placement)\n        .data('bs.' + this.type, this)\n\n      this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)\n      this.$element.trigger('inserted.bs.' + this.type)\n\n      var pos          = this.getPosition()\n      var actualWidth  = $tip[0].offsetWidth\n      var actualHeight = $tip[0].offsetHeight\n\n      if (autoPlace) {\n        var orgPlacement = placement\n        var viewportDim = this.getPosition(this.$viewport)\n\n        placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :\n                    placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :\n                    placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :\n                    placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :\n                    placement\n\n        $tip\n          .removeClass(orgPlacement)\n          .addClass(placement)\n      }\n\n      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n      this.applyPlacement(calculatedOffset, placement)\n\n      var complete = function () {\n        var prevHoverState = that.hoverState\n        that.$element.trigger('shown.bs.' + that.type)\n        that.hoverState = null\n\n        if (prevHoverState == 'out') that.leave(that)\n      }\n\n      $.support.transition && this.$tip.hasClass('fade') ?\n        $tip\n          .one('bsTransitionEnd', complete)\n          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n        complete()\n    }\n  }\n\n  Tooltip.prototype.applyPlacement = function (offset, placement) {\n    var $tip   = this.tip()\n    var width  = $tip[0].offsetWidth\n    var height = $tip[0].offsetHeight\n\n    // manually read margins because getBoundingClientRect includes difference\n    var marginTop = parseInt($tip.css('margin-top'), 10)\n    var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n    // we must check for NaN for ie 8/9\n    if (isNaN(marginTop))  marginTop  = 0\n    if (isNaN(marginLeft)) marginLeft = 0\n\n    offset.top  += marginTop\n    offset.left += marginLeft\n\n    // $.fn.offset doesn't round pixel values\n    // so we use setOffset directly with our own function B-0\n    $.offset.setOffset($tip[0], $.extend({\n      using: function (props) {\n        $tip.css({\n          top: Math.round(props.top),\n          left: Math.round(props.left)\n        })\n      }\n    }, offset), 0)\n\n    $tip.addClass('in')\n\n    // check to see if placing tip in new offset caused the tip to resize itself\n    var actualWidth  = $tip[0].offsetWidth\n    var actualHeight = $tip[0].offsetHeight\n\n    if (placement == 'top' && actualHeight != height) {\n      offset.top = offset.top + height - actualHeight\n    }\n\n    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)\n\n    if (delta.left) offset.left += delta.left\n    else offset.top += delta.top\n\n    var isVertical          = /top|bottom/.test(placement)\n    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight\n    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'\n\n    $tip.offset(offset)\n    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)\n  }\n\n  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {\n    this.arrow()\n      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')\n      .css(isVertical ? 'top' : 'left', '')\n  }\n\n  Tooltip.prototype.setContent = function () {\n    var $tip  = this.tip()\n    var title = this.getTitle()\n\n    if (this.options.html) {\n      if (this.options.sanitize) {\n        title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn)\n      }\n\n      $tip.find('.tooltip-inner').html(title)\n    } else {\n      $tip.find('.tooltip-inner').text(title)\n    }\n\n    $tip.removeClass('fade in top bottom left right')\n  }\n\n  Tooltip.prototype.hide = function (callback) {\n    var that = this\n    var $tip = $(this.$tip)\n    var e    = $.Event('hide.bs.' + this.type)\n\n    function complete() {\n      if (that.hoverState != 'in') $tip.detach()\n      if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.\n        that.$element\n          .removeAttr('aria-describedby')\n          .trigger('hidden.bs.' + that.type)\n      }\n      callback && callback()\n    }\n\n    this.$element.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    $tip.removeClass('in')\n\n    $.support.transition && $tip.hasClass('fade') ?\n      $tip\n        .one('bsTransitionEnd', complete)\n        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n      complete()\n\n    this.hoverState = null\n\n    return this\n  }\n\n  Tooltip.prototype.fixTitle = function () {\n    var $e = this.$element\n    if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {\n      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n    }\n  }\n\n  Tooltip.prototype.hasContent = function () {\n    return this.getTitle()\n  }\n\n  Tooltip.prototype.getPosition = function ($element) {\n    $element   = $element || this.$element\n\n    var el     = $element[0]\n    var isBody = el.tagName == 'BODY'\n\n    var elRect    = el.getBoundingClientRect()\n    if (elRect.width == null) {\n      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093\n      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })\n    }\n    var isSvg = window.SVGElement && el instanceof window.SVGElement\n    // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.\n    // See https://github.com/twbs/bootstrap/issues/20280\n    var elOffset  = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())\n    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }\n    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null\n\n    return $.extend({}, elRect, scroll, outerDims, elOffset)\n  }\n\n  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }\n\n  }\n\n  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {\n    var delta = { top: 0, left: 0 }\n    if (!this.$viewport) return delta\n\n    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0\n    var viewportDimensions = this.getPosition(this.$viewport)\n\n    if (/right|left/.test(placement)) {\n      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll\n      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight\n      if (topEdgeOffset < viewportDimensions.top) { // top overflow\n        delta.top = viewportDimensions.top - topEdgeOffset\n      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow\n        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset\n      }\n    } else {\n      var leftEdgeOffset  = pos.left - viewportPadding\n      var rightEdgeOffset = pos.left + viewportPadding + actualWidth\n      if (leftEdgeOffset < viewportDimensions.left) { // left overflow\n        delta.left = viewportDimensions.left - leftEdgeOffset\n      } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow\n        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset\n      }\n    }\n\n    return delta\n  }\n\n  Tooltip.prototype.getTitle = function () {\n    var title\n    var $e = this.$element\n    var o  = this.options\n\n    title = $e.attr('data-original-title')\n      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)\n\n    return title\n  }\n\n  Tooltip.prototype.getUID = function (prefix) {\n    do prefix += ~~(Math.random() * 1000000)\n    while (document.getElementById(prefix))\n    return prefix\n  }\n\n  Tooltip.prototype.tip = function () {\n    if (!this.$tip) {\n      this.$tip = $(this.options.template)\n      if (this.$tip.length != 1) {\n        throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')\n      }\n    }\n    return this.$tip\n  }\n\n  Tooltip.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))\n  }\n\n  Tooltip.prototype.enable = function () {\n    this.enabled = true\n  }\n\n  Tooltip.prototype.disable = function () {\n    this.enabled = false\n  }\n\n  Tooltip.prototype.toggleEnabled = function () {\n    this.enabled = !this.enabled\n  }\n\n  Tooltip.prototype.toggle = function (e) {\n    var self = this\n    if (e) {\n      self = $(e.currentTarget).data('bs.' + this.type)\n      if (!self) {\n        self = new this.constructor(e.currentTarget, this.getDelegateOptions())\n        $(e.currentTarget).data('bs.' + this.type, self)\n      }\n    }\n\n    if (e) {\n      self.inState.click = !self.inState.click\n      if (self.isInStateTrue()) self.enter(self)\n      else self.leave(self)\n    } else {\n      self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n    }\n  }\n\n  Tooltip.prototype.destroy = function () {\n    var that = this\n    clearTimeout(this.timeout)\n    this.hide(function () {\n      that.$element.off('.' + that.type).removeData('bs.' + that.type)\n      if (that.$tip) {\n        that.$tip.detach()\n      }\n      that.$tip = null\n      that.$arrow = null\n      that.$viewport = null\n      that.$element = null\n    })\n  }\n\n  Tooltip.prototype.sanitizeHtml = function (unsafeHtml) {\n    return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn)\n  }\n\n  // TOOLTIP PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.tooltip')\n      var options = typeof option == 'object' && option\n\n      if (!data && /destroy|hide/.test(option)) return\n      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip             = Plugin\n  $.fn.tooltip.Constructor = Tooltip\n\n\n  // TOOLTIP NO CONFLICT\n  // ===================\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: popover.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#popovers\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // POPOVER PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n  Popover.VERSION  = '3.4.1'\n\n  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {\n    placement: 'right',\n    trigger: 'click',\n    content: '',\n    template: '<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  // NOTE: POPOVER EXTENDS tooltip.js\n  // ================================\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n  Popover.prototype.constructor = Popover\n\n  Popover.prototype.getDefaults = function () {\n    return Popover.DEFAULTS\n  }\n\n  Popover.prototype.setContent = function () {\n    var $tip    = this.tip()\n    var title   = this.getTitle()\n    var content = this.getContent()\n\n    if (this.options.html) {\n      var typeContent = typeof content\n\n      if (this.options.sanitize) {\n        title = this.sanitizeHtml(title)\n\n        if (typeContent === 'string') {\n          content = this.sanitizeHtml(content)\n        }\n      }\n\n      $tip.find('.popover-title').html(title)\n      $tip.find('.popover-content').children().detach().end()[\n        typeContent === 'string' ? 'html' : 'append'\n      ](content)\n    } else {\n      $tip.find('.popover-title').text(title)\n      $tip.find('.popover-content').children().detach().end().text(content)\n    }\n\n    $tip.removeClass('fade top bottom left right in')\n\n    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n    // this manually by checking the contents.\n    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n  }\n\n  Popover.prototype.hasContent = function () {\n    return this.getTitle() || this.getContent()\n  }\n\n  Popover.prototype.getContent = function () {\n    var $e = this.$element\n    var o  = this.options\n\n    return $e.attr('data-content')\n      || (typeof o.content == 'function' ?\n        o.content.call($e[0]) :\n        o.content)\n  }\n\n  Popover.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))\n  }\n\n\n  // POPOVER PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.popover')\n      var options = typeof option == 'object' && option\n\n      if (!data && /destroy|hide/.test(option)) return\n      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.popover\n\n  $.fn.popover             = Plugin\n  $.fn.popover.Constructor = Popover\n\n\n  // POPOVER NO CONFLICT\n  // ===================\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: scrollspy.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#scrollspy\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // SCROLLSPY CLASS DEFINITION\n  // ==========================\n\n  function ScrollSpy(element, options) {\n    this.$body          = $(document.body)\n    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)\n    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)\n    this.selector       = (this.options.target || '') + ' .nav li > a'\n    this.offsets        = []\n    this.targets        = []\n    this.activeTarget   = null\n    this.scrollHeight   = 0\n\n    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.VERSION  = '3.4.1'\n\n  ScrollSpy.DEFAULTS = {\n    offset: 10\n  }\n\n  ScrollSpy.prototype.getScrollHeight = function () {\n    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)\n  }\n\n  ScrollSpy.prototype.refresh = function () {\n    var that          = this\n    var offsetMethod  = 'offset'\n    var offsetBase    = 0\n\n    this.offsets      = []\n    this.targets      = []\n    this.scrollHeight = this.getScrollHeight()\n\n    if (!$.isWindow(this.$scrollElement[0])) {\n      offsetMethod = 'position'\n      offsetBase   = this.$scrollElement.scrollTop()\n    }\n\n    this.$body\n      .find(this.selector)\n      .map(function () {\n        var $el   = $(this)\n        var href  = $el.data('target') || $el.attr('href')\n        var $href = /^#./.test(href) && $(href)\n\n        return ($href\n          && $href.length\n          && $href.is(':visible')\n          && [[$href[offsetMethod]().top + offsetBase, href]]) || null\n      })\n      .sort(function (a, b) { return a[0] - b[0] })\n      .each(function () {\n        that.offsets.push(this[0])\n        that.targets.push(this[1])\n      })\n  }\n\n  ScrollSpy.prototype.process = function () {\n    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset\n    var scrollHeight = this.getScrollHeight()\n    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()\n    var offsets      = this.offsets\n    var targets      = this.targets\n    var activeTarget = this.activeTarget\n    var i\n\n    if (this.scrollHeight != scrollHeight) {\n      this.refresh()\n    }\n\n    if (scrollTop >= maxScroll) {\n      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)\n    }\n\n    if (activeTarget && scrollTop < offsets[0]) {\n      this.activeTarget = null\n      return this.clear()\n    }\n\n    for (i = offsets.length; i--;) {\n      activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])\n        && this.activate(targets[i])\n    }\n  }\n\n  ScrollSpy.prototype.activate = function (target) {\n    this.activeTarget = target\n\n    this.clear()\n\n    var selector = this.selector +\n      '[data-target=\"' + target + '\"],' +\n      this.selector + '[href=\"' + target + '\"]'\n\n    var active = $(selector)\n      .parents('li')\n      .addClass('active')\n\n    if (active.parent('.dropdown-menu').length) {\n      active = active\n        .closest('li.dropdown')\n        .addClass('active')\n    }\n\n    active.trigger('activate.bs.scrollspy')\n  }\n\n  ScrollSpy.prototype.clear = function () {\n    $(this.selector)\n      .parentsUntil(this.options.target, '.active')\n      .removeClass('active')\n  }\n\n\n  // SCROLLSPY PLUGIN DEFINITION\n  // ===========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.scrollspy')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy             = Plugin\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n\n  // SCROLLSPY NO CONFLICT\n  // =====================\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  // SCROLLSPY DATA-API\n  // ==================\n\n  $(window).on('load.bs.scrollspy.data-api', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      Plugin.call($spy, $spy.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tab.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#tabs\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TAB CLASS DEFINITION\n  // ====================\n\n  var Tab = function (element) {\n    // jscs:disable requireDollarBeforejQueryAssignment\n    this.element = $(element)\n    // jscs:enable requireDollarBeforejQueryAssignment\n  }\n\n  Tab.VERSION = '3.4.1'\n\n  Tab.TRANSITION_DURATION = 150\n\n  Tab.prototype.show = function () {\n    var $this    = this.element\n    var $ul      = $this.closest('ul:not(.dropdown-menu)')\n    var selector = $this.data('target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    if ($this.parent('li').hasClass('active')) return\n\n    var $previous = $ul.find('.active:last a')\n    var hideEvent = $.Event('hide.bs.tab', {\n      relatedTarget: $this[0]\n    })\n    var showEvent = $.Event('show.bs.tab', {\n      relatedTarget: $previous[0]\n    })\n\n    $previous.trigger(hideEvent)\n    $this.trigger(showEvent)\n\n    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return\n\n    var $target = $(document).find(selector)\n\n    this.activate($this.closest('li'), $ul)\n    this.activate($target, $target.parent(), function () {\n      $previous.trigger({\n        type: 'hidden.bs.tab',\n        relatedTarget: $this[0]\n      })\n      $this.trigger({\n        type: 'shown.bs.tab',\n        relatedTarget: $previous[0]\n      })\n    })\n  }\n\n  Tab.prototype.activate = function (element, container, callback) {\n    var $active    = container.find('> .active')\n    var transition = callback\n      && $.support.transition\n      && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)\n\n    function next() {\n      $active\n        .removeClass('active')\n        .find('> .dropdown-menu > .active')\n        .removeClass('active')\n        .end()\n        .find('[data-toggle=\"tab\"]')\n        .attr('aria-expanded', false)\n\n      element\n        .addClass('active')\n        .find('[data-toggle=\"tab\"]')\n        .attr('aria-expanded', true)\n\n      if (transition) {\n        element[0].offsetWidth // reflow for transition\n        element.addClass('in')\n      } else {\n        element.removeClass('fade')\n      }\n\n      if (element.parent('.dropdown-menu').length) {\n        element\n          .closest('li.dropdown')\n          .addClass('active')\n          .end()\n          .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', true)\n      }\n\n      callback && callback()\n    }\n\n    $active.length && transition ?\n      $active\n        .one('bsTransitionEnd', next)\n        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :\n      next()\n\n    $active.removeClass('in')\n  }\n\n\n  // TAB PLUGIN DEFINITION\n  // =====================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.tab')\n\n      if (!data) $this.data('bs.tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tab\n\n  $.fn.tab             = Plugin\n  $.fn.tab.Constructor = Tab\n\n\n  // TAB NO CONFLICT\n  // ===============\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  // TAB DATA-API\n  // ============\n\n  var clickHandler = function (e) {\n    e.preventDefault()\n    Plugin.call($(this), 'show')\n  }\n\n  $(document)\n    .on('click.bs.tab.data-api', '[data-toggle=\"tab\"]', clickHandler)\n    .on('click.bs.tab.data-api', '[data-toggle=\"pill\"]', clickHandler)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: affix.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#affix\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // AFFIX CLASS DEFINITION\n  // ======================\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, Affix.DEFAULTS, options)\n\n    var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target)\n\n    this.$target = target\n      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))\n\n    this.$element     = $(element)\n    this.affixed      = null\n    this.unpin        = null\n    this.pinnedOffset = null\n\n    this.checkPosition()\n  }\n\n  Affix.VERSION  = '3.4.1'\n\n  Affix.RESET    = 'affix affix-top affix-bottom'\n\n  Affix.DEFAULTS = {\n    offset: 0,\n    target: window\n  }\n\n  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {\n    var scrollTop    = this.$target.scrollTop()\n    var position     = this.$element.offset()\n    var targetHeight = this.$target.height()\n\n    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false\n\n    if (this.affixed == 'bottom') {\n      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'\n      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'\n    }\n\n    var initializing   = this.affixed == null\n    var colliderTop    = initializing ? scrollTop : position.top\n    var colliderHeight = initializing ? targetHeight : height\n\n    if (offsetTop != null && scrollTop <= offsetTop) return 'top'\n    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'\n\n    return false\n  }\n\n  Affix.prototype.getPinnedOffset = function () {\n    if (this.pinnedOffset) return this.pinnedOffset\n    this.$element.removeClass(Affix.RESET).addClass('affix')\n    var scrollTop = this.$target.scrollTop()\n    var position  = this.$element.offset()\n    return (this.pinnedOffset = position.top - scrollTop)\n  }\n\n  Affix.prototype.checkPositionWithEventLoop = function () {\n    setTimeout($.proxy(this.checkPosition, this), 1)\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var height       = this.$element.height()\n    var offset       = this.options.offset\n    var offsetTop    = offset.top\n    var offsetBottom = offset.bottom\n    var scrollHeight = Math.max($(document).height(), $(document.body).height())\n\n    if (typeof offset != 'object')         offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)\n\n    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)\n\n    if (this.affixed != affix) {\n      if (this.unpin != null) this.$element.css('top', '')\n\n      var affixType = 'affix' + (affix ? '-' + affix : '')\n      var e         = $.Event(affixType + '.bs.affix')\n\n      this.$element.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      this.affixed = affix\n      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null\n\n      this.$element\n        .removeClass(Affix.RESET)\n        .addClass(affixType)\n        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')\n    }\n\n    if (affix == 'bottom') {\n      this.$element.offset({\n        top: scrollHeight - height - offsetBottom\n      })\n    }\n  }\n\n\n  // AFFIX PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.affix')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.affix\n\n  $.fn.affix             = Plugin\n  $.fn.affix.Constructor = Affix\n\n\n  // AFFIX NO CONFLICT\n  // =================\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  // AFFIX DATA-API\n  // ==============\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n      var data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom\n      if (data.offsetTop    != null) data.offset.top    = data.offsetTop\n\n      Plugin.call($spy, data)\n    })\n  })\n\n}(jQuery);"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/button.js",
    "content": "/* ========================================================================\n * Bootstrap: button.js v3.3.4\n * http://getbootstrap.com/javascript/#buttons\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // BUTTON PUBLIC CLASS DEFINITION\n  // ==============================\n\n  var Button = function (element, options) {\n    this.$element  = $(element)\n    this.options   = $.extend({}, Button.DEFAULTS, options)\n    this.isLoading = false\n  }\n\n  Button.VERSION  = '3.3.4'\n\n  Button.DEFAULTS = {\n    loadingText: 'loading...'\n  }\n\n  Button.prototype.setState = function (state) {\n    var d    = 'disabled'\n    var $el  = this.$element\n    var val  = $el.is('input') ? 'val' : 'html'\n    var data = $el.data()\n\n    state = state + 'Text'\n\n    if (data.resetText == null) $el.data('resetText', $el[val]())\n\n    // push to event loop to allow forms to submit\n    setTimeout($.proxy(function () {\n      $el[val](data[state] == null ? this.options[state] : data[state])\n\n      if (state == 'loadingText') {\n        this.isLoading = true\n        $el.addClass(d).attr(d, d)\n      } else if (this.isLoading) {\n        this.isLoading = false\n        $el.removeClass(d).removeAttr(d)\n      }\n    }, this), 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var changed = true\n    var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n    if ($parent.length) {\n      var $input = this.$element.find('input')\n      if ($input.prop('type') == 'radio') {\n        if ($input.prop('checked') && this.$element.hasClass('active')) changed = false\n        else $parent.find('.active').removeClass('active')\n      }\n      if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')\n    } else {\n      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))\n    }\n\n    if (changed) this.$element.toggleClass('active')\n  }\n\n\n  // BUTTON PLUGIN DEFINITION\n  // ========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.button')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  var old = $.fn.button\n\n  $.fn.button             = Plugin\n  $.fn.button.Constructor = Button\n\n\n  // BUTTON NO CONFLICT\n  // ==================\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  // BUTTON DATA-API\n  // ===============\n\n  $(document)\n    .on('click.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      var $btn = $(e.target)\n      if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')\n      Plugin.call($btn, 'toggle')\n      e.preventDefault()\n    })\n    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))\n    })\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/carousel.js",
    "content": "/* ========================================================================\n * Bootstrap: carousel.js v3.3.4\n * http://getbootstrap.com/javascript/#carousel\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CAROUSEL CLASS DEFINITION\n  // =========================\n\n  var Carousel = function (element, options) {\n    this.$element    = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options     = options\n    this.paused      = null\n    this.sliding     = null\n    this.interval    = null\n    this.$active     = null\n    this.$items      = null\n\n    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))\n\n    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element\n      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))\n      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))\n  }\n\n  Carousel.VERSION  = '3.3.4'\n\n  Carousel.TRANSITION_DURATION = 600\n\n  Carousel.DEFAULTS = {\n    interval: 5000,\n    pause: 'hover',\n    wrap: true,\n    keyboard: true\n  }\n\n  Carousel.prototype.keydown = function (e) {\n    if (/input|textarea/i.test(e.target.tagName)) return\n    switch (e.which) {\n      case 37: this.prev(); break\n      case 39: this.next(); break\n      default: return\n    }\n\n    e.preventDefault()\n  }\n\n  Carousel.prototype.cycle = function (e) {\n    e || (this.paused = false)\n\n    this.interval && clearInterval(this.interval)\n\n    this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n    return this\n  }\n\n  Carousel.prototype.getItemIndex = function (item) {\n    this.$items = item.parent().children('.item')\n    return this.$items.index(item || this.$active)\n  }\n\n  Carousel.prototype.getItemForDirection = function (direction, active) {\n    var activeIndex = this.getItemIndex(active)\n    var willWrap = (direction == 'prev' && activeIndex === 0)\n                || (direction == 'next' && activeIndex == (this.$items.length - 1))\n    if (willWrap && !this.options.wrap) return active\n    var delta = direction == 'prev' ? -1 : 1\n    var itemIndex = (activeIndex + delta) % this.$items.length\n    return this.$items.eq(itemIndex)\n  }\n\n  Carousel.prototype.to = function (pos) {\n    var that        = this\n    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))\n\n    if (pos > (this.$items.length - 1) || pos < 0) return\n\n    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, \"slid\"\n    if (activeIndex == pos) return this.pause().cycle()\n\n    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))\n  }\n\n  Carousel.prototype.pause = function (e) {\n    e || (this.paused = true)\n\n    if (this.$element.find('.next, .prev').length && $.support.transition) {\n      this.$element.trigger($.support.transition.end)\n      this.cycle(true)\n    }\n\n    this.interval = clearInterval(this.interval)\n\n    return this\n  }\n\n  Carousel.prototype.next = function () {\n    if (this.sliding) return\n    return this.slide('next')\n  }\n\n  Carousel.prototype.prev = function () {\n    if (this.sliding) return\n    return this.slide('prev')\n  }\n\n  Carousel.prototype.slide = function (type, next) {\n    var $active   = this.$element.find('.item.active')\n    var $next     = next || this.getItemForDirection(type, $active)\n    var isCycling = this.interval\n    var direction = type == 'next' ? 'left' : 'right'\n    var that      = this\n\n    if ($next.hasClass('active')) return (this.sliding = false)\n\n    var relatedTarget = $next[0]\n    var slideEvent = $.Event('slide.bs.carousel', {\n      relatedTarget: relatedTarget,\n      direction: direction\n    })\n    this.$element.trigger(slideEvent)\n    if (slideEvent.isDefaultPrevented()) return\n\n    this.sliding = true\n\n    isCycling && this.pause()\n\n    if (this.$indicators.length) {\n      this.$indicators.find('.active').removeClass('active')\n      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])\n      $nextIndicator && $nextIndicator.addClass('active')\n    }\n\n    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, \"slid\"\n    if ($.support.transition && this.$element.hasClass('slide')) {\n      $next.addClass(type)\n      $next[0].offsetWidth // force reflow\n      $active.addClass(direction)\n      $next.addClass(direction)\n      $active\n        .one('bsTransitionEnd', function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () {\n            that.$element.trigger(slidEvent)\n          }, 0)\n        })\n        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)\n    } else {\n      $active.removeClass('active')\n      $next.addClass('active')\n      this.sliding = false\n      this.$element.trigger(slidEvent)\n    }\n\n    isCycling && this.cycle()\n\n    return this\n  }\n\n\n  // CAROUSEL PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.carousel')\n      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n      var action  = typeof option == 'string' ? option : options.slide\n\n      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  var old = $.fn.carousel\n\n  $.fn.carousel             = Plugin\n  $.fn.carousel.Constructor = Carousel\n\n\n  // CAROUSEL NO CONFLICT\n  // ====================\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n\n  // CAROUSEL DATA-API\n  // =================\n\n  var clickHandler = function (e) {\n    var href\n    var $this   = $(this)\n    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) // strip for ie7\n    if (!$target.hasClass('carousel')) return\n    var options = $.extend({}, $target.data(), $this.data())\n    var slideIndex = $this.attr('data-slide-to')\n    if (slideIndex) options.interval = false\n\n    Plugin.call($target, options)\n\n    if (slideIndex) {\n      $target.data('bs.carousel').to(slideIndex)\n    }\n\n    e.preventDefault()\n  }\n\n  $(document)\n    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)\n    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)\n\n  $(window).on('load', function () {\n    $('[data-ride=\"carousel\"]').each(function () {\n      var $carousel = $(this)\n      Plugin.call($carousel, $carousel.data())\n    })\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/collapse.js",
    "content": "/* ========================================================================\n * Bootstrap: collapse.js v3.3.4\n * http://getbootstrap.com/javascript/#collapse\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // COLLAPSE PUBLIC CLASS DEFINITION\n  // ================================\n\n  var Collapse = function (element, options) {\n    this.$element      = $(element)\n    this.options       = $.extend({}, Collapse.DEFAULTS, options)\n    this.$trigger      = $('[data-toggle=\"collapse\"][href=\"#' + element.id + '\"],' +\n                           '[data-toggle=\"collapse\"][data-target=\"#' + element.id + '\"]')\n    this.transitioning = null\n\n    if (this.options.parent) {\n      this.$parent = this.getParent()\n    } else {\n      this.addAriaAndCollapsedClass(this.$element, this.$trigger)\n    }\n\n    if (this.options.toggle) this.toggle()\n  }\n\n  Collapse.VERSION  = '3.3.4'\n\n  Collapse.TRANSITION_DURATION = 350\n\n  Collapse.DEFAULTS = {\n    toggle: true\n  }\n\n  Collapse.prototype.dimension = function () {\n    var hasWidth = this.$element.hasClass('width')\n    return hasWidth ? 'width' : 'height'\n  }\n\n  Collapse.prototype.show = function () {\n    if (this.transitioning || this.$element.hasClass('in')) return\n\n    var activesData\n    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')\n\n    if (actives && actives.length) {\n      activesData = actives.data('bs.collapse')\n      if (activesData && activesData.transitioning) return\n    }\n\n    var startEvent = $.Event('show.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    if (actives && actives.length) {\n      Plugin.call(actives, 'hide')\n      activesData || actives.data('bs.collapse', null)\n    }\n\n    var dimension = this.dimension()\n\n    this.$element\n      .removeClass('collapse')\n      .addClass('collapsing')[dimension](0)\n      .attr('aria-expanded', true)\n\n    this.$trigger\n      .removeClass('collapsed')\n      .attr('aria-expanded', true)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse in')[dimension]('')\n      this.transitioning = 0\n      this.$element\n        .trigger('shown.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n    this.$element\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])\n  }\n\n  Collapse.prototype.hide = function () {\n    if (this.transitioning || !this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('hide.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var dimension = this.dimension()\n\n    this.$element[dimension](this.$element[dimension]())[0].offsetHeight\n\n    this.$element\n      .addClass('collapsing')\n      .removeClass('collapse in')\n      .attr('aria-expanded', false)\n\n    this.$trigger\n      .addClass('collapsed')\n      .attr('aria-expanded', false)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.transitioning = 0\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse')\n        .trigger('hidden.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    this.$element\n      [dimension](0)\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)\n  }\n\n  Collapse.prototype.toggle = function () {\n    this[this.$element.hasClass('in') ? 'hide' : 'show']()\n  }\n\n  Collapse.prototype.getParent = function () {\n    return $(this.options.parent)\n      .find('[data-toggle=\"collapse\"][data-parent=\"' + this.options.parent + '\"]')\n      .each($.proxy(function (i, element) {\n        var $element = $(element)\n        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)\n      }, this))\n      .end()\n  }\n\n  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {\n    var isOpen = $element.hasClass('in')\n\n    $element.attr('aria-expanded', isOpen)\n    $trigger\n      .toggleClass('collapsed', !isOpen)\n      .attr('aria-expanded', isOpen)\n  }\n\n  function getTargetFromTrigger($trigger) {\n    var href\n    var target = $trigger.attr('data-target')\n      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n\n    return $(target)\n  }\n\n\n  // COLLAPSE PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.collapse')\n      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false\n      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.collapse\n\n  $.fn.collapse             = Plugin\n  $.fn.collapse.Constructor = Collapse\n\n\n  // COLLAPSE NO CONFLICT\n  // ====================\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  // COLLAPSE DATA-API\n  // =================\n\n  $(document).on('click.bs.collapse.data-api', '[data-toggle=\"collapse\"]', function (e) {\n    var $this   = $(this)\n\n    if (!$this.attr('data-target')) e.preventDefault()\n\n    var $target = getTargetFromTrigger($this)\n    var data    = $target.data('bs.collapse')\n    var option  = data ? 'toggle' : $this.data()\n\n    Plugin.call($target, option)\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/dropdown.js",
    "content": "/* ========================================================================\n * Bootstrap: dropdown.js v3.3.4\n * http://getbootstrap.com/javascript/#dropdowns\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // DROPDOWN CLASS DEFINITION\n  // =========================\n\n  var backdrop = '.dropdown-backdrop'\n  var toggle   = '[data-toggle=\"dropdown\"]'\n  var Dropdown = function (element) {\n    $(element).on('click.bs.dropdown', this.toggle)\n  }\n\n  Dropdown.VERSION = '3.3.4'\n\n  Dropdown.prototype.toggle = function (e) {\n    var $this = $(this)\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    clearMenus()\n\n    if (!isActive) {\n      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n        // if mobile we use a backdrop because click events don't delegate\n        $('<div class=\"dropdown-backdrop\"/>').insertAfter($(this)).on('click', clearMenus)\n      }\n\n      var relatedTarget = { relatedTarget: this }\n      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this\n        .trigger('focus')\n        .attr('aria-expanded', 'true')\n\n      $parent\n        .toggleClass('open')\n        .trigger('shown.bs.dropdown', relatedTarget)\n    }\n\n    return false\n  }\n\n  Dropdown.prototype.keydown = function (e) {\n    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return\n\n    var $this = $(this)\n\n    e.preventDefault()\n    e.stopPropagation()\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {\n      if (e.which == 27) $parent.find(toggle).trigger('focus')\n      return $this.trigger('click')\n    }\n\n    var desc = ' li:not(.disabled):visible a'\n    var $items = $parent.find('[role=\"menu\"]' + desc + ', [role=\"listbox\"]' + desc)\n\n    if (!$items.length) return\n\n    var index = $items.index(e.target)\n\n    if (e.which == 38 && index > 0)                 index--                        // up\n    if (e.which == 40 && index < $items.length - 1) index++                        // down\n    if (!~index)                                      index = 0\n\n    $items.eq(index).trigger('focus')\n  }\n\n  function clearMenus(e) {\n    if (e && e.which === 3) return\n    $(backdrop).remove()\n    $(toggle).each(function () {\n      var $this         = $(this)\n      var $parent       = getParent($this)\n      var relatedTarget = { relatedTarget: this }\n\n      if (!$parent.hasClass('open')) return\n\n      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this.attr('aria-expanded', 'false')\n      $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)\n    })\n  }\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = selector && $(selector)\n\n    return $parent && $parent.length ? $parent : $this.parent()\n  }\n\n\n  // DROPDOWN PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.dropdown')\n\n      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown             = Plugin\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  // DROPDOWN NO CONFLICT\n  // ====================\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  // APPLY TO STANDARD DROPDOWN ELEMENTS\n  // ===================================\n\n  $(document)\n    .on('click.bs.dropdown.data-api', clearMenus)\n    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"menu\"]', Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"listbox\"]', Dropdown.prototype.keydown)\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/modal.js",
    "content": "/* ========================================================================\n * Bootstrap: modal.js v3.3.4\n * http://getbootstrap.com/javascript/#modals\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // MODAL CLASS DEFINITION\n  // ======================\n\n  var Modal = function (element, options) {\n    this.options             = options\n    this.$body               = $(document.body)\n    this.$element            = $(element)\n    this.$dialog             = this.$element.find('.modal-dialog')\n    this.$backdrop           = null\n    this.isShown             = null\n    this.originalBodyPad     = null\n    this.scrollbarWidth      = 0\n    this.ignoreBackdropClick = false\n\n    if (this.options.remote) {\n      this.$element\n        .find('.modal-content')\n        .load(this.options.remote, $.proxy(function () {\n          this.$element.trigger('loaded.bs.modal')\n        }, this))\n    }\n  }\n\n  Modal.VERSION  = '3.3.4'\n\n  Modal.TRANSITION_DURATION = 300\n  Modal.BACKDROP_TRANSITION_DURATION = 150\n\n  Modal.DEFAULTS = {\n    backdrop: true,\n    keyboard: true,\n    show: true\n  }\n\n  Modal.prototype.toggle = function (_relatedTarget) {\n    return this.isShown ? this.hide() : this.show(_relatedTarget)\n  }\n\n  Modal.prototype.show = function (_relatedTarget) {\n    var that = this\n    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n    this.$element.trigger(e)\n\n    if (this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = true\n\n    this.checkScrollbar()\n    this.setScrollbar()\n    this.$body.addClass('modal-open')\n\n    this.escape()\n    this.resize()\n\n    this.$element.on('click.dismiss.bs.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n    this.$dialog.on('mousedown.dismiss.bs.modal', function () {\n      that.$element.one('mouseup.dismiss.bs.modal', function (e) {\n        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true\n      })\n    })\n\n    this.backdrop(function () {\n      var transition = $.support.transition && that.$element.hasClass('fade')\n\n      if (!that.$element.parent().length) {\n        that.$element.appendTo(that.$body) // don't move modals dom position\n      }\n\n      that.$element\n        .show()\n        .scrollTop(0)\n\n      that.adjustDialog()\n\n      if (transition) {\n        that.$element[0].offsetWidth // force reflow\n      }\n\n      that.$element\n        .addClass('in')\n        .attr('aria-hidden', false)\n\n      that.enforceFocus()\n\n      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n      transition ?\n        that.$dialog // wait for modal to slide in\n          .one('bsTransitionEnd', function () {\n            that.$element.trigger('focus').trigger(e)\n          })\n          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n        that.$element.trigger('focus').trigger(e)\n    })\n  }\n\n  Modal.prototype.hide = function (e) {\n    if (e) e.preventDefault()\n\n    e = $.Event('hide.bs.modal')\n\n    this.$element.trigger(e)\n\n    if (!this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = false\n\n    this.escape()\n    this.resize()\n\n    $(document).off('focusin.bs.modal')\n\n    this.$element\n      .removeClass('in')\n      .attr('aria-hidden', true)\n      .off('click.dismiss.bs.modal')\n      .off('mouseup.dismiss.bs.modal')\n\n    this.$dialog.off('mousedown.dismiss.bs.modal')\n\n    $.support.transition && this.$element.hasClass('fade') ?\n      this.$element\n        .one('bsTransitionEnd', $.proxy(this.hideModal, this))\n        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n      this.hideModal()\n  }\n\n  Modal.prototype.enforceFocus = function () {\n    $(document)\n      .off('focusin.bs.modal') // guard against infinite focus loop\n      .on('focusin.bs.modal', $.proxy(function (e) {\n        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {\n          this.$element.trigger('focus')\n        }\n      }, this))\n  }\n\n  Modal.prototype.escape = function () {\n    if (this.isShown && this.options.keyboard) {\n      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {\n        e.which == 27 && this.hide()\n      }, this))\n    } else if (!this.isShown) {\n      this.$element.off('keydown.dismiss.bs.modal')\n    }\n  }\n\n  Modal.prototype.resize = function () {\n    if (this.isShown) {\n      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))\n    } else {\n      $(window).off('resize.bs.modal')\n    }\n  }\n\n  Modal.prototype.hideModal = function () {\n    var that = this\n    this.$element.hide()\n    this.backdrop(function () {\n      that.$body.removeClass('modal-open')\n      that.resetAdjustments()\n      that.resetScrollbar()\n      that.$element.trigger('hidden.bs.modal')\n    })\n  }\n\n  Modal.prototype.removeBackdrop = function () {\n    this.$backdrop && this.$backdrop.remove()\n    this.$backdrop = null\n  }\n\n  Modal.prototype.backdrop = function (callback) {\n    var that = this\n    var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n    if (this.isShown && this.options.backdrop) {\n      var doAnimate = $.support.transition && animate\n\n      this.$backdrop = $('<div class=\"modal-backdrop ' + animate + '\" />')\n        .appendTo(this.$body)\n\n      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {\n        if (this.ignoreBackdropClick) {\n          this.ignoreBackdropClick = false\n          return\n        }\n        if (e.target !== e.currentTarget) return\n        this.options.backdrop == 'static'\n          ? this.$element[0].focus()\n          : this.hide()\n      }, this))\n\n      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n      this.$backdrop.addClass('in')\n\n      if (!callback) return\n\n      doAnimate ?\n        this.$backdrop\n          .one('bsTransitionEnd', callback)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callback()\n\n    } else if (!this.isShown && this.$backdrop) {\n      this.$backdrop.removeClass('in')\n\n      var callbackRemove = function () {\n        that.removeBackdrop()\n        callback && callback()\n      }\n      $.support.transition && this.$element.hasClass('fade') ?\n        this.$backdrop\n          .one('bsTransitionEnd', callbackRemove)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callbackRemove()\n\n    } else if (callback) {\n      callback()\n    }\n  }\n\n  // these following methods are used to handle overflowing modals\n\n  Modal.prototype.handleUpdate = function () {\n    this.adjustDialog()\n  }\n\n  Modal.prototype.adjustDialog = function () {\n    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight\n\n    this.$element.css({\n      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',\n      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''\n    })\n  }\n\n  Modal.prototype.resetAdjustments = function () {\n    this.$element.css({\n      paddingLeft: '',\n      paddingRight: ''\n    })\n  }\n\n  Modal.prototype.checkScrollbar = function () {\n    var fullWindowWidth = window.innerWidth\n    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8\n      var documentElementRect = document.documentElement.getBoundingClientRect()\n      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)\n    }\n    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth\n    this.scrollbarWidth = this.measureScrollbar()\n  }\n\n  Modal.prototype.setScrollbar = function () {\n    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)\n    this.originalBodyPad = document.body.style.paddingRight || ''\n    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)\n  }\n\n  Modal.prototype.resetScrollbar = function () {\n    this.$body.css('padding-right', this.originalBodyPad)\n  }\n\n  Modal.prototype.measureScrollbar = function () { // thx walsh\n    var scrollDiv = document.createElement('div')\n    scrollDiv.className = 'modal-scrollbar-measure'\n    this.$body.append(scrollDiv)\n    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n    this.$body[0].removeChild(scrollDiv)\n    return scrollbarWidth\n  }\n\n\n  // MODAL PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option, _relatedTarget) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.modal')\n      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option](_relatedTarget)\n      else if (options.show) data.show(_relatedTarget)\n    })\n  }\n\n  var old = $.fn.modal\n\n  $.fn.modal             = Plugin\n  $.fn.modal.Constructor = Modal\n\n\n  // MODAL NO CONFLICT\n  // =================\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  // MODAL DATA-API\n  // ==============\n\n  $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this   = $(this)\n    var href    = $this.attr('href')\n    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))) // strip for ie7\n    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    if ($this.is('a')) e.preventDefault()\n\n    $target.one('show.bs.modal', function (showEvent) {\n      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown\n      $target.one('hidden.bs.modal', function () {\n        $this.is(':visible') && $this.trigger('focus')\n      })\n    })\n    Plugin.call($target, option, this)\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/npm.js",
    "content": "// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.\nrequire('../../js/transition.js')\nrequire('../../js/alert.js')\nrequire('../../js/button.js')\nrequire('../../js/carousel.js')\nrequire('../../js/collapse.js')\nrequire('../../js/dropdown.js')\nrequire('../../js/modal.js')\nrequire('../../js/tooltip.js')\nrequire('../../js/popover.js')\nrequire('../../js/scrollspy.js')\nrequire('../../js/tab.js')\nrequire('../../js/affix.js')"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/popover.js",
    "content": "/* ========================================================================\n * Bootstrap: popover.js v3.3.4\n * http://getbootstrap.com/javascript/#popovers\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // POPOVER PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n  Popover.VERSION  = '3.3.4'\n\n  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {\n    placement: 'right',\n    trigger: 'click',\n    content: '',\n    template: '<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  // NOTE: POPOVER EXTENDS tooltip.js\n  // ================================\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n  Popover.prototype.constructor = Popover\n\n  Popover.prototype.getDefaults = function () {\n    return Popover.DEFAULTS\n  }\n\n  Popover.prototype.setContent = function () {\n    var $tip    = this.tip()\n    var title   = this.getTitle()\n    var content = this.getContent()\n\n    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)\n    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events\n      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'\n    ](content)\n\n    $tip.removeClass('fade top bottom left right in')\n\n    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n    // this manually by checking the contents.\n    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n  }\n\n  Popover.prototype.hasContent = function () {\n    return this.getTitle() || this.getContent()\n  }\n\n  Popover.prototype.getContent = function () {\n    var $e = this.$element\n    var o  = this.options\n\n    return $e.attr('data-content')\n      || (typeof o.content == 'function' ?\n            o.content.call($e[0]) :\n            o.content)\n  }\n\n  Popover.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))\n  }\n\n\n  // POPOVER PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.popover')\n      var options = typeof option == 'object' && option\n\n      if (!data && /destroy|hide/.test(option)) return\n      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.popover\n\n  $.fn.popover             = Plugin\n  $.fn.popover.Constructor = Popover\n\n\n  // POPOVER NO CONFLICT\n  // ===================\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/scrollspy.js",
    "content": "/* ========================================================================\n * Bootstrap: scrollspy.js v3.3.4\n * http://getbootstrap.com/javascript/#scrollspy\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // SCROLLSPY CLASS DEFINITION\n  // ==========================\n\n  function ScrollSpy(element, options) {\n    this.$body          = $(document.body)\n    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)\n    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)\n    this.selector       = (this.options.target || '') + ' .nav li > a'\n    this.offsets        = []\n    this.targets        = []\n    this.activeTarget   = null\n    this.scrollHeight   = 0\n\n    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.VERSION  = '3.3.4'\n\n  ScrollSpy.DEFAULTS = {\n    offset: 10\n  }\n\n  ScrollSpy.prototype.getScrollHeight = function () {\n    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)\n  }\n\n  ScrollSpy.prototype.refresh = function () {\n    var that          = this\n    var offsetMethod  = 'offset'\n    var offsetBase    = 0\n\n    this.offsets      = []\n    this.targets      = []\n    this.scrollHeight = this.getScrollHeight()\n\n    if (!$.isWindow(this.$scrollElement[0])) {\n      offsetMethod = 'position'\n      offsetBase   = this.$scrollElement.scrollTop()\n    }\n\n    this.$body\n      .find(this.selector)\n      .map(function () {\n        var $el   = $(this)\n        var href  = $el.data('target') || $el.attr('href')\n        var $href = /^#./.test(href) && $(href)\n\n        return ($href\n          && $href.length\n          && $href.is(':visible')\n          && [[$href[offsetMethod]().top + offsetBase, href]]) || null\n      })\n      .sort(function (a, b) { return a[0] - b[0] })\n      .each(function () {\n        that.offsets.push(this[0])\n        that.targets.push(this[1])\n      })\n  }\n\n  ScrollSpy.prototype.process = function () {\n    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset\n    var scrollHeight = this.getScrollHeight()\n    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()\n    var offsets      = this.offsets\n    var targets      = this.targets\n    var activeTarget = this.activeTarget\n    var i\n\n    if (this.scrollHeight != scrollHeight) {\n      this.refresh()\n    }\n\n    if (scrollTop >= maxScroll) {\n      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)\n    }\n\n    if (activeTarget && scrollTop < offsets[0]) {\n      this.activeTarget = null\n      return this.clear()\n    }\n\n    for (i = offsets.length; i--;) {\n      activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])\n        && this.activate(targets[i])\n    }\n  }\n\n  ScrollSpy.prototype.activate = function (target) {\n    this.activeTarget = target\n\n    this.clear()\n\n    var selector = this.selector +\n      '[data-target=\"' + target + '\"],' +\n      this.selector + '[href=\"' + target + '\"]'\n\n    var active = $(selector)\n      .parents('li')\n      .addClass('active')\n\n    if (active.parent('.dropdown-menu').length) {\n      active = active\n        .closest('li.dropdown')\n        .addClass('active')\n    }\n\n    active.trigger('activate.bs.scrollspy')\n  }\n\n  ScrollSpy.prototype.clear = function () {\n    $(this.selector)\n      .parentsUntil(this.options.target, '.active')\n      .removeClass('active')\n  }\n\n\n  // SCROLLSPY PLUGIN DEFINITION\n  // ===========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.scrollspy')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy             = Plugin\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n\n  // SCROLLSPY NO CONFLICT\n  // =====================\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  // SCROLLSPY DATA-API\n  // ==================\n\n  $(window).on('load.bs.scrollspy.data-api', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      Plugin.call($spy, $spy.data())\n    })\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/tab.js",
    "content": "/* ========================================================================\n * Bootstrap: tab.js v3.3.4\n * http://getbootstrap.com/javascript/#tabs\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TAB CLASS DEFINITION\n  // ====================\n\n  var Tab = function (element) {\n    this.element = $(element)\n  }\n\n  Tab.VERSION = '3.3.4'\n\n  Tab.TRANSITION_DURATION = 150\n\n  Tab.prototype.show = function () {\n    var $this    = this.element\n    var $ul      = $this.closest('ul:not(.dropdown-menu)')\n    var selector = $this.data('target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    if ($this.parent('li').hasClass('active')) return\n\n    var $previous = $ul.find('.active:last a')\n    var hideEvent = $.Event('hide.bs.tab', {\n      relatedTarget: $this[0]\n    })\n    var showEvent = $.Event('show.bs.tab', {\n      relatedTarget: $previous[0]\n    })\n\n    $previous.trigger(hideEvent)\n    $this.trigger(showEvent)\n\n    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return\n\n    var $target = $(selector)\n\n    this.activate($this.closest('li'), $ul)\n    this.activate($target, $target.parent(), function () {\n      $previous.trigger({\n        type: 'hidden.bs.tab',\n        relatedTarget: $this[0]\n      })\n      $this.trigger({\n        type: 'shown.bs.tab',\n        relatedTarget: $previous[0]\n      })\n    })\n  }\n\n  Tab.prototype.activate = function (element, container, callback) {\n    var $active    = container.find('> .active')\n    var transition = callback\n      && $.support.transition\n      && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)\n\n    function next() {\n      $active\n        .removeClass('active')\n        .find('> .dropdown-menu > .active')\n          .removeClass('active')\n        .end()\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', false)\n\n      element\n        .addClass('active')\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', true)\n\n      if (transition) {\n        element[0].offsetWidth // reflow for transition\n        element.addClass('in')\n      } else {\n        element.removeClass('fade')\n      }\n\n      if (element.parent('.dropdown-menu').length) {\n        element\n          .closest('li.dropdown')\n            .addClass('active')\n          .end()\n          .find('[data-toggle=\"tab\"]')\n            .attr('aria-expanded', true)\n      }\n\n      callback && callback()\n    }\n\n    $active.length && transition ?\n      $active\n        .one('bsTransitionEnd', next)\n        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :\n      next()\n\n    $active.removeClass('in')\n  }\n\n\n  // TAB PLUGIN DEFINITION\n  // =====================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.tab')\n\n      if (!data) $this.data('bs.tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tab\n\n  $.fn.tab             = Plugin\n  $.fn.tab.Constructor = Tab\n\n\n  // TAB NO CONFLICT\n  // ===============\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  // TAB DATA-API\n  // ============\n\n  var clickHandler = function (e) {\n    e.preventDefault()\n    Plugin.call($(this), 'show')\n  }\n\n  $(document)\n    .on('click.bs.tab.data-api', '[data-toggle=\"tab\"]', clickHandler)\n    .on('click.bs.tab.data-api', '[data-toggle=\"pill\"]', clickHandler)\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/tooltip.js",
    "content": "/* ========================================================================\n * Bootstrap: tooltip.js v3.3.4\n * http://getbootstrap.com/javascript/#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TOOLTIP PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Tooltip = function (element, options) {\n    this.type       = null\n    this.options    = null\n    this.enabled    = null\n    this.timeout    = null\n    this.hoverState = null\n    this.$element   = null\n\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.VERSION  = '3.3.4'\n\n  Tooltip.TRANSITION_DURATION = 150\n\n  Tooltip.DEFAULTS = {\n    animation: true,\n    placement: 'top',\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>',\n    trigger: 'hover focus',\n    title: '',\n    delay: 0,\n    html: false,\n    container: false,\n    viewport: {\n      selector: 'body',\n      padding: 0\n    }\n  }\n\n  Tooltip.prototype.init = function (type, element, options) {\n    this.enabled   = true\n    this.type      = type\n    this.$element  = $(element)\n    this.options   = this.getOptions(options)\n    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)\n\n    if (this.$element[0] instanceof document.constructor && !this.options.selector) {\n      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')\n    }\n\n    var triggers = this.options.trigger.split(' ')\n\n    for (var i = triggers.length; i--;) {\n      var trigger = triggers[i]\n\n      if (trigger == 'click') {\n        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n      } else if (trigger != 'manual') {\n        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'\n        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'\n\n        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n      }\n    }\n\n    this.options.selector ?\n      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n      this.fixTitle()\n  }\n\n  Tooltip.prototype.getDefaults = function () {\n    return Tooltip.DEFAULTS\n  }\n\n  Tooltip.prototype.getOptions = function (options) {\n    options = $.extend({}, this.getDefaults(), this.$element.data(), options)\n\n    if (options.delay && typeof options.delay == 'number') {\n      options.delay = {\n        show: options.delay,\n        hide: options.delay\n      }\n    }\n\n    return options\n  }\n\n  Tooltip.prototype.getDelegateOptions = function () {\n    var options  = {}\n    var defaults = this.getDefaults()\n\n    this._options && $.each(this._options, function (key, value) {\n      if (defaults[key] != value) options[key] = value\n    })\n\n    return options\n  }\n\n  Tooltip.prototype.enter = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (self && self.$tip && self.$tip.is(':visible')) {\n      self.hoverState = 'in'\n      return\n    }\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'in'\n\n    if (!self.options.delay || !self.options.delay.show) return self.show()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'in') self.show()\n    }, self.options.delay.show)\n  }\n\n  Tooltip.prototype.leave = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'out'\n\n    if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'out') self.hide()\n    }, self.options.delay.hide)\n  }\n\n  Tooltip.prototype.show = function () {\n    var e = $.Event('show.bs.' + this.type)\n\n    if (this.hasContent() && this.enabled) {\n      this.$element.trigger(e)\n\n      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])\n      if (e.isDefaultPrevented() || !inDom) return\n      var that = this\n\n      var $tip = this.tip()\n\n      var tipId = this.getUID(this.type)\n\n      this.setContent()\n      $tip.attr('id', tipId)\n      this.$element.attr('aria-describedby', tipId)\n\n      if (this.options.animation) $tip.addClass('fade')\n\n      var placement = typeof this.options.placement == 'function' ?\n        this.options.placement.call(this, $tip[0], this.$element[0]) :\n        this.options.placement\n\n      var autoToken = /\\s?auto?\\s?/i\n      var autoPlace = autoToken.test(placement)\n      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n      $tip\n        .detach()\n        .css({ top: 0, left: 0, display: 'block' })\n        .addClass(placement)\n        .data('bs.' + this.type, this)\n\n      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)\n\n      var pos          = this.getPosition()\n      var actualWidth  = $tip[0].offsetWidth\n      var actualHeight = $tip[0].offsetHeight\n\n      if (autoPlace) {\n        var orgPlacement = placement\n        var $container   = this.options.container ? $(this.options.container) : this.$element.parent()\n        var containerDim = this.getPosition($container)\n\n        placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top'    :\n                    placement == 'top'    && pos.top    - actualHeight < containerDim.top    ? 'bottom' :\n                    placement == 'right'  && pos.right  + actualWidth  > containerDim.width  ? 'left'   :\n                    placement == 'left'   && pos.left   - actualWidth  < containerDim.left   ? 'right'  :\n                    placement\n\n        $tip\n          .removeClass(orgPlacement)\n          .addClass(placement)\n      }\n\n      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n      this.applyPlacement(calculatedOffset, placement)\n\n      var complete = function () {\n        var prevHoverState = that.hoverState\n        that.$element.trigger('shown.bs.' + that.type)\n        that.hoverState = null\n\n        if (prevHoverState == 'out') that.leave(that)\n      }\n\n      $.support.transition && this.$tip.hasClass('fade') ?\n        $tip\n          .one('bsTransitionEnd', complete)\n          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n        complete()\n    }\n  }\n\n  Tooltip.prototype.applyPlacement = function (offset, placement) {\n    var $tip   = this.tip()\n    var width  = $tip[0].offsetWidth\n    var height = $tip[0].offsetHeight\n\n    // manually read margins because getBoundingClientRect includes difference\n    var marginTop = parseInt($tip.css('margin-top'), 10)\n    var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n    // we must check for NaN for ie 8/9\n    if (isNaN(marginTop))  marginTop  = 0\n    if (isNaN(marginLeft)) marginLeft = 0\n\n    offset.top  = offset.top  + marginTop\n    offset.left = offset.left + marginLeft\n\n    // $.fn.offset doesn't round pixel values\n    // so we use setOffset directly with our own function B-0\n    $.offset.setOffset($tip[0], $.extend({\n      using: function (props) {\n        $tip.css({\n          top: Math.round(props.top),\n          left: Math.round(props.left)\n        })\n      }\n    }, offset), 0)\n\n    $tip.addClass('in')\n\n    // check to see if placing tip in new offset caused the tip to resize itself\n    var actualWidth  = $tip[0].offsetWidth\n    var actualHeight = $tip[0].offsetHeight\n\n    if (placement == 'top' && actualHeight != height) {\n      offset.top = offset.top + height - actualHeight\n    }\n\n    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)\n\n    if (delta.left) offset.left += delta.left\n    else offset.top += delta.top\n\n    var isVertical          = /top|bottom/.test(placement)\n    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight\n    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'\n\n    $tip.offset(offset)\n    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)\n  }\n\n  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {\n    this.arrow()\n      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')\n      .css(isVertical ? 'top' : 'left', '')\n  }\n\n  Tooltip.prototype.setContent = function () {\n    var $tip  = this.tip()\n    var title = this.getTitle()\n\n    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)\n    $tip.removeClass('fade in top bottom left right')\n  }\n\n  Tooltip.prototype.hide = function (callback) {\n    var that = this\n    var $tip = $(this.$tip)\n    var e    = $.Event('hide.bs.' + this.type)\n\n    function complete() {\n      if (that.hoverState != 'in') $tip.detach()\n      that.$element\n        .removeAttr('aria-describedby')\n        .trigger('hidden.bs.' + that.type)\n      callback && callback()\n    }\n\n    this.$element.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    $tip.removeClass('in')\n\n    $.support.transition && $tip.hasClass('fade') ?\n      $tip\n        .one('bsTransitionEnd', complete)\n        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n      complete()\n\n    this.hoverState = null\n\n    return this\n  }\n\n  Tooltip.prototype.fixTitle = function () {\n    var $e = this.$element\n    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {\n      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n    }\n  }\n\n  Tooltip.prototype.hasContent = function () {\n    return this.getTitle()\n  }\n\n  Tooltip.prototype.getPosition = function ($element) {\n    $element   = $element || this.$element\n\n    var el     = $element[0]\n    var isBody = el.tagName == 'BODY'\n\n    var elRect    = el.getBoundingClientRect()\n    if (elRect.width == null) {\n      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093\n      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })\n    }\n    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()\n    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }\n    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null\n\n    return $.extend({}, elRect, scroll, outerDims, elOffset)\n  }\n\n  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }\n\n  }\n\n  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {\n    var delta = { top: 0, left: 0 }\n    if (!this.$viewport) return delta\n\n    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0\n    var viewportDimensions = this.getPosition(this.$viewport)\n\n    if (/right|left/.test(placement)) {\n      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll\n      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight\n      if (topEdgeOffset < viewportDimensions.top) { // top overflow\n        delta.top = viewportDimensions.top - topEdgeOffset\n      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow\n        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset\n      }\n    } else {\n      var leftEdgeOffset  = pos.left - viewportPadding\n      var rightEdgeOffset = pos.left + viewportPadding + actualWidth\n      if (leftEdgeOffset < viewportDimensions.left) { // left overflow\n        delta.left = viewportDimensions.left - leftEdgeOffset\n      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow\n        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset\n      }\n    }\n\n    return delta\n  }\n\n  Tooltip.prototype.getTitle = function () {\n    var title\n    var $e = this.$element\n    var o  = this.options\n\n    title = $e.attr('data-original-title')\n      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)\n\n    return title\n  }\n\n  Tooltip.prototype.getUID = function (prefix) {\n    do prefix += ~~(Math.random() * 1000000)\n    while (document.getElementById(prefix))\n    return prefix\n  }\n\n  Tooltip.prototype.tip = function () {\n    return (this.$tip = this.$tip || $(this.options.template))\n  }\n\n  Tooltip.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))\n  }\n\n  Tooltip.prototype.enable = function () {\n    this.enabled = true\n  }\n\n  Tooltip.prototype.disable = function () {\n    this.enabled = false\n  }\n\n  Tooltip.prototype.toggleEnabled = function () {\n    this.enabled = !this.enabled\n  }\n\n  Tooltip.prototype.toggle = function (e) {\n    var self = this\n    if (e) {\n      self = $(e.currentTarget).data('bs.' + this.type)\n      if (!self) {\n        self = new this.constructor(e.currentTarget, this.getDelegateOptions())\n        $(e.currentTarget).data('bs.' + this.type, self)\n      }\n    }\n\n    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n  }\n\n  Tooltip.prototype.destroy = function () {\n    var that = this\n    clearTimeout(this.timeout)\n    this.hide(function () {\n      that.$element.off('.' + that.type).removeData('bs.' + that.type)\n    })\n  }\n\n\n  // TOOLTIP PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.tooltip')\n      var options = typeof option == 'object' && option\n\n      if (!data && /destroy|hide/.test(option)) return\n      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip             = Plugin\n  $.fn.tooltip.Constructor = Tooltip\n\n\n  // TOOLTIP NO CONFLICT\n  // ===================\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/bootstrap/transition.js",
    "content": "/* ========================================================================\n * Bootstrap: transition.js v3.3.4\n * http://getbootstrap.com/javascript/#transitions\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)\n  // ============================================================\n\n  function transitionEnd() {\n    var el = document.createElement('bootstrap')\n\n    var transEndEventNames = {\n      WebkitTransition : 'webkitTransitionEnd',\n      MozTransition    : 'transitionend',\n      OTransition      : 'oTransitionEnd otransitionend',\n      transition       : 'transitionend'\n    }\n\n    for (var name in transEndEventNames) {\n      if (el.style[name] !== undefined) {\n        return { end: transEndEventNames[name] }\n      }\n    }\n\n    return false // explicit for ie8 (  ._.)\n  }\n\n  // http://blog.alexmaccaw.com/css-transitions\n  $.fn.emulateTransitionEnd = function (duration) {\n    var called = false\n    var $el = this\n    $(this).one('bsTransitionEnd', function () { called = true })\n    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n    setTimeout(callback, duration)\n    return this\n  }\n\n  $(function () {\n    $.support.transition = transitionEnd()\n\n    if (!$.support.transition) return\n\n    $.event.special.bsTransitionEnd = {\n      bindType: $.support.transition.end,\n      delegateType: $.support.transition.end,\n      handle: function (e) {\n        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)\n      }\n    }\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/dexie/LICENSE.txt",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/LICENSE.txt",
    "content": "Copyright (c) 2007-2014 IOLA and Ole Laursen\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/jquery.event.drag.LICENSE.txt",
    "content": "Copyright (c) 2008-2017 ThreeDubMedia\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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."
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/jquery.mousewheel.LICENSE.txt",
    "content": "Copyright jQuery Foundation and other contributors\nhttps://jquery.org/\n\nThis software consists of voluntary contributions made by many\nindividuals. For exact contribution history, see the revision history\navailable at https://github.com/jquery/jquery-mousewheel\n\nThe following license applies to all parts of this software except as\ndocumented below:\n\n====\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n====\n\nAll files located in the node_modules and external directories are\nexternally maintained libraries used by this software which have their\nown licenses; we recommend you read them, as their terms may differ from\nthe terms above."
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/lib/globalize.culture.en-US.js",
    "content": "/*\n * Globalize Culture en-US\n *\n * http://github.com/jquery/globalize\n *\n * Copyright Software Freedom Conservancy, Inc.\n * Dual licensed under the MIT or GPL Version 2 licenses.\n * http://jquery.org/license\n *\n * This file was generated by the Globalize Culture Generator\n * Translation: bugs found in this file need to be fixed in the generator\n */\n\n(function( window, undefined ) {\n\nvar Globalize;\n\nif ( typeof require !== \"undefined\" &&\n\ttypeof exports !== \"undefined\" &&\n\ttypeof module !== \"undefined\" ) {\n\t// Assume CommonJS\n\tGlobalize = require( \"globalize\" );\n} else {\n\t// Global variable\n\tGlobalize = window.Globalize;\n}\n\nGlobalize.addCultureInfo( \"en-US\", \"default\", {\n\tname: \"en-US\",\n\tenglishName: \"English (United States)\"\n});\n\n}( this ));\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/lib/globalize.js",
    "content": "/*!\n * Globalize\n *\n * http://github.com/jquery/globalize\n *\n * Copyright Software Freedom Conservancy, Inc.\n * Dual licensed under the MIT or GPL Version 2 licenses.\n * http://jquery.org/license\n */\n\n(function( window, undefined ) {\n\nvar Globalize,\n\t// private variables\n\tregexHex,\n\tregexInfinity,\n\tregexParseFloat,\n\tregexTrim,\n\t// private JavaScript utility functions\n\tarrayIndexOf,\n\tendsWith,\n\textend,\n\tisArray,\n\tisFunction,\n\tisObject,\n\tstartsWith,\n\ttrim,\n\ttruncate,\n\tzeroPad,\n\t// private Globalization utility functions\n\tappendPreOrPostMatch,\n\texpandFormat,\n\tformatDate,\n\tformatNumber,\n\tgetTokenRegExp,\n\tgetEra,\n\tgetEraYear,\n\tparseExact,\n\tparseNegativePattern;\n\n// Global variable (Globalize) or CommonJS module (globalize)\nGlobalize = function( cultureSelector ) {\n\treturn new Globalize.prototype.init( cultureSelector );\n};\n\nif ( typeof require !== \"undefined\" &&\n\ttypeof exports !== \"undefined\" &&\n\ttypeof module !== \"undefined\" ) {\n\t// Assume CommonJS\n\tmodule.exports = Globalize;\n} else {\n\t// Export as global variable\n\twindow.Globalize = Globalize;\n}\n\nGlobalize.cultures = {};\n\nGlobalize.prototype = {\n\tconstructor: Globalize,\n\tinit: function( cultureSelector ) {\n\t\tthis.cultures = Globalize.cultures;\n\t\tthis.cultureSelector = cultureSelector;\n\n\t\treturn this;\n\t}\n};\nGlobalize.prototype.init.prototype = Globalize.prototype;\n\n// 1. When defining a culture, all fields are required except the ones stated as optional.\n// 2. Each culture should have a \".calendars\" object with at least one calendar named \"standard\"\n//    which serves as the default calendar in use by that culture.\n// 3. Each culture should have a \".calendar\" object which is the current calendar being used,\n//    it may be dynamically changed at any time to one of the calendars in \".calendars\".\nGlobalize.cultures[ \"default\" ] = {\n\t// A unique name for the culture in the form <language code>-<country/region code>\n\tname: \"en\",\n\t// the name of the culture in the english language\n\tenglishName: \"English\",\n\t// the name of the culture in its own language\n\tnativeName: \"English\",\n\t// whether the culture uses right-to-left text\n\tisRTL: false,\n\t// \"language\" is used for so-called \"specific\" cultures.\n\t// For example, the culture \"es-CL\" means \"Spanish, in Chili\".\n\t// It represents the Spanish-speaking culture as it is in Chili,\n\t// which might have different formatting rules or even translations\n\t// than Spanish in Spain. A \"neutral\" culture is one that is not\n\t// specific to a region. For example, the culture \"es\" is the generic\n\t// Spanish culture, which may be a more generalized version of the language\n\t// that may or may not be what a specific culture expects.\n\t// For a specific culture like \"es-CL\", the \"language\" field refers to the\n\t// neutral, generic culture information for the language it is using.\n\t// This is not always a simple matter of the string before the dash.\n\t// For example, the \"zh-Hans\" culture is netural (Simplified Chinese).\n\t// And the \"zh-SG\" culture is Simplified Chinese in Singapore, whose lanugage\n\t// field is \"zh-CHS\", not \"zh\".\n\t// This field should be used to navigate from a specific culture to it's\n\t// more general, neutral culture. If a culture is already as general as it\n\t// can get, the language may refer to itself.\n\tlanguage: \"en\",\n\t// numberFormat defines general number formatting rules, like the digits in\n\t// each grouping, the group separator, and how negative numbers are displayed.\n\tnumberFormat: {\n\t\t// [negativePattern]\n\t\t// Note, numberFormat.pattern has no \"positivePattern\" unlike percent and currency,\n\t\t// but is still defined as an array for consistency with them.\n\t\t//   negativePattern: one of \"(n)|-n|- n|n-|n -\"\n\t\tpattern: [ \"-n\" ],\n\t\t// number of decimal places normally shown\n\t\tdecimals: 2,\n\t\t// string that separates number groups, as in 1,000,000\n\t\t\",\": \",\",\n\t\t// string that separates a number from the fractional portion, as in 1.99\n\t\t\".\": \".\",\n\t\t// array of numbers indicating the size of each number group.\n\t\t// TODO: more detailed description and example\n\t\tgroupSizes: [ 3 ],\n\t\t// symbol used for positive numbers\n\t\t\"+\": \"+\",\n\t\t// symbol used for negative numbers\n\t\t\"-\": \"-\",\n\t\t// symbol used for NaN (Not-A-Number)\n\t\t\"NaN\": \"NaN\",\n\t\t// symbol used for Negative Infinity\n\t\tnegativeInfinity: \"-Infinity\",\n\t\t// symbol used for Positive Infinity\n\t\tpositiveInfinity: \"Infinity\",\n\t\tpercent: {\n\t\t\t// [negativePattern, positivePattern]\n\t\t\t//   negativePattern: one of \"-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %\"\n\t\t\t//   positivePattern: one of \"n %|n%|%n|% n\"\n\t\t\tpattern: [ \"-n %\", \"n %\" ],\n\t\t\t// number of decimal places normally shown\n\t\t\tdecimals: 2,\n\t\t\t// array of numbers indicating the size of each number group.\n\t\t\t// TODO: more detailed description and example\n\t\t\tgroupSizes: [ 3 ],\n\t\t\t// string that separates number groups, as in 1,000,000\n\t\t\t\",\": \",\",\n\t\t\t// string that separates a number from the fractional portion, as in 1.99\n\t\t\t\".\": \".\",\n\t\t\t// symbol used to represent a percentage\n\t\t\tsymbol: \"%\"\n\t\t},\n\t\tcurrency: {\n\t\t\t// [negativePattern, positivePattern]\n\t\t\t//   negativePattern: one of \"($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)\"\n\t\t\t//   positivePattern: one of \"$n|n$|$ n|n $\"\n\t\t\tpattern: [ \"($n)\", \"$n\" ],\n\t\t\t// number of decimal places normally shown\n\t\t\tdecimals: 2,\n\t\t\t// array of numbers indicating the size of each number group.\n\t\t\t// TODO: more detailed description and example\n\t\t\tgroupSizes: [ 3 ],\n\t\t\t// string that separates number groups, as in 1,000,000\n\t\t\t\",\": \",\",\n\t\t\t// string that separates a number from the fractional portion, as in 1.99\n\t\t\t\".\": \".\",\n\t\t\t// symbol used to represent currency\n\t\t\tsymbol: \"$\"\n\t\t}\n\t},\n\t// calendars defines all the possible calendars used by this culture.\n\t// There should be at least one defined with name \"standard\", and is the default\n\t// calendar used by the culture.\n\t// A calendar contains information about how dates are formatted, information about\n\t// the calendar's eras, a standard set of the date formats,\n\t// translations for day and month names, and if the calendar is not based on the Gregorian\n\t// calendar, conversion functions to and from the Gregorian calendar.\n\tcalendars: {\n\t\tstandard: {\n\t\t\t// name that identifies the type of calendar this is\n\t\t\tname: \"Gregorian_USEnglish\",\n\t\t\t// separator of parts of a date (e.g. \"/\" in 11/05/1955)\n\t\t\t\"/\": \"/\",\n\t\t\t// separator of parts of a time (e.g. \":\" in 05:44 PM)\n\t\t\t\":\": \":\",\n\t\t\t// the first day of the week (0 = Sunday, 1 = Monday, etc)\n\t\t\tfirstDay: 0,\n\t\t\tdays: {\n\t\t\t\t// full day names\n\t\t\t\tnames: [ \"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\" ],\n\t\t\t\t// abbreviated day names\n\t\t\t\tnamesAbbr: [ \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\" ],\n\t\t\t\t// shortest day names\n\t\t\t\tnamesShort: [ \"Su\", \"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\" ]\n\t\t\t},\n\t\t\tmonths: {\n\t\t\t\t// full month names (13 months for lunar calendards -- 13th month should be \"\" if not lunar)\n\t\t\t\tnames: [ \"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\", \"\" ],\n\t\t\t\t// abbreviated month names\n\t\t\t\tnamesAbbr: [ \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\", \"\" ]\n\t\t\t},\n\t\t\t// AM and PM designators in one of these forms:\n\t\t\t// The usual view, and the upper and lower case versions\n\t\t\t//   [ standard, lowercase, uppercase ]\n\t\t\t// The culture does not use AM or PM (likely all standard date formats use 24 hour time)\n\t\t\t//   null\n\t\t\tAM: [ \"AM\", \"am\", \"AM\" ],\n\t\t\tPM: [ \"PM\", \"pm\", \"PM\" ],\n\t\t\teras: [\n\t\t\t\t// eras in reverse chronological order.\n\t\t\t\t// name: the name of the era in this culture (e.g. A.D., C.E.)\n\t\t\t\t// start: when the era starts in ticks (gregorian, gmt), null if it is the earliest supported era.\n\t\t\t\t// offset: offset in years from gregorian calendar\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"A.D.\",\n\t\t\t\t\t\"start\": null,\n\t\t\t\t\t\"offset\": 0\n\t\t\t\t}\n\t\t\t],\n\t\t\t// when a two digit year is given, it will never be parsed as a four digit\n\t\t\t// year greater than this year (in the appropriate era for the culture)\n\t\t\t// Set it as a full year (e.g. 2029) or use an offset format starting from\n\t\t\t// the current year: \"+19\" would correspond to 2029 if the current year 2010.\n\t\t\ttwoDigitYearMax: 2029,\n\t\t\t// set of predefined date and time patterns used by the culture\n\t\t\t// these represent the format someone in this culture would expect\n\t\t\t// to see given the portions of the date that are shown.\n\t\t\tpatterns: {\n\t\t\t\t// short date pattern\n\t\t\t\td: \"M/d/yyyy\",\n\t\t\t\t// long date pattern\n\t\t\t\tD: \"dddd, MMMM dd, yyyy\",\n\t\t\t\t// short time pattern\n\t\t\t\tt: \"h:mm tt\",\n\t\t\t\t// long time pattern\n\t\t\t\tT: \"h:mm:ss tt\",\n\t\t\t\t// long date, short time pattern\n\t\t\t\tf: \"dddd, MMMM dd, yyyy h:mm tt\",\n\t\t\t\t// long date, long time pattern\n\t\t\t\tF: \"dddd, MMMM dd, yyyy h:mm:ss tt\",\n\t\t\t\t// month/day pattern\n\t\t\t\tM: \"MMMM dd\",\n\t\t\t\t// month/year pattern\n\t\t\t\tY: \"yyyy MMMM\",\n\t\t\t\t// S is a sortable format that does not vary by culture\n\t\t\t\tS: \"yyyy\\u0027-\\u0027MM\\u0027-\\u0027dd\\u0027T\\u0027HH\\u0027:\\u0027mm\\u0027:\\u0027ss\"\n\t\t\t}\n\t\t\t// optional fields for each calendar:\n\t\t\t/*\n\t\t\tmonthsGenitive:\n\t\t\t\tSame as months but used when the day preceeds the month.\n\t\t\t\tOmit if the culture has no genitive distinction in month names.\n\t\t\t\tFor an explaination of genitive months, see http://blogs.msdn.com/michkap/archive/2004/12/25/332259.aspx\n\t\t\tconvert:\n\t\t\t\tAllows for the support of non-gregorian based calendars. This convert object is used to\n\t\t\t\tto convert a date to and from a gregorian calendar date to handle parsing and formatting.\n\t\t\t\tThe two functions:\n\t\t\t\t\tfromGregorian( date )\n\t\t\t\t\t\tGiven the date as a parameter, return an array with parts [ year, month, day ]\n\t\t\t\t\t\tcorresponding to the non-gregorian based year, month, and day for the calendar.\n\t\t\t\t\ttoGregorian( year, month, day )\n\t\t\t\t\t\tGiven the non-gregorian year, month, and day, return a new Date() object\n\t\t\t\t\t\tset to the corresponding date in the gregorian calendar.\n\t\t\t*/\n\t\t}\n\t},\n\t// For localized strings\n\tmessages: {}\n};\n\nGlobalize.cultures[ \"default\" ].calendar = Globalize.cultures[ \"default\" ].calendars.standard;\n\nGlobalize.cultures.en = Globalize.cultures[ \"default\" ];\n\nGlobalize.cultureSelector = \"en\";\n\n//\n// private variables\n//\n\nregexHex = /^0x[a-f0-9]+$/i;\nregexInfinity = /^[+\\-]?infinity$/i;\nregexParseFloat = /^[+\\-]?\\d*\\.?\\d*(e[+\\-]?\\d+)?$/;\nregexTrim = /^\\s+|\\s+$/g;\n\n//\n// private JavaScript utility functions\n//\n\narrayIndexOf = function( array, item ) {\n\tif ( array.indexOf ) {\n\t\treturn array.indexOf( item );\n\t}\n\tfor ( var i = 0, length = array.length; i < length; i++ ) {\n\t\tif ( array[i] === item ) {\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn -1;\n};\n\nendsWith = function( value, pattern ) {\n\treturn value.substr( value.length - pattern.length ) === pattern;\n};\n\nextend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\t\ttarget = arguments[1] || {};\n\t\t// skip the boolean and the target\n\t\ti = 2;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( isObject(copy) || (copyIsArray = isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && isObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\nisArray = Array.isArray || function( obj ) {\n\treturn Object.prototype.toString.call( obj ) === \"[object Array]\";\n};\n\nisFunction = function( obj ) {\n\treturn Object.prototype.toString.call( obj ) === \"[object Function]\";\n};\n\nisObject = function( obj ) {\n\treturn Object.prototype.toString.call( obj ) === \"[object Object]\";\n};\n\nstartsWith = function( value, pattern ) {\n\treturn value.indexOf( pattern ) === 0;\n};\n\ntrim = function( value ) {\n\treturn ( value + \"\" ).replace( regexTrim, \"\" );\n};\n\ntruncate = function( value ) {\n\tif ( isNaN( value ) ) {\n\t\treturn NaN;\n\t}\n\treturn Math[ value < 0 ? \"ceil\" : \"floor\" ]( value );\n};\n\nzeroPad = function( str, count, left ) {\n\tvar l;\n\tfor ( l = str.length; l < count; l += 1 ) {\n\t\tstr = ( left ? (\"0\" + str) : (str + \"0\") );\n\t}\n\treturn str;\n};\n\n//\n// private Globalization utility functions\n//\n\nappendPreOrPostMatch = function( preMatch, strings ) {\n\t// appends pre- and post- token match strings while removing escaped characters.\n\t// Returns a single quote count which is used to determine if the token occurs\n\t// in a string literal.\n\tvar quoteCount = 0,\n\t\tescaped = false;\n\tfor ( var i = 0, il = preMatch.length; i < il; i++ ) {\n\t\tvar c = preMatch.charAt( i );\n\t\tswitch ( c ) {\n\t\t\tcase \"\\'\":\n\t\t\t\tif ( escaped ) {\n\t\t\t\t\tstrings.push( \"\\'\" );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tquoteCount++;\n\t\t\t\t}\n\t\t\t\tescaped = false;\n\t\t\t\tbreak;\n\t\t\tcase \"\\\\\":\n\t\t\t\tif ( escaped ) {\n\t\t\t\t\tstrings.push( \"\\\\\" );\n\t\t\t\t}\n\t\t\t\tescaped = !escaped;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tstrings.push( c );\n\t\t\t\tescaped = false;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn quoteCount;\n};\n\nexpandFormat = function( cal, format ) {\n\t// expands unspecified or single character date formats into the full pattern.\n\tformat = format || \"F\";\n\tvar pattern,\n\t\tpatterns = cal.patterns,\n\t\tlen = format.length;\n\tif ( len === 1 ) {\n\t\tpattern = patterns[ format ];\n\t\tif ( !pattern ) {\n\t\t\tthrow \"Invalid date format string \\'\" + format + \"\\'.\";\n\t\t}\n\t\tformat = pattern;\n\t}\n\telse if ( len === 2 && format.charAt(0) === \"%\" ) {\n\t\t// %X escape format -- intended as a custom format string that is only one character, not a built-in format.\n\t\tformat = format.charAt( 1 );\n\t}\n\treturn format;\n};\n\nformatDate = function( value, format, culture ) {\n\tvar cal = culture.calendar,\n\t\tconvert = cal.convert,\n\t\tret;\n\n\tif ( !format || !format.length || format === \"i\" ) {\n\t\tif ( culture && culture.name.length ) {\n\t\t\tif ( convert ) {\n\t\t\t\t// non-gregorian calendar, so we cannot use built-in toLocaleString()\n\t\t\t\tret = formatDate( value, cal.patterns.F, culture );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tvar eraDate = new Date( value.getTime() ),\n\t\t\t\t\tera = getEra( value, cal.eras );\n\t\t\t\teraDate.setFullYear( getEraYear(value, cal, era) );\n\t\t\t\tret = eraDate.toLocaleString();\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tret = value.toString();\n\t\t}\n\t\treturn ret;\n\t}\n\n\tvar eras = cal.eras,\n\t\tsortable = format === \"s\";\n\tformat = expandFormat( cal, format );\n\n\t// Start with an empty string\n\tret = [];\n\tvar hour,\n\t\tzeros = [ \"0\", \"00\", \"000\" ],\n\t\tfoundDay,\n\t\tcheckedDay,\n\t\tdayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g,\n\t\tquoteCount = 0,\n\t\ttokenRegExp = getTokenRegExp(),\n\t\tconverted;\n\n\t//function padZeros( num, c ) {\n\t//\tvar r, s = num + \"\";\n\t//\tif ( c > 1 && s.length < c ) {\n\t//\t\tr = ( zeros[c - 2] + s);\n\t//\t\treturn r.substr( r.length - c, c );\n\t//\t}\n\t//\telse {\n\t//\t\tr = s;\n\t//\t}\n\t//\treturn r;\n\t//}\n\n\tfunction padZeros(num, c) {\n\t    if (num < 0) {\n\t        return \"-\" + padZeros(-num, c);\n\t    }\n\t    var r, s = num + \"\";\n\t    if (c > 1 && s.length < c) {\n\t        r = (zeros[c - 2] + s);\n\t        return r.substr(r.length - c, c);\n\t    }\n\t    else {\n\t        r = s;\n\t    }\n\t    return r;\n\t}\n\n\tfunction hasDay() {\n\t\tif ( foundDay || checkedDay ) {\n\t\t\treturn foundDay;\n\t\t}\n\t\tfoundDay = dayPartRegExp.test( format );\n\t\tcheckedDay = true;\n\t\treturn foundDay;\n\t}\n\n\tfunction getPart( date, part ) {\n\t\tif ( converted ) {\n\t\t\treturn converted[ part ];\n\t\t}\n\t\tswitch ( part ) {\n\t\t\tcase 0:\n\t\t\t\treturn date.getFullYear();\n\t\t\tcase 1:\n\t\t\t\treturn date.getMonth();\n\t\t\tcase 2:\n\t\t\t\treturn date.getDate();\n\t\t\tdefault:\n\t\t\t\tthrow \"Invalid part value \" + part;\n\t\t}\n\t}\n\n\tif ( !sortable && convert ) {\n\t\tconverted = convert.fromGregorian( value );\n\t}\n\n\tfor ( ; ; ) {\n\t\t// Save the current index\n\t\tvar index = tokenRegExp.lastIndex,\n\t\t\t// Look for the next pattern\n\t\t\tar = tokenRegExp.exec( format );\n\n\t\t// Append the text before the pattern (or the end of the string if not found)\n\t\tvar preMatch = format.slice( index, ar ? ar.index : format.length );\n\t\tquoteCount += appendPreOrPostMatch( preMatch, ret );\n\n\t\tif ( !ar ) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// do not replace any matches that occur inside a string literal.\n\t\tif ( quoteCount % 2 ) {\n\t\t\tret.push( ar[0] );\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar current = ar[ 0 ],\n\t\t\tclength = current.length;\n\n\t\tswitch ( current ) {\n\t\t\tcase \"ddd\":\n\t\t\t\t//Day of the week, as a three-letter abbreviation\n\t\t\tcase \"dddd\":\n\t\t\t\t// Day of the week, using the full name\n\t\t\t\tvar names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names;\n\t\t\t\tret.push( names[value.getDay()] );\n\t\t\t\tbreak;\n\t\t\tcase \"d\":\n\t\t\t\t// Day of month, without leading zero for single-digit days\n\t\t\tcase \"dd\":\n\t\t\t\t// Day of month, with leading zero for single-digit days\n\t\t\t\tfoundDay = true;\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( getPart(value, 2), clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"MMM\":\n\t\t\t\t// Month, as a three-letter abbreviation\n\t\t\tcase \"MMMM\":\n\t\t\t\t// Month, using the full name\n\t\t\t\tvar part = getPart( value, 1 );\n\t\t\t\tret.push(\n\t\t\t\t\t( cal.monthsGenitive && hasDay() ) ?\n\t\t\t\t\t( cal.monthsGenitive[ clength === 3 ? \"namesAbbr\" : \"names\" ][ part ] ) :\n\t\t\t\t\t( cal.months[ clength === 3 ? \"namesAbbr\" : \"names\" ][ part ] )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"M\":\n\t\t\t\t// Month, as digits, with no leading zero for single-digit months\n\t\t\tcase \"MM\":\n\t\t\t\t// Month, as digits, with leading zero for single-digit months\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( getPart(value, 1) + 1, clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"y\":\n\t\t\t\t// Year, as two digits, but with no leading zero for years less than 10\n\t\t\tcase \"yy\":\n\t\t\t\t// Year, as two digits, with leading zero for years less than 10\n\t\t\tcase \"yyyy\":\n\t\t\t\t// Year represented by four full digits\n\t\t\t\tpart = converted ? converted[ 0 ] : getEraYear( value, cal, getEra(value, eras), sortable );\n\t\t\t\tif ( clength < 4 ) {\n\t\t\t\t\tpart = part % 100;\n\t\t\t\t}\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( part, clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"h\":\n\t\t\t\t// Hours with no leading zero for single-digit hours, using 12-hour clock\n\t\t\tcase \"hh\":\n\t\t\t\t// Hours with leading zero for single-digit hours, using 12-hour clock\n\t\t\t\thour = value.getHours() % 12;\n\t\t\t\tif ( hour === 0 ) hour = 12;\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( hour, clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"H\":\n\t\t\t\t// Hours with no leading zero for single-digit hours, using 24-hour clock\n\t\t\tcase \"HH\":\n\t\t\t\t// Hours with leading zero for single-digit hours, using 24-hour clock\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( value.getHours(), clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"m\":\n\t\t\t\t// Minutes with no leading zero for single-digit minutes\n\t\t\tcase \"mm\":\n\t\t\t\t// Minutes with leading zero for single-digit minutes\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( value.getMinutes(), clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"s\":\n\t\t\t\t// Seconds with no leading zero for single-digit seconds\n\t\t\tcase \"ss\":\n\t\t\t\t// Seconds with leading zero for single-digit seconds\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( value.getSeconds(), clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"t\":\n\t\t\t\t// One character am/pm indicator (\"a\" or \"p\")\n\t\t\tcase \"tt\":\n\t\t\t\t// Multicharacter am/pm indicator\n\t\t\t\tpart = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : \" \" ) : ( cal.PM ? cal.PM[0] : \" \" );\n\t\t\t\tret.push( clength === 1 ? part.charAt(0) : part );\n\t\t\t\tbreak;\n\t\t\tcase \"f\":\n\t\t\t\t// Deciseconds\n\t\t\tcase \"ff\":\n\t\t\t\t// Centiseconds\n\t\t\tcase \"fff\":\n\t\t\t\t// Milliseconds\n\t\t\t\tret.push(\n\t\t\t\t\tpadZeros( value.getMilliseconds(), 3 ).substr( 0, clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"z\":\n\t\t\t\t// Time zone offset, no leading zero\n\t\t\tcase \"zz\":\n\t\t\t\t// Time zone offset with leading zero\n\t\t\t\thour = value.getTimezoneOffset() / 60;\n\t\t\t\tret.push(\n\t\t\t\t\t( hour <= 0 ? \"+\" : \"-\" ) + padZeros( Math.floor(Math.abs(hour)), clength )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"zzz\":\n\t\t\t\t// Time zone offset with leading zero\n\t\t\t\thour = value.getTimezoneOffset() / 60;\n\t\t\t\tret.push(\n\t\t\t\t\t( hour <= 0 ? \"+\" : \"-\" ) + padZeros( Math.floor(Math.abs(hour)), 2 ) +\n\t\t\t\t\t// Hard coded \":\" separator, rather than using cal.TimeSeparator\n\t\t\t\t\t// Repeated here for consistency, plus \":\" was already assumed in date parsing.\n\t\t\t\t\t\":\" + padZeros( Math.abs(value.getTimezoneOffset() % 60), 2 )\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"g\":\n\t\t\tcase \"gg\":\n\t\t\t\tif ( cal.eras ) {\n\t\t\t\t\tret.push(\n\t\t\t\t\t\tcal.eras[ getEra(value, eras) ].name\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\tcase \"/\":\n\t\t\tret.push( cal[\"/\"] );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow \"Invalid date format pattern \\'\" + current + \"\\'.\";\n\t\t}\n\t}\n\treturn ret.join( \"\" );\n};\n\n// formatNumber\n(function() {\n\tvar expandNumber;\n\n\texpandNumber = function( number, precision, formatInfo ) {\n\t\tvar groupSizes = formatInfo.groupSizes,\n\t\t\tcurSize = groupSizes[ 0 ],\n\t\t\tcurGroupIndex = 1,\n\t\t\tfactor = Math.pow( 10, precision ),\n\t\t\trounded = Math.round( number * factor ) / factor;\n\n\t\tif ( !isFinite(rounded) ) {\n\t\t\trounded = number;\n\t\t}\n\t\tnumber = rounded;\n\n\t\tvar numberString = number+\"\",\n\t\t\tright = \"\",\n\t\t\tsplit = numberString.split( /e/i ),\n\t\t\texponent = split.length > 1 ? parseInt( split[1], 10 ) : 0;\n\t\tnumberString = split[ 0 ];\n\t\tsplit = numberString.split( \".\" );\n\t\tnumberString = split[ 0 ];\n\t\tright = split.length > 1 ? split[ 1 ] : \"\";\n\n\t\tvar l;\n\t\tif ( exponent > 0 ) {\n\t\t\tright = zeroPad( right, exponent, false );\n\t\t\tnumberString += right.slice( 0, exponent );\n\t\t\tright = right.substr( exponent );\n\t\t}\n\t\telse if ( exponent < 0 ) {\n\t\t\texponent = -exponent;\n\t\t\tnumberString = zeroPad( numberString, exponent + 1, true );\n\t\t\tright = numberString.slice( -exponent, numberString.length ) + right;\n\t\t\tnumberString = numberString.slice( 0, -exponent );\n\t\t}\n\n\t\tif ( precision > 0 ) {\n\t\t\tright = formatInfo[ \".\" ] +\n\t\t\t\t( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) );\n\t\t}\n\t\telse {\n\t\t\tright = \"\";\n\t\t}\n\n\t\tvar stringIndex = numberString.length - 1,\n\t\t\tsep = formatInfo[ \",\" ],\n\t\t\tret = \"\";\n\n\t\twhile ( stringIndex >= 0 ) {\n\t\t\tif ( curSize === 0 || curSize > stringIndex ) {\n\t\t\t\treturn numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right );\n\t\t\t}\n\t\t\tret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : \"\" );\n\n\t\t\tstringIndex -= curSize;\n\n\t\t\tif ( curGroupIndex < groupSizes.length ) {\n\t\t\t\tcurSize = groupSizes[ curGroupIndex ];\n\t\t\t\tcurGroupIndex++;\n\t\t\t}\n\t\t}\n\n\t\treturn numberString.slice( 0, stringIndex + 1 ) + sep + ret + right;\n\t};\n\n\tformatNumber = function( value, format, culture ) {\n\t\tif ( !isFinite(value) ) {\n\t\t\tif ( value === Infinity ) {\n\t\t\t\treturn culture.numberFormat.positiveInfinity;\n\t\t\t}\n\t\t\tif ( value === -Infinity ) {\n\t\t\t\treturn culture.numberFormat.negativeInfinity;\n\t\t\t}\n\t\t\treturn culture.numberFormat.NaN;\n\t\t}\n\t\tif ( !format || format === \"i\" ) {\n\t\t\treturn culture.name.length ? value.toLocaleString() : value.toString();\n\t\t}\n\t\tformat = format || \"D\";\n\n\t\tvar nf = culture.numberFormat,\n\t\t\tnumber = Math.abs( value ),\n\t\t\tprecision = -1,\n\t\t\tpattern;\n\t\tif ( format.length > 1 ) precision = parseInt( format.slice(1), 10 );\n\n\t\tvar current = format.charAt( 0 ).toUpperCase(),\n\t\t\tformatInfo;\n\n\t\tswitch ( current ) {\n\t\t\tcase \"D\":\n\t\t\t\tpattern = \"n\";\n\t\t\t\tnumber = truncate( number );\n\t\t\t\tif ( precision !== -1 ) {\n\t\t\t\t\tnumber = zeroPad( \"\" + number, precision, true );\n\t\t\t\t}\n\t\t\t\tif ( value < 0 ) number = \"-\" + number;\n\t\t\t\tbreak;\n\t\t\tcase \"N\":\n\t\t\t\tformatInfo = nf;\n\t\t\t\t/* falls through */\n\t\t\tcase \"C\":\n\t\t\t\tformatInfo = formatInfo || nf.currency;\n\t\t\t\t/* falls through */\n\t\t\tcase \"P\":\n\t\t\t\tformatInfo = formatInfo || nf.percent;\n\t\t\t\tpattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || \"n\" );\n\t\t\t\tif ( precision === -1 ) precision = formatInfo.decimals;\n\t\t\t\tnumber = expandNumber( number * (current === \"P\" ? 100 : 1), precision, formatInfo );\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow \"Bad number format specifier: \" + current;\n\t\t}\n\n\t\tvar patternParts = /n|\\$|-|%/g,\n\t\t\tret = \"\";\n\t\tfor ( ; ; ) {\n\t\t\tvar index = patternParts.lastIndex,\n\t\t\t\tar = patternParts.exec( pattern );\n\n\t\t\tret += pattern.slice( index, ar ? ar.index : pattern.length );\n\n\t\t\tif ( !ar ) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tswitch ( ar[0] ) {\n\t\t\t\tcase \"n\":\n\t\t\t\t\tret += number;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"$\":\n\t\t\t\t\tret += nf.currency.symbol;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"-\":\n\t\t\t\t\t// don't make 0 negative\n\t\t\t\t\tif ( /[1-9]/.test(number) ) {\n\t\t\t\t\t\tret += nf[ \"-\" ];\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"%\":\n\t\t\t\t\tret += nf.percent.symbol;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t};\n\n}());\n\ngetTokenRegExp = function() {\n\t// regular expression for matching date and time tokens in format strings.\n\treturn (/\\/|dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|y|hh|h|HH|H|mm|m|ss|s|tt|t|fff|ff|f|zzz|zz|z|gg|g/g);\n};\n\ngetEra = function( date, eras ) {\n\tif ( !eras ) return 0;\n\tvar start, ticks = date.getTime();\n\tfor ( var i = 0, l = eras.length; i < l; i++ ) {\n\t\tstart = eras[ i ].start;\n\t\tif ( start === null || ticks >= start ) {\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn 0;\n};\n\ngetEraYear = function( date, cal, era, sortable ) {\n\tvar year = date.getFullYear();\n\tif ( !sortable && cal.eras ) {\n\t\t// convert normal gregorian year to era-shifted gregorian\n\t\t// year by subtracting the era offset\n\t\tyear -= cal.eras[ era ].offset;\n\t}\n\treturn year;\n};\n\n// parseExact\n(function() {\n\tvar expandYear,\n\t\tgetDayIndex,\n\t\tgetMonthIndex,\n\t\tgetParseRegExp,\n\t\toutOfRange,\n\t\ttoUpper,\n\t\ttoUpperArray;\n\n\texpandYear = function( cal, year ) {\n\t\t// expands 2-digit year into 4 digits.\n\t\tif ( year < 100 ) {\n\t\t\tvar now = new Date(),\n\t\t\t\tera = getEra( now ),\n\t\t\t\tcurr = getEraYear( now, cal, era ),\n\t\t\t\ttwoDigitYearMax = cal.twoDigitYearMax;\n\t\t\ttwoDigitYearMax = typeof twoDigitYearMax === \"string\" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax;\n\t\t\tyear += curr - ( curr % 100 );\n\t\t\tif ( year > twoDigitYearMax ) {\n\t\t\t\tyear -= 100;\n\t\t\t}\n\t\t}\n\t\treturn year;\n\t};\n\n\tgetDayIndex = function\t( cal, value, abbr ) {\n\t\tvar ret,\n\t\t\tdays = cal.days,\n\t\t\tupperDays = cal._upperDays;\n\t\tif ( !upperDays ) {\n\t\t\tcal._upperDays = upperDays = [\n\t\t\t\ttoUpperArray( days.names ),\n\t\t\t\ttoUpperArray( days.namesAbbr ),\n\t\t\t\ttoUpperArray( days.namesShort )\n\t\t\t];\n\t\t}\n\t\tvalue = toUpper( value );\n\t\tif ( abbr ) {\n\t\t\tret = arrayIndexOf( upperDays[1], value );\n\t\t\tif ( ret === -1 ) {\n\t\t\t\tret = arrayIndexOf( upperDays[2], value );\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tret = arrayIndexOf( upperDays[0], value );\n\t\t}\n\t\treturn ret;\n\t};\n\n\tgetMonthIndex = function( cal, value, abbr ) {\n\t\tvar months = cal.months,\n\t\t\tmonthsGen = cal.monthsGenitive || cal.months,\n\t\t\tupperMonths = cal._upperMonths,\n\t\t\tupperMonthsGen = cal._upperMonthsGen;\n\t\tif ( !upperMonths ) {\n\t\t\tcal._upperMonths = upperMonths = [\n\t\t\t\ttoUpperArray( months.names ),\n\t\t\t\ttoUpperArray( months.namesAbbr )\n\t\t\t];\n\t\t\tcal._upperMonthsGen = upperMonthsGen = [\n\t\t\t\ttoUpperArray( monthsGen.names ),\n\t\t\t\ttoUpperArray( monthsGen.namesAbbr )\n\t\t\t];\n\t\t}\n\t\tvalue = toUpper( value );\n\t\tvar i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value );\n\t\tif ( i < 0 ) {\n\t\t\ti = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value );\n\t\t}\n\t\treturn i;\n\t};\n\n\tgetParseRegExp = function( cal, format ) {\n\t\t// converts a format string into a regular expression with groups that\n\t\t// can be used to extract date fields from a date string.\n\t\t// check for a cached parse regex.\n\t\tvar re = cal._parseRegExp;\n\t\tif ( !re ) {\n\t\t\tcal._parseRegExp = re = {};\n\t\t}\n\t\telse {\n\t\t\tvar reFormat = re[ format ];\n\t\t\tif ( reFormat ) {\n\t\t\t\treturn reFormat;\n\t\t\t}\n\t\t}\n\n\t\t// expand single digit formats, then escape regular expression characters.\n\t\tvar expFormat = expandFormat( cal, format ).replace( /([\\^\\$\\.\\*\\+\\?\\|\\[\\]\\(\\)\\{\\}])/g, \"\\\\\\\\$1\" ),\n\t\t\tregexp = [ \"^\" ],\n\t\t\tgroups = [],\n\t\t\tindex = 0,\n\t\t\tquoteCount = 0,\n\t\t\ttokenRegExp = getTokenRegExp(),\n\t\t\tmatch;\n\n\t\t// iterate through each date token found.\n\t\twhile ( (match = tokenRegExp.exec(expFormat)) !== null ) {\n\t\t\tvar preMatch = expFormat.slice( index, match.index );\n\t\t\tindex = tokenRegExp.lastIndex;\n\n\t\t\t// don't replace any matches that occur inside a string literal.\n\t\t\tquoteCount += appendPreOrPostMatch( preMatch, regexp );\n\t\t\tif ( quoteCount % 2 ) {\n\t\t\t\tregexp.push( match[0] );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// add a regex group for the token.\n\t\t\tvar m = match[ 0 ],\n\t\t\t\tlen = m.length,\n\t\t\t\tadd;\n\t\t\tswitch ( m ) {\n\t\t\t\tcase \"dddd\": case \"ddd\":\n\t\t\t\tcase \"MMMM\": case \"MMM\":\n\t\t\t\tcase \"gg\": case \"g\":\n\t\t\t\t\tadd = \"(\\\\D+)\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tt\": case \"t\":\n\t\t\t\t\tadd = \"(\\\\D*)\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"yyyy\":\n\t\t\t\tcase \"fff\":\n\t\t\t\tcase \"ff\":\n\t\t\t\tcase \"f\":\n\t\t\t\t\tadd = \"(\\\\d{\" + len + \"})\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"dd\": case \"d\":\n\t\t\t\tcase \"MM\": case \"M\":\n\t\t\t\tcase \"yy\": case \"y\":\n\t\t\t\tcase \"HH\": case \"H\":\n\t\t\t\tcase \"hh\": case \"h\":\n\t\t\t\tcase \"mm\": case \"m\":\n\t\t\t\tcase \"ss\": case \"s\":\n\t\t\t\t\tadd = \"(\\\\d\\\\d?)\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"zzz\":\n\t\t\t\t\tadd = \"([+-]?\\\\d\\\\d?:\\\\d{2})\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"zz\": case \"z\":\n\t\t\t\t\tadd = \"([+-]?\\\\d\\\\d?)\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"/\":\n\t\t\t\t\tadd = \"(\\\\/)\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthrow \"Invalid date format pattern \\'\" + m + \"\\'.\";\n\t\t\t}\n\t\t\tif ( add ) {\n\t\t\t\tregexp.push( add );\n\t\t\t}\n\t\t\tgroups.push( match[0] );\n\t\t}\n\t\tappendPreOrPostMatch( expFormat.slice(index), regexp );\n\t\tregexp.push( \"$\" );\n\n\t\t// allow whitespace to differ when matching formats.\n\t\tvar regexpStr = regexp.join( \"\" ).replace( /\\s+/g, \"\\\\s+\" ),\n\t\t\tparseRegExp = { \"regExp\": regexpStr, \"groups\": groups };\n\n\t\t// cache the regex for this format.\n\t\treturn re[ format ] = parseRegExp;\n\t};\n\n\toutOfRange = function( value, low, high ) {\n\t\treturn value < low || value > high;\n\t};\n\n\ttoUpper = function( value ) {\n\t\t// \"he-IL\" has non-breaking space in weekday names.\n\t\treturn value.split( \"\\u00A0\" ).join( \" \" ).toUpperCase();\n\t};\n\n\ttoUpperArray = function( arr ) {\n\t\tvar results = [];\n\t\tfor ( var i = 0, l = arr.length; i < l; i++ ) {\n\t\t\tresults[ i ] = toUpper( arr[i] );\n\t\t}\n\t\treturn results;\n\t};\n\n\tparseExact = function( value, format, culture ) {\n\t\t// try to parse the date string by matching against the format string\n\t\t// while using the specified culture for date field names.\n\t\tvalue = trim( value );\n\t\tvar cal = culture.calendar,\n\t\t\t// convert date formats into regular expressions with groupings.\n\t\t\t// use the regexp to determine the input format and extract the date fields.\n\t\t\tparseInfo = getParseRegExp( cal, format ),\n\t\t\tmatch = new RegExp( parseInfo.regExp ).exec( value );\n\t\tif ( match === null ) {\n\t\t\treturn null;\n\t\t}\n\t\t// found a date format that matches the input.\n\t\tvar groups = parseInfo.groups,\n\t\t\tera = null, year = null, month = null, date = null, weekDay = null,\n\t\t\thour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null,\n\t\t\tpmHour = false;\n\t\t// iterate the format groups to extract and set the date fields.\n\t\tfor ( var j = 0, jl = groups.length; j < jl; j++ ) {\n\t\t\tvar matchGroup = match[ j + 1 ];\n\t\t\tif ( matchGroup ) {\n\t\t\t\tvar current = groups[ j ],\n\t\t\t\t\tclength = current.length,\n\t\t\t\t\tmatchInt = parseInt( matchGroup, 10 );\n\t\t\t\tswitch ( current ) {\n\t\t\t\t\tcase \"dd\": case \"d\":\n\t\t\t\t\t\t// Day of month.\n\t\t\t\t\t\tdate = matchInt;\n\t\t\t\t\t\t// check that date is generally in valid range, also checking overflow below.\n\t\t\t\t\t\tif ( outOfRange(date, 1, 31) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"MMM\": case \"MMMM\":\n\t\t\t\t\t\tmonth = getMonthIndex( cal, matchGroup, clength === 3 );\n\t\t\t\t\t\tif ( outOfRange(month, 0, 11) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"M\": case \"MM\":\n\t\t\t\t\t\t// Month.\n\t\t\t\t\t\tmonth = matchInt - 1;\n\t\t\t\t\t\tif ( outOfRange(month, 0, 11) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"y\": case \"yy\":\n\t\t\t\t\tcase \"yyyy\":\n\t\t\t\t\t\tyear = clength < 4 ? expandYear( cal, matchInt ) : matchInt;\n\t\t\t\t\t\tif ( outOfRange(year, 0, 9999) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"h\": case \"hh\":\n\t\t\t\t\t\t// Hours (12-hour clock).\n\t\t\t\t\t\thour = matchInt;\n\t\t\t\t\t\tif ( hour === 12 ) hour = 0;\n\t\t\t\t\t\tif ( outOfRange(hour, 0, 11) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"H\": case \"HH\":\n\t\t\t\t\t\t// Hours (24-hour clock).\n\t\t\t\t\t\thour = matchInt;\n\t\t\t\t\t\tif ( outOfRange(hour, 0, 23) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"m\": case \"mm\":\n\t\t\t\t\t\t// Minutes.\n\t\t\t\t\t\tmin = matchInt;\n\t\t\t\t\t\tif ( outOfRange(min, 0, 59) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"s\": case \"ss\":\n\t\t\t\t\t\t// Seconds.\n\t\t\t\t\t\tsec = matchInt;\n\t\t\t\t\t\tif ( outOfRange(sec, 0, 59) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"tt\": case \"t\":\n\t\t\t\t\t\t// AM/PM designator.\n\t\t\t\t\t\t// see if it is standard, upper, or lower case PM. If not, ensure it is at least one of\n\t\t\t\t\t\t// the AM tokens. If not, fail the parse for this format.\n\t\t\t\t\t\tpmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] );\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t!pmHour && (\n\t\t\t\t\t\t\t\t!cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] )\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"f\":\n\t\t\t\t\t\t// Deciseconds.\n\t\t\t\t\tcase \"ff\":\n\t\t\t\t\t\t// Centiseconds.\n\t\t\t\t\tcase \"fff\":\n\t\t\t\t\t\t// Milliseconds.\n\t\t\t\t\t\tmsec = matchInt * Math.pow( 10, 3 - clength );\n\t\t\t\t\t\tif ( outOfRange(msec, 0, 999) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"ddd\":\n\t\t\t\t\t\t// Day of week.\n\t\t\t\t\tcase \"dddd\":\n\t\t\t\t\t\t// Day of week.\n\t\t\t\t\t\tweekDay = getDayIndex( cal, matchGroup, clength === 3 );\n\t\t\t\t\t\tif ( outOfRange(weekDay, 0, 6) ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"zzz\":\n\t\t\t\t\t\t// Time zone offset in +/- hours:min.\n\t\t\t\t\t\tvar offsets = matchGroup.split( /:/ );\n\t\t\t\t\t\tif ( offsets.length !== 2 ) return null;\n\t\t\t\t\t\thourOffset = parseInt( offsets[0], 10 );\n\t\t\t\t\t\tif ( outOfRange(hourOffset, -12, 13) ) return null;\n\t\t\t\t\t\tvar minOffset = parseInt( offsets[1], 10 );\n\t\t\t\t\t\tif ( outOfRange(minOffset, 0, 59) ) return null;\n\t\t\t\t\t\ttzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, \"-\") ? -minOffset : minOffset );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"z\": case \"zz\":\n\t\t\t\t\t\t// Time zone offset in +/- hours.\n\t\t\t\t\t\thourOffset = matchInt;\n\t\t\t\t\t\tif ( outOfRange(hourOffset, -12, 13) ) return null;\n\t\t\t\t\t\ttzMinOffset = hourOffset * 60;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"g\": case \"gg\":\n\t\t\t\t\t\tvar eraName = matchGroup;\n\t\t\t\t\t\tif ( !eraName || !cal.eras ) return null;\n\t\t\t\t\t\teraName = trim( eraName.toLowerCase() );\n\t\t\t\t\t\tfor ( var i = 0, l = cal.eras.length; i < l; i++ ) {\n\t\t\t\t\t\t\tif ( eraName === cal.eras[i].name.toLowerCase() ) {\n\t\t\t\t\t\t\t\tera = i;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// could not find an era with that name\n\t\t\t\t\t\tif ( era === null ) return null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tvar result = new Date(), defaultYear, convert = cal.convert;\n\t\tdefaultYear = convert ? convert.fromGregorian( result )[ 0 ] : result.getFullYear();\n\t\tif ( year === null ) {\n\t\t\tyear = defaultYear;\n\t\t}\n\t\telse if ( cal.eras ) {\n\t\t\t// year must be shifted to normal gregorian year\n\t\t\t// but not if year was not specified, its already normal gregorian\n\t\t\t// per the main if clause above.\n\t\t\tyear += cal.eras[( era || 0 )].offset;\n\t\t}\n\t\t// set default day and month to 1 and January, so if unspecified, these are the defaults\n\t\t// instead of the current day/month.\n\t\tif ( month === null ) {\n\t\t\tmonth = 0;\n\t\t}\n\t\tif ( date === null ) {\n\t\t\tdate = 1;\n\t\t}\n\t\t// now have year, month, and date, but in the culture's calendar.\n\t\t// convert to gregorian if necessary\n\t\tif ( convert ) {\n\t\t\tresult = convert.toGregorian( year, month, date );\n\t\t\t// conversion failed, must be an invalid match\n\t\t\tif ( result === null ) return null;\n\t\t}\n\t\telse {\n\t\t\t// have to set year, month and date together to avoid overflow based on current date.\n\t\t\tresult.setFullYear( year, month, date );\n\t\t\t// check to see if date overflowed for specified month (only checked 1-31 above).\n\t\t\tif ( result.getDate() !== date ) return null;\n\t\t\t// invalid day of week.\n\t\t\tif ( weekDay !== null && result.getDay() !== weekDay ) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\t// if pm designator token was found make sure the hours fit the 24-hour clock.\n\t\tif ( pmHour && hour < 12 ) {\n\t\t\thour += 12;\n\t\t}\n\t\tresult.setHours( hour, min, sec, msec );\n\t\tif ( tzMinOffset !== null ) {\n\t\t\t// adjust timezone to utc before applying local offset.\n\t\t\tvar adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() );\n\t\t\t// Safari limits hours and minutes to the range of -127 to 127.  We need to use setHours\n\t\t\t// to ensure both these fields will not exceed this range.\tadjustedMin will range\n\t\t\t// somewhere between -1440 and 1500, so we only need to split this into hours.\n\t\t\tresult.setHours( result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60 );\n\t\t}\n\t\treturn result;\n\t};\n}());\n\nparseNegativePattern = function( value, nf, negativePattern ) {\n\tvar neg = nf[ \"-\" ],\n\t\tpos = nf[ \"+\" ],\n\t\tret;\n\tswitch ( negativePattern ) {\n\t\tcase \"n -\":\n\t\t\tneg = \" \" + neg;\n\t\t\tpos = \" \" + pos;\n\t\t\t/* falls through */\n\t\tcase \"n-\":\n\t\t\tif ( endsWith(value, neg) ) {\n\t\t\t\tret = [ \"-\", value.substr(0, value.length - neg.length) ];\n\t\t\t}\n\t\t\telse if ( endsWith(value, pos) ) {\n\t\t\t\tret = [ \"+\", value.substr(0, value.length - pos.length) ];\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"- n\":\n\t\t\tneg += \" \";\n\t\t\tpos += \" \";\n\t\t\t/* falls through */\n\t\tcase \"-n\":\n\t\t\tif ( startsWith(value, neg) ) {\n\t\t\t\tret = [ \"-\", value.substr(neg.length) ];\n\t\t\t}\n\t\t\telse if ( startsWith(value, pos) ) {\n\t\t\t\tret = [ \"+\", value.substr(pos.length) ];\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"(n)\":\n\t\t\tif ( startsWith(value, \"(\") && endsWith(value, \")\") ) {\n\t\t\t\tret = [ \"-\", value.substr(1, value.length - 2) ];\n\t\t\t}\n\t\t\tbreak;\n\t}\n\treturn ret || [ \"\", value ];\n};\n\n//\n// public instance functions\n//\n\nGlobalize.prototype.findClosestCulture = function( cultureSelector ) {\n\treturn Globalize.findClosestCulture.call( this, cultureSelector );\n};\n\nGlobalize.prototype.format = function( value, format, cultureSelector ) {\n\treturn Globalize.format.call( this, value, format, cultureSelector );\n};\n\nGlobalize.prototype.localize = function( key, cultureSelector ) {\n\treturn Globalize.localize.call( this, key, cultureSelector );\n};\n\nGlobalize.prototype.parseInt = function( value, radix, cultureSelector ) {\n\treturn Globalize.parseInt.call( this, value, radix, cultureSelector );\n};\n\nGlobalize.prototype.parseFloat = function( value, radix, cultureSelector ) {\n\treturn Globalize.parseFloat.call( this, value, radix, cultureSelector );\n};\n\nGlobalize.prototype.culture = function( cultureSelector ) {\n\treturn Globalize.culture.call( this, cultureSelector );\n};\n\n//\n// public singleton functions\n//\n\nGlobalize.addCultureInfo = function( cultureName, baseCultureName, info ) {\n\n\tvar base = {},\n\t\tisNew = false;\n\n\tif ( typeof cultureName !== \"string\" ) {\n\t\t// cultureName argument is optional string. If not specified, assume info is first\n\t\t// and only argument. Specified info deep-extends current culture.\n\t\tinfo = cultureName;\n\t\tcultureName = this.culture().name;\n\t\tbase = this.cultures[ cultureName ];\n\t} else if ( typeof baseCultureName !== \"string\" ) {\n\t\t// baseCultureName argument is optional string. If not specified, assume info is second\n\t\t// argument. Specified info deep-extends specified culture.\n\t\t// If specified culture does not exist, create by deep-extending default\n\t\tinfo = baseCultureName;\n\t\tisNew = ( this.cultures[ cultureName ] == null );\n\t\tbase = this.cultures[ cultureName ] || this.cultures[ \"default\" ];\n\t} else {\n\t\t// cultureName and baseCultureName specified. Assume a new culture is being created\n\t\t// by deep-extending an specified base culture\n\t\tisNew = true;\n\t\tbase = this.cultures[ baseCultureName ];\n\t}\n\n\tthis.cultures[ cultureName ] = extend(true, {},\n\t\tbase,\n\t\tinfo\n\t);\n\t// Make the standard calendar the current culture if it's a new culture\n\tif ( isNew ) {\n\t\tthis.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard;\n\t}\n};\n\nGlobalize.findClosestCulture = function( name ) {\n\tvar match;\n\tif ( !name ) {\n\t\treturn this.findClosestCulture( this.cultureSelector ) || this.cultures[ \"default\" ];\n\t}\n\tif ( typeof name === \"string\" ) {\n\t\tname = name.split( \",\" );\n\t}\n\tif ( isArray(name) ) {\n\t\tvar lang,\n\t\t\tcultures = this.cultures,\n\t\t\tlist = name,\n\t\t\ti, l = list.length,\n\t\t\tprioritized = [];\n\t\tfor ( i = 0; i < l; i++ ) {\n\t\t\tname = trim( list[i] );\n\t\t\tvar pri, parts = name.split( \";\" );\n\t\t\tlang = trim( parts[0] );\n\t\t\tif ( parts.length === 1 ) {\n\t\t\t\tpri = 1;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tname = trim( parts[1] );\n\t\t\t\tif ( name.indexOf(\"q=\") === 0 ) {\n\t\t\t\t\tname = name.substr( 2 );\n\t\t\t\t\tpri = parseFloat( name );\n\t\t\t\t\tpri = isNaN( pri ) ? 0 : pri;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tpri = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tprioritized.push({ lang: lang, pri: pri });\n\t\t}\n\t\tprioritized.sort(function( a, b ) {\n\t\t\tif ( a.pri < b.pri ) {\n\t\t\t\treturn 1;\n\t\t\t} else if ( a.pri > b.pri ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t});\n\t\t// exact match\n\t\tfor ( i = 0; i < l; i++ ) {\n\t\t\tlang = prioritized[ i ].lang;\n\t\t\tmatch = cultures[ lang ];\n\t\t\tif ( match ) {\n\t\t\t\treturn match;\n\t\t\t}\n\t\t}\n\n\t\t// neutral language match\n\t\tfor ( i = 0; i < l; i++ ) {\n\t\t\tlang = prioritized[ i ].lang;\n\t\t\tdo {\n\t\t\t\tvar index = lang.lastIndexOf( \"-\" );\n\t\t\t\tif ( index === -1 ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// strip off the last part. e.g. en-US => en\n\t\t\t\tlang = lang.substr( 0, index );\n\t\t\t\tmatch = cultures[ lang ];\n\t\t\t\tif ( match ) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile ( 1 );\n\t\t}\n\n\t\t// last resort: match first culture using that language\n\t\tfor ( i = 0; i < l; i++ ) {\n\t\t\tlang = prioritized[ i ].lang;\n\t\t\tfor ( var cultureKey in cultures ) {\n\t\t\t\tvar culture = cultures[ cultureKey ];\n\t\t\t\tif ( culture.language == lang ) {\n\t\t\t\t\treturn culture;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse if ( typeof name === \"object\" ) {\n\t\treturn name;\n\t}\n\treturn match || null;\n};\n\nGlobalize.format = function( value, format, cultureSelector ) {\n\tvar culture = this.findClosestCulture( cultureSelector );\n\tif ( value instanceof Date ) {\n\t\tvalue = formatDate( value, format, culture );\n\t}\n\telse if ( typeof value === \"number\" ) {\n\t\tvalue = formatNumber( value, format, culture );\n\t}\n\treturn value;\n};\n\nGlobalize.localize = function( key, cultureSelector ) {\n\treturn this.findClosestCulture( cultureSelector ).messages[ key ] ||\n\t\tthis.cultures[ \"default\" ].messages[ key ];\n};\n\nGlobalize.parseDate = function( value, formats, culture ) {\n\tculture = this.findClosestCulture( culture );\n\n\tvar date, prop, patterns;\n\tif ( formats ) {\n\t\tif ( typeof formats === \"string\" ) {\n\t\t\tformats = [ formats ];\n\t\t}\n\t\tif ( formats.length ) {\n\t\t\tfor ( var i = 0, l = formats.length; i < l; i++ ) {\n\t\t\t\tvar format = formats[ i ];\n\t\t\t\tif ( format ) {\n\t\t\t\t\tdate = parseExact( value, format, culture );\n\t\t\t\t\tif ( date ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tpatterns = culture.calendar.patterns;\n\t\tfor ( prop in patterns ) {\n\t\t\tdate = parseExact( value, patterns[prop], culture );\n\t\t\tif ( date ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn date || null;\n};\n\nGlobalize.parseInt = function( value, radix, cultureSelector ) {\n\treturn truncate( Globalize.parseFloat(value, radix, cultureSelector) );\n};\n\nGlobalize.parseFloat = function( value, radix, cultureSelector ) {\n\t// radix argument is optional\n\tif ( typeof radix !== \"number\" ) {\n\t\tcultureSelector = radix;\n\t\tradix = 10;\n\t}\n\n\tvar culture = this.findClosestCulture( cultureSelector );\n\tvar ret = NaN,\n\t\tnf = culture.numberFormat;\n\n\tif ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) {\n\t\t// remove currency symbol\n\t\tvalue = value.replace( culture.numberFormat.currency.symbol, \"\" );\n\t\t// replace decimal seperator\n\t\tvalue = value.replace( culture.numberFormat.currency[\".\"], culture.numberFormat[\".\"] );\n\t}\n\n\t//Remove percentage character from number string before parsing\n\tif ( value.indexOf(culture.numberFormat.percent.symbol) > -1){\n\t\tvalue = value.replace( culture.numberFormat.percent.symbol, \"\" );\n\t}\n\n\t// remove spaces: leading, trailing and between - and number. Used for negative currency pt-BR\n\tvalue = value.replace( / /g, \"\" );\n\n\t// allow infinity or hexidecimal\n\tif ( regexInfinity.test(value) ) {\n\t\tret = parseFloat( value );\n\t}\n\telse if ( !radix && regexHex.test(value) ) {\n\t\tret = parseInt( value, 16 );\n\t}\n\telse {\n\n\t\t// determine sign and number\n\t\tvar signInfo = parseNegativePattern( value, nf, nf.pattern[0] ),\n\t\t\tsign = signInfo[ 0 ],\n\t\t\tnum = signInfo[ 1 ];\n\n\t\t// #44 - try parsing as \"(n)\"\n\t\tif ( sign === \"\" && nf.pattern[0] !== \"(n)\" ) {\n\t\t\tsignInfo = parseNegativePattern( value, nf, \"(n)\" );\n\t\t\tsign = signInfo[ 0 ];\n\t\t\tnum = signInfo[ 1 ];\n\t\t}\n\n\t\t// try parsing as \"-n\"\n\t\tif ( sign === \"\" && nf.pattern[0] !== \"-n\" ) {\n\t\t\tsignInfo = parseNegativePattern( value, nf, \"-n\" );\n\t\t\tsign = signInfo[ 0 ];\n\t\t\tnum = signInfo[ 1 ];\n\t\t}\n\n\t\tsign = sign || \"+\";\n\n\t\t// determine exponent and number\n\t\tvar exponent,\n\t\t\tintAndFraction,\n\t\t\texponentPos = num.indexOf( \"e\" );\n\t\tif ( exponentPos < 0 ) exponentPos = num.indexOf( \"E\" );\n\t\tif ( exponentPos < 0 ) {\n\t\t\tintAndFraction = num;\n\t\t\texponent = null;\n\t\t}\n\t\telse {\n\t\t\tintAndFraction = num.substr( 0, exponentPos );\n\t\t\texponent = num.substr( exponentPos + 1 );\n\t\t}\n\t\t// determine decimal position\n\t\tvar integer,\n\t\t\tfraction,\n\t\t\tdecSep = nf[ \".\" ],\n\t\t\tdecimalPos = intAndFraction.indexOf( decSep );\n\t\tif ( decimalPos < 0 ) {\n\t\t\tinteger = intAndFraction;\n\t\t\tfraction = null;\n\t\t}\n\t\telse {\n\t\t\tinteger = intAndFraction.substr( 0, decimalPos );\n\t\t\tfraction = intAndFraction.substr( decimalPos + decSep.length );\n\t\t}\n\t\t// handle groups (e.g. 1,000,000)\n\t\tvar groupSep = nf[ \",\" ];\n\t\tinteger = integer.split( groupSep ).join( \"\" );\n\t\tvar altGroupSep = groupSep.replace( /\\u00A0/g, \" \" );\n\t\tif ( groupSep !== altGroupSep ) {\n\t\t\tinteger = integer.split( altGroupSep ).join( \"\" );\n\t\t}\n\t\t// build a natively parsable number string\n\t\tvar p = sign + integer;\n\t\tif ( fraction !== null ) {\n\t\t\tp += \".\" + fraction;\n\t\t}\n\t\tif ( exponent !== null ) {\n\t\t\t// exponent itself may have a number patternd\n\t\t\tvar expSignInfo = parseNegativePattern( exponent, nf, \"-n\" );\n\t\t\tp += \"e\" + ( expSignInfo[0] || \"+\" ) + expSignInfo[ 1 ];\n\t\t}\n\t\tif ( regexParseFloat.test(p) ) {\n\t\t\tret = parseFloat( p );\n\t\t}\n\t}\n\treturn ret;\n};\n\nGlobalize.culture = function( cultureSelector ) {\n\t// setter\n\tif ( typeof cultureSelector !== \"undefined\" ) {\n\t\tthis.cultureSelector = cultureSelector;\n\t}\n\t// getter\n\treturn this.findClosestCulture( cultureSelector ) || this.cultures[ \"default\" ];\n};\n\n}( this ));\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/lib/jquery.event.drag.js",
    "content": "// Source: https://github.com/devongovett/jquery.event.drag/blob/451d90e1a737f49f613d0966082ce67582b0afd1/drag/jquery.event.drag.js\n//\n//\tWarning! Make sure the hijack() is patch to work with any jquery version:\n//\n//   ($.event.dispatch || $.event.handle).call( elem, event );\n//\n\n/*!\njquery.event.drag.js ~ v1.6 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)\nLiscensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt\n*/\n;(function($){ // secure $ jQuery alias\n/*******************************************************************************************/\n// Created: 2008-06-04 | Updated: 2009-04-21\n/*******************************************************************************************/\n// Events: drag, dragstart, dragend\n/*******************************************************************************************/\n\n// jquery method\n$.fn.drag = function( fn1, fn2, fn3 ){\n\tif ( fn2 ) this.bind('dragstart', fn1 ); // 2+ args\n\tif ( fn3 ) this.bind('dragend', fn3 ); // 3 args\n\treturn !fn1 ? this.trigger('drag') // 0 args\n\t\t: this.bind('drag', fn2 ? fn2 : fn1 ); // 1+ args\n\t};\n\n// local refs\nvar $event = $.event, $special = $event.special,\n\n// special event configuration\ndrag = $special.drag = {\n\tnot: ':input', // don't begin to drag on event.targets that match this selector\n\tdistance: 0, // distance dragged before dragstart\n\twhich: 1, // mouse button pressed to start drag sequence\n\tdrop: false, // false to suppress drop events\n\tdragging: false, // hold the active target element\n\tsetup: function( data ){\n\t\tdata = $.extend({\n\t\t\tdistance: drag.distance,\n\t\t\twhich: drag.which,\n\t\t\tnot: drag.not,\n\t\t\tdrop: drag.drop\n\t\t\t}, data || {});\n\t\tdata.distance = squared( data.distance ); //  x² + y² = distance²\n\t\t$event.add( this, \"mousedown\", handler, data );\n\t\tif ( this.attachEvent ) this.attachEvent(\"ondragstart\", dontStart ); // prevent image dragging in IE...\n\t\t},\n\tteardown: function(){\n\t\t$event.remove( this, \"mousedown\", handler );\n\t\tif ( this === drag.dragging ) drag.dragging = drag.proxy = false; // deactivate element\n\t\tselectable( this, true ); // enable text selection\n\t\tif ( this.detachEvent ) this.detachEvent(\"ondragstart\", dontStart ); // prevent image dragging in IE...\n\t\t}\n\t};\n\n// prevent normal event binding...\n$special.dragstart = $special.dragend = { setup:function(){}, teardown:function(){} };\n\n// handle drag-releatd DOM events\nfunction handler ( event ){\n\tvar elem = this, returned, data = event.data || {};\n\t// mousemove or mouseup\n\tif ( data.elem ){\n\t\t// update event properties...\n\t\telem = event.dragTarget = data.elem; // drag source element\n\t\tevent.dragProxy = drag.proxy || elem; // proxy element or source\n\t\tevent.cursorOffsetX = data.pageX - data.left; // mousedown offset\n\t\tevent.cursorOffsetY = data.pageY - data.top; // mousedown offset\n\t\tevent.offsetX = event.pageX - event.cursorOffsetX; // element offset\n\t\tevent.offsetY = event.pageY - event.cursorOffsetY; // element offset\n\t\t}\n\t// mousedown, check some initial props to avoid the switch statement\n\telse if ( drag.dragging || ( data.which>0 && event.which!=data.which ) ||\n\t\t$( event.target ).is( data.not ) ) return;\n\t// handle various events\n\tswitch ( event.type ){\n\t\t// mousedown, left click, event.target is not restricted, init dragging\n\t\tcase 'mousedown':\n\t\t\t$.extend( data, $( elem ).offset(), {\n\t\t\t\telem: elem, target: event.target,\n\t\t\t\tpageX: event.pageX, pageY: event.pageY\n\t\t\t\t}); // store some initial attributes\n\t\t\t$event.add( document, \"mousemove mouseup\", handler, data );\n\t\t\tselectable( elem, false ); // disable text selection\n\t\t\tdrag.dragging = null; // pending state\n\t\t\tbreak; // prevents text selection in safari\n\t\t// mousemove, check distance, start dragging\n\t\tcase !drag.dragging && 'mousemove':\n\t\t\tif ( squared( event.pageX-data.pageX )\n\t\t\t\t+ squared( event.pageY-data.pageY ) //  x² + y² = distance²\n\t\t\t\t< data.distance ) break; // distance tolerance not reached\n\t\t\tevent.target = data.target; // force target from \"mousedown\" event (fix distance issue)\n\t\t\treturned = hijack( event, \"dragstart\", elem ); // trigger \"dragstart\", return proxy element\n\t\t\tif ( returned !== false ){ // \"dragstart\" not rejected\n\t\t\t\tdrag.dragging = elem; // activate element\n\t\t\t\tdrag.proxy = event.dragProxy = $( returned || elem )[0]; // set proxy\n\t\t\t\t}\n\t\t// mousemove, dragging\n\t\tcase 'mousemove':\n\t\t\tif ( drag.dragging ){\n\t\t\t\treturned = hijack( event, \"drag\", elem ); // trigger \"drag\"\n\t\t\t\tif ( data.drop && $special.drop ){ // manage drop events\n\t\t\t\t\t$special.drop.allowed = ( returned !== false ); // prevent drop\n\t\t\t\t\t$special.drop.handler( event ); // \"dropstart\", \"dropend\"\n\t\t\t\t\t}\n\t\t\t\tif ( returned !== false ) break; // \"drag\" not rejected, stop\n\t\t\t\tevent.type = \"mouseup\"; // helps \"drop\" handler behave\n\t\t\t\t}\n\t\t// mouseup, stop dragging\n\t\tcase 'mouseup':\n\t\t\t$event.remove( document, \"mousemove mouseup\", handler ); // remove page events\n\t\t\tif ( drag.dragging ){\n\t\t\t\tif ( data.drop && $special.drop ) $special.drop.handler( event ); // \"drop\"\n\t\t\t\thijack( event, \"dragend\", elem ); // trigger \"dragend\"\n\t\t\t\t}\n\t\t\tselectable( elem, true ); // enable text selection\n\t\t\tdrag.dragging = drag.proxy = data.elem = false; // deactivate element\n\t\t\tbreak;\n\t\t}\n\t};\n\n// set event type to custom value, and handle it\nfunction hijack ( event, type, elem ){\n\tevent.type = type; // force the event type\n\tvar result = ($.event.dispatch || $.event.handle).call( elem, event );\n\treturn result===false ? false : result || event.result;\n\t};\n\n// return the value squared\nfunction squared ( value ){ return Math.pow( value, 2 ); };\n\n// suppress default dragstart IE events...\nfunction dontStart(){ return ( drag.dragging === false ); };\n\n// toggles text selection attributes\nfunction selectable ( elem, bool ){\n\tif ( !elem ) return; // maybe element was removed ?\n\telem = elem.ownerDocument ? elem.ownerDocument : elem;\n\telem.unselectable = bool ? \"off\" : \"on\"; // IE\n\tif ( elem.style ) elem.style.MozUserSelect = bool ? \"\" : \"none\"; // FF\n\t$.event[ bool ? \"remove\" : \"add\" ]( elem, \"selectstart mousedown\", dontStart ); // IE/Opera\n\t};\n\n/*******************************************************************************************/\n})( jQuery ); // confine scope\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/lib/jquery.mousewheel.js",
    "content": "// Source: https://github.com/jquery/jquery-mousewheel/blob/a06ef4e1a127795606642c55e22d4f2945edc061/jquery.mousewheel.js\n\n/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)\n * Licensed under the MIT License (LICENSE.txt).\n *\n * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.\n * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.\n * Thanks to: Seamus Leahy for adding deltaX and deltaY\n *\n * Version: 3.0.6\n *\n * Requires: 1.2.2+\n */\n\n(function($) {\n\nvar types = ['DOMMouseScroll', 'mousewheel'];\n\nif ($.event.fixHooks) {\n    for ( var i=types.length; i; ) {\n        $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;\n    }\n}\n\n$.event.special.mousewheel = {\n    setup: function() {\n        if ( this.addEventListener ) {\n            for ( var i=types.length; i; ) {\n                this.addEventListener( types[--i], handler, false );\n            }\n        } else {\n            this.onmousewheel = handler;\n        }\n    },\n\n    teardown: function() {\n        if ( this.removeEventListener ) {\n            for ( var i=types.length; i; ) {\n                this.removeEventListener( types[--i], handler, false );\n            }\n        } else {\n            this.onmousewheel = null;\n        }\n    }\n};\n\n$.fn.extend({\n    mousewheel: function(fn) {\n        return fn ? this.bind(\"mousewheel\", fn) : this.trigger(\"mousewheel\");\n    },\n\n    unmousewheel: function(fn) {\n        return this.unbind(\"mousewheel\", fn);\n    }\n});\n\n\nfunction handler(event) {\n    var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;\n    event = $.event.fix(orgEvent);\n    event.type = \"mousewheel\";\n\n    // Old school scrollwheel delta\n    if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }\n    if ( orgEvent.detail     ) { delta = -orgEvent.detail/3; }\n\n    // New school multidimensional scroll (touchpads) deltas\n    deltaY = delta;\n\n    // Gecko\n    if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {\n        deltaY = 0;\n        deltaX = -1*delta;\n    }\n\n    // Webkit\n    if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }\n    if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }\n\n    // Add event and delta to the front of the arguments\n    args.unshift(event, delta, deltaX, deltaY);\n\n    return ($.event.dispatch || $.event.handle).apply(this, args);\n}\n\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.canvaswrapper.js",
    "content": "/** ## jquery.flot.canvaswrapper\n\nThis plugin contains the function for creating and manipulating both the canvas\nlayers and svg layers.\n\nThe Canvas object is a wrapper around an HTML5 canvas tag.\nThe constructor Canvas(cls, container) takes as parameters cls,\nthe list of classes to apply to the canvas adnd the containter,\nelement onto which to append the canvas. The canvas operations\ndon't work unless the canvas is attached to the DOM.\n\n### jquery.canvaswrapper.js API functions\n*/\n\n(function($) {\n    var Canvas = function(cls, container) {\n        var element = container.getElementsByClassName(cls)[0];\n\n        if (!element) {\n            element = document.createElement('canvas');\n            element.className = cls;\n            element.style.direction = 'ltr';\n            element.style.position = 'absolute';\n            element.style.left = '0px';\n            element.style.top = '0px';\n\n            container.appendChild(element);\n\n            // If HTML5 Canvas isn't available, throw\n\n            if (!element.getContext) {\n                throw new Error('Canvas is not available.');\n            }\n        }\n\n        this.element = element;\n\n        var context = this.context = element.getContext('2d');\n        this.pixelRatio = $.plot.browser.getPixelRatio(context);\n\n        // Size the canvas to match the internal dimensions of its container\n        var width = $(container).width();\n        var height = $(container).height();\n        this.resize(width, height);\n\n        // Collection of HTML div layers for text overlaid onto the canvas\n\n        this.SVGContainer = null;\n        this.SVG = {};\n\n        // Cache of text fragments and metrics, so we can avoid expensively\n        // re-calculating them when the plot is re-rendered in a loop.\n\n        this._textCache = {};\n    }\n\n    /**\n    - resize(width, height)\n\n     Resizes the canvas to the given dimensions.\n     The width represents the new width of the canvas, meanwhile the height\n     is the new height of the canvas, both of them in pixels.\n    */\n\n    Canvas.prototype.resize = function(width, height) {\n        var minSize = 10;\n        width = width < minSize ? minSize : width;\n        height = height < minSize ? minSize : height;\n\n        var element = this.element,\n            context = this.context,\n            pixelRatio = this.pixelRatio;\n\n        // Resize the canvas, increasing its density based on the display's\n        // pixel ratio; basically giving it more pixels without increasing the\n        // size of its element, to take advantage of the fact that retina\n        // displays have that many more pixels in the same advertised space.\n\n        // Resizing should reset the state (excanvas seems to be buggy though)\n\n        if (this.width !== width) {\n            element.width = width * pixelRatio;\n            element.style.width = width + 'px';\n            this.width = width;\n        }\n\n        if (this.height !== height) {\n            element.height = height * pixelRatio;\n            element.style.height = height + 'px';\n            this.height = height;\n        }\n\n        // Save the context, so we can reset in case we get replotted.  The\n        // restore ensure that we're really back at the initial state, and\n        // should be safe even if we haven't saved the initial state yet.\n\n        context.restore();\n        context.save();\n\n        // Scale the coordinate space to match the display density; so even though we\n        // may have twice as many pixels, we still want lines and other drawing to\n        // appear at the same size; the extra pixels will just make them crisper.\n\n        context.scale(pixelRatio, pixelRatio);\n    };\n\n    /**\n    - clear()\n\n     Clears the entire canvas area, not including any overlaid HTML text\n    */\n    Canvas.prototype.clear = function() {\n        this.context.clearRect(0, 0, this.width, this.height);\n    };\n\n    /**\n    - render()\n\n     Finishes rendering the canvas, including managing the text overlay.\n    */\n    Canvas.prototype.render = function() {\n        var cache = this._textCache;\n\n        // For each text layer, add elements marked as active that haven't\n        // already been rendered, and remove those that are no longer active.\n\n        for (var layerKey in cache) {\n            if (hasOwnProperty.call(cache, layerKey)) {\n                var layer = this.getSVGLayer(layerKey),\n                    layerCache = cache[layerKey];\n\n                var display = layer.style.display;\n                layer.style.display = 'none';\n\n                for (var styleKey in layerCache) {\n                    if (hasOwnProperty.call(layerCache, styleKey)) {\n                        var styleCache = layerCache[styleKey];\n                        for (var key in styleCache) {\n                            if (hasOwnProperty.call(styleCache, key)) {\n                                var val = styleCache[key],\n                                    positions = val.positions;\n\n                                for (var i = 0, position; positions[i]; i++) {\n                                    position = positions[i];\n                                    if (position.active) {\n                                        if (!position.rendered) {\n                                            layer.appendChild(position.element);\n                                            position.rendered = true;\n                                        }\n                                    } else {\n                                        positions.splice(i--, 1);\n                                        if (position.rendered) {\n                                            while (position.element.firstChild) {\n                                                position.element.removeChild(position.element.firstChild);\n                                            }\n                                            position.element.parentNode.removeChild(position.element);\n                                        }\n                                    }\n                                }\n\n                                if (positions.length === 0) {\n                                    if (val.measured) {\n                                        val.measured = false;\n                                    } else {\n                                        delete styleCache[key];\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                layer.style.display = display;\n            }\n        }\n    };\n\n    /**\n    - getSVGLayer(classes)\n\n     Creates (if necessary) and returns the SVG overlay container.\n     The classes string represents the string of space-separated CSS classes\n     used to uniquely identify the text layer. It return the svg-layer div.\n    */\n    Canvas.prototype.getSVGLayer = function(classes) {\n        var layer = this.SVG[classes];\n\n        // Create the SVG layer if it doesn't exist\n\n        if (!layer) {\n            // Create the svg layer container, if it doesn't exist\n\n            var svgElement;\n\n            if (!this.SVGContainer) {\n                this.SVGContainer = document.createElement('div');\n                this.SVGContainer.className = 'flot-svg';\n                this.SVGContainer.style.position = 'absolute';\n                this.SVGContainer.style.top = '0px';\n                this.SVGContainer.style.left = '0px';\n                this.SVGContainer.style.height = '100%';\n                this.SVGContainer.style.width = '100%';\n                this.SVGContainer.style.pointerEvents = 'none';\n                this.element.parentNode.appendChild(this.SVGContainer);\n\n                svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n                svgElement.style.width = '100%';\n                svgElement.style.height = '100%';\n\n                this.SVGContainer.appendChild(svgElement);\n            } else {\n                svgElement = this.SVGContainer.firstChild;\n            }\n\n            layer = document.createElementNS('http://www.w3.org/2000/svg', 'g');\n            layer.setAttribute('class', classes);\n            layer.style.position = 'absolute';\n            layer.style.top = '0px';\n            layer.style.left = '0px';\n            layer.style.bottom = '0px';\n            layer.style.right = '0px';\n            svgElement.appendChild(layer);\n            this.SVG[classes] = layer;\n        }\n\n        return layer;\n    };\n\n    /**\n    - getTextInfo(layer, text, font, angle, width)\n\n     Creates (if necessary) and returns a text info object.\n     The object looks like this:\n     ```js\n     {\n         width //Width of the text's wrapper div.\n         height //Height of the text's wrapper div.\n         element //The HTML div containing the text.\n         positions //Array of positions at which this text is drawn.\n      }\n      ```\n      The positions array contains objects that look like this:\n      ```js\n      {\n         active //Flag indicating whether the text should be visible.\n         rendered //Flag indicating whether the text is currently visible.\n         element //The HTML div containing the text.\n         text //The actual text and is identical with element[0].textContent.\n         x //X coordinate at which to draw the text.\n         y //Y coordinate at which to draw the text.\n      }\n      ```\n      Each position after the first receives a clone of the original element.\n      The idea is that that the width, height, and general 'identity' of the\n      text is constant no matter where it is placed; the placements are a\n      secondary property.\n\n      Canvas maintains a cache of recently-used text info objects; getTextInfo\n      either returns the cached element or creates a new entry.\n\n     The layer parameter is string of space-separated CSS classes uniquely\n     identifying the layer containing this text.\n     Text is the text string to retrieve info for.\n     Font is either a string of space-separated CSS classes or a font-spec object,\n     defining the text's font and style.\n     Angle is the angle at which to rotate the text, in degrees. Angle is currently unused,\n     it will be implemented in the future.\n     The last parameter is the Maximum width of the text before it wraps.\n     The method returns a text info object.\n    */\n    Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {\n        var textStyle, layerCache, styleCache, info;\n\n        // Cast the value to a string, in case we were given a number or such\n\n        text = '' + text;\n\n        // If the font is a font-spec object, generate a CSS font definition\n\n        if (typeof font === 'object') {\n            textStyle = font.style + ' ' + font.variant + ' ' + font.weight + ' ' + font.size + 'px/' + font.lineHeight + 'px ' + font.family;\n        } else {\n            textStyle = font;\n        }\n\n        // Retrieve (or create) the cache for the text's layer and styles\n\n        layerCache = this._textCache[layer];\n\n        if (layerCache == null) {\n            layerCache = this._textCache[layer] = {};\n        }\n\n        styleCache = layerCache[textStyle];\n\n        if (styleCache == null) {\n            styleCache = layerCache[textStyle] = {};\n        }\n\n        var key = generateKey(text);\n        info = styleCache[key];\n\n        // If we can't find a matching element in our cache, create a new one\n\n        if (!info) {\n            var element = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n            if (text.indexOf('<br>') !== -1) {\n                addTspanElements(text, element, -9999);\n            } else {\n                var textNode = document.createTextNode(text);\n                element.appendChild(textNode);\n            }\n\n            element.style.position = 'absolute';\n            element.style.maxWidth = width;\n            element.setAttributeNS(null, 'x', -9999);\n            element.setAttributeNS(null, 'y', -9999);\n\n            if (typeof font === 'object') {\n                element.style.font = textStyle;\n                element.style.fill = font.fill;\n            } else if (typeof font === 'string') {\n                element.setAttribute('class', font);\n            }\n\n            this.getSVGLayer(layer).appendChild(element);\n            var elementRect = element.getBBox();\n\n            info = styleCache[key] = {\n                width: elementRect.width,\n                height: elementRect.height,\n                measured: true,\n                element: element,\n                positions: []\n            };\n\n            //remove elements from dom\n            while (element.firstChild) {\n                element.removeChild(element.firstChild);\n            }\n            element.parentNode.removeChild(element);\n        }\n\n        info.measured = true;\n        return info;\n    };\n\n    function updateTransforms (element, transforms) {\n        element.transform.baseVal.clear();\n        if (transforms) {\n            transforms.forEach(function(t) {\n                element.transform.baseVal.appendItem(t);\n            });\n        }\n    }\n\n    /**\n    - addText (layer, x, y, text, font, angle, width, halign, valign, transforms)\n\n     Adds a text string to the canvas text overlay.\n     The text isn't drawn immediately; it is marked as rendering, which will\n     result in its addition to the canvas on the next render pass.\n\n     The layer is string of space-separated CSS classes uniquely\n     identifying the layer containing this text.\n     X and Y represents the X and Y coordinate at which to draw the text.\n     and text is the string to draw\n    */\n    Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign, transforms) {\n        var info = this.getTextInfo(layer, text, font, angle, width),\n            positions = info.positions;\n\n        // Tweak the div's position to match the text's alignment\n\n        if (halign === 'center') {\n            x -= info.width / 2;\n        } else if (halign === 'right') {\n            x -= info.width;\n        }\n\n        if (valign === 'middle') {\n            y -= info.height / 2;\n        } else if (valign === 'bottom') {\n            y -= info.height;\n        }\n\n        y += 0.75 * info.height;\n\n        // Determine whether this text already exists at this position.\n        // If so, mark it for inclusion in the next render pass.\n\n        for (var i = 0, position; positions[i]; i++) {\n            position = positions[i];\n            if (position.x === x && position.y === y && position.text === text) {\n                position.active = true;\n                // update the transforms\n                updateTransforms(position.element, transforms);\n\n                return;\n            } else if (position.active === false) {\n                position.active = true;\n                position.text = text;\n                if (text.indexOf('<br>') !== -1) {\n                    y -= 0.25 * info.height;\n                    addTspanElements(text, position.element, x);\n                } else {\n                    position.element.textContent = text;\n                }\n                position.element.setAttributeNS(null, 'x', x);\n                position.element.setAttributeNS(null, 'y', y);\n                position.x = x;\n                position.y = y;\n                // update the transforms\n                updateTransforms(position.element, transforms);\n\n                return;\n            }\n        }\n\n        // If the text doesn't exist at this position, create a new entry\n\n        // For the very first position we'll re-use the original element,\n        // while for subsequent ones we'll clone it.\n\n        position = {\n            active: true,\n            rendered: false,\n            element: positions.length ? info.element.cloneNode() : info.element,\n            text: text,\n            x: x,\n            y: y\n        };\n\n        positions.push(position);\n\n        if (text.indexOf('<br>') !== -1) {\n            y -= 0.25 * info.height;\n            addTspanElements(text, position.element, x);\n        } else {\n            position.element.textContent = text;\n        }\n\n        // Move the element to its final position within the container\n        position.element.setAttributeNS(null, 'x', x);\n        position.element.setAttributeNS(null, 'y', y);\n        position.element.style.textAlign = halign;\n        // update the transforms\n        updateTransforms(position.element, transforms);\n    };\n\n    var addTspanElements = function(text, element, x) {\n        var lines = text.split('<br>'),\n            tspan, i, offset;\n\n        for (i = 0; i < lines.length; i++) {\n            if (!element.childNodes[i]) {\n                tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');\n                element.appendChild(tspan);\n            } else {\n                tspan = element.childNodes[i];\n            }\n            tspan.textContent = lines[i];\n            offset = (i === 0 ? 0 : 1) + 'em';\n            tspan.setAttributeNS(null, 'dy', offset);\n            tspan.setAttributeNS(null, 'x', x);\n        }\n    }\n\n    /**\n    - removeText (layer, x, y, text, font, angle)\n\n      The function removes one or more text strings from the canvas text overlay.\n      If no parameters are given, all text within the layer is removed.\n\n      Note that the text is not immediately removed; it is simply marked as\n      inactive, which will result in its removal on the next render pass.\n      This avoids the performance penalty for 'clear and redraw' behavior,\n      where we potentially get rid of all text on a layer, but will likely\n      add back most or all of it later, as when redrawing axes, for example.\n\n      The layer is a string of space-separated CSS classes uniquely\n      identifying the layer containing this text. The following parameter are\n      X and Y coordinate of the text.\n      Text is the string to remove, while the font is either a string of space-separated CSS\n      classes or a font-spec object, defining the text's font and style.\n     */\n    Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {\n        var info, htmlYCoord;\n        if (text == null) {\n            var layerCache = this._textCache[layer];\n            if (layerCache != null) {\n                for (var styleKey in layerCache) {\n                    if (hasOwnProperty.call(layerCache, styleKey)) {\n                        var styleCache = layerCache[styleKey];\n                        for (var key in styleCache) {\n                            if (hasOwnProperty.call(styleCache, key)) {\n                                var positions = styleCache[key].positions;\n                                positions.forEach(function(position) {\n                                    position.active = false;\n                                });\n                            }\n                        }\n                    }\n                }\n            }\n        } else {\n            info = this.getTextInfo(layer, text, font, angle);\n            positions = info.positions;\n            positions.forEach(function(position) {\n                htmlYCoord = y + 0.75 * info.height;\n                if (position.x === x && position.y === htmlYCoord && position.text === text) {\n                    position.active = false;\n                }\n            });\n        }\n    };\n\n    /**\n    - clearCache()\n\n     Clears the cache used to speed up the text size measurements.\n     As an (unfortunate) side effect all text within the text Layer is removed.\n     Use this function before plot.setupGrid() and plot.draw() if the plot just\n     became visible or the styles changed.\n    */\n    Canvas.prototype.clearCache = function() {\n        var cache = this._textCache;\n        for (var layerKey in cache) {\n            if (hasOwnProperty.call(cache, layerKey)) {\n                var layer = this.getSVGLayer(layerKey);\n                while (layer.firstChild) {\n                    layer.removeChild(layer.firstChild);\n                }\n            }\n        };\n\n        this._textCache = {};\n    };\n\n    function generateKey(text) {\n        return text.replace(/0|1|2|3|4|5|6|7|8|9/g, '0');\n    }\n\n    if (!window.Flot) {\n        window.Flot = {};\n    }\n\n    window.Flot.Canvas = Canvas;\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.colorhelpers.js",
    "content": "/* Plugin for jQuery for working with colors.\n *\n * Version 1.1.\n *\n * Inspiration from jQuery color animation plugin by John Resig.\n *\n * Released under the MIT license by Ole Laursen, October 2009.\n *\n * Examples:\n *\n *   $.color.parse(\"#fff\").scale('rgb', 0.25).add('a', -0.5).toString()\n *   var c = $.color.extract($(\"#mydiv\"), 'background-color');\n *   console.log(c.r, c.g, c.b, c.a);\n *   $.color.make(100, 50, 25, 0.4).toString() // returns \"rgba(100,50,25,0.4)\"\n *\n * Note that .scale() and .add() return the same modified object\n * instead of making a new one.\n *\n * V. 1.1: Fix error handling so e.g. parsing an empty string does\n * produce a color rather than just crashing.\n */\n\n(function($) {\n    $.color = {};\n\n    // construct color object with some convenient chainable helpers\n    $.color.make = function (r, g, b, a) {\n        var o = {};\n        o.r = r || 0;\n        o.g = g || 0;\n        o.b = b || 0;\n        o.a = a != null ? a : 1;\n\n        o.add = function (c, d) {\n            for (var i = 0; i < c.length; ++i) {\n                o[c.charAt(i)] += d;\n            }\n\n            return o.normalize();\n        };\n\n        o.scale = function (c, f) {\n            for (var i = 0; i < c.length; ++i) {\n                o[c.charAt(i)] *= f;\n            }\n\n            return o.normalize();\n        };\n\n        o.toString = function () {\n            if (o.a >= 1.0) {\n                return \"rgb(\" + [o.r, o.g, o.b].join(\",\") + \")\";\n            } else {\n                return \"rgba(\" + [o.r, o.g, o.b, o.a].join(\",\") + \")\";\n            }\n        };\n\n        o.normalize = function () {\n            function clamp(min, value, max) {\n                return value < min ? min : (value > max ? max : value);\n            }\n\n            o.r = clamp(0, parseInt(o.r), 255);\n            o.g = clamp(0, parseInt(o.g), 255);\n            o.b = clamp(0, parseInt(o.b), 255);\n            o.a = clamp(0, o.a, 1);\n            return o;\n        };\n\n        o.clone = function () {\n            return $.color.make(o.r, o.b, o.g, o.a);\n        };\n\n        return o.normalize();\n    }\n\n    // extract CSS color property from element, going up in the DOM\n    // if it's \"transparent\"\n    $.color.extract = function (elem, css) {\n        var c;\n\n        do {\n            c = elem.css(css).toLowerCase();\n            // keep going until we find an element that has color, or\n            // we hit the body or root (have no parent)\n            if (c !== '' && c !== 'transparent') {\n                break;\n            }\n\n            elem = elem.parent();\n        } while (elem.length && !$.nodeName(elem.get(0), \"body\"));\n\n        // catch Safari's way of signalling transparent\n        if (c === \"rgba(0, 0, 0, 0)\") {\n            c = \"transparent\";\n        }\n\n        return $.color.parse(c);\n    }\n\n    // parse CSS color string (like \"rgb(10, 32, 43)\" or \"#fff\"),\n    // returns color object, if parsing failed, you get black (0, 0,\n    // 0) out\n    $.color.parse = function (str) {\n        var res, m = $.color.make;\n\n        // Look for rgb(num,num,num)\n        res = /rgb\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*\\)/.exec(str);\n        if (res) {\n            return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));\n        }\n\n        // Look for rgba(num,num,num,num)\n        res = /rgba\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\s*\\)/.exec(str)\n        if (res) {\n            return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));\n        }\n\n        // Look for rgb(num%,num%,num%)\n        res = /rgb\\(\\s*([0-9]+(?:\\.[0-9]+)?)%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)%\\s*\\)/.exec(str);\n        if (res) {\n            return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55);\n        }\n\n        // Look for rgba(num%,num%,num%,num)\n        res = /rgba\\(\\s*([0-9]+(?:\\.[0-9]+)?)%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\s*\\)/.exec(str);\n        if (res) {\n            return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55, parseFloat(res[4]));\n        }\n\n        // Look for #a0b1c2\n        res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str);\n        if (res) {\n            return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));\n        }\n\n        // Look for #fff\n        res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str);\n        if (res) {\n            return m(parseInt(res[1] + res[1], 16), parseInt(res[2] + res[2], 16), parseInt(res[3] + res[3], 16));\n        }\n\n        // Otherwise, we're most likely dealing with a named color\n        var name = $.trim(str).toLowerCase();\n        if (name === \"transparent\") {\n            return m(255, 255, 255, 0);\n        } else {\n            // default to black\n            res = lookupColors[name] || [0, 0, 0];\n            return m(res[0], res[1], res[2]);\n        }\n    }\n\n    var lookupColors = {\n        aqua: [0, 255, 255],\n        azure: [240, 255, 255],\n        beige: [245, 245, 220],\n        black: [0, 0, 0],\n        blue: [0, 0, 255],\n        brown: [165, 42, 42],\n        cyan: [0, 255, 255],\n        darkblue: [0, 0, 139],\n        darkcyan: [0, 139, 139],\n        darkgrey: [169, 169, 169],\n        darkgreen: [0, 100, 0],\n        darkkhaki: [189, 183, 107],\n        darkmagenta: [139, 0, 139],\n        darkolivegreen: [85, 107, 47],\n        darkorange: [255, 140, 0],\n        darkorchid: [153, 50, 204],\n        darkred: [139, 0, 0],\n        darksalmon: [233, 150, 122],\n        darkviolet: [148, 0, 211],\n        fuchsia: [255, 0, 255],\n        gold: [255, 215, 0],\n        green: [0, 128, 0],\n        indigo: [75, 0, 130],\n        khaki: [240, 230, 140],\n        lightblue: [173, 216, 230],\n        lightcyan: [224, 255, 255],\n        lightgreen: [144, 238, 144],\n        lightgrey: [211, 211, 211],\n        lightpink: [255, 182, 193],\n        lightyellow: [255, 255, 224],\n        lime: [0, 255, 0],\n        magenta: [255, 0, 255],\n        maroon: [128, 0, 0],\n        navy: [0, 0, 128],\n        olive: [128, 128, 0],\n        orange: [255, 165, 0],\n        pink: [255, 192, 203],\n        purple: [128, 0, 128],\n        violet: [128, 0, 128],\n        red: [255, 0, 0],\n        silver: [192, 192, 192],\n        white: [255, 255, 255],\n        yellow: [255, 255, 0]\n    };\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.axislabels.js",
    "content": "/*\nAxis label plugin for flot\n\nDerived from:\nAxis Labels Plugin for flot.\nhttp://github.com/markrcote/flot-axislabels\n\nOriginal code is Copyright (c) 2010 Xuan Luo.\nOriginal code was released under the GPLv3 license by Xuan Luo, September 2010.\nOriginal code was rereleased under the MIT license by Xuan Luo, April 2012.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n(function($) {\n    \"use strict\";\n\n    var options = {\n        axisLabels: {\n            show: true\n        }\n    };\n\n    function AxisLabel(axisName, position, padding, placeholder, axisLabel, surface) {\n        this.axisName = axisName;\n        this.position = position;\n        this.padding = padding;\n        this.placeholder = placeholder;\n        this.axisLabel = axisLabel;\n        this.surface = surface;\n        this.width = 0;\n        this.height = 0;\n        this.elem = null;\n    }\n\n    AxisLabel.prototype.calculateSize = function() {\n        var axisId = this.axisName + 'Label',\n            layerId = axisId + 'Layer',\n            className = axisId + ' axisLabels';\n\n        var info = this.surface.getTextInfo(layerId, this.axisLabel, className);\n        this.labelWidth = info.width;\n        this.labelHeight = info.height;\n\n        if (this.position === 'left' || this.position === 'right') {\n            this.width = this.labelHeight + this.padding;\n            this.height = 0;\n        } else {\n            this.width = 0;\n            this.height = this.labelHeight + this.padding;\n        }\n    };\n\n    AxisLabel.prototype.transforms = function(degrees, x, y, svgLayer) {\n        var transforms = [], translate, rotate;\n        if (x !== 0 || y !== 0) {\n            translate = svgLayer.createSVGTransform();\n            translate.setTranslate(x, y);\n            transforms.push(translate);\n        }\n        if (degrees !== 0) {\n            rotate = svgLayer.createSVGTransform();\n            var centerX = Math.round(this.labelWidth / 2),\n                centerY = 0;\n            rotate.setRotate(degrees, centerX, centerY);\n            transforms.push(rotate);\n        }\n\n        return transforms;\n    };\n\n    AxisLabel.prototype.calculateOffsets = function(box) {\n        var offsets = {\n            x: 0,\n            y: 0,\n            degrees: 0\n        };\n        if (this.position === 'bottom') {\n            offsets.x = box.left + box.width / 2 - this.labelWidth / 2;\n            offsets.y = box.top + box.height - this.labelHeight;\n        } else if (this.position === 'top') {\n            offsets.x = box.left + box.width / 2 - this.labelWidth / 2;\n            offsets.y = box.top;\n        } else if (this.position === 'left') {\n            offsets.degrees = -90;\n            offsets.x = box.left - this.labelWidth / 2;\n            offsets.y = box.height / 2 + box.top;\n        } else if (this.position === 'right') {\n            offsets.degrees = 90;\n            offsets.x = box.left + box.width - this.labelWidth / 2;\n            offsets.y = box.height / 2 + box.top;\n        }\n        offsets.x = Math.round(offsets.x);\n        offsets.y = Math.round(offsets.y);\n\n        return offsets;\n    };\n\n    AxisLabel.prototype.cleanup = function() {\n        var axisId = this.axisName + 'Label',\n            layerId = axisId + 'Layer',\n            className = axisId + ' axisLabels';\n        this.surface.removeText(layerId, 0, 0, this.axisLabel, className);\n    };\n\n    AxisLabel.prototype.draw = function(box) {\n        var axisId = this.axisName + 'Label',\n            layerId = axisId + 'Layer',\n            className = axisId + ' axisLabels',\n            offsets = this.calculateOffsets(box),\n            style = {\n                position: 'absolute',\n                bottom: '',\n                right: '',\n                display: 'inline-block',\n                'white-space': 'nowrap'\n            };\n\n        var layer = this.surface.getSVGLayer(layerId);\n        var transforms = this.transforms(offsets.degrees, offsets.x, offsets.y, layer.parentNode);\n\n        this.surface.addText(layerId, 0, 0, this.axisLabel, className, undefined, undefined, undefined, undefined, transforms);\n        this.surface.render();\n        Object.keys(style).forEach(function(key) {\n            layer.style[key] = style[key];\n        });\n    };\n\n    function init(plot) {\n        plot.hooks.processOptions.push(function(plot, options) {\n            if (!options.axisLabels.show) {\n                return;\n            }\n\n            var axisLabels = {};\n            var defaultPadding = 2; // padding between axis and tick labels\n\n            plot.hooks.axisReserveSpace.push(function(plot, axis) {\n                var opts = axis.options;\n                var axisName = axis.direction + axis.n;\n\n                axis.labelHeight += axis.boxPosition.centerY;\n                axis.labelWidth += axis.boxPosition.centerX;\n\n                if (!opts || !opts.axisLabel || !axis.show) {\n                    return;\n                }\n\n                var padding = opts.axisLabelPadding === undefined\n                    ? defaultPadding\n                    : opts.axisLabelPadding;\n\n                var axisLabel = axisLabels[axisName];\n                if (!axisLabel) {\n                    axisLabel = new AxisLabel(axisName,\n                        opts.position, padding,\n                        plot.getPlaceholder()[0], opts.axisLabel, plot.getSurface());\n                    axisLabels[axisName] = axisLabel;\n                }\n\n                axisLabel.calculateSize();\n\n                // Incrementing the sizes of the tick labels.\n                axis.labelHeight += axisLabel.height;\n                axis.labelWidth += axisLabel.width;\n            });\n\n            // TODO - use the drawAxis hook\n            plot.hooks.draw.push(function(plot, ctx) {\n                $.each(plot.getAxes(), function(flotAxisName, axis) {\n                    var opts = axis.options;\n                    if (!opts || !opts.axisLabel || !axis.show) {\n                        return;\n                    }\n\n                    var axisName = axis.direction + axis.n;\n                    axisLabels[axisName].draw(axis.box);\n                });\n            });\n\n            plot.hooks.shutdown.push(function(plot, eventHolder) {\n                for (var axisName in axisLabels) {\n                    axisLabels[axisName].cleanup();\n                }\n            });\n        });\n    };\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'axisLabels',\n        version: '3.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.browser.js",
    "content": "/** ## jquery.flot.browser.js\n\nThis plugin is used to make available some browser-related utility functions.\n\n### Methods\n*/\n\n(function ($) {\n    'use strict';\n\n    var browser = {\n        /**\n        - getPageXY(e)\n\n         Calculates the pageX and pageY using the screenX, screenY properties of the event\n         and the scrolling of the page. This is needed because the pageX and pageY\n         properties of the event are not correct while running tests in Edge. */\n        getPageXY: function (e) {\n            // This code is inspired from https://stackoverflow.com/a/3464890\n            var doc = document.documentElement,\n                pageX = e.clientX + (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),\n                pageY = e.clientY + (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);\n            return { X: pageX, Y: pageY };\n        },\n\n        /**\n        - getPixelRatio(context)\n\n         This function returns the current pixel ratio defined by the product of desktop\n         zoom and page zoom.\n         Additional info: https://www.html5rocks.com/en/tutorials/canvas/hidpi/\n        */\n        getPixelRatio: function(context) {\n            var devicePixelRatio = window.devicePixelRatio || 1,\n                backingStoreRatio =\n                context.webkitBackingStorePixelRatio ||\n                context.mozBackingStorePixelRatio ||\n                context.msBackingStorePixelRatio ||\n                context.oBackingStorePixelRatio ||\n                context.backingStorePixelRatio || 1;\n            return devicePixelRatio / backingStoreRatio;\n        },\n\n        /**\n        - isSafari, isMobileSafari, isOpera, isFirefox, isIE, isEdge, isChrome, isBlink\n\n         This is a collection of functions, used to check if the code is running in a\n         particular browser or Javascript engine.\n        */\n        isSafari: function() {\n            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\n            // Safari 3.0+ \"[object HTMLElementConstructor]\"\n            return /constructor/i.test(window.top.HTMLElement) || (function (p) { return p.toString() === \"[object SafariRemoteNotification]\"; })(!window.top['safari'] || (typeof window.top.safari !== 'undefined' && window.top.safari.pushNotification));\n        },\n\n        isMobileSafari: function() {\n            //isMobileSafari adapted from https://stackoverflow.com/questions/3007480/determine-if-user-navigated-from-mobile-safari\n            return navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/);\n        },\n\n        isOpera: function() {\n            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\n            //Opera 8.0+\n            return (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;\n        },\n\n        isFirefox: function() {\n            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\n            // Firefox 1.0+\n            return typeof InstallTrigger !== 'undefined';\n        },\n\n        isIE: function() {\n            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\n            // Internet Explorer 6-11\n            return /*@cc_on!@*/false || !!document.documentMode;\n        },\n\n        isEdge: function() {\n            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\n            // Edge 20+\n            return !browser.isIE() && !!window.StyleMedia;\n        },\n\n        isChrome: function() {\n            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\n            // Chrome 1+\n            return !!window.chrome && !!window.chrome.webstore;\n        },\n\n        isBlink: function() {\n            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\n            return (browser.isChrome() || browser.isOpera()) && !!window.CSS;\n        }\n    };\n\n    $.plot.browser = browser;\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.categories.js",
    "content": "/* Flot plugin for plotting textual data or categories.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nConsider a dataset like [[\"February\", 34], [\"March\", 20], ...]. This plugin\nallows you to plot such a dataset directly.\n\nTo enable it, you must specify mode: \"categories\" on the axis with the textual\nlabels, e.g.\n\n    $.plot(\"#placeholder\", data, { xaxis: { mode: \"categories\" } });\n\nBy default, the labels are ordered as they are met in the data series. If you\nneed a different ordering, you can specify \"categories\" on the axis options\nand list the categories there:\n\n    xaxis: {\n        mode: \"categories\",\n        categories: [\"February\", \"March\", \"April\"]\n    }\n\nIf you need to customize the distances between the categories, you can specify\n\"categories\" as an object mapping labels to values\n\n    xaxis: {\n        mode: \"categories\",\n        categories: { \"February\": 1, \"March\": 3, \"April\": 4 }\n    }\n\nIf you don't specify all categories, the remaining categories will be numbered\nfrom the max value plus 1 (with a spacing of 1 between each).\n\nInternally, the plugin works by transforming the input data through an auto-\ngenerated mapping where the first category becomes 0, the second 1, etc.\nHence, a point like [\"February\", 34] becomes [0, 34] internally in Flot (this\nis visible in hover and click events that return numbers rather than the\ncategory labels). The plugin also overrides the tick generator to spit out the\ncategories as ticks instead of the values.\n\nIf you need to map a value back to its label, the mapping is always accessible\nas \"categories\" on the axis object, e.g. plot.getAxes().xaxis.categories.\n\n*/\n\n(function ($) {\n    var options = {\n        xaxis: {\n            categories: null\n        },\n        yaxis: {\n            categories: null\n        }\n    };\n\n    function processRawData(plot, series, data, datapoints) {\n        // if categories are enabled, we need to disable\n        // auto-transformation to numbers so the strings are intact\n        // for later processing\n\n        var xCategories = series.xaxis.options.mode === \"categories\",\n            yCategories = series.yaxis.options.mode === \"categories\";\n\n        if (!(xCategories || yCategories)) {\n            return;\n        }\n\n        var format = datapoints.format;\n\n        if (!format) {\n            // FIXME: auto-detection should really not be defined here\n            var s = series;\n            format = [];\n            format.push({ x: true, number: true, required: true, computeRange: true});\n            format.push({ y: true, number: true, required: true, computeRange: true });\n\n            if (s.bars.show || (s.lines.show && s.lines.fill)) {\n                var autoScale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));\n                format.push({ y: true, number: true, required: false, defaultValue: 0, computeRange: autoScale });\n                if (s.bars.horizontal) {\n                    delete format[format.length - 1].y;\n                    format[format.length - 1].x = true;\n                }\n            }\n\n            datapoints.format = format;\n        }\n\n        for (var m = 0; m < format.length; ++m) {\n            if (format[m].x && xCategories) {\n                format[m].number = false;\n            }\n\n            if (format[m].y && yCategories) {\n                format[m].number = false;\n                format[m].computeRange = false;\n            }\n        }\n    }\n\n    function getNextIndex(categories) {\n        var index = -1;\n\n        for (var v in categories) {\n            if (categories[v] > index) {\n                index = categories[v];\n            }\n        }\n\n        return index + 1;\n    }\n\n    function categoriesTickGenerator(axis) {\n        var res = [];\n        for (var label in axis.categories) {\n            var v = axis.categories[label];\n            if (v >= axis.min && v <= axis.max) {\n                res.push([v, label]);\n            }\n        }\n\n        res.sort(function (a, b) { return a[0] - b[0]; });\n\n        return res;\n    }\n\n    function setupCategoriesForAxis(series, axis, datapoints) {\n        if (series[axis].options.mode !== \"categories\") {\n            return;\n        }\n\n        if (!series[axis].categories) {\n            // parse options\n            var c = {}, o = series[axis].options.categories || {};\n            if ($.isArray(o)) {\n                for (var i = 0; i < o.length; ++i) {\n                    c[o[i]] = i;\n                }\n            } else {\n                for (var v in o) {\n                    c[v] = o[v];\n                }\n            }\n\n            series[axis].categories = c;\n        }\n\n        // fix ticks\n        if (!series[axis].options.ticks) {\n            series[axis].options.ticks = categoriesTickGenerator;\n        }\n\n        transformPointsOnAxis(datapoints, axis, series[axis].categories);\n    }\n\n    function transformPointsOnAxis(datapoints, axis, categories) {\n        // go through the points, transforming them\n        var points = datapoints.points,\n            ps = datapoints.pointsize,\n            format = datapoints.format,\n            formatColumn = axis.charAt(0),\n            index = getNextIndex(categories);\n\n        for (var i = 0; i < points.length; i += ps) {\n            if (points[i] == null) {\n                continue;\n            }\n\n            for (var m = 0; m < ps; ++m) {\n                var val = points[i + m];\n\n                if (val == null || !format[m][formatColumn]) {\n                    continue;\n                }\n\n                if (!(val in categories)) {\n                    categories[val] = index;\n                    ++index;\n                }\n\n                points[i + m] = categories[val];\n            }\n        }\n    }\n\n    function processDatapoints(plot, series, datapoints) {\n        setupCategoriesForAxis(series, \"xaxis\", datapoints);\n        setupCategoriesForAxis(series, \"yaxis\", datapoints);\n    }\n\n    function init(plot) {\n        plot.hooks.processRawData.push(processRawData);\n        plot.hooks.processDatapoints.push(processDatapoints);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'categories',\n        version: '1.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.composeImages.js",
    "content": "/** ## jquery.flot.composeImages.js\n\nThis plugin is used to expose a function used to overlap several canvases and\nSVGs, for the purpose of creating a snaphot out of them.\n\n### When composeImages is used:\nWhen multiple canvases and SVGs have to be overlapped into a single image\nand their offset on the page, must be preserved.\n\n### Where can be used:\nIn creating a downloadable snapshot of the plots, axes, cursors etc of a graph.\n\n### How it works:\nThe entry point is composeImages function. It expects an array of objects,\nwhich should be either canvases or SVGs (or a mix). It does a prevalidation\nof them, by verifying if they will be usable or not, later in the flow.\nAfter selecting only usable sources, it passes them to getGenerateTempImg\nfunction, which generates temporary images out of them. This function\nexpects that some of the passed sources (canvas or SVG) may still have\nproblems being converted to an image and makes sure the promises system,\nused by composeImages function, moves forward. As an example, SVGs with\nmissing information from header or with unsupported content, may lead to\nfailure in generating the temporary image. Temporary images are required\nmostly on extracting content from SVGs, but this is also where the x/y\noffsets are extracted for each image which will be added. For SVGs in\nparticular, their CSS rules have to be applied.\nAfter all temporary images are generated, they are overlapped using\ngetExecuteImgComposition function. This is where the destination canvas\nis set to the proper dimensions. It is then output by composeImages.\nThis function returns a promise, which can be used to wait for the whole\ncomposition process. It requires to be asynchronous, because this is how\ntemporary images load their data.\n*/\n\n(function($) {\n    \"use strict\";\n    const GENERALFAILURECALLBACKERROR = -100; //simply a negative number\n    const SUCCESSFULIMAGEPREPARATION = 0;\n    const EMPTYARRAYOFIMAGESOURCES = -1;\n    const NEGATIVEIMAGESIZE = -2;\n    var pixelRatio = 1;\n    var browser = $.plot.browser;\n    var getPixelRatio = browser.getPixelRatio;\n\n    function composeImages(canvasOrSvgSources, destinationCanvas) {\n        var validCanvasOrSvgSources = canvasOrSvgSources.filter(isValidSource);\n        pixelRatio = getPixelRatio(destinationCanvas.getContext('2d'));\n\n        var allImgCompositionPromises = validCanvasOrSvgSources.map(function(validCanvasOrSvgSource) {\n            var tempImg = new Image();\n            var currentPromise = new Promise(getGenerateTempImg(tempImg, validCanvasOrSvgSource));\n            return currentPromise;\n        });\n\n        var lastPromise = Promise.all(allImgCompositionPromises).then(getExecuteImgComposition(destinationCanvas), failureCallback);\n        return lastPromise;\n    }\n\n    function isValidSource(canvasOrSvgSource) {\n        var isValidFromCanvas = true;\n        var isValidFromContent = true;\n        if ((canvasOrSvgSource === null) || (canvasOrSvgSource === undefined)) {\n            isValidFromContent = false;\n        } else {\n            if (canvasOrSvgSource.tagName === 'CANVAS') {\n                if ((canvasOrSvgSource.getBoundingClientRect().right === canvasOrSvgSource.getBoundingClientRect().left) ||\n                    (canvasOrSvgSource.getBoundingClientRect().bottom === canvasOrSvgSource.getBoundingClientRect().top)) {\n                    isValidFromCanvas = false;\n                }\n            }\n        }\n        return isValidFromContent && isValidFromCanvas && (window.getComputedStyle(canvasOrSvgSource).visibility === 'visible');\n    }\n\n    function getGenerateTempImg(tempImg, canvasOrSvgSource) {\n        tempImg.sourceDescription = '<info className=\"' + canvasOrSvgSource.className + '\" tagName=\"' + canvasOrSvgSource.tagName + '\" id=\"' + canvasOrSvgSource.id + '\">';\n        tempImg.sourceComponent = canvasOrSvgSource;\n\n        return function doGenerateTempImg(successCallbackFunc, failureCallbackFunc) {\n            tempImg.onload = function(evt) {\n                tempImg.successfullyLoaded = true;\n                successCallbackFunc(tempImg);\n            };\n\n            tempImg.onabort = function(evt) {\n                tempImg.successfullyLoaded = false;\n                console.log('Can\\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);\n                successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images\n            };\n\n            tempImg.onerror = function(evt) {\n                tempImg.successfullyLoaded = false;\n                console.log('Can\\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);\n                successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images\n            };\n\n            generateTempImageFromCanvasOrSvg(canvasOrSvgSource, tempImg);\n        };\n    }\n\n    function getExecuteImgComposition(destinationCanvas) {\n        return function executeImgComposition(tempImgs) {\n            var compositionResult = copyImgsToCanvas(tempImgs, destinationCanvas);\n            return compositionResult;\n        };\n    }\n\n    function copyCanvasToImg(canvas, img) {\n        img.src = canvas.toDataURL('image/png');\n    }\n\n    function getCSSRules(document) {\n        var styleSheets = document.styleSheets,\n            rulesList = [];\n        for (var i = 0; i < styleSheets.length; i++) {\n            // CORS requests for style sheets throw and an exception on Chrome > 64\n            try {\n                // in Chrome, the external CSS files are empty when the page is directly loaded from disk\n                var rules = styleSheets[i].cssRules || [];\n                for (var j = 0; j < rules.length; j++) {\n                    var rule = rules[j];\n                    rulesList.push(rule.cssText);\n                }\n            } catch (e) {\n                console.log('Failed to get some css rules');\n            }\n        }\n        return rulesList;\n    }\n\n    function embedCSSRulesInSVG(rules, svg) {\n        var text = [\n            '<svg class=\"snapshot ' + svg.classList + '\" width=\"' + svg.width.baseVal.value * pixelRatio + '\" height=\"' + svg.height.baseVal.value * pixelRatio + '\" viewBox=\"0 0 ' + svg.width.baseVal.value + ' ' + svg.height.baseVal.value + '\" xmlns=\"http://www.w3.org/2000/svg\">',\n            '<style>',\n            '/* <![CDATA[ */',\n            rules.join('\\n'),\n            '/* ]]> */',\n            '</style>',\n            svg.innerHTML,\n            '</svg>'\n        ].join('\\n');\n        return text;\n    }\n\n    function copySVGToImgMostBrowsers(svg, img) {\n        var rules = getCSSRules(document),\n            source = embedCSSRulesInSVG(rules, svg);\n\n        source = patchSVGSource(source);\n\n        var blob = new Blob([source], {type: \"image/svg+xml;charset=utf-8\"}),\n            domURL = self.URL || self.webkitURL || self,\n            url = domURL.createObjectURL(blob);\n        img.src = url;\n    }\n\n    function copySVGToImgSafari(svg, img) {\n        // Use this method to convert a string buffer array to a binary string.\n        // Do so by breaking up large strings into smaller substrings; this is necessary to avoid the\n        // \"maximum call stack size exceeded\" exception that can happen when calling 'String.fromCharCode.apply'\n        // with a very long array.\n        function buildBinaryString (arrayBuffer) {\n            var binaryString = \"\";\n            const utf8Array = new Uint8Array(arrayBuffer);\n            const blockSize = 16384;\n            for (var i = 0; i < utf8Array.length; i = i + blockSize) {\n                const binarySubString = String.fromCharCode.apply(null, utf8Array.subarray(i, i + blockSize));\n                binaryString = binaryString + binarySubString;\n            }\n            return binaryString;\n        };\n\n        var rules = getCSSRules(document),\n            source = embedCSSRulesInSVG(rules, svg),\n            data,\n            utf8BinaryString;\n\n        source = patchSVGSource(source);\n\n        // Encode the string as UTF-8 and convert it to a binary string. The UTF-8 encoding is required to\n        // capture unicode characters correctly.\n        utf8BinaryString = buildBinaryString(new (TextEncoder || TextEncoderLite)('utf-8').encode(source));\n\n        data = \"data:image/svg+xml;base64,\" + btoa(utf8BinaryString);\n        img.src = data;\n    }\n\n    function patchSVGSource(svgSource) {\n        var source = '';\n        //add name spaces.\n        if (!svgSource.match(/^<svg[^>]+xmlns=\"http:\\/\\/www\\.w3\\.org\\/2000\\/svg\"/)) {\n            source = svgSource.replace(/^<svg/, '<svg xmlns=\"http://www.w3.org/2000/svg\"');\n        }\n        if (!svgSource.match(/^<svg[^>]+\"http:\\/\\/www\\.w3\\.org\\/1999\\/xlink\"/)) {\n            source = svgSource.replace(/^<svg/, '<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\"');\n        }\n\n        //add xml declaration\n        return '<?xml version=\"1.0\" standalone=\"no\"?>\\r\\n' + source;\n    }\n\n    function copySVGToImg(svg, img) {\n        if (browser.isSafari() || browser.isMobileSafari()) {\n            copySVGToImgSafari(svg, img);\n        } else {\n            copySVGToImgMostBrowsers(svg, img);\n        }\n    }\n\n    function adaptDestSizeToZoom(destinationCanvas, sources) {\n        function containsSVGs(source) {\n            return source.srcImgTagName === 'svg';\n        }\n\n        if (sources.find(containsSVGs) !== undefined) {\n            if (pixelRatio < 1) {\n                destinationCanvas.width = destinationCanvas.width * pixelRatio;\n                destinationCanvas.height = destinationCanvas.height * pixelRatio;\n            }\n        }\n    }\n\n    function prepareImagesToBeComposed(sources, destination) {\n        var result = SUCCESSFULIMAGEPREPARATION;\n        if (sources.length === 0) {\n            result = EMPTYARRAYOFIMAGESOURCES; //nothing to do if called without sources\n        } else {\n            var minX = sources[0].genLeft;\n            var minY = sources[0].genTop;\n            var maxX = sources[0].genRight;\n            var maxY = sources[0].genBottom;\n            var i = 0;\n\n            for (i = 1; i < sources.length; i++) {\n                if (minX > sources[i].genLeft) {\n                    minX = sources[i].genLeft;\n                }\n\n                if (minY > sources[i].genTop) {\n                    minY = sources[i].genTop;\n                }\n            }\n\n            for (i = 1; i < sources.length; i++) {\n                if (maxX < sources[i].genRight) {\n                    maxX = sources[i].genRight;\n                }\n\n                if (maxY < sources[i].genBottom) {\n                    maxY = sources[i].genBottom;\n                }\n            }\n\n            if ((maxX - minX <= 0) || (maxY - minY <= 0)) {\n                result = NEGATIVEIMAGESIZE; //this might occur on hidden images\n            } else {\n                destination.width = Math.round(maxX - minX);\n                destination.height = Math.round(maxY - minY);\n\n                for (i = 0; i < sources.length; i++) {\n                    sources[i].xCompOffset = sources[i].genLeft - minX;\n                    sources[i].yCompOffset = sources[i].genTop - minY;\n                }\n\n                adaptDestSizeToZoom(destination, sources);\n            }\n        }\n        return result;\n    }\n\n    function copyImgsToCanvas(sources, destination) {\n        var prepareImagesResult = prepareImagesToBeComposed(sources, destination);\n        if (prepareImagesResult === SUCCESSFULIMAGEPREPARATION) {\n            var destinationCtx = destination.getContext('2d');\n\n            for (var i = 0; i < sources.length; i++) {\n                if (sources[i].successfullyLoaded === true) {\n                    destinationCtx.drawImage(sources[i], sources[i].xCompOffset * pixelRatio, sources[i].yCompOffset * pixelRatio);\n                }\n            }\n        }\n        return prepareImagesResult;\n    }\n\n    function adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg) {\n        destImg.genLeft = srcCanvasOrSvg.getBoundingClientRect().left;\n        destImg.genTop = srcCanvasOrSvg.getBoundingClientRect().top;\n\n        if (srcCanvasOrSvg.tagName === 'CANVAS') {\n            destImg.genRight = destImg.genLeft + srcCanvasOrSvg.width;\n            destImg.genBottom = destImg.genTop + srcCanvasOrSvg.height;\n        }\n\n        if (srcCanvasOrSvg.tagName === 'svg') {\n            destImg.genRight = srcCanvasOrSvg.getBoundingClientRect().right;\n            destImg.genBottom = srcCanvasOrSvg.getBoundingClientRect().bottom;\n        }\n    }\n\n    function generateTempImageFromCanvasOrSvg(srcCanvasOrSvg, destImg) {\n        if (srcCanvasOrSvg.tagName === 'CANVAS') {\n            copyCanvasToImg(srcCanvasOrSvg, destImg);\n        }\n\n        if (srcCanvasOrSvg.tagName === 'svg') {\n            copySVGToImg(srcCanvasOrSvg, destImg);\n        }\n\n        destImg.srcImgTagName = srcCanvasOrSvg.tagName;\n        adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg);\n    }\n\n    function failureCallback() {\n        return GENERALFAILURECALLBACKERROR;\n    }\n\n    // used for testing\n    $.plot.composeImages = composeImages;\n\n    function init(plot) {\n        // used to extend the public API of the plot\n        plot.composeImages = composeImages;\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        name: 'composeImages',\n        version: '1.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.crosshair.js",
    "content": "/* Flot plugin for showing crosshairs when the mouse hovers over the plot.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe plugin supports these options:\n\n    crosshair: {\n        mode: null or \"x\" or \"y\" or \"xy\"\n        color: color\n        lineWidth: number\n    }\n\nSet the mode to one of \"x\", \"y\" or \"xy\". The \"x\" mode enables a vertical\ncrosshair that lets you trace the values on the x axis, \"y\" enables a\nhorizontal crosshair and \"xy\" enables them both. \"color\" is the color of the\ncrosshair (default is \"rgba(170, 0, 0, 0.80)\"), \"lineWidth\" is the width of\nthe drawn lines (default is 1).\n\nThe plugin also adds four public methods:\n\n  - setCrosshair( pos )\n\n    Set the position of the crosshair. Note that this is cleared if the user\n    moves the mouse. \"pos\" is in coordinates of the plot and should be on the\n    form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple\n    axes), which is coincidentally the same format as what you get from a\n    \"plothover\" event. If \"pos\" is null, the crosshair is cleared.\n\n  - clearCrosshair()\n\n    Clear the crosshair.\n\n  - lockCrosshair(pos)\n\n    Cause the crosshair to lock to the current location, no longer updating if\n    the user moves the mouse. Optionally supply a position (passed on to\n    setCrosshair()) to move it to.\n\n    Example usage:\n\n    var myFlot = $.plot( $(\"#graph\"), ..., { crosshair: { mode: \"x\" } } };\n    $(\"#graph\").bind( \"plothover\", function ( evt, position, item ) {\n        if ( item ) {\n            // Lock the crosshair to the data point being hovered\n            myFlot.lockCrosshair({\n                x: item.datapoint[ 0 ],\n                y: item.datapoint[ 1 ]\n            });\n        } else {\n            // Return normal crosshair operation\n            myFlot.unlockCrosshair();\n        }\n    });\n\n  - unlockCrosshair()\n\n    Free the crosshair to move again after locking it.\n*/\n\n(function ($) {\n    var options = {\n        crosshair: {\n            mode: null, // one of null, \"x\", \"y\" or \"xy\",\n            color: \"rgba(170, 0, 0, 0.80)\",\n            lineWidth: 1\n        }\n    };\n\n    function init(plot) {\n        // position of crosshair in pixels\n        var crosshair = {x: -1, y: -1, locked: false, highlighted: false};\n\n        plot.setCrosshair = function setCrosshair(pos) {\n            if (!pos) {\n                crosshair.x = -1;\n            } else {\n                var o = plot.p2c(pos);\n                crosshair.x = Math.max(0, Math.min(o.left, plot.width()));\n                crosshair.y = Math.max(0, Math.min(o.top, plot.height()));\n            }\n\n            plot.triggerRedrawOverlay();\n        };\n\n        plot.clearCrosshair = plot.setCrosshair; // passes null for pos\n\n        plot.lockCrosshair = function lockCrosshair(pos) {\n            if (pos) {\n                plot.setCrosshair(pos);\n            }\n\n            crosshair.locked = true;\n        };\n\n        plot.unlockCrosshair = function unlockCrosshair() {\n            crosshair.locked = false;\n            crosshair.rect = null;\n        };\n\n        function onMouseOut(e) {\n            if (crosshair.locked) {\n                return;\n            }\n\n            if (crosshair.x !== -1) {\n                crosshair.x = -1;\n                plot.triggerRedrawOverlay();\n            }\n        }\n\n        function onMouseMove(e) {\n            var offset = plot.offset();\n            if (crosshair.locked) {\n                var mouseX = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));\n                var mouseY = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));\n\n                if ((mouseX > crosshair.x - 4) && (mouseX < crosshair.x + 4) && (mouseY > crosshair.y - 4) && (mouseY < crosshair.y + 4)) {\n                    if (!crosshair.highlighted) {\n                        crosshair.highlighted = true;\n                        plot.triggerRedrawOverlay();\n                    }\n                } else {\n                    if (crosshair.highlighted) {\n                        crosshair.highlighted = false;\n                        plot.triggerRedrawOverlay();\n                    }\n                }\n                return;\n            }\n\n            if (plot.getSelection && plot.getSelection()) {\n                crosshair.x = -1; // hide the crosshair while selecting\n                return;\n            }\n\n            crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));\n            crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));\n            plot.triggerRedrawOverlay();\n        }\n\n        plot.hooks.bindEvents.push(function (plot, eventHolder) {\n            if (!plot.getOptions().crosshair.mode) {\n                return;\n            }\n\n            eventHolder.mouseout(onMouseOut);\n            eventHolder.mousemove(onMouseMove);\n        });\n\n        plot.hooks.drawOverlay.push(function (plot, ctx) {\n            var c = plot.getOptions().crosshair;\n            if (!c.mode) {\n                return;\n            }\n\n            var plotOffset = plot.getPlotOffset();\n\n            ctx.save();\n            ctx.translate(plotOffset.left, plotOffset.top);\n\n            if (crosshair.x !== -1) {\n                var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;\n\n                ctx.strokeStyle = c.color;\n                ctx.lineWidth = c.lineWidth;\n                ctx.lineJoin = \"round\";\n\n                ctx.beginPath();\n                if (c.mode.indexOf(\"x\") !== -1) {\n                    var drawX = Math.floor(crosshair.x) + adj;\n                    ctx.moveTo(drawX, 0);\n                    ctx.lineTo(drawX, plot.height());\n                }\n                if (c.mode.indexOf(\"y\") !== -1) {\n                    var drawY = Math.floor(crosshair.y) + adj;\n                    ctx.moveTo(0, drawY);\n                    ctx.lineTo(plot.width(), drawY);\n                }\n                if (crosshair.locked) {\n                    if (crosshair.highlighted) ctx.fillStyle = 'orange';\n                    else ctx.fillStyle = c.color;\n                    ctx.fillRect(Math.floor(crosshair.x) + adj - 4, Math.floor(crosshair.y) + adj - 4, 8, 8);\n                }\n                ctx.stroke();\n            }\n            ctx.restore();\n        });\n\n        plot.hooks.shutdown.push(function (plot, eventHolder) {\n            eventHolder.unbind(\"mouseout\", onMouseOut);\n            eventHolder.unbind(\"mousemove\", onMouseMove);\n        });\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'crosshair',\n        version: '1.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.drawSeries.js",
    "content": "/**\n## jquery.flot.drawSeries.js\n\nThis plugin is used by flot for drawing lines, plots, bars or area.\n\n### Public methods\n*/\n\n(function($) {\n    \"use strict\";\n\n    function DrawSeries() {\n        function plotLine(datapoints, xoffset, yoffset, axisx, axisy, ctx, steps) {\n            var points = datapoints.points,\n                ps = datapoints.pointsize,\n                prevx = null,\n                prevy = null;\n            var x1 = 0.0,\n                y1 = 0.0,\n                x2 = 0.0,\n                y2 = 0.0,\n                mx = null,\n                my = null,\n                i = 0;\n\n            var initPoints = function (i) {\n                x1 = points[i - ps];\n                y1 = points[i - ps + 1];\n                x2 = points[i];\n                y2 = points[i + 1];\n            };\n\n            var handleSteps = function () {\n                if (mx !== null && my !== null) {\n                    // if middle point exists, transfer p2 -> p1 and p1 -> mp\n                    x2 = x1;\n                    y2 = y1;\n                    x1 = mx;\n                    y1 = my;\n\n                    // 'remove' middle point\n                    mx = null;\n                    my = null;\n\n                    return true;\n                } else if (y1 !== y2 && x1 !== x2) {\n                    // create a middle point\n                    y2 = y1;\n                    mx = x2;\n                    my = y1;\n                }\n\n                return false;\n            };\n\n            var handleYMinClipping = function () {\n                if (y1 <= y2 && y1 < axisy.min) {\n                    if (y2 < axisy.min) {\n                        // line segment is outside\n                        return true;\n                    }\n                    // compute new intersection point\n                    x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y1 = axisy.min;\n                } else if (y2 <= y1 && y2 < axisy.min) {\n                    if (y1 < axisy.min) {\n                        return true;\n                    }\n\n                    x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y2 = axisy.min;\n                }\n            };\n\n            var handleYMaxClipping = function () {\n                if (y1 >= y2 && y1 > axisy.max) {\n                    if (y2 > axisy.max) {\n                        return true;\n                    }\n\n                    x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y1 = axisy.max;\n                } else if (y2 >= y1 && y2 > axisy.max) {\n                    if (y1 > axisy.max) {\n                        return true;\n                    }\n\n                    x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y2 = axisy.max;\n                }\n            };\n\n            var handleXMinClipping = function () {\n                if (x1 <= x2 && x1 < axisx.min) {\n                    if (x2 < axisx.min) {\n                        return true;\n                    }\n\n                    y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x1 = axisx.min;\n                } else if (x2 <= x1 && x2 < axisx.min) {\n                    if (x1 < axisx.min) {\n                        return true;\n                    }\n\n                    y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x2 = axisx.min;\n                }\n            };\n\n            var handleXMaxClipping = function () {\n                if (x1 >= x2 && x1 > axisx.max) {\n                    if (x2 > axisx.max) {\n                        return true;\n                    }\n\n                    y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x1 = axisx.max;\n                } else if (x2 >= x1 && x2 > axisx.max) {\n                    if (x1 > axisx.max) {\n                        return true;\n                    }\n\n                    y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x2 = axisx.max;\n                }\n            };\n\n            var drawLine = function () {\n                if (x1 !== prevx || y1 !== prevy) {\n                    ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);\n                }\n\n                prevx = x2;\n                prevy = y2;\n                ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);\n            };\n\n            ctx.beginPath();\n            for (i = ps; i < points.length; i += ps) {\n                initPoints(i);\n\n                if (x1 === null || x2 === null) {\n                    mx = null;\n                    my = null;\n                    continue;\n                }\n\n                if (isNaN(x1) || isNaN(x2) || isNaN(y1) || isNaN(y2)) {\n                    prevx = null;\n                    prevy = null;\n                    continue;\n                }\n\n                if (steps) {\n                    var hadMiddlePoint = handleSteps();\n                    if (hadMiddlePoint) {\n                        // Subtract pointsize from i to have current point p1 handled again.\n                        i -= ps;\n                    }\n                }\n                if (handleYMinClipping()) continue;\n                if (handleYMaxClipping()) continue;\n                if (handleXMinClipping()) continue;\n                if (handleXMaxClipping()) continue;\n\n                drawLine();\n            }\n\n            // Connects last two points in case middle point exists after the loop.\n            if (mx !== null && my !== null) {\n                initPoints(i);\n                handleSteps();\n\n                if (!handleYMinClipping() &&\n                    !handleYMaxClipping() &&\n                    !handleXMinClipping() &&\n                    !handleXMaxClipping()) {\n                    drawLine();\n                }\n            }\n\n            ctx.stroke();\n        }\n\n        function plotLineArea(datapoints, axisx, axisy, fillTowards, ctx, steps) {\n            var points = datapoints.points,\n                ps = datapoints.pointsize,\n                bottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min,\n                i = 0,\n                ypos = 1,\n                areaOpen = false,\n                segmentStart = 0,\n                segmentEnd = 0,\n                mx = null,\n                my = null;\n\n            // we process each segment in two turns, first forward\n            // direction to sketch out top, then once we hit the\n            // end we go backwards to sketch the bottom\n            while (true) {\n                if (ps > 0 && i > points.length + ps) {\n                    break;\n                }\n\n                i += ps; // ps is negative if going backwards\n\n                var x1 = points[i - ps],\n                    y1 = points[i - ps + ypos],\n                    x2 = points[i],\n                    y2 = points[i + ypos];\n\n                if (ps === -2) {\n                    /* going backwards and no value for the bottom provided in the series*/\n                    y1 = y2 = bottom;\n                }\n\n                if (areaOpen) {\n                    if (ps > 0 && x1 != null && x2 == null) {\n                        // at turning point\n                        segmentEnd = i;\n                        ps = -ps;\n                        ypos = 2;\n                        continue;\n                    }\n\n                    if (ps < 0 && i === segmentStart + ps) {\n                        // done with the reverse sweep\n                        ctx.fill();\n                        areaOpen = false;\n                        ps = -ps;\n                        ypos = 1;\n                        i = segmentStart = segmentEnd + ps;\n                        continue;\n                    }\n                }\n\n                if (x1 == null || x2 == null) {\n                    mx = null;\n                    my = null;\n                    continue;\n                }\n\n                if (steps) {\n                    if (mx !== null && my !== null) {\n                        // if middle point exists, transfer p2 -> p1 and p1 -> mp\n                        x2 = x1;\n                        y2 = y1;\n                        x1 = mx;\n                        y1 = my;\n\n                        // 'remove' middle point\n                        mx = null;\n                        my = null;\n\n                        // subtract pointsize from i to have current point p1 handled again\n                        i -= ps;\n                    } else if (y1 !== y2 && x1 !== x2) {\n                        // create a middle point\n                        y2 = y1;\n                        mx = x2;\n                        my = y1;\n                    }\n                }\n\n                // clip x values\n\n                // clip with xmin\n                if (x1 <= x2 && x1 < axisx.min) {\n                    if (x2 < axisx.min) {\n                        continue;\n                    }\n\n                    y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x1 = axisx.min;\n                } else if (x2 <= x1 && x2 < axisx.min) {\n                    if (x1 < axisx.min) {\n                        continue;\n                    }\n\n                    y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x2 = axisx.min;\n                }\n\n                // clip with xmax\n                if (x1 >= x2 && x1 > axisx.max) {\n                    if (x2 > axisx.max) {\n                        continue;\n                    }\n\n                    y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x1 = axisx.max;\n                } else if (x2 >= x1 && x2 > axisx.max) {\n                    if (x1 > axisx.max) {\n                        continue;\n                    }\n\n                    y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;\n                    x2 = axisx.max;\n                }\n\n                if (!areaOpen) {\n                    // open area\n                    ctx.beginPath();\n                    ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));\n                    areaOpen = true;\n                }\n\n                // now first check the case where both is outside\n                if (y1 >= axisy.max && y2 >= axisy.max) {\n                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));\n                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));\n                    continue;\n                } else if (y1 <= axisy.min && y2 <= axisy.min) {\n                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));\n                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));\n                    continue;\n                }\n\n                // else it's a bit more complicated, there might\n                // be a flat maxed out rectangle first, then a\n                // triangular cutout or reverse; to find these\n                // keep track of the current x values\n                var x1old = x1,\n                    x2old = x2;\n\n                // clip the y values, without shortcutting, we\n                // go through all cases in turn\n\n                // clip with ymin\n                if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {\n                    x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y1 = axisy.min;\n                } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {\n                    x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y2 = axisy.min;\n                }\n\n                // clip with ymax\n                if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {\n                    x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y1 = axisy.max;\n                } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {\n                    x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;\n                    y2 = axisy.max;\n                }\n\n                // if the x value was changed we got a rectangle\n                // to fill\n                if (x1 !== x1old) {\n                    ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));\n                    // it goes to (x1, y1), but we fill that below\n                }\n\n                // fill triangular section, this sometimes result\n                // in redundant points if (x1, y1) hasn't changed\n                // from previous line to, but we just ignore that\n                ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));\n                ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));\n\n                // fill the other rectangle if it's there\n                if (x2 !== x2old) {\n                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));\n                    ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));\n                }\n            }\n        }\n\n        /**\n        - drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)\n\n         This function is used for drawing lines or area fill.  In case the series has line decimation function\n         attached, before starting to draw, as an optimization the points will first be decimated.\n\n         The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and\n         plotHeight are the corresponding parameters of flot used to determine the drawing surface.\n         The function getColorOrGradient is used to compute the fill style of lines and area.\n        */\n        function drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {\n            ctx.save();\n            ctx.translate(plotOffset.left, plotOffset.top);\n            ctx.lineJoin = \"round\";\n\n            if (series.lines.dashes && ctx.setLineDash) {\n                ctx.setLineDash(series.lines.dashes);\n            }\n\n            var datapoints = {\n                format: series.datapoints.format,\n                points: series.datapoints.points,\n                pointsize: series.datapoints.pointsize\n            };\n\n            if (series.decimate) {\n                datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);\n            }\n\n            var lw = series.lines.lineWidth;\n\n            ctx.lineWidth = lw;\n            ctx.strokeStyle = series.color;\n            var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight, getColorOrGradient);\n            if (fillStyle) {\n                ctx.fillStyle = fillStyle;\n                plotLineArea(datapoints, series.xaxis, series.yaxis, series.lines.fillTowards || 0, ctx, series.lines.steps);\n            }\n\n            if (lw > 0) {\n                plotLine(datapoints, 0, 0, series.xaxis, series.yaxis, ctx, series.lines.steps);\n            }\n\n            ctx.restore();\n        }\n\n        /**\n        - drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)\n\n         This function is used for drawing points using a given symbol. In case the series has points decimation\n         function attached, before starting to draw, as an optimization the points will first be decimated.\n\n         The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and\n         plotHeight are the corresponding parameters of flot used to determine the drawing surface.\n         The function drawSymbol is used to compute and draw the symbol chosen for the points.\n        */\n        function drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {\n            function drawCircle(ctx, x, y, radius, shadow, fill) {\n                ctx.moveTo(x + radius, y);\n                ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);\n            }\n            drawCircle.fill = true;\n            function plotPoints(datapoints, radius, fill, offset, shadow, axisx, axisy, drawSymbolFn) {\n                var points = datapoints.points,\n                    ps = datapoints.pointsize;\n\n                ctx.beginPath();\n                for (var i = 0; i < points.length; i += ps) {\n                    var x = points[i],\n                        y = points[i + 1];\n                    if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {\n                        continue;\n                    }\n\n                    x = axisx.p2c(x);\n                    y = axisy.p2c(y) + offset;\n\n                    drawSymbolFn(ctx, x, y, radius, shadow, fill);\n                }\n                if (drawSymbolFn.fill && !shadow) {\n                    ctx.fill();\n                }\n                ctx.stroke();\n            }\n\n            ctx.save();\n            ctx.translate(plotOffset.left, plotOffset.top);\n\n            var datapoints = {\n                format: series.datapoints.format,\n                points: series.datapoints.points,\n                pointsize: series.datapoints.pointsize\n            };\n\n            if (series.decimatePoints) {\n                datapoints.points = series.decimatePoints(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);\n            }\n\n            var lw = series.points.lineWidth,\n                radius = series.points.radius,\n                symbol = series.points.symbol,\n                drawSymbolFn;\n\n            if (symbol === 'circle') {\n                drawSymbolFn = drawCircle;\n            } else if (typeof symbol === 'string' && drawSymbol && drawSymbol[symbol]) {\n                drawSymbolFn = drawSymbol[symbol];\n            } else if (typeof drawSymbol === 'function') {\n                drawSymbolFn = drawSymbol;\n            }\n\n            // If the user sets the line width to 0, we change it to a very\n            // small value. A line width of 0 seems to force the default of 1.\n\n            if (lw === 0) {\n                lw = 0.0001;\n            }\n\n            ctx.lineWidth = lw;\n            ctx.fillStyle = getFillStyle(series.points, series.color, null, null, getColorOrGradient);\n            ctx.strokeStyle = series.color;\n            plotPoints(datapoints, radius,\n                true, 0, false,\n                series.xaxis, series.yaxis, drawSymbolFn);\n            ctx.restore();\n        }\n\n        function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {\n            var left = x + barLeft,\n                right = x + barRight,\n                bottom = b, top = y,\n                drawLeft, drawRight, drawTop, drawBottom = false,\n                tmp;\n\n            drawLeft = drawRight = drawTop = true;\n\n            // in horizontal mode, we start the bar from the left\n            // instead of from the bottom so it appears to be\n            // horizontal rather than vertical\n            if (horizontal) {\n                drawBottom = drawRight = drawTop = true;\n                drawLeft = false;\n                left = b;\n                right = x;\n                top = y + barLeft;\n                bottom = y + barRight;\n\n                // account for negative bars\n                if (right < left) {\n                    tmp = right;\n                    right = left;\n                    left = tmp;\n                    drawLeft = true;\n                    drawRight = false;\n                }\n            } else {\n                drawLeft = drawRight = drawTop = true;\n                drawBottom = false;\n                left = x + barLeft;\n                right = x + barRight;\n                bottom = b;\n                top = y;\n\n                // account for negative bars\n                if (top < bottom) {\n                    tmp = top;\n                    top = bottom;\n                    bottom = tmp;\n                    drawBottom = true;\n                    drawTop = false;\n                }\n            }\n\n            // clip\n            if (right < axisx.min || left > axisx.max ||\n                top < axisy.min || bottom > axisy.max) {\n                return;\n            }\n\n            if (left < axisx.min) {\n                left = axisx.min;\n                drawLeft = false;\n            }\n\n            if (right > axisx.max) {\n                right = axisx.max;\n                drawRight = false;\n            }\n\n            if (bottom < axisy.min) {\n                bottom = axisy.min;\n                drawBottom = false;\n            }\n\n            if (top > axisy.max) {\n                top = axisy.max;\n                drawTop = false;\n            }\n\n            left = axisx.p2c(left);\n            bottom = axisy.p2c(bottom);\n            right = axisx.p2c(right);\n            top = axisy.p2c(top);\n\n            // fill the bar\n            if (fillStyleCallback) {\n                c.fillStyle = fillStyleCallback(bottom, top);\n                c.fillRect(left, top, right - left, bottom - top)\n            }\n\n            // draw outline\n            if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {\n                c.beginPath();\n\n                // FIXME: inline moveTo is buggy with excanvas\n                c.moveTo(left, bottom);\n                if (drawLeft) {\n                    c.lineTo(left, top);\n                } else {\n                    c.moveTo(left, top);\n                }\n\n                if (drawTop) {\n                    c.lineTo(right, top);\n                } else {\n                    c.moveTo(right, top);\n                }\n\n                if (drawRight) {\n                    c.lineTo(right, bottom);\n                } else {\n                    c.moveTo(right, bottom);\n                }\n\n                if (drawBottom) {\n                    c.lineTo(left, bottom);\n                } else {\n                    c.moveTo(left, bottom);\n                }\n\n                c.stroke();\n            }\n        }\n\n        /**\n        - drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)\n\n         This function is used for drawing series represented as bars. In case the series has decimation\n         function attached, before starting to draw, as an optimization the points will first be decimated.\n\n         The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and\n         plotHeight are the corresponding parameters of flot used to determine the drawing surface.\n         The function getColorOrGradient is used to compute the fill style of bars.\n        */\n        function drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {\n            function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {\n                var points = datapoints.points,\n                    ps = datapoints.pointsize,\n                    fillTowards = series.bars.fillTowards || 0,\n                    defaultBottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min;\n\n                for (var i = 0; i < points.length; i += ps) {\n                    if (points[i] == null) {\n                        continue;\n                    }\n\n                    // Use third point as bottom if pointsize is 3\n                    var bottom = ps === 3 ? points[i + 2] : defaultBottom;\n                    drawBar(points[i], points[i + 1], bottom, barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);\n                }\n            }\n\n            ctx.save();\n            ctx.translate(plotOffset.left, plotOffset.top);\n\n            var datapoints = {\n                format: series.datapoints.format,\n                points: series.datapoints.points,\n                pointsize: series.datapoints.pointsize\n            };\n\n            if (series.decimate) {\n                datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth);\n            }\n\n            ctx.lineWidth = series.bars.lineWidth;\n            ctx.strokeStyle = series.color;\n\n            var barLeft;\n            var barWidth = series.bars.barWidth[0] || series.bars.barWidth;\n            switch (series.bars.align) {\n                case \"left\":\n                    barLeft = 0;\n                    break;\n                case \"right\":\n                    barLeft = -barWidth;\n                    break;\n                default:\n                    barLeft = -barWidth / 2;\n            }\n\n            var fillStyleCallback = series.bars.fill ? function(bottom, top) {\n                return getFillStyle(series.bars, series.color, bottom, top, getColorOrGradient);\n            } : null;\n\n            plotBars(datapoints, barLeft, barLeft + barWidth, fillStyleCallback, series.xaxis, series.yaxis);\n            ctx.restore();\n        }\n\n        function getFillStyle(filloptions, seriesColor, bottom, top, getColorOrGradient) {\n            var fill = filloptions.fill;\n            if (!fill) {\n                return null;\n            }\n\n            if (filloptions.fillColor) {\n                return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);\n            }\n\n            var c = $.color.parse(seriesColor);\n            c.a = typeof fill === \"number\" ? fill : 0.4;\n            c.normalize();\n            return c.toString();\n        }\n\n        this.drawSeriesLines = drawSeriesLines;\n        this.drawSeriesPoints = drawSeriesPoints;\n        this.drawSeriesBars = drawSeriesBars;\n        this.drawBar = drawBar;\n    };\n\n    $.plot.drawSeries = new DrawSeries();\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.errorbars.js",
    "content": "/* Flot plugin for plotting error bars.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nError bars are used to show standard deviation and other statistical\nproperties in a plot.\n\n* Created by Rui Pereira  -  rui (dot) pereira (at) gmail (dot) com\n\nThis plugin allows you to plot error-bars over points. Set \"errorbars\" inside\nthe points series to the axis name over which there will be error values in\nyour data array (*even* if you do not intend to plot them later, by setting\n\"show: null\" on xerr/yerr).\n\nThe plugin supports these options:\n\n    series: {\n        points: {\n            errorbars: \"x\" or \"y\" or \"xy\",\n            xerr: {\n                show: null/false or true,\n                asymmetric: null/false or true,\n                upperCap: null or \"-\" or function,\n                lowerCap: null or \"-\" or function,\n                color: null or color,\n                radius: null or number\n            },\n            yerr: { same options as xerr }\n        }\n    }\n\nEach data point array is expected to be of the type:\n\n    \"x\"  [ x, y, xerr ]\n    \"y\"  [ x, y, yerr ]\n    \"xy\" [ x, y, xerr, yerr ]\n\nWhere xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and\nequivalently for yerr. Eg., a datapoint for the \"xy\" case with symmetric\nerror-bars on X and asymmetric on Y would be:\n\n    [ x, y, xerr, yerr_lower, yerr_upper ]\n\nBy default no end caps are drawn. Setting upperCap and/or lowerCap to \"-\" will\ndraw a small cap perpendicular to the error bar. They can also be set to a\nuser-defined drawing function, with (ctx, x, y, radius) as parameters, as eg.\n\n    function drawSemiCircle( ctx, x, y, radius ) {\n        ctx.beginPath();\n        ctx.arc( x, y, radius, 0, Math.PI, false );\n        ctx.moveTo( x - radius, y );\n        ctx.lineTo( x + radius, y );\n        ctx.stroke();\n    }\n\nColor and radius both default to the same ones of the points series if not\nset. The independent radius parameter on xerr/yerr is useful for the case when\nwe may want to add error-bars to a line, without showing the interconnecting\npoints (with radius: 0), and still showing end caps on the error-bars.\nshadowSize and lineWidth are derived as well from the points series.\n\n*/\n\n(function ($) {\n    var options = {\n        series: {\n            points: {\n                errorbars: null, //should be 'x', 'y' or 'xy'\n                xerr: {err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},\n                yerr: {err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}\n            }\n        }\n    };\n\n    function processRawData(plot, series, data, datapoints) {\n        if (!series.points.errorbars) {\n            return;\n        }\n\n        // x,y values\n        var format = [\n            { x: true, number: true, required: true },\n            { y: true, number: true, required: true }\n        ];\n\n        var errors = series.points.errorbars;\n        // error bars - first X then Y\n        if (errors === 'x' || errors === 'xy') {\n            // lower / upper error\n            if (series.points.xerr.asymmetric) {\n                format.push({ x: true, number: true, required: true });\n                format.push({ x: true, number: true, required: true });\n            } else {\n                format.push({ x: true, number: true, required: true });\n            }\n        }\n        if (errors === 'y' || errors === 'xy') {\n            // lower / upper error\n            if (series.points.yerr.asymmetric) {\n                format.push({ y: true, number: true, required: true });\n                format.push({ y: true, number: true, required: true });\n            } else {\n                format.push({ y: true, number: true, required: true });\n            }\n        }\n        datapoints.format = format;\n    }\n\n    function parseErrors(series, i) {\n        var points = series.datapoints.points;\n\n        // read errors from points array\n        var exl = null,\n            exu = null,\n            eyl = null,\n            eyu = null;\n        var xerr = series.points.xerr,\n            yerr = series.points.yerr;\n\n        var eb = series.points.errorbars;\n        // error bars - first X\n        if (eb === 'x' || eb === 'xy') {\n            if (xerr.asymmetric) {\n                exl = points[i + 2];\n                exu = points[i + 3];\n                if (eb === 'xy') {\n                    if (yerr.asymmetric) {\n                        eyl = points[i + 4];\n                        eyu = points[i + 5];\n                    } else {\n                        eyl = points[i + 4];\n                    }\n                }\n            } else {\n                exl = points[i + 2];\n                if (eb === 'xy') {\n                    if (yerr.asymmetric) {\n                        eyl = points[i + 3];\n                        eyu = points[i + 4];\n                    } else {\n                        eyl = points[i + 3];\n                    }\n                }\n            }\n        // only Y\n        } else {\n            if (eb === 'y') {\n                if (yerr.asymmetric) {\n                    eyl = points[i + 2];\n                    eyu = points[i + 3];\n                } else {\n                    eyl = points[i + 2];\n                }\n            }\n        }\n\n        // symmetric errors?\n        if (exu == null) exu = exl;\n        if (eyu == null) eyu = eyl;\n\n        var errRanges = [exl, exu, eyl, eyu];\n        // nullify if not showing\n        if (!xerr.show) {\n            errRanges[0] = null;\n            errRanges[1] = null;\n        }\n        if (!yerr.show) {\n            errRanges[2] = null;\n            errRanges[3] = null;\n        }\n        return errRanges;\n    }\n\n    function drawSeriesErrors(plot, ctx, s) {\n        var points = s.datapoints.points,\n            ps = s.datapoints.pointsize,\n            ax = [s.xaxis, s.yaxis],\n            radius = s.points.radius,\n            err = [s.points.xerr, s.points.yerr],\n            tmp;\n\n        //sanity check, in case some inverted axis hack is applied to flot\n        var invertX = false;\n        if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {\n            invertX = true;\n            tmp = err[0].lowerCap;\n            err[0].lowerCap = err[0].upperCap;\n            err[0].upperCap = tmp;\n        }\n\n        var invertY = false;\n        if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {\n            invertY = true;\n            tmp = err[1].lowerCap;\n            err[1].lowerCap = err[1].upperCap;\n            err[1].upperCap = tmp;\n        }\n\n        for (var i = 0; i < s.datapoints.points.length; i += ps) {\n            //parse\n            var errRanges = parseErrors(s, i);\n\n            //cycle xerr & yerr\n            for (var e = 0; e < err.length; e++) {\n                var minmax = [ax[e].min, ax[e].max];\n\n                //draw this error?\n                if (errRanges[e * err.length]) {\n                    //data coordinates\n                    var x = points[i],\n                        y = points[i + 1];\n\n                    //errorbar ranges\n                    var upper = [x, y][e] + errRanges[e * err.length + 1],\n                        lower = [x, y][e] - errRanges[e * err.length];\n\n                    //points outside of the canvas\n                    if (err[e].err === 'x') {\n                        if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) {\n                            continue;\n                        }\n                    }\n\n                    if (err[e].err === 'y') {\n                        if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) {\n                            continue;\n                        }\n                    }\n\n                    // prevent errorbars getting out of the canvas\n                    var drawUpper = true,\n                        drawLower = true;\n\n                    if (upper > minmax[1]) {\n                        drawUpper = false;\n                        upper = minmax[1];\n                    }\n                    if (lower < minmax[0]) {\n                        drawLower = false;\n                        lower = minmax[0];\n                    }\n\n                    //sanity check, in case some inverted axis hack is applied to flot\n                    if ((err[e].err === 'x' && invertX) || (err[e].err === 'y' && invertY)) {\n                        //swap coordinates\n                        tmp = lower;\n                        lower = upper;\n                        upper = tmp;\n                        tmp = drawLower;\n                        drawLower = drawUpper;\n                        drawUpper = tmp;\n                        tmp = minmax[0];\n                        minmax[0] = minmax[1];\n                        minmax[1] = tmp;\n                    }\n\n                    // convert to pixels\n                    x = ax[0].p2c(x);\n                    y = ax[1].p2c(y);\n                    upper = ax[e].p2c(upper);\n                    lower = ax[e].p2c(lower);\n                    minmax[0] = ax[e].p2c(minmax[0]);\n                    minmax[1] = ax[e].p2c(minmax[1]);\n\n                    //same style as points by default\n                    var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth,\n                        sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize;\n\n                    //shadow as for points\n                    if (lw > 0 && sw > 0) {\n                        var w = sw / 2;\n                        ctx.lineWidth = w;\n                        ctx.strokeStyle = \"rgba(0,0,0,0.1)\";\n                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w / 2, minmax);\n\n                        ctx.strokeStyle = \"rgba(0,0,0,0.2)\";\n                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w / 2, minmax);\n                    }\n\n                    ctx.strokeStyle = err[e].color\n                        ? err[e].color\n                        : s.color;\n                    ctx.lineWidth = lw;\n                    //draw it\n                    drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);\n                }\n            }\n        }\n    }\n\n    function drawError(ctx, err, x, y, upper, lower, drawUpper, drawLower, radius, offset, minmax) {\n        //shadow offset\n        y += offset;\n        upper += offset;\n        lower += offset;\n\n        // error bar - avoid plotting over circles\n        if (err.err === 'x') {\n            if (upper > x + radius) drawPath(ctx, [[upper, y], [Math.max(x + radius, minmax[0]), y]]);\n            else drawUpper = false;\n\n            if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius, minmax[1]), y], [lower, y]]);\n            else drawLower = false;\n        } else {\n            if (upper < y - radius) drawPath(ctx, [[x, upper], [x, Math.min(y - radius, minmax[0])]]);\n            else drawUpper = false;\n\n            if (lower > y + radius) drawPath(ctx, [[x, Math.max(y + radius, minmax[1])], [x, lower]]);\n            else drawLower = false;\n        }\n\n        //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps\n        //this is a way to get errorbars on lines without visible connecting dots\n        radius = err.radius != null\n            ? err.radius\n            : radius;\n\n        // upper cap\n        if (drawUpper) {\n            if (err.upperCap === '-') {\n                if (err.err === 'x') drawPath(ctx, [[upper, y - radius], [upper, y + radius]]);\n                else drawPath(ctx, [[x - radius, upper], [x + radius, upper]]);\n            } else if ($.isFunction(err.upperCap)) {\n                if (err.err === 'x') err.upperCap(ctx, upper, y, radius);\n                else err.upperCap(ctx, x, upper, radius);\n            }\n        }\n        // lower cap\n        if (drawLower) {\n            if (err.lowerCap === '-') {\n                if (err.err === 'x') drawPath(ctx, [[lower, y - radius], [lower, y + radius]]);\n                else drawPath(ctx, [[x - radius, lower], [x + radius, lower]]);\n            } else if ($.isFunction(err.lowerCap)) {\n                if (err.err === 'x') err.lowerCap(ctx, lower, y, radius);\n                else err.lowerCap(ctx, x, lower, radius);\n            }\n        }\n    }\n\n    function drawPath(ctx, pts) {\n        ctx.beginPath();\n        ctx.moveTo(pts[0][0], pts[0][1]);\n        for (var p = 1; p < pts.length; p++) {\n            ctx.lineTo(pts[p][0], pts[p][1]);\n        }\n\n        ctx.stroke();\n    }\n\n    function draw(plot, ctx) {\n        var plotOffset = plot.getPlotOffset();\n\n        ctx.save();\n        ctx.translate(plotOffset.left, plotOffset.top);\n        $.each(plot.getData(), function (i, s) {\n            if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) {\n                drawSeriesErrors(plot, ctx, s);\n            }\n        });\n        ctx.restore();\n    }\n\n    function init(plot) {\n        plot.hooks.processRawData.push(processRawData);\n        plot.hooks.draw.push(draw);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'errorbars',\n        version: '1.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.fillbetween.js",
    "content": "/* Flot plugin for computing bottoms for filled line and bar charts.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe case: you've got two series that you want to fill the area between. In Flot\nterms, you need to use one as the fill bottom of the other. You can specify the\nbottom of each data point as the third coordinate manually, or you can use this\nplugin to compute it for you.\n\nIn order to name the other series, you need to give it an id, like this:\n\n    var dataset = [\n        { data: [ ... ], id: \"foo\" } ,         // use default bottom\n        { data: [ ... ], fillBetween: \"foo\" }, // use first dataset as bottom\n    ];\n\n    $.plot($(\"#placeholder\"), dataset, { lines: { show: true, fill: true }});\n\nAs a convenience, if the id given is a number that doesn't appear as an id in\nthe series, it is interpreted as the index in the array instead (so fillBetween:\n0 can also mean the first series).\n\nInternally, the plugin modifies the datapoints in each series. For line series,\nextra data points might be inserted through interpolation. Note that at points\nwhere the bottom line is not defined (due to a null point or start/end of line),\nthe current line will show a gap too. The algorithm comes from the\njquery.flot.stack.js plugin, possibly some code could be shared.\n\n*/\n\n(function ($) {\n    var options = {\n        series: {\n            fillBetween: null // or number\n        }\n    };\n\n    function init(plot) {\n        function findBottomSeries(s, allseries) {\n            var i;\n\n            for (i = 0; i < allseries.length; ++i) {\n                if (allseries[ i ].id === s.fillBetween) {\n                    return allseries[ i ];\n                }\n            }\n\n            if (typeof s.fillBetween === \"number\") {\n                if (s.fillBetween < 0 || s.fillBetween >= allseries.length) {\n                    return null;\n                }\n                return allseries[ s.fillBetween ];\n            }\n\n            return null;\n        }\n\n        function computeFormat(plot, s, data, datapoints) {\n            if (s.fillBetween == null) {\n                return;\n            }\n\n            var format = datapoints.format;\n            var plotHasId = function(id) {\n                var plotData = plot.getData();\n                for (var i = 0; i < plotData.length; i++) {\n                    if (plotData[i].id === id) {\n                        return true;\n                    }\n                }\n\n                return false;\n            }\n\n            if (!format) {\n                format = [];\n\n                format.push({\n                    x: true,\n                    number: true,\n                    computeRange: s.xaxis.options.autoScale !== 'none',\n                    required: true\n                });\n                format.push({\n                    y: true,\n                    number: true,\n                    computeRange: s.yaxis.options.autoScale !== 'none',\n                    required: true\n                });\n\n                if (s.fillBetween !== undefined && s.fillBetween !== '' && plotHasId(s.fillBetween) && s.fillBetween !== s.id) {\n                    format.push({\n                        x: false,\n                        y: true,\n                        number: true,\n                        required: false,\n                        computeRange: s.yaxis.options.autoScale !== 'none',\n                        defaultValue: 0\n                    });\n                }\n\n                datapoints.format = format;\n            }\n        }\n\n        function computeFillBottoms(plot, s, datapoints) {\n            if (s.fillBetween == null) {\n                return;\n            }\n\n            var other = findBottomSeries(s, plot.getData());\n\n            if (!other) {\n                return;\n            }\n\n            var ps = datapoints.pointsize,\n                points = datapoints.points,\n                otherps = other.datapoints.pointsize,\n                otherpoints = other.datapoints.points,\n                newpoints = [],\n                px, py, intery, qx, qy, bottom,\n                withlines = s.lines.show,\n                withbottom = ps > 2 && datapoints.format[2].y,\n                withsteps = withlines && s.lines.steps,\n                fromgap = true,\n                i = 0,\n                j = 0,\n                l, m;\n\n            while (true) {\n                if (i >= points.length) {\n                    break;\n                }\n\n                l = newpoints.length;\n\n                if (points[ i ] == null) {\n                    // copy gaps\n                    for (m = 0; m < ps; ++m) {\n                        newpoints.push(points[ i + m ]);\n                    }\n\n                    i += ps;\n                } else if (j >= otherpoints.length) {\n                    // for lines, we can't use the rest of the points\n                    if (!withlines) {\n                        for (m = 0; m < ps; ++m) {\n                            newpoints.push(points[ i + m ]);\n                        }\n                    }\n\n                    i += ps;\n                } else if (otherpoints[ j ] == null) {\n                    // oops, got a gap\n                    for (m = 0; m < ps; ++m) {\n                        newpoints.push(null);\n                    }\n\n                    fromgap = true;\n                    j += otherps;\n                } else {\n                    // cases where we actually got two points\n                    px = points[ i ];\n                    py = points[ i + 1 ];\n                    qx = otherpoints[ j ];\n                    qy = otherpoints[ j + 1 ];\n                    bottom = 0;\n\n                    if (px === qx) {\n                        for (m = 0; m < ps; ++m) {\n                            newpoints.push(points[ i + m ]);\n                        }\n\n                        //newpoints[ l + 1 ] += qy;\n                        bottom = qy;\n\n                        i += ps;\n                        j += otherps;\n                    } else if (px > qx) {\n                        // we got past point below, might need to\n                        // insert interpolated extra point\n\n                        if (withlines && i > 0 && points[ i - ps ] != null) {\n                            intery = py + (points[ i - ps + 1 ] - py) * (qx - px) / (points[ i - ps ] - px);\n                            newpoints.push(qx);\n                            newpoints.push(intery);\n                            for (m = 2; m < ps; ++m) {\n                                newpoints.push(points[ i + m ]);\n                            }\n                            bottom = qy;\n                        }\n\n                        j += otherps;\n                    } else {\n                        // px < qx\n                        // if we come from a gap, we just skip this point\n\n                        if (fromgap && withlines) {\n                            i += ps;\n                            continue;\n                        }\n\n                        for (m = 0; m < ps; ++m) {\n                            newpoints.push(points[ i + m ]);\n                        }\n\n                        // we might be able to interpolate a point below,\n                        // this can give us a better y\n\n                        if (withlines && j > 0 && otherpoints[ j - otherps ] != null) {\n                            bottom = qy + (otherpoints[ j - otherps + 1 ] - qy) * (px - qx) / (otherpoints[ j - otherps ] - qx);\n                        }\n\n                        //newpoints[l + 1] += bottom;\n\n                        i += ps;\n                    }\n\n                    fromgap = false;\n\n                    if (l !== newpoints.length && withbottom) {\n                        newpoints[ l + 2 ] = bottom;\n                    }\n                }\n\n                // maintain the line steps invariant\n\n                if (withsteps && l !== newpoints.length && l > 0 &&\n                    newpoints[ l ] !== null &&\n                    newpoints[ l ] !== newpoints[ l - ps ] &&\n                    newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ]) {\n                    for (m = 0; m < ps; ++m) {\n                        newpoints[ l + ps + m ] = newpoints[ l + m ];\n                    }\n                    newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];\n                }\n            }\n\n            datapoints.points = newpoints;\n        }\n\n        plot.hooks.processRawData.push(computeFormat);\n        plot.hooks.processDatapoints.push(computeFillBottoms);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: \"fillbetween\",\n        version: \"1.0\"\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.flatdata.js",
    "content": "/* Support for flat 1D data series.\n\nA 1D flat data series is a data series in the form of a regular 1D array. The\nmain reason for using a flat data series is that it performs better, consumes\nless memory and generates less garbage collection than the regular flot format.\n\nExample:\n\n    plot.setData([[[0,0], [1,1], [2,2], [3,3]]]); // regular flot format\n    plot.setData([{flatdata: true, data: [0, 1, 2, 3]}]); // flatdata format\n\nSet series.flatdata to true to enable this plugin.\n\nYou can use series.start to specify the starting index of the series (default is 0)\nYou can use series.step to specify the interval between consecutive indexes of the series (default is 1)\n*/\n\n/* global jQuery*/\n\n(function ($) {\n    'use strict';\n\n    function process1DRawData(plot, series, data, datapoints) {\n        if (series.flatdata === true) {\n            var start = series.start || 0;\n            var step = typeof series.step === 'number' ? series.step : 1;\n            datapoints.pointsize = 2;\n            for (var i = 0, j = 0; i < data.length; i++, j += 2) {\n                datapoints.points[j] = start + (i * step);\n                datapoints.points[j + 1] = data[i];\n            }\n            if (datapoints.points !== undefined) {\n                datapoints.points.length = data.length * 2;\n            } else {\n                datapoints.points = [];\n            }\n        }\n    }\n\n    $.plot.plugins.push({\n        init: function(plot) {\n            plot.hooks.processRawData.push(process1DRawData);\n        },\n        name: 'flatdata',\n        version: '0.0.2'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.gauge.js",
    "content": "/*!\n * jquery.flot.gauge v1.1.0 *\n *\n * Flot plugin for rendering gauge charts.\n *\n * Copyright (c) 2015 @toyoty99.\n * Licensed under the MIT license.\n */\n\n/**\n * @module flot.gauge\n */\n(function($) {\n    /**\n     * Logger class\n     *\n     * @class Logger\n     */\n    var Logger = (function() {\n        /**\n         * constructor\n         *\n         * @class Logger\n         * @constructor\n         * @param {Object} debugOptions debug options\n         */\n        var Logger = function(debugOptions) {\n            var log;\n            // create log function\n            if (debugOptions.log) {\n                if (window.console && console.log) {\n                    if (console.log.bind) {\n                        log = console.log.bind(console);\n                    } else {\n                        log = function() {\n                            var text = Array.prototype.join.apply(arguments, [\"\"]);\n                            console.log(text);\n                        }\n                    }\n                } else if (debugOptions.alert) {\n                    log = function() {\n                        var text = Array.prototype.join.apply(arguments, [\"\"]);\n                        alert(text);\n                    }\n                } else {\n                    log = function(){};\n                }\n            } else {\n                log = function(){};\n            }\n            /**\n             * log\n             *\n             * @method log\n             * @param {Object} ...obj\n             */\n            Logger.prototype.log = log;\n        }\n\n        /**\n         * debug the layout\n         *\n         * @method debugLayout\n         * @param  {Object} context the context of canvas\n         * @param  {Number} seriesLength the length of series\n         * @param  {Object} layout the layout properties\n         */\n        Logger.prototype.debugLayout = function(context, seriesLength, layout) {\n            context.save();\n            context.strokeStyle = \"gray\";\n            context.lineWidth = 1;\n            context.strokeRect(0, 0, layout.canvasWidth, layout.canvasHeight);\n            for (var i = 0; i < seriesLength; i++) {\n                var c = col(layout.columns, i);\n                var r = row(layout.columns, i);\n                context.strokeRect(\n                    layout.margin + layout.cellWidth * c + layout.hMargin * c,\n                    layout.margin + layout.cellHeight * r + layout.vMargin * r,\n                    layout.cellWidth,\n                    layout.cellHeight);\n            }\n            context.restore();\n        }\n\n        /**\n         * debug the cell layout\n         *\n         * @method debugCellLayout\n         * @param  {Object} context the context of canvas\n         * @param  {Object} gaugeOptions the option of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Object} cellLayout the cell layout properties\n         */\n        Logger.prototype.debugCellLayout = function(context, gaugeOptions, layout, cellLayout) {\n            context.save();\n            context.strokeStyle = \"gray\";\n            context.lineWidth = 1;\n            // debug label layout\n            if (gaugeOptions.label.show) {\n                var labelMarginWidth = (cellLayout.cellWidth / 3) + (layout.labelMargin * 2);\n                var labelMarginHeight = layout.labelFontSize + (layout.labelMargin * 2);\n                var labelWidth = (cellLayout.cellWidth / 3);\n                var labelHeight = layout.labelFontSize;\n                context.strokeRect(\n                    cellLayout.cx - (labelMarginWidth / 2),\n                    cellLayout.y + cellLayout.cellMargin + cellLayout.offsetY,\n                    labelMarginWidth,\n                    labelMarginHeight);\n                context.strokeRect(\n                    cellLayout.cx - (labelWidth / 2),\n                    cellLayout.y + cellLayout.cellMargin + layout.labelMargin + cellLayout.offsetY,\n                    labelWidth,\n                    labelHeight);\n            }\n            // debug value layout\n            if (gaugeOptions.value.show) {\n                var valueMarginWidth = (cellLayout.cellWidth / 3) + (layout.valueMargin * 2);\n                var valueMarginHeight = layout.valueFontSize + (layout.valueMargin * 2);\n                var valueWidth = (cellLayout.cellWidth / 3);\n                var valueHeight = layout.valueFontSize;\n                context.strokeRect(\n                    cellLayout.cx - (valueMarginWidth / 2),\n                    cellLayout.cy - (valueMarginHeight / 2),\n                    valueMarginWidth,\n                    valueMarginHeight);\n                context.strokeRect(\n                    cellLayout.cx - (valueWidth / 2),\n                    cellLayout.cy - (valueHeight / 2),\n                    valueWidth,\n                    valueHeight);\n            }\n            // debug gauge center\n            context.strokeRect(cellLayout.cx, cellLayout.cy, 1, 1);\n            // debug gauge outer height\n            context.strokeRect(\n                cellLayout.x + cellLayout.cellMargin,\n                cellLayout.y + cellLayout.cellMargin + labelMarginHeight + cellLayout.offsetY,\n                cellLayout.cellWidth - (cellLayout.cellMargin * 2),\n                layout.gaugeOuterHeight);\n            // debug gauge layout\n            drawArc(\n                context,\n                cellLayout.cx,\n                cellLayout.cy,\n                layout.radius,\n                layout.width,\n                toRad(gaugeOptions.gauge.startAngle),\n                toRad(gaugeOptions.gauge.endAngle),\n                context.strokeStyle);\n            // debug threshold layout\n            if (gaugeOptions.threshold.show) {\n                drawArc(\n                    context,\n                    cellLayout.cx, cellLayout.cy,\n                    layout.radius + layout.thresholdWidth,\n                    layout.thresholdWidth,\n                    toRad(gaugeOptions.gauge.startAngle),\n                    toRad(gaugeOptions.gauge.endAngle),\n                    context.strokeStyle);\n            }\n            // debug threshold label layout\n            if (gaugeOptions.threshold.label.show) {\n                drawArc(\n                    context,\n                    cellLayout.cx,\n                    cellLayout.cy,\n                    layout.radius + layout.thresholdWidth + layout.thresholdLabelFontSize + (layout.thresholdLabelMargin * 2),\n                    layout.thresholdLabelFontSize + (layout.thresholdLabelMargin * 2),\n                    toRad(gaugeOptions.gauge.startAngle),\n                    toRad(gaugeOptions.gauge.endAngle),\n                    context.strokeStyle);\n            }\n            context.restore();\n        }\n\n        return Logger;\n    })();\n\n    /**\n     * Gauge class\n     *\n     * @class Gauge\n     */\n    var Gauge = (function() {\n        /**\n         * context of canvas\n         *\n         * @property context\n         * @type Object\n         */\n        var context;\n        /**\n         * placeholder of canvas\n         *\n         * @property placeholder\n         * @type Object\n         */\n        var placeholder;\n        /**\n         * options of plot\n         *\n         * @property options\n         * @type Object\n         */\n        var options;\n        /**\n         * options of gauge\n         *\n         * @property gaugeOptions\n         * @type Object\n         */\n        var gaugeOptions;\n        /**\n         * data series\n         *\n         * @property series\n         * @type Array\n         */\n        var series;\n        /**\n         * logger\n         *\n         * @property logger\n         * @type Object\n         */\n        var logger;\n\n        /**\n         * constructor\n         *\n         * @class Gauge\n         * @constructor\n         * @param  {Object} gaugeOptions gauge options\n         */\n        var Gauge = function(plot, ctx) {\n            context = ctx;\n            placeholder = plot.getPlaceholder();\n            options = plot.getOptions();\n            gaugeOptions = options.series.gauges;\n            series = plot.getData();\n            logger = getLogger(gaugeOptions.debug);\n        }\n\n        /**\n         * calculate layout\n         *\n         * @method calculateLayout\n         * @return the calculated layout properties\n         */\n        Gauge.prototype.calculateLayout = function() {\n            logger.log(\"flot.gauge.calculateLayout\");\n            var canvasWidth = placeholder.width();\n            var canvasHeight = placeholder.height();\n            logger.log(\"canvasWidth=\", canvasWidth);\n            logger.log(\"canvasHeight=\", canvasHeight);\n\n            // calculate cell size\n            var columns = Math.min(series.length, gaugeOptions.layout.columns);\n            var rows = Math.ceil(series.length / columns);\n            logger.log(\"columns=\", columns);\n            logger.log(\"rows=\", rows);\n\n            var margin = gaugeOptions.layout.margin;\n            var hMargin = gaugeOptions.layout.hMargin;\n            var vMargin = gaugeOptions.layout.vMargin;\n            var cellWidth = (canvasWidth - (margin * 2) - (hMargin * (columns - 1))) / columns;\n            var cellHeight = (canvasHeight - (margin * 2) - (vMargin * (rows - 1))) / rows;\n            if (gaugeOptions.layout.square) {\n                var cell = Math.min(cellWidth, cellHeight);\n                cellWidth = cell;\n                cellHeight = cell;\n            }\n            logger.log(\"cellWidth=\", cellWidth);\n            logger.log(\"cellHeight=\", cellHeight);\n\n            // calculate 'auto' values\n            calculateAutoValues(gaugeOptions, cellWidth);\n\n            // calculate maximum radius\n            var cellMargin = gaugeOptions.cell.margin;\n            var labelMargin = 0;\n            var labelFontSize = 0;\n            if (gaugeOptions.label.show) {\n                labelMargin = gaugeOptions.label.margin;\n                labelFontSize = gaugeOptions.label.font.size;\n            }\n            var valueMargin = 0;\n            var valueFontSize = 0;\n            if (gaugeOptions.value.show) {\n                valueMargin = gaugeOptions.value.margin;\n                valueFontSize = gaugeOptions.value.font.size;\n            }\n            var thresholdWidth = 0;\n            if (gaugeOptions.threshold.show) {\n                thresholdWidth = gaugeOptions.threshold.width;\n            }\n            var thresholdLabelMargin = 0;\n            var thresholdLabelFontSize = 0;\n            if (gaugeOptions.threshold.label.show) {\n                thresholdLabelMargin = gaugeOptions.threshold.label.margin;\n                thresholdLabelFontSize = gaugeOptions.threshold.label.font.size;\n            }\n\n            var maxRadiusH = (cellWidth / 2) - cellMargin - thresholdWidth - (thresholdLabelMargin * 2) - thresholdLabelFontSize;\n\n            var startAngle = gaugeOptions.gauge.startAngle;\n            var endAngle = gaugeOptions.gauge.endAngle;\n            var dAngle = (endAngle - startAngle) / 100;\n            var heightRatioV = -1;\n            for (var a = startAngle; a < endAngle; a += dAngle) {\n                heightRatioV = Math.max(heightRatioV, Math.sin(toRad(a)));\n            }\n            heightRatioV = Math.max(heightRatioV, Math.sin(toRad(endAngle)));\n            var outerRadiusV = (cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize) / (1 + heightRatioV);\n            if (outerRadiusV * heightRatioV < valueMargin + (valueFontSize / 2)) {\n                outerRadiusV = cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize - valueMargin - (valueFontSize / 2);\n            }\n            var maxRadiusV = outerRadiusV - (thresholdLabelMargin * 2) - thresholdLabelFontSize - thresholdWidth;\n\n            var radius = Math.min(maxRadiusH, maxRadiusV);\n            logger.log(\"radius=\", radius);\n\n            var width = gaugeOptions.gauge.width;\n            if (width >= radius) {\n                width = Math.max(3, radius / 3);\n            }\n            logger.log(\"width=\", width);\n\n            var outerRadius = (thresholdLabelMargin * 2) + thresholdLabelFontSize + thresholdWidth + radius;\n            var gaugeOuterHeight = Math.max(outerRadius * (1 + heightRatioV), outerRadius + valueMargin + (valueFontSize / 2));\n\n            return {\n                canvasWidth: canvasWidth,\n                canvasHeight: canvasHeight,\n                margin: margin,\n                hMargin: hMargin,\n                vMargin: vMargin,\n                columns: columns,\n                rows: rows,\n                cellWidth: cellWidth,\n                cellHeight: cellHeight,\n                cellMargin: cellMargin,\n                labelMargin: labelMargin,\n                labelFontSize: labelFontSize,\n                valueMargin: valueMargin,\n                valueFontSize: valueFontSize,\n                width: width,\n                radius: radius,\n                thresholdWidth: thresholdWidth,\n                thresholdLabelMargin: thresholdLabelMargin,\n                thresholdLabelFontSize: thresholdLabelFontSize,\n                gaugeOuterHeight: gaugeOuterHeight\n            };\n        }\n\n        /**\n         * calculate the values which are set as 'auto'\n         *\n         * @method calculateAutoValues\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Number} cellWidth the width of cell\n         */\n        function calculateAutoValues(gaugeOptionsi, cellWidth) {\n            logger.log(\"flot.gauge.calculateAutoValues\");\n            if (gaugeOptionsi.gauge.width === \"auto\") {\n                gaugeOptionsi.gauge.width = Math.max(5, cellWidth / 8);\n            }\n            if (gaugeOptionsi.label.margin === \"auto\") {\n                gaugeOptionsi.label.margin = Math.max(1, cellWidth / 20);\n            }\n            if (gaugeOptionsi.label.font.size === \"auto\") {\n                gaugeOptionsi.label.font.size = Math.max(5, cellWidth / 8);\n            }\n            if (gaugeOptionsi.value.margin === \"auto\") {\n                gaugeOptionsi.value.margin = Math.max(1, cellWidth / 30);\n            }\n            if (gaugeOptionsi.value.font.size === \"auto\") {\n                gaugeOptionsi.value.font.size = Math.max(5, cellWidth / 9);\n            }\n            if (gaugeOptionsi.threshold.width === \"auto\") {\n                gaugeOptionsi.threshold.width = Math.max(3, cellWidth / 100);\n            }\n            if (gaugeOptionsi.threshold.label.margin === \"auto\") {\n                gaugeOptionsi.threshold.label.margin = Math.max(3, cellWidth / 40);\n            }\n            if (gaugeOptionsi.threshold.label.font.size === \"auto\") {\n                gaugeOptionsi.threshold.label.font.size = Math.max(5, cellWidth / 15);\n            }\n            logger.log(\"gaugeOptions=\", gaugeOptionsi);\n        }\n        Gauge.prototype.calculateAutoValues = calculateAutoValues;\n\n        /**\n         * calculate the layout of the cell inside\n         *\n         * @method calculateCellLayout\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Number} cellWidth the width of cell\n         * @param  {Number} i the index of the series\n         * @return the calculated cell layout properties\n         */\n        Gauge.prototype.calculateCellLayout = function(gaugeOptionsi, layout, i) {\n            logger.log(\"flot.gauge.calculateCellLayout\");\n            // calculate top, left and center\n            var c = col(layout.columns, i);\n            var r = row(layout.columns, i);\n            var x = layout.margin + (layout.cellWidth + layout.hMargin) * c;\n            var y = layout.margin + (layout.cellHeight + layout.vMargin) * r;\n            var cx = x + (layout.cellWidth / 2);\n            var cy = y + layout.cellMargin + (layout.labelMargin * 2) + layout.labelFontSize + layout.thresholdWidth\n                        + layout.thresholdLabelFontSize + (layout.thresholdLabelMargin * 2) + layout.radius;\n            var blank = layout.cellHeight - (layout.cellMargin * 2) - (layout.labelMargin * 2) - layout.labelFontSize - layout.gaugeOuterHeight;\n            var offsetY = 0;\n            if (gaugeOptionsi.cell.vAlign === \"middle\") {\n                offsetY = (blank / 2);\n            } else if (gaugeOptionsi.cell.vAlign === \"bottom\") {\n                offsetY = blank;\n            }\n            cy += offsetY;\n\n            return {\n                col: c,\n                row: r,\n                x: x,\n                y: y,\n                offsetY: offsetY,\n                cellWidth: layout.cellWidth,\n                cellHeight: layout.cellHeight,\n                cellMargin: layout.cellMargin,\n                cx: cx,\n                cy: cy\n            }\n        }\n\n        /**\n         * draw the background of chart\n         *\n         * @method drawBackground\n         * @param  {Object} layout the layout properties\n         */\n        Gauge.prototype.drawBackground = function(layout) {\n            logger.log(\"flot.gauge.drawBackground\");\n            if (!gaugeOptions.frame.show) {\n                return;\n            }\n            context.save();\n            context.strokeStyle = options.grid.borderColor;\n            context.lineWidth = options.grid.borderWidth;\n            context.strokeRect(0, 0, layout.canvasWidth, layout.canvasHeight);\n            if (options.grid.backgroundColor) {\n                context.fillStyle = options.grid.backgroundColor;\n                context.fillRect(0, 0, layout.canvasWidth, layout.canvasHeight);\n            }\n            context.restore();\n        }\n\n        /**\n         * draw the background of cell\n         *\n         * @method drawCellBackground\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} cellLayout the cell layout properties\n         */\n        Gauge.prototype.drawCellBackground = function(gaugeOptionsi, cellLayout) {\n            logger.log(\"flot.gauge.drawCellBackground\");\n            context.save();\n            if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.show && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) {\n                context.strokeStyle = gaugeOptionsi.cell.border.color;\n                context.lineWidth = gaugeOptionsi.cell.border.width;\n                context.strokeRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight);\n            }\n            if (gaugeOptionsi.cell.background && gaugeOptionsi.cell.background.color) {\n                context.fillStyle = gaugeOptionsi.cell.background.color;\n                context.fillRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight);\n            }\n            context.restore();\n        }\n\n        /**\n         * draw the gauge\n         *\n         * @method drawGauge\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Object} cellLayout the cell layout properties\n         * @param  {String} label the label of data\n         * @param  {Number} data the value of the gauge\n         */\n        Gauge.prototype.drawGauge = function(gaugeOptionsi, layout, cellLayout, label, data) {\n            logger.log(\"flot.gauge.drawGauge\");\n\n            var blur = gaugeOptionsi.gauge.shadow.show ? gaugeOptionsi.gauge.shadow.blur : 0;\n            logger.log(\"blur=\", blur);\n\n            // draw gauge frame\n            drawArcWithShadow(\n                cellLayout.cx, // center x\n                cellLayout.cy, // center y\n                layout.radius,\n                layout.width,\n                toRad(gaugeOptionsi.gauge.startAngle),\n                toRad(gaugeOptionsi.gauge.endAngle),\n                gaugeOptionsi.gauge.border.color,      // line color\n                gaugeOptionsi.gauge.border.width,      // line width\n                gaugeOptionsi.gauge.background.color,  // fill color\n                blur);\n\n            // draw gauge\n            var c1 = getColor(gaugeOptionsi, data);\n            var a2 = calculateAngle(gaugeOptionsi, layout, data);\n            drawArcWithShadow(\n                cellLayout.cx, // center x\n                cellLayout.cy, // center y\n                layout.radius - 1,\n                layout.width - 2,\n                toRad(gaugeOptionsi.gauge.startAngle),\n                toRad(a2),\n                c1,           // line color\n                1,            // line width\n                c1,           // fill color\n                blur);\n        }\n\n        /**\n         * decide the color of the data from the threshold options\n         *\n         * @method getColor\n         * @private\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Number} data the value of the gauge\n         */\n        function getColor(gaugeOptionsi, data) {\n            var color;\n            for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) {\n                var threshold = gaugeOptionsi.threshold.values[i];\n                color = threshold.color;\n                if (data <= threshold.value) {\n                    break;\n                }\n            }\n            return color;\n        }\n\n        /**\n         * calculate the angle of the data\n         *\n         * @method calculateAngle\n         * @private\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Number} data the value of the gauge\n         */\n        function calculateAngle(gaugeOptionsi, layout, data) {\n            var a =\n                gaugeOptionsi.gauge.startAngle\n                    + (gaugeOptionsi.gauge.endAngle - gaugeOptionsi.gauge.startAngle)\n                        * ((data - gaugeOptionsi.gauge.min) / (gaugeOptionsi.gauge.max - gaugeOptionsi.gauge.min));\n\n            if (a < gaugeOptionsi.gauge.startAngle) {\n                a = gaugeOptionsi.gauge.startAngle;\n            } else if (a > gaugeOptionsi.gauge.endAngle) {\n                a = gaugeOptionsi.gauge.endAngle;\n            }\n            return a;\n        }\n\n        /**\n         * draw the arc of the threshold\n         *\n         * @method drawThreshold\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Object} cellLayout the cell layout properties\n         */\n        Gauge.prototype.drawThreshold = function(gaugeOptionsi, layout, cellLayout) {\n            logger.log(\"flot.gauge.drawThreshold\");\n            var a1 = gaugeOptionsi.gauge.startAngle;\n            for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) {\n                var threshold = gaugeOptionsi.threshold.values[i];\n                c1 = threshold.color;\n                a2 = calculateAngle(gaugeOptionsi, layout, threshold.value);\n                drawArc(\n                    context,\n                    cellLayout.cx, // center x\n                    cellLayout.cy, // center y\n                    layout.radius + layout.thresholdWidth,\n                    layout.thresholdWidth - 2,\n                    toRad(a1),\n                    toRad(a2),\n                    c1,           // line color\n                    1,            // line width\n                    c1);          // fill color\n                a1 = a2;\n            }\n        }\n\n        /**\n         * draw an arc with a shadow\n         *\n         * @method drawArcWithShadow\n         * @private\n         * @param  {Number} cx the x position of the center\n         * @param  {Number} cy the y position of the center\n         * @param  {Number} r the radius of an arc\n         * @param  {Number} w the width of an arc\n         * @param  {Number} rd1 the start angle of an arc in radians\n         * @param  {Number} rd2 the end angle of an arc in radians\n         * @param  {String} lc the color of a line\n         * @param  {Number} lw the widht of a line\n         * @param  {String} fc the fill color  of an arc\n         * @param  {Number} blur the shdow blur\n         */\n        function drawArcWithShadow(cx, cy, r, w, rd1, rd2, lc, lw, fc, blur) {\n            if (rd1 === rd2) {\n                return;\n            }\n            context.save();\n\n            drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc);\n\n            if (blur) {\n                drawArc(context, cx, cy, r, w, rd1, rd2);\n                context.clip();\n                context.shadowOffsetX = 0;\n                context.shadowOffsetY = 0;\n                context.shadowBlur = 10;\n                context.shadowColor = \"gray\";\n                drawArc(context, cx, cy, r + 1, w + 2, rd1, rd2, lc, 1);\n            }\n            context.restore();\n        }\n\n        /**\n         * draw the label of the gauge\n         *\n         * @method drawLable\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Object} cellLayout the cell layout properties\n         * @param  {Number} i the index of the series\n         * @param  {Object} item the item of the series\n         */\n        Gauge.prototype.drawLable = function(gaugeOptionsi, layout, cellLayout, i, item) {\n            logger.log(\"flot.gauge.drawLable\");\n            drawText(\n                cellLayout.cx,\n                cellLayout.y + cellLayout.cellMargin + layout.labelMargin + cellLayout.offsetY,\n                \"flotGagueLabel\" + i,\n                gaugeOptionsi.label.formatter ? gaugeOptionsi.label.formatter(item.label, item.data[0][1]) : text,\n                gaugeOptionsi.label);\n        }\n\n        /**\n         * draw the value of the gauge\n         *\n         * @method drawValue\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Object} cellLayout the cell layout properties\n         * @param  {Number} i the index of the series\n         * @param  {Object} item the item of the series\n         */\n        Gauge.prototype.drawValue = function(gaugeOptionsi, layout, cellLayout, i, item) {\n            logger.log(\"flot.gauge.drawValue\");\n            drawText(\n                cellLayout.cx,\n                cellLayout.cy - (gaugeOptionsi.value.font.size / 2),\n                \"flotGagueValue\" + i,\n                gaugeOptionsi.value.formatter ? gaugeOptionsi.value.formatter(item.label, item.data[0][1]) : text,\n                gaugeOptionsi.value);\n        }\n\n        /**\n         * draw the values of the threshold\n         *\n         * @method drawThresholdValues\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Object} cellLayout the cell layout properties\n         * @param  {Number} i the index of the series\n         */\n        Gauge.prototype.drawThresholdValues = function(gaugeOptionsi, layout, cellLayout, i) {\n            logger.log(\"flot.gauge.drawThresholdValues\");\n            // min, max\n            drawThresholdValue(gaugeOptionsi, layout, cellLayout, \"Min\" + i, gaugeOptionsi.gauge.min, gaugeOptionsi.gauge.startAngle);\n            drawThresholdValue(gaugeOptionsi, layout, cellLayout, \"Max\" + i, gaugeOptionsi.gauge.max, gaugeOptionsi.gauge.endAngle);\n            // threshold values\n            for (var j = 0; j < gaugeOptionsi.threshold.values.length; j++) {\n                var threshold = gaugeOptionsi.threshold.values[j];\n                if (threshold.value > gaugeOptionsi.gauge.min && threshold.value < gaugeOptionsi.gauge.max) {\n                    var a = calculateAngle(gaugeOptionsi, layout, threshold.value);\n                    drawThresholdValue(gaugeOptionsi, layout, cellLayout, i + \"_\" + j, threshold.value, a);\n                }\n            }\n        }\n\n        /**\n         * draw the value of the threshold\n         *\n         * @method drawThresholdValue\n         * @param  {Object} gaugeOptionsi the options of the gauge\n         * @param  {Object} layout the layout properties\n         * @param  {Object} cellLayout the cell layout properties\n         * @param  {Number} i the index of the series\n         * @param  {Number} value the value of the threshold\n         * @param  {Number} a the angle of the value drawn\n         */\n        function drawThresholdValue(gaugeOptionsi, layout, cellLayout, i, value, a) {\n            drawText(\n                cellLayout.cx\n                    + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius)\n                        * Math.cos(toRad(a))),\n                cellLayout.cy\n                    + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius)\n                        * Math.sin(toRad(a))),\n                \"flotGagueThresholdValue\" + i,\n                gaugeOptionsi.threshold.label.formatter ? gaugeOptionsi.threshold.label.formatter(value) : value,\n                gaugeOptionsi.threshold.label,\n                a);\n        }\n\n        /**\n         * draw a text\n         *\n         * the textOptions is assumed as follows:\n         *\n         *   textOptions: {\n         *       background: {\n         *           color: null,\n         *           opacity: 0\n         *       },\n         *       font: {\n         *           size: \"auto\"\n         *           family: \"\\\"ＭＳ ゴシック\\\",sans-serif\"\n         *       },\n         *       color: null\n         *   }\n         *\n         * @method drawText\n         * @private\n         * @param  {Number} x the x position of the text drawn (left top)\n         * @param  {Number} y the y position of the text drawn (left top)\n         * @param  {String} id the id of the dom element\n         * @param  {String} text the text drawn\n         * @param  {Object} textOptions the option of the text\n         * @param  {Number} [a] the angle of the value drawn\n         */\n        function drawText(x, y, id, text, textOptions, a) {\n            var span = $(placeholder).find(\"#\" + id);\n            var exists = span.length;\n            if (!exists) {\n                span = $(\"<span></span>\")\n                span.attr(\"id\", id);\n                span.css(\"position\", \"absolute\");\n                span.css(\"top\", y + \"px\");\n                if (textOptions.font.size) {\n                    span.css(\"font-size\", textOptions.font.size + \"px\");\n                }\n                if (textOptions.font.family) {\n                    span.css(\"font-family\", textOptions.font.family);\n                }\n                if (textOptions.color) {\n                    span.css(\"color\", textOptions.color);\n                }\n                if (textOptions.background.color) {\n                    span.css(\"background-color\", textOptions.background.color);\n                }\n                if (textOptions.background.opacity) {\n                    span.css(\"opacity\", textOptions.background.opacity);\n                }\n                placeholder.append(span);\n            }\n            span.text(text);\n            // after append, readjust the left position\n            span.css(\"left\", x + \"px\"); // for redraw, resetting the left position is needed here\n            span.css(\"left\", (parseInt(span.css(\"left\")) - (span.width()/ 2)) + \"px\");\n\n            // at last, set angle\n            if (!exists && a) {\n                span.css(\"top\", (parseInt(span.css(\"top\")) - (span.height()/ 2)) + \"px\");\n                span.css(\"transform\", \"rotate(\" + ((180 * a) + 90) + \"deg)\"); // not supported for ie8\n            }\n        }\n\n        return Gauge;\n    })();\n    /**\n     * get a instance of Logger\n     *\n     * @method  getLogger\n     * @for flot.gauge\n     * @private\n     * @param  {Object} debugOptions the options of debug\n     */\n    function getLogger(debugOptions) {\n        return typeof Logger !== \"undefined\" ? new Logger(debugOptions) : null;\n    }\n\n    /**\n     * calculate the index of columns for the specified data\n     *\n     * @method col\n     * @for flot.gauge\n     * @param  {Number} columns the number of columns\n     * @param  {Number} i       the index of the series\n     * @return the index of columns\n     */\n    function col(columns, i) {\n        return i % columns;\n    }\n\n    /**\n     * calculate the index of rows for the specified data\n     *\n     * @method row\n     * @for flot.gauge\n     * @param  {Number} columns the number of rows\n     * @param  {Number} i       the index of the series\n     * @return the index of rows\n     */\n    function row(columns, i) {\n        return Math.floor(i / columns);\n    }\n\n    /**\n     * calculate the angle in radians\n     *\n     * internally, use a number without PI (0 - 2).\n     * so, in this function, multiply PI\n     *\n     * @method toRad\n     * @for flot.gauge\n     * @param  {Number} a the number of angle without PI\n     * @return the angle in radians\n     */\n    function toRad(a) {\n        return a * Math.PI;\n    }\n\n    /**\n     * draw an arc\n     *\n     * @method drawArc\n     * @for flot.gauge\n     * @param  {Object} context the context of canvas\n     * @param  {Number} cx the x position of the center\n     * @param  {Number} cy the y position of the center\n     * @param  {Number} r the radius of an arc\n     * @param  {Number} w the width of an arc\n     * @param  {Number} rd1 the start angle of an arc in radians\n     * @param  {Number} rd2 the end angle of an arc in radians\n     * @param  {String} lc the color of a line\n     * @param  {Number} lw the widht of a line\n     * @param  {String} fc the fill color  of an arc\n     */\n    function drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc) {\n        if (rd1 === rd2) {\n            return;\n        }\n        var counterClockwise = false;\n        context.save();\n        context.beginPath();\n        context.arc(cx, cy, r, rd1, rd2, counterClockwise);\n        context.lineTo(cx + (r - w) * Math.cos(rd2),\n                       cy + (r - w) * Math.sin(rd2));\n        context.arc(cx, cy, r - w, rd2, rd1, !counterClockwise);\n        context.closePath();\n        if (lw) {\n            context.lineWidth = lw;\n        }\n        if (lc) {\n            context.strokeStyle = lc;\n            context.stroke();\n        }\n        if (fc) {\n            context.fillStyle = fc;\n            context.fill();\n        }\n        context.restore();\n    }\n\n    /**\n     * initialize plugin\n     *\n     * @method init\n     * @for flot.gauge\n     * @private\n     * @param  {Object} plot a instance of plot\n     */\n    function init (plot) {\n        // add processOptions hook\n        plot.hooks.processOptions.push(function(plot, options) {\n            var logger = getLogger(options.series.gauges.debug);\n\n            logger.log(\"flot.gauge.processOptions\");\n            logger.log(\"options=\", options);\n\n            // turn 'grid' and 'legend' off\n            if (options.series.gauges.show) {\n                options.grid.show = false;\n                options.legend.show = false;\n            }\n\n            // sort threshold\n            var thresholds = options.series.gauges.threshold.values;\n            logger.log(\"thresholds=\", thresholds);\n            thresholds.sort(function(a, b) {\n                if (a.value < b.value) {\n                    return -1;\n                } else if (a.value > b.value) {\n                    return 1;\n                } else {\n                    return 0;\n                }\n            });\n            logger.log(\"thresholds(sorted)=\", thresholds);\n\n            logger.log(\"options=\", options);\n        });\n\n        // add draw hook\n        plot.hooks.draw.push(function(plot, context) {\n            var options = plot.getOptions();\n            var gaugeOptions = options.series.gauges;\n\n            var logger = getLogger(gaugeOptions.debug);\n            logger.log(\"flot.gauge.draw\");\n\n            if (!gaugeOptions.show) {\n                return;\n            }\n\n            var series = plot.getData();\n            logger.log(\"series=\", series);\n            if (!series || !series.length) {\n                return; // if no series were passed\n            }\n\n            var gauge = new Gauge(plot, context);\n\n            // calculate layout\n            var layout = gauge.calculateLayout();\n            logger.log(\"layout=\", layout);\n            // debug layout\n            if (gaugeOptions.debug.layout) {\n                logger.debugLayout(context, series.length, layout);\n            }\n\n            // draw background\n            gauge.drawBackground(layout)\n\n            // draw cells (label, gauge, value, threshold)\n            for (var i = 0; i < series.length; i++) {\n                var item = series[i];\n                logger.log(\"item[\" + i + \"]=\", item);\n                var gaugeOptionsi = $.extend({}, gaugeOptions, item.gauges);\n                if (item.gauges) {\n                    // re-calculate 'auto' values\n                    gauge.calculateAutoValues(gaugeOptionsi, layout.cellWidth);\n                }\n                logger.log(\"gaugeOptions[\" + i + \"]=\", gaugeOptionsi);\n                // calculate cell layout\n                var cellLayout = gauge.calculateCellLayout(gaugeOptionsi, layout, i);\n                logger.log(\"cellLayout=\", cellLayout);\n                // draw cell background\n                gauge.drawCellBackground(gaugeOptionsi, cellLayout)\n                // debug layout\n                if (gaugeOptionsi.debug.layout) {\n                    logger.debugCellLayout(context, gaugeOptionsi, layout, cellLayout);\n                }\n                // draw label\n                if (gaugeOptionsi.label.show) {\n                    gauge.drawLable(gaugeOptionsi, layout, cellLayout, i, item);\n                }\n                // draw gauge\n                gauge.drawGauge(gaugeOptionsi, layout, cellLayout, item.label, item.data[0][1]);\n                // draw threshold\n                if (gaugeOptionsi.threshold.show) {\n                    gauge.drawThreshold(gaugeOptionsi, layout, cellLayout);\n                }\n                if (gaugeOptionsi.threshold.label.show) {\n                    gauge.drawThresholdValues(gaugeOptionsi, layout, cellLayout, i)\n                }\n                // draw value\n                if (gaugeOptionsi.value.show) {\n                    gauge.drawValue(gaugeOptionsi, layout, cellLayout, i, item);\n                }\n            }\n        });\n    }\n\n    /**\n     * [defaults description]\n     *\n     * @property defaults\n     * @type {Object}\n     */\n    var defaults = {\n        series: {\n            gauges: {\n                debug: {\n                    log: false,\n                    layout: false,\n                    alert: false\n                },\n                show: false,\n                layout: {\n                    margin: 5,\n                    columns: 3,\n                    hMargin: 5,\n                    vMargin: 5,\n                    square: false\n                },\n                frame: {\n                    show: true\n                },\n                cell: {\n                    background: {\n                        color: null\n                    },\n                    border: {\n                        show: true,\n                        color: \"black\",\n                        width: 1\n                    },\n                    margin: 5,\n                    vAlign: \"middle\" // 'top' or 'middle' or 'bottom'\n                },\n                gauge: {\n                    width: \"auto\", // a specified number, or 'auto'\n                    startAngle: 0.9, // 0 - 2 factor of the radians\n                    endAngle: 2.1, // 0 - 2 factor of the radians\n                    min: 0,\n                    max: 100,\n                    background: {\n                        color: \"white\"\n                    },\n                    border: {\n                        color: \"lightgray\",\n                        width: 2\n                    },\n                    shadow: {\n                        show: true,\n                        blur: 5\n                    }\n                },\n                label: {\n                    show: true,\n                    margin: \"auto\", // a specified number, or 'auto'\n                    background: {\n                        color: null,\n                        opacity: 0\n                    },\n                    font: {\n                        size: \"auto\", // a specified number, or 'auto'\n                        family: \"sans-serif\"\n                    },\n                    color: null,\n                    formatter: function(label, value) {\n                        return label;\n                    }\n                },\n                value: {\n                    show: true,\n                    margin: \"auto\", // a specified number, or 'auto'\n                    background: {\n                        color: null,\n                        opacity: 0\n                    },\n                    font: {\n                        size: \"auto\", // a specified number, or 'auto'\n                        family: \"sans-serif\"\n                    },\n                    color: null,\n                    formatter: function(label, value) {\n                        return parseInt(value);\n                    }\n                },\n                threshold: {\n                    show: true,\n                    width: \"auto\", // a specified number, or 'auto'\n                    label: {\n                        show: true,\n                        margin: \"auto\", // a specified number, or 'auto'\n                        background: {\n                            color: null,\n                            opacity: 0\n                        },\n                        font: {\n                            size: \"auto\", // a specified number, or 'auto'\n                            family: \",sans-serif\"\n                        },\n                        color: null,\n                        formatter: function(value) {\n                            return value;\n                        }\n                    },\n                    values: [\n                        {\n                            value: 50,\n                            color: \"lightgreen\"\n                        }, {\n                            value: 80,\n                            color: \"yellow\"\n                        }, {\n                            value: 100,\n                            color: \"red\"\n                        }\n                    ]\n                }\n            }\n        }\n    };\n\n    // register the gauge plugin\n    $.plot.plugins.push({\n        init: init,\n        options: defaults,\n        name: \"gauge\",\n        version: \"1.1.0\"\n    });\n\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.hover.js",
    "content": "/* global jQuery */\n\n/**\n## jquery.flot.hover.js\n\nThis plugin is used for mouse hover and tap on a point of plot series.\nIt supports the following options:\n```js\ngrid: {\n    hoverable: false, //to trigger plothover event on mouse hover or tap on a point\n    clickable: false //to trigger plotclick event on mouse hover\n}\n```\n\nIt listens to native mouse move event or click, as well as artificial generated\ntap and touchevent.\n\nWhen the mouse is over a point or a tap on a point is performed, that point or\nthe correscponding bar will be highlighted and a \"plothover\" event will be generated.\n\nCustom \"touchevent\" is triggered when any touch interaction is made. Hover plugin\nhandles this events by unhighlighting all of the previously highlighted points and generates\n\"plothovercleanup\" event to notify any part that is handling plothover (for exemple to cleanup\nthe tooltip from webcharts).\n*/\n\n(function($) {\n    'use strict';\n\n    var options = {\n        grid: {\n            hoverable: false,\n            clickable: false\n        }\n    };\n\n    var browser = $.plot.browser;\n\n    var eventType = {\n        click: 'click',\n        hover: 'hover'\n    }\n\n    function init(plot) {\n        var lastMouseMoveEvent;\n        var highlights = [];\n\n        function bindEvents(plot, eventHolder) {\n            var o = plot.getOptions();\n\n            if (o.grid.hoverable || o.grid.clickable) {\n                eventHolder[0].addEventListener('touchevent', triggerCleanupEvent, false);\n                eventHolder[0].addEventListener('tap', generatePlothoverEvent, false);\n            }\n\n            if (o.grid.clickable) {\n                eventHolder.bind(\"click\", onClick);\n            }\n\n            if (o.grid.hoverable) {\n                eventHolder.bind(\"mousemove\", onMouseMove);\n\n                // Use bind, rather than .mouseleave, because we officially\n                // still support jQuery 1.2.6, which doesn't define a shortcut\n                // for mouseenter or mouseleave.  This was a bug/oversight that\n                // was fixed somewhere around 1.3.x.  We can return to using\n                // .mouseleave when we drop support for 1.2.6.\n\n                eventHolder.bind(\"mouseleave\", onMouseLeave);\n            }\n        }\n\n        function shutdown(plot, eventHolder) {\n            eventHolder[0].removeEventListener('tap', generatePlothoverEvent);\n            eventHolder[0].removeEventListener('touchevent', triggerCleanupEvent);\n            eventHolder.unbind(\"mousemove\", onMouseMove);\n            eventHolder.unbind(\"mouseleave\", onMouseLeave);\n            eventHolder.unbind(\"click\", onClick);\n            highlights = [];\n        }\n\n        function generatePlothoverEvent(e) {\n            var o = plot.getOptions(),\n                newEvent = new CustomEvent('mouseevent');\n\n            //transform from touch event to mouse event format\n            newEvent.pageX = e.detail.changedTouches[0].pageX;\n            newEvent.pageY = e.detail.changedTouches[0].pageY;\n            newEvent.clientX = e.detail.changedTouches[0].clientX;\n            newEvent.clientY = e.detail.changedTouches[0].clientY;\n\n            if (o.grid.hoverable) {\n                doTriggerClickHoverEvent(newEvent, eventType.hover, 30);\n            }\n            return false;\n        }\n\n        function doTriggerClickHoverEvent(event, eventType, searchDistance) {\n            var series = plot.getData();\n            if (event !== undefined &&\n                series.length > 0 &&\n                series[0].xaxis.c2p !== undefined &&\n                series[0].yaxis.c2p !== undefined) {\n                var eventToTrigger = \"plot\" + eventType;\n                var seriesFlag = eventType + \"able\";\n                triggerClickHoverEvent(eventToTrigger, event,\n                    function(i) {\n                        return series[i][seriesFlag] !== false;\n                    }, searchDistance);\n            }\n        }\n\n        function onMouseMove(e) {\n            lastMouseMoveEvent = e;\n            plot.getPlaceholder()[0].lastMouseMoveEvent = e;\n            doTriggerClickHoverEvent(e, eventType.hover);\n        }\n\n        function onMouseLeave(e) {\n            lastMouseMoveEvent = undefined;\n            plot.getPlaceholder()[0].lastMouseMoveEvent = undefined;\n            triggerClickHoverEvent(\"plothover\", e,\n                function(i) {\n                    return false;\n                });\n        }\n\n        function onClick(e) {\n            doTriggerClickHoverEvent(e, eventType.click);\n        }\n\n        function triggerCleanupEvent() {\n            plot.unhighlight();\n            plot.getPlaceholder().trigger('plothovercleanup');\n        }\n\n        // trigger click or hover event (they send the same parameters\n        // so we share their code)\n        function triggerClickHoverEvent(eventname, event, seriesFilter, searchDistance) {\n            var options = plot.getOptions(),\n                offset = plot.offset(),\n                page = browser.getPageXY(event),\n                canvasX = page.X - offset.left,\n                canvasY = page.Y - offset.top,\n                pos = plot.c2p({\n                    left: canvasX,\n                    top: canvasY\n                }),\n                distance = searchDistance !== undefined ? searchDistance : options.grid.mouseActiveRadius;\n\n            pos.pageX = page.X;\n            pos.pageY = page.Y;\n\n            var items = plot.findNearbyItems(canvasX, canvasY, seriesFilter, distance);\n            var item = items[0];\n\n            for (let i = 1; i < items.length; ++i) {\n                if (item.distance === undefined ||\n                    items[i].distance < item.distance) {\n                    item = items[i];\n                }\n            }\n\n            if (item) {\n                // fill in mouse pos for any listeners out there\n                item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left, 10);\n                item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top, 10);\n            } else {\n                item = null;\n            }\n\n            if (options.grid.autoHighlight) {\n                // clear auto-highlights\n                for (let i = 0; i < highlights.length; ++i) {\n                    var h = highlights[i];\n                    if ((h.auto === eventname &&\n                        !(item && h.series === item.series &&\n                            h.point[0] === item.datapoint[0] &&\n                            h.point[1] === item.datapoint[1])) || !item) {\n                        unhighlight(h.series, h.point);\n                    }\n                }\n\n                if (item) {\n                    highlight(item.series, item.datapoint, eventname);\n                }\n            }\n\n            plot.getPlaceholder().trigger(eventname, [pos, item, items]);\n        }\n\n        function highlight(s, point, auto) {\n            if (typeof s === \"number\") {\n                s = plot.getData()[s];\n            }\n\n            if (typeof point === \"number\") {\n                var ps = s.datapoints.pointsize;\n                point = s.datapoints.points.slice(ps * point, ps * (point + 1));\n            }\n\n            var i = indexOfHighlight(s, point);\n            if (i === -1) {\n                highlights.push({\n                    series: s,\n                    point: point,\n                    auto: auto\n                });\n\n                plot.triggerRedrawOverlay();\n            } else if (!auto) {\n                highlights[i].auto = false;\n            }\n        }\n\n        function unhighlight(s, point) {\n            if (s == null && point == null) {\n                highlights = [];\n                plot.triggerRedrawOverlay();\n                return;\n            }\n\n            if (typeof s === \"number\") {\n                s = plot.getData()[s];\n            }\n\n            if (typeof point === \"number\") {\n                var ps = s.datapoints.pointsize;\n                point = s.datapoints.points.slice(ps * point, ps * (point + 1));\n            }\n\n            var i = indexOfHighlight(s, point);\n            if (i !== -1) {\n                highlights.splice(i, 1);\n\n                plot.triggerRedrawOverlay();\n            }\n        }\n\n        function indexOfHighlight(s, p) {\n            for (var i = 0; i < highlights.length; ++i) {\n                var h = highlights[i];\n                if (h.series === s &&\n                    h.point[0] === p[0] &&\n                    h.point[1] === p[1]) {\n                    return i;\n                }\n            }\n\n            return -1;\n        }\n\n        function processDatapoints() {\n            triggerCleanupEvent();\n            doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);\n        }\n\n        function setupGrid() {\n            doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);\n        }\n\n        function drawOverlay(plot, octx, overlay) {\n            var plotOffset = plot.getPlotOffset(),\n                i, hi;\n\n            octx.save();\n            octx.translate(plotOffset.left, plotOffset.top);\n            for (i = 0; i < highlights.length; ++i) {\n                hi = highlights[i];\n\n                if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point, octx);\n                else drawPointHighlight(hi.series, hi.point, octx, plot);\n            }\n            octx.restore();\n        }\n\n        function drawPointHighlight(series, point, octx, plot) {\n            var x = point[0],\n                y = point[1],\n                axisx = series.xaxis,\n                axisy = series.yaxis,\n                highlightColor = (typeof series.highlightColor === \"string\") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();\n\n            if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {\n                return;\n            }\n\n            var pointRadius = series.points.radius + series.points.lineWidth / 2;\n            octx.lineWidth = pointRadius;\n            octx.strokeStyle = highlightColor;\n            var radius = 1.5 * pointRadius;\n            x = axisx.p2c(x);\n            y = axisy.p2c(y);\n\n            octx.beginPath();\n            var symbol = series.points.symbol;\n            if (symbol === 'circle') {\n                octx.arc(x, y, radius, 0, 2 * Math.PI, false);\n            } else if (typeof symbol === 'string' && plot.drawSymbol && plot.drawSymbol[symbol]) {\n                plot.drawSymbol[symbol](octx, x, y, radius, false);\n            }\n\n            octx.closePath();\n            octx.stroke();\n        }\n\n        function drawBarHighlight(series, point, octx) {\n            var highlightColor = (typeof series.highlightColor === \"string\") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),\n                fillStyle = highlightColor,\n                barLeft;\n\n            var barWidth = series.bars.barWidth[0] || series.bars.barWidth;\n            switch (series.bars.align) {\n                case \"left\":\n                    barLeft = 0;\n                    break;\n                case \"right\":\n                    barLeft = -barWidth;\n                    break;\n                default:\n                    barLeft = -barWidth / 2;\n            }\n\n            octx.lineWidth = series.bars.lineWidth;\n            octx.strokeStyle = highlightColor;\n\n            var fillTowards = series.bars.fillTowards || 0,\n                bottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min;\n\n            $.plot.drawSeries.drawBar(point[0], point[1], point[2] || bottom, barLeft, barLeft + barWidth,\n                function() {\n                    return fillStyle;\n                }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);\n        }\n\n        function initHover(plot, options) {\n            plot.highlight = highlight;\n            plot.unhighlight = unhighlight;\n            if (options.grid.hoverable || options.grid.clickable) {\n                plot.hooks.drawOverlay.push(drawOverlay);\n                plot.hooks.processDatapoints.push(processDatapoints);\n                plot.hooks.setupGrid.push(setupGrid);\n            }\n\n            lastMouseMoveEvent = plot.getPlaceholder()[0].lastMouseMoveEvent;\n        }\n\n        plot.hooks.bindEvents.push(bindEvents);\n        plot.hooks.shutdown.push(shutdown);\n        plot.hooks.processOptions.push(initHover);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'hover',\n        version: '0.1'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.image.js",
    "content": "/* Flot plugin for plotting images.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and\n(x2, y2) are where you intend the two opposite corners of the image to end up\nin the plot. Image must be a fully loaded Javascript image (you can make one\nwith new Image()). If the image is not complete, it's skipped when plotting.\n\nThere are two helpers included for retrieving images. The easiest work the way\nthat you put in URLs instead of images in the data, like this:\n\n    [ \"myimage.png\", 0, 0, 10, 10 ]\n\nThen call $.plot.image.loadData( data, options, callback ) where data and\noptions are the same as you pass in to $.plot. This loads the images, replaces\nthe URLs in the data with the corresponding images and calls \"callback\" when\nall images are loaded (or failed loading). In the callback, you can then call\n$.plot with the data set. See the included example.\n\nA more low-level helper, $.plot.image.load(urls, callback) is also included.\nGiven a list of URLs, it calls callback with an object mapping from URL to\nImage object when all images are loaded or have failed loading.\n\nThe plugin supports these options:\n\n    series: {\n        images: {\n            show: boolean\n            anchor: \"corner\" or \"center\"\n            alpha: [ 0, 1 ]\n        }\n    }\n\nThey can be specified for a specific series:\n\n    $.plot( $(\"#placeholder\"), [{\n        data: [ ... ],\n        images: { ... }\n    ])\n\nNote that because the data format is different from usual data points, you\ncan't use images with anything else in a specific data series.\n\nSetting \"anchor\" to \"center\" causes the pixels in the image to be anchored at\nthe corner pixel centers inside of at the pixel corners, effectively letting\nhalf a pixel stick out to each side in the plot.\n\nA possible future direction could be support for tiling for large images (like\nGoogle Maps).\n\n*/\n\n(function ($) {\n    var options = {\n        series: {\n            images: {\n                show: false,\n                alpha: 1,\n                anchor: \"corner\" // or \"center\"\n            }\n        }\n    };\n\n    $.plot.image = {};\n\n    $.plot.image.loadDataImages = function (series, options, callback) {\n        var urls = [], points = [];\n\n        var defaultShow = options.series.images.show;\n\n        $.each(series, function (i, s) {\n            if (!(defaultShow || s.images.show)) {\n                return;\n            }\n\n            if (s.data) {\n                s = s.data;\n            }\n\n            $.each(s, function (i, p) {\n                if (typeof p[0] === \"string\") {\n                    urls.push(p[0]);\n                    points.push(p);\n                }\n            });\n        });\n\n        $.plot.image.load(urls, function (loadedImages) {\n            $.each(points, function (i, p) {\n                var url = p[0];\n                if (loadedImages[url]) {\n                    p[0] = loadedImages[url];\n                }\n            });\n\n            callback();\n        });\n    }\n\n    $.plot.image.load = function (urls, callback) {\n        var missing = urls.length, loaded = {};\n        if (missing === 0) {\n            callback({});\n        }\n\n        $.each(urls, function (i, url) {\n            var handler = function () {\n                --missing;\n                loaded[url] = this;\n\n                if (missing === 0) {\n                    callback(loaded);\n                }\n            };\n\n            $('<img />').load(handler).error(handler).attr('src', url);\n        });\n    };\n\n    function drawSeries(plot, ctx, series) {\n        var plotOffset = plot.getPlotOffset();\n\n        if (!series.images || !series.images.show) {\n            return;\n        }\n\n        var points = series.datapoints.points,\n            ps = series.datapoints.pointsize;\n\n        for (var i = 0; i < points.length; i += ps) {\n            var img = points[i],\n                x1 = points[i + 1], y1 = points[i + 2],\n                x2 = points[i + 3], y2 = points[i + 4],\n                xaxis = series.xaxis, yaxis = series.yaxis,\n                tmp;\n\n            // actually we should check img.complete, but it\n            // appears to be a somewhat unreliable indicator in\n            // IE6 (false even after load event)\n            if (!img || img.width <= 0 || img.height <= 0) {\n                continue;\n            }\n\n            if (x1 > x2) {\n                tmp = x2;\n                x2 = x1;\n                x1 = tmp;\n            }\n            if (y1 > y2) {\n                tmp = y2;\n                y2 = y1;\n                y1 = tmp;\n            }\n\n            // if the anchor is at the center of the pixel, expand the\n            // image by 1/2 pixel in each direction\n            if (series.images.anchor === \"center\") {\n                tmp = 0.5 * (x2 - x1) / (img.width - 1);\n                x1 -= tmp;\n                x2 += tmp;\n                tmp = 0.5 * (y2 - y1) / (img.height - 1);\n                y1 -= tmp;\n                y2 += tmp;\n            }\n\n            // clip\n            if (x1 === x2 || y1 === y2 ||\n                x1 >= xaxis.max || x2 <= xaxis.min ||\n                y1 >= yaxis.max || y2 <= yaxis.min) {\n                continue;\n            }\n\n            var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height;\n            if (x1 < xaxis.min) {\n                sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1);\n                x1 = xaxis.min;\n            }\n\n            if (x2 > xaxis.max) {\n                sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1);\n                x2 = xaxis.max;\n            }\n\n            if (y1 < yaxis.min) {\n                sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1);\n                y1 = yaxis.min;\n            }\n\n            if (y2 > yaxis.max) {\n                sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1);\n                y2 = yaxis.max;\n            }\n\n            x1 = xaxis.p2c(x1);\n            x2 = xaxis.p2c(x2);\n            y1 = yaxis.p2c(y1);\n            y2 = yaxis.p2c(y2);\n\n            // the transformation may have swapped us\n            if (x1 > x2) {\n                tmp = x2;\n                x2 = x1;\n                x1 = tmp;\n            }\n            if (y1 > y2) {\n                tmp = y2;\n                y2 = y1;\n                y1 = tmp;\n            }\n\n            tmp = ctx.globalAlpha;\n            ctx.globalAlpha *= series.images.alpha;\n            ctx.drawImage(img,\n                          sx1, sy1, sx2 - sx1, sy2 - sy1,\n                          x1 + plotOffset.left, y1 + plotOffset.top,\n                          x2 - x1, y2 - y1);\n            ctx.globalAlpha = tmp;\n        }\n    }\n\n    function processRawData(plot, series, data, datapoints) {\n        if (!series.images.show) {\n            return;\n        }\n\n        // format is Image, x1, y1, x2, y2 (opposite corners)\n        datapoints.format = [\n            { required: true },\n            { x: true, number: true, required: true },\n            { y: true, number: true, required: true },\n            { x: true, number: true, required: true },\n            { y: true, number: true, required: true }\n        ];\n    }\n\n    function init(plot) {\n        plot.hooks.processRawData.push(processRawData);\n        plot.hooks.drawSeries.push(drawSeries);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'image',\n        version: '1.1'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.js",
    "content": "/* Javascript plotting library for jQuery, version 3.0.0.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\n*/\n\n// the actual Flot code\n(function($) {\n    \"use strict\";\n\n    var Canvas = window.Flot.Canvas;\n\n    function defaultTickGenerator(axis) {\n        var ticks = [],\n            start = $.plot.saturated.saturate($.plot.saturated.floorInBase(axis.min, axis.tickSize)),\n            i = 0,\n            v = Number.NaN,\n            prev;\n\n        if (start === -Number.MAX_VALUE) {\n            ticks.push(start);\n            start = $.plot.saturated.floorInBase(axis.min + axis.tickSize, axis.tickSize);\n        }\n\n        do {\n            prev = v;\n            //v = start + i * axis.tickSize;\n            v = $.plot.saturated.multiplyAdd(axis.tickSize, i, start);\n            ticks.push(v);\n            ++i;\n        } while (v < axis.max && v !== prev);\n\n        return ticks;\n    }\n\n    function defaultTickFormatter(value, axis, precision) {\n        var oldTickDecimals = axis.tickDecimals,\n            expPosition = (\"\" + value).indexOf(\"e\");\n\n        if (expPosition !== -1) {\n            return expRepTickFormatter(value, axis, precision);\n        }\n\n        if (precision > 0) {\n            axis.tickDecimals = precision;\n        }\n\n        var factor = axis.tickDecimals ? parseFloat('1e' + axis.tickDecimals) : 1,\n            formatted = \"\" + Math.round(value * factor) / factor;\n\n        // If tickDecimals was specified, ensure that we have exactly that\n        // much precision; otherwise default to the value's own precision.\n        if (axis.tickDecimals != null) {\n            var decimal = formatted.indexOf(\".\"),\n                decimalPrecision = decimal === -1 ? 0 : formatted.length - decimal - 1;\n            if (decimalPrecision < axis.tickDecimals) {\n                var decimals = (\"\" + factor).substr(1, axis.tickDecimals - decimalPrecision);\n                formatted = (decimalPrecision ? formatted : formatted + \".\") + decimals;\n            }\n        }\n\n        axis.tickDecimals = oldTickDecimals;\n        return formatted;\n    };\n\n    function expRepTickFormatter(value, axis, precision) {\n        var expPosition = (\"\" + value).indexOf(\"e\"),\n            exponentValue = parseInt((\"\" + value).substr(expPosition + 1)),\n            tenExponent = expPosition !== -1 ? exponentValue : (value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0),\n            roundWith = parseFloat('1e' + tenExponent),\n            x = value / roundWith;\n\n        if (precision) {\n            var updatedPrecision = recomputePrecision(value, precision);\n            return (value / roundWith).toFixed(updatedPrecision) + 'e' + tenExponent;\n        }\n\n        if (axis.tickDecimals > 0) {\n            return x.toFixed(recomputePrecision(value, axis.tickDecimals)) + 'e' + tenExponent;\n        }\n        return x.toFixed() + 'e' + tenExponent;\n    }\n\n    function recomputePrecision(num, precision) {\n        //for numbers close to zero, the precision from flot will be a big number\n        //while for big numbers, the precision will be negative\n        var log10Value = Math.log(Math.abs(num)) * Math.LOG10E,\n            newPrecision = Math.abs(log10Value + precision);\n\n        return newPrecision <= 20 ? Math.floor(newPrecision) : 20;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // The top-level container for the entire plot.\n    function Plot(placeholder, data_, options_, plugins) {\n        // data is on the form:\n        //   [ series1, series2 ... ]\n        // where series is either just the data as [ [x1, y1], [x2, y2], ... ]\n        // or { data: [ [x1, y1], [x2, y2], ... ], label: \"some label\", ... }\n\n        var series = [],\n            options = {\n                // the color theme used for graphs\n                colors: [\"#edc240\", \"#afd8f8\", \"#cb4b4b\", \"#4da74d\", \"#9440ed\"],\n                xaxis: {\n                    show: null, // null = auto-detect, true = always, false = never\n                    position: \"bottom\", // or \"top\"\n                    mode: null, // null or \"time\"\n                    font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: \"italic\", weight: \"bold\", family: \"sans-serif\", variant: \"small-caps\" }\n                    color: null, // base color, labels, ticks\n                    tickColor: null, // possibly different color of ticks, e.g. \"rgba(0,0,0,0.15)\"\n                    transform: null, // null or f: number -> number to transform axis\n                    inverseTransform: null, // if transform is set, this should be the inverse function\n                    min: null, // min. value to show, null means set automatically\n                    max: null, // max. value to show, null means set automatically\n                    autoScaleMargin: null, // margin in % to add if autoScale option is on \"loose\" mode,\n                    autoScale: \"exact\", // Available modes: \"none\", \"loose\", \"exact\", \"sliding-window\"\n                    windowSize: null, // null or number. This is the size of sliding-window.\n                    growOnly: null, // grow only, useful for smoother auto-scale, the scales will grow to accomodate data but won't shrink back.\n                    ticks: null, // either [1, 3] or [[1, \"a\"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks\n                    tickFormatter: null, // fn: number -> string\n                    showTickLabels: \"major\", // \"none\", \"endpoints\", \"major\", \"all\"\n                    labelWidth: null, // size of tick labels in pixels\n                    labelHeight: null,\n                    reserveSpace: null, // whether to reserve space even if axis isn't shown\n                    tickLength: null, // size in pixels of major tick marks\n                    showMinorTicks: null, // true = show minor tick marks, false = hide minor tick marks\n                    showTicks: null, // true = show tick marks, false = hide all tick marks\n                    gridLines: null, // true = show grid lines, false = hide grid lines\n                    alignTicksWithAxis: null, // axis number or null for no sync\n                    tickDecimals: null, // no. of decimals, null means auto\n                    tickSize: null, // number or [number, \"unit\"]\n                    minTickSize: null, // number or [number, \"unit\"]\n                    offset: { below: 0, above: 0 }, // the plot drawing offset. this is calculated by the flot.navigate for each axis\n                    boxPosition: { centerX: 0, centerY: 0 } //position of the axis on the corresponding axis box\n                },\n                yaxis: {\n                    autoScaleMargin: 0.02, // margin in % to add if autoScale option is on \"loose\" mode\n                    autoScale: \"loose\", // Available modes: \"none\", \"loose\", \"exact\"\n                    growOnly: null, // grow only, useful for smoother auto-scale, the scales will grow to accomodate data but won't shrink back.\n                    position: \"left\", // or \"right\"\n                    showTickLabels: \"major\", // \"none\", \"endpoints\", \"major\", \"all\"\n                    offset: { below: 0, above: 0 }, // the plot drawing offset. this is calculated by the flot.navigate for each axis\n                    boxPosition: { centerX: 0, centerY: 0 } //position of the axis on the corresponding axis box\n                },\n                xaxes: [],\n                yaxes: [],\n                series: {\n                    points: {\n                        show: false,\n                        radius: 3,\n                        lineWidth: 2, // in pixels\n                        fill: true,\n                        fillColor: \"#ffffff\",\n                        symbol: 'circle' // or callback\n                    },\n                    lines: {\n                        // we don't put in show: false so we can see\n                        // whether lines were actively disabled\n                        lineWidth: 1, // in pixels\n                        fill: false,\n                        fillColor: null,\n                        steps: false\n                        // Omit 'zero', so we can later default its value to\n                        // match that of the 'fill' option.\n                    },\n                    bars: {\n                        show: false,\n                        lineWidth: 2, // in pixels\n                        // barWidth: number or [number, absolute]\n                        // when 'absolute' is false, 'number' is relative to the minimum distance between points for the series\n                        // when 'absolute' is true, 'number' is considered to be in units of the x-axis\n                        horizontal: false,\n                        barWidth: 0.8,\n                        fill: true,\n                        fillColor: null,\n                        align: \"left\", // \"left\", \"right\", or \"center\"\n                        zero: true\n                    },\n                    shadowSize: 3,\n                    highlightColor: null\n                },\n                grid: {\n                    show: true,\n                    aboveData: false,\n                    color: \"#545454\", // primary color used for outline and labels\n                    backgroundColor: null, // null for transparent, else color\n                    borderColor: null, // set if different from the grid color\n                    tickColor: null, // color for the ticks, e.g. \"rgba(0,0,0,0.15)\"\n                    margin: 0, // distance from the canvas edge to the grid\n                    labelMargin: 5, // in pixels\n                    axisMargin: 8, // in pixels\n                    borderWidth: 1, // in pixels\n                    minBorderMargin: null, // in pixels, null means taken from points radius\n                    markings: null, // array of ranges or fn: axes -> array of ranges\n                    markingsColor: \"#f4f4f4\",\n                    markingsLineWidth: 2,\n                    // interactive stuff\n                    clickable: false,\n                    hoverable: false,\n                    autoHighlight: true, // highlight in case mouse is near\n                    mouseActiveRadius: 15 // how far the mouse can be away to activate an item\n                },\n                interaction: {\n                    redrawOverlayInterval: 1000 / 60 // time between updates, -1 means in same flow\n                },\n                hooks: {}\n            },\n            surface = null, // the canvas for the plot itself\n            overlay = null, // canvas for interactive stuff on top of plot\n            eventHolder = null, // jQuery object that events should be bound to\n            ctx = null,\n            octx = null,\n            xaxes = [],\n            yaxes = [],\n            plotOffset = {\n                left: 0,\n                right: 0,\n                top: 0,\n                bottom: 0\n            },\n            plotWidth = 0,\n            plotHeight = 0,\n            hooks = {\n                processOptions: [],\n                processRawData: [],\n                processDatapoints: [],\n                processOffset: [],\n                setupGrid: [],\n                adjustSeriesDataRange: [],\n                setRange: [],\n                drawBackground: [],\n                drawSeries: [],\n                drawAxis: [],\n                draw: [],\n                findNearbyItems: [],\n                axisReserveSpace: [],\n                bindEvents: [],\n                drawOverlay: [],\n                resize: [],\n                shutdown: []\n            },\n            plot = this;\n\n        var eventManager = {};\n\n        // interactive features\n\n        var redrawTimeout = null;\n\n        // public functions\n        plot.setData = setData;\n        plot.setupGrid = setupGrid;\n        plot.draw = draw;\n        plot.getPlaceholder = function() {\n            return placeholder;\n        };\n        plot.getCanvas = function() {\n            return surface.element;\n        };\n        plot.getSurface = function() {\n            return surface;\n        };\n        plot.getEventHolder = function() {\n            return eventHolder[0];\n        };\n        plot.getPlotOffset = function() {\n            return plotOffset;\n        };\n        plot.width = function() {\n            return plotWidth;\n        };\n        plot.height = function() {\n            return plotHeight;\n        };\n        plot.offset = function() {\n            var o = eventHolder.offset();\n            o.left += plotOffset.left;\n            o.top += plotOffset.top;\n            return o;\n        };\n        plot.getData = function() {\n            return series;\n        };\n        plot.getAxes = function() {\n            var res = {};\n            $.each(xaxes.concat(yaxes), function(_, axis) {\n                if (axis) {\n                    res[axis.direction + (axis.n !== 1 ? axis.n : \"\") + \"axis\"] = axis;\n                }\n            });\n            return res;\n        };\n        plot.getXAxes = function() {\n            return xaxes;\n        };\n        plot.getYAxes = function() {\n            return yaxes;\n        };\n        plot.c2p = canvasToCartesianAxisCoords;\n        plot.p2c = cartesianAxisToCanvasCoords;\n        plot.getOptions = function() {\n            return options;\n        };\n        plot.triggerRedrawOverlay = triggerRedrawOverlay;\n        plot.pointOffset = function(point) {\n            return {\n                left: parseInt(xaxes[axisNumber(point, \"x\") - 1].p2c(+point.x) + plotOffset.left, 10),\n                top: parseInt(yaxes[axisNumber(point, \"y\") - 1].p2c(+point.y) + plotOffset.top, 10)\n            };\n        };\n        plot.shutdown = shutdown;\n        plot.destroy = function() {\n            shutdown();\n            placeholder.removeData(\"plot\").empty();\n\n            series = [];\n            options = null;\n            surface = null;\n            overlay = null;\n            eventHolder = null;\n            ctx = null;\n            octx = null;\n            xaxes = [];\n            yaxes = [];\n            hooks = null;\n            plot = null;\n        };\n\n        plot.resize = function() {\n            var width = placeholder.width(),\n                height = placeholder.height();\n            surface.resize(width, height);\n            overlay.resize(width, height);\n\n            executeHooks(hooks.resize, [width, height]);\n        };\n\n        plot.clearTextCache = function () {\n            surface.clearCache();\n            overlay.clearCache();\n        };\n\n        plot.autoScaleAxis = autoScaleAxis;\n        plot.computeRangeForDataSeries = computeRangeForDataSeries;\n        plot.adjustSeriesDataRange = adjustSeriesDataRange;\n        plot.findNearbyItem = findNearbyItem;\n        plot.findNearbyItems = findNearbyItems;\n        plot.findNearbyInterpolationPoint = findNearbyInterpolationPoint;\n        plot.computeValuePrecision = computeValuePrecision;\n        plot.computeTickSize = computeTickSize;\n        plot.addEventHandler = addEventHandler;\n\n        // public attributes\n        plot.hooks = hooks;\n\n        // initialize\n        var MINOR_TICKS_COUNT_CONSTANT = $.plot.uiConstants.MINOR_TICKS_COUNT_CONSTANT;\n        var TICK_LENGTH_CONSTANT = $.plot.uiConstants.TICK_LENGTH_CONSTANT;\n        initPlugins(plot);\n        setupCanvases();\n        parseOptions(options_);\n        setData(data_);\n        setupGrid(true);\n        draw();\n        bindEvents();\n\n        function executeHooks(hook, args) {\n            args = [plot].concat(args);\n            for (var i = 0; i < hook.length; ++i) {\n                hook[i].apply(this, args);\n            }\n        }\n\n        function initPlugins() {\n            // References to key classes, allowing plugins to modify them\n\n            var classes = {\n                Canvas: Canvas\n            };\n\n            for (var i = 0; i < plugins.length; ++i) {\n                var p = plugins[i];\n                p.init(plot, classes);\n                if (p.options) {\n                    $.extend(true, options, p.options);\n                }\n            }\n        }\n\n        function parseOptions(opts) {\n            $.extend(true, options, opts);\n\n            // $.extend merges arrays, rather than replacing them.  When less\n            // colors are provided than the size of the default palette, we\n            // end up with those colors plus the remaining defaults, which is\n            // not expected behavior; avoid it by replacing them here.\n\n            if (opts && opts.colors) {\n                options.colors = opts.colors;\n            }\n\n            if (options.xaxis.color == null) {\n                options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();\n            }\n\n            if (options.yaxis.color == null) {\n                options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();\n            }\n\n            if (options.xaxis.tickColor == null) {\n                // grid.tickColor for back-compatibility\n                options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;\n            }\n\n            if (options.yaxis.tickColor == null) {\n                // grid.tickColor for back-compatibility\n                options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;\n            }\n\n            if (options.grid.borderColor == null) {\n                options.grid.borderColor = options.grid.color;\n            }\n\n            if (options.grid.tickColor == null) {\n                options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();\n            }\n\n            // Fill in defaults for axis options, including any unspecified\n            // font-spec fields, if a font-spec was provided.\n\n            // If no x/y axis options were provided, create one of each anyway,\n            // since the rest of the code assumes that they exist.\n\n            var i, axisOptions, axisCount,\n                fontSize = placeholder.css(\"font-size\"),\n                fontSizeDefault = fontSize ? +fontSize.replace(\"px\", \"\") : 13,\n                fontDefaults = {\n                    style: placeholder.css(\"font-style\"),\n                    size: Math.round(0.8 * fontSizeDefault),\n                    variant: placeholder.css(\"font-variant\"),\n                    weight: placeholder.css(\"font-weight\"),\n                    family: placeholder.css(\"font-family\")\n                };\n\n            axisCount = options.xaxes.length || 1;\n            for (i = 0; i < axisCount; ++i) {\n                axisOptions = options.xaxes[i];\n                if (axisOptions && !axisOptions.tickColor) {\n                    axisOptions.tickColor = axisOptions.color;\n                }\n\n                axisOptions = $.extend(true, {}, options.xaxis, axisOptions);\n                options.xaxes[i] = axisOptions;\n\n                if (axisOptions.font) {\n                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);\n                    if (!axisOptions.font.color) {\n                        axisOptions.font.color = axisOptions.color;\n                    }\n                    if (!axisOptions.font.lineHeight) {\n                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);\n                    }\n                }\n            }\n\n            axisCount = options.yaxes.length || 1;\n            for (i = 0; i < axisCount; ++i) {\n                axisOptions = options.yaxes[i];\n                if (axisOptions && !axisOptions.tickColor) {\n                    axisOptions.tickColor = axisOptions.color;\n                }\n\n                axisOptions = $.extend(true, {}, options.yaxis, axisOptions);\n                options.yaxes[i] = axisOptions;\n\n                if (axisOptions.font) {\n                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);\n                    if (!axisOptions.font.color) {\n                        axisOptions.font.color = axisOptions.color;\n                    }\n                    if (!axisOptions.font.lineHeight) {\n                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);\n                    }\n                }\n            }\n\n            // save options on axes for future reference\n            for (i = 0; i < options.xaxes.length; ++i) {\n                getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];\n            }\n\n            for (i = 0; i < options.yaxes.length; ++i) {\n                getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];\n            }\n\n            //process boxPosition options used for axis.box size\n            $.each(allAxes(), function(_, axis) {\n                axis.boxPosition = axis.options.boxPosition || {centerX: 0, centerY: 0};\n            });\n\n            // add hooks from options\n            for (var n in hooks) {\n                if (options.hooks[n] && options.hooks[n].length) {\n                    hooks[n] = hooks[n].concat(options.hooks[n]);\n                }\n            }\n\n            executeHooks(hooks.processOptions, [options]);\n        }\n\n        function setData(d) {\n            var oldseries = series;\n            series = parseData(d);\n            fillInSeriesOptions();\n            processData(oldseries);\n        }\n\n        function parseData(d) {\n            var res = [];\n            for (var i = 0; i < d.length; ++i) {\n                var s = $.extend(true, {}, options.series);\n\n                if (d[i].data != null) {\n                    s.data = d[i].data; // move the data instead of deep-copy\n                    delete d[i].data;\n\n                    $.extend(true, s, d[i]);\n\n                    d[i].data = s.data;\n                } else {\n                    s.data = d[i];\n                }\n\n                res.push(s);\n            }\n\n            return res;\n        }\n\n        function axisNumber(obj, coord) {\n            var a = obj[coord + \"axis\"];\n            if (typeof a === \"object\") {\n                // if we got a real axis, extract number\n                a = a.n;\n            }\n\n            if (typeof a !== \"number\") {\n                a = 1; // default to first axis\n            }\n\n            return a;\n        }\n\n        function allAxes() {\n            // return flat array without annoying null entries\n            return xaxes.concat(yaxes).filter(function(a) {\n                return a;\n            });\n        }\n\n        // canvas to axis for cartesian axes\n        function canvasToCartesianAxisCoords(pos) {\n            // return an object with x/y corresponding to all used axes\n            var res = {},\n                i, axis;\n            for (i = 0; i < xaxes.length; ++i) {\n                axis = xaxes[i];\n                if (axis && axis.used) {\n                    res[\"x\" + axis.n] = axis.c2p(pos.left);\n                }\n            }\n\n            for (i = 0; i < yaxes.length; ++i) {\n                axis = yaxes[i];\n                if (axis && axis.used) {\n                    res[\"y\" + axis.n] = axis.c2p(pos.top);\n                }\n            }\n\n            if (res.x1 !== undefined) {\n                res.x = res.x1;\n            }\n\n            if (res.y1 !== undefined) {\n                res.y = res.y1;\n            }\n\n            return res;\n        }\n\n        // axis to canvas for cartesian axes\n        function cartesianAxisToCanvasCoords(pos) {\n            // get canvas coords from the first pair of x/y found in pos\n            var res = {},\n                i, axis, key;\n\n            for (i = 0; i < xaxes.length; ++i) {\n                axis = xaxes[i];\n                if (axis && axis.used) {\n                    key = \"x\" + axis.n;\n                    if (pos[key] == null && axis.n === 1) {\n                        key = \"x\";\n                    }\n\n                    if (pos[key] != null) {\n                        res.left = axis.p2c(pos[key]);\n                        break;\n                    }\n                }\n            }\n\n            for (i = 0; i < yaxes.length; ++i) {\n                axis = yaxes[i];\n                if (axis && axis.used) {\n                    key = \"y\" + axis.n;\n                    if (pos[key] == null && axis.n === 1) {\n                        key = \"y\";\n                    }\n\n                    if (pos[key] != null) {\n                        res.top = axis.p2c(pos[key]);\n                        break;\n                    }\n                }\n            }\n\n            return res;\n        }\n\n        function getOrCreateAxis(axes, number) {\n            if (!axes[number - 1]) {\n                axes[number - 1] = {\n                    n: number, // save the number for future reference\n                    direction: axes === xaxes ? \"x\" : \"y\",\n                    options: $.extend(true, {}, axes === xaxes ? options.xaxis : options.yaxis)\n                };\n            }\n\n            return axes[number - 1];\n        }\n\n        function fillInSeriesOptions() {\n            var neededColors = series.length,\n                maxIndex = -1,\n                i;\n\n            // Subtract the number of series that already have fixed colors or\n            // color indexes from the number that we still need to generate.\n\n            for (i = 0; i < series.length; ++i) {\n                var sc = series[i].color;\n                if (sc != null) {\n                    neededColors--;\n                    if (typeof sc === \"number\" && sc > maxIndex) {\n                        maxIndex = sc;\n                    }\n                }\n            }\n\n            // If any of the series have fixed color indexes, then we need to\n            // generate at least as many colors as the highest index.\n\n            if (neededColors <= maxIndex) {\n                neededColors = maxIndex + 1;\n            }\n\n            // Generate all the colors, using first the option colors and then\n            // variations on those colors once they're exhausted.\n\n            var c, colors = [],\n                colorPool = options.colors,\n                colorPoolSize = colorPool.length,\n                variation = 0,\n                definedColors = Math.max(0, series.length - neededColors);\n\n            for (i = 0; i < neededColors; i++) {\n                c = $.color.parse(colorPool[(definedColors + i) % colorPoolSize] || \"#666\");\n\n                // Each time we exhaust the colors in the pool we adjust\n                // a scaling factor used to produce more variations on\n                // those colors. The factor alternates negative/positive\n                // to produce lighter/darker colors.\n\n                // Reset the variation after every few cycles, or else\n                // it will end up producing only white or black colors.\n\n                if (i % colorPoolSize === 0 && i) {\n                    if (variation >= 0) {\n                        if (variation < 0.5) {\n                            variation = -variation - 0.2;\n                        } else variation = 0;\n                    } else variation = -variation;\n                }\n\n                colors[i] = c.scale('rgb', 1 + variation);\n            }\n\n            // Finalize the series options, filling in their colors\n\n            var colori = 0,\n                s;\n            for (i = 0; i < series.length; ++i) {\n                s = series[i];\n\n                // assign colors\n                if (s.color == null) {\n                    s.color = colors[colori].toString();\n                    ++colori;\n                } else if (typeof s.color === \"number\") {\n                    s.color = colors[s.color].toString();\n                }\n\n                // turn on lines automatically in case nothing is set\n                if (s.lines.show == null) {\n                    var v, show = true;\n                    for (v in s) {\n                        if (s[v] && s[v].show) {\n                            show = false;\n                            break;\n                        }\n                    }\n\n                    if (show) {\n                        s.lines.show = true;\n                    }\n                }\n\n                // If nothing was provided for lines.zero, default it to match\n                // lines.fill, since areas by default should extend to zero.\n\n                if (s.lines.zero == null) {\n                    s.lines.zero = !!s.lines.fill;\n                }\n\n                // setup axes\n                s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, \"x\"));\n                s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, \"y\"));\n            }\n        }\n\n        function processData(prevSeries) {\n            var topSentry = Number.POSITIVE_INFINITY,\n                bottomSentry = Number.NEGATIVE_INFINITY,\n                i, j, k, m,\n                s, points, ps, val, f, p,\n                data, format;\n\n            function updateAxis(axis, min, max) {\n                if (min < axis.datamin && min !== -Infinity) {\n                    axis.datamin = min;\n                }\n\n                if (max > axis.datamax && max !== Infinity) {\n                    axis.datamax = max;\n                }\n            }\n\n            function reusePoints(prevSeries, i) {\n                if (prevSeries && prevSeries[i] && prevSeries[i].datapoints && prevSeries[i].datapoints.points) {\n                    return prevSeries[i].datapoints.points;\n                }\n\n                return [];\n            }\n\n            $.each(allAxes(), function(_, axis) {\n                // init axis\n                if (axis.options.growOnly !== true) {\n                    axis.datamin = topSentry;\n                    axis.datamax = bottomSentry;\n                } else {\n                    if (axis.datamin === undefined) {\n                        axis.datamin = topSentry;\n                    }\n                    if (axis.datamax === undefined) {\n                        axis.datamax = bottomSentry;\n                    }\n                }\n                axis.used = false;\n            });\n\n            for (i = 0; i < series.length; ++i) {\n                s = series[i];\n                s.datapoints = {\n                    points: []\n                };\n\n                if (s.datapoints.points.length === 0) {\n                    s.datapoints.points = reusePoints(prevSeries, i);\n                }\n\n                executeHooks(hooks.processRawData, [s, s.data, s.datapoints]);\n            }\n\n            // first pass: clean and copy data\n            for (i = 0; i < series.length; ++i) {\n                s = series[i];\n\n                data = s.data;\n                format = s.datapoints.format;\n\n                if (!format) {\n                    format = [];\n                    // find out how to copy\n                    format.push({\n                        x: true,\n                        y: false,\n                        number: true,\n                        required: true,\n                        computeRange: s.xaxis.options.autoScale !== 'none',\n                        defaultValue: null\n                    });\n\n                    format.push({\n                        x: false,\n                        y: true,\n                        number: true,\n                        required: true,\n                        computeRange: s.yaxis.options.autoScale !== 'none',\n                        defaultValue: null\n                    });\n\n                    if (s.stack || s.bars.show || (s.lines.show && s.lines.fill)) {\n                        var expectedPs = s.datapoints.pointsize != null ? s.datapoints.pointsize : (s.data && s.data[0] && s.data[0].length ? s.data[0].length : 3);\n                        if (expectedPs > 2) {\n                            format.push({\n                                x: s.bars.horizontal,\n                                y: !s.bars.horizontal,\n                                number: true,\n                                required: false,\n                                computeRange: s.yaxis.options.autoScale !== 'none',\n                                defaultValue: 0\n                            });\n                        }\n                    }\n\n                    s.datapoints.format = format;\n                }\n\n                s.xaxis.used = s.yaxis.used = true;\n\n                if (s.datapoints.pointsize != null) continue; // already filled in\n\n                s.datapoints.pointsize = format.length;\n                ps = s.datapoints.pointsize;\n                points = s.datapoints.points;\n\n                for (j = k = 0; j < data.length; ++j, k += ps) {\n                    p = data[j];\n\n                    var nullify = p == null;\n                    if (!nullify) {\n                        for (m = 0; m < ps; ++m) {\n                            val = p[m];\n                            f = format[m];\n\n                            if (f) {\n                                if (f.number && val != null) {\n                                    val = +val; // convert to number\n                                    if (isNaN(val)) {\n                                        val = null;\n                                    }\n                                }\n\n                                if (val == null) {\n                                    if (f.required) nullify = true;\n\n                                    if (f.defaultValue != null) val = f.defaultValue;\n                                }\n                            }\n\n                            points[k + m] = val;\n                        }\n                    }\n\n                    if (nullify) {\n                        for (m = 0; m < ps; ++m) {\n                            val = points[k + m];\n                            if (val != null) {\n                                f = format[m];\n                                // extract min/max info\n                                if (f.computeRange) {\n                                    if (f.x) {\n                                        updateAxis(s.xaxis, val, val);\n                                    }\n                                    if (f.y) {\n                                        updateAxis(s.yaxis, val, val);\n                                    }\n                                }\n                            }\n                            points[k + m] = null;\n                        }\n                    }\n                }\n\n                points.length = k; //trims the internal buffer to the correct length\n            }\n\n            // give the hooks a chance to run\n            for (i = 0; i < series.length; ++i) {\n                s = series[i];\n\n                executeHooks(hooks.processDatapoints, [s, s.datapoints]);\n            }\n\n            // second pass: find datamax/datamin for auto-scaling\n            for (i = 0; i < series.length; ++i) {\n                s = series[i];\n                format = s.datapoints.format;\n\n                if (format.every(function (f) { return !f.computeRange; })) {\n                    continue;\n                }\n\n                var range = plot.adjustSeriesDataRange(s,\n                    plot.computeRangeForDataSeries(s));\n\n                executeHooks(hooks.adjustSeriesDataRange, [s, range]);\n\n                updateAxis(s.xaxis, range.xmin, range.xmax);\n                updateAxis(s.yaxis, range.ymin, range.ymax);\n            }\n\n            $.each(allAxes(), function(_, axis) {\n                if (axis.datamin === topSentry) {\n                    axis.datamin = null;\n                }\n\n                if (axis.datamax === bottomSentry) {\n                    axis.datamax = null;\n                }\n            });\n        }\n\n        function setupCanvases() {\n            // Make sure the placeholder is clear of everything except canvases\n            // from a previous plot in this container that we'll try to re-use.\n\n            placeholder.css(\"padding\", 0) // padding messes up the positioning\n                .children().filter(function() {\n                    return !$(this).hasClass(\"flot-overlay\") && !$(this).hasClass('flot-base');\n                }).remove();\n\n            if (placeholder.css(\"position\") === 'static') {\n                placeholder.css(\"position\", \"relative\"); // for positioning labels and overlay\n            }\n\n            surface = new Canvas(\"flot-base\", placeholder[0]);\n            overlay = new Canvas(\"flot-overlay\", placeholder[0]); // overlay canvas for interactive features\n\n            ctx = surface.context;\n            octx = overlay.context;\n\n            // define which element we're listening for events on\n            eventHolder = $(overlay.element).unbind();\n\n            // If we're re-using a plot object, shut down the old one\n\n            var existing = placeholder.data(\"plot\");\n\n            if (existing) {\n                existing.shutdown();\n                overlay.clear();\n            }\n\n            // save in case we get replotted\n            placeholder.data(\"plot\", plot);\n        }\n\n        function bindEvents() {\n            executeHooks(hooks.bindEvents, [eventHolder]);\n        }\n\n        function addEventHandler(event, handler, eventHolder, priority) {\n            var key = eventHolder + event;\n            var eventList = eventManager[key] || [];\n\n            eventList.push({\"event\": event, \"handler\": handler, \"eventHolder\": eventHolder, \"priority\": priority});\n            eventList.sort((a, b) => b.priority - a.priority);\n            eventList.forEach(eventData => {\n                eventData.eventHolder.unbind(eventData.event, eventData.handler);\n                eventData.eventHolder.bind(eventData.event, eventData.handler);\n            });\n\n            eventManager[key] = eventList;\n        }\n\n        function shutdown() {\n            if (redrawTimeout) {\n                clearTimeout(redrawTimeout);\n            }\n\n            executeHooks(hooks.shutdown, [eventHolder]);\n        }\n\n        function setTransformationHelpers(axis) {\n            // set helper functions on the axis, assumes plot area\n            // has been computed already\n\n            function identity(x) {\n                return x;\n            }\n\n            var s, m, t = axis.options.transform || identity,\n                it = axis.options.inverseTransform;\n\n            // precompute how much the axis is scaling a point\n            // in canvas space\n            if (axis.direction === \"x\") {\n                if (isFinite(t(axis.max) - t(axis.min))) {\n                    s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));\n                } else {\n                    s = axis.scale = 1 / Math.abs($.plot.saturated.delta(t(axis.min), t(axis.max), plotWidth));\n                }\n                m = Math.min(t(axis.max), t(axis.min));\n            } else {\n                if (isFinite(t(axis.max) - t(axis.min))) {\n                    s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));\n                } else {\n                    s = axis.scale = 1 / Math.abs($.plot.saturated.delta(t(axis.min), t(axis.max), plotHeight));\n                }\n                s = -s;\n                m = Math.max(t(axis.max), t(axis.min));\n            }\n\n            // data point to canvas coordinate\n            if (t === identity) {\n                // slight optimization\n                axis.p2c = function(p) {\n                    if (isFinite(p - m)) {\n                        return (p - m) * s;\n                    } else {\n                        return (p / 4 - m / 4) * s * 4;\n                    }\n                };\n            } else {\n                axis.p2c = function(p) {\n                    var tp = t(p);\n\n                    if (isFinite(tp - m)) {\n                        return (tp - m) * s;\n                    } else {\n                        return (tp / 4 - m / 4) * s * 4;\n                    }\n                };\n            }\n\n            // canvas coordinate to data point\n            if (!it) {\n                axis.c2p = function(c) {\n                    return m + c / s;\n                };\n            } else {\n                axis.c2p = function(c) {\n                    return it(m + c / s);\n                };\n            }\n        }\n\n        function measureTickLabels(axis) {\n            var opts = axis.options,\n                ticks = opts.showTickLabels !== 'none' && axis.ticks ? axis.ticks : [],\n                showMajorTickLabels = opts.showTickLabels === 'major' || opts.showTickLabels === 'all',\n                showEndpointsTickLabels = opts.showTickLabels === 'endpoints' || opts.showTickLabels === 'all',\n                labelWidth = opts.labelWidth || 0,\n                labelHeight = opts.labelHeight || 0,\n                legacyStyles = axis.direction + \"Axis \" + axis.direction + axis.n + \"Axis\",\n                layer = \"flot-\" + axis.direction + \"-axis flot-\" + axis.direction + axis.n + \"-axis \" + legacyStyles,\n                font = opts.font || \"flot-tick-label tickLabel\";\n\n            for (var i = 0; i < ticks.length; ++i) {\n                var t = ticks[i];\n                var label = t.label;\n\n                if (!t.label ||\n                    (showMajorTickLabels === false && i > 0 && i < ticks.length - 1) ||\n                    (showEndpointsTickLabels === false && (i === 0 || i === ticks.length - 1))) {\n                    continue;\n                }\n\n                if (typeof t.label === 'object') {\n                    label = t.label.name;\n                }\n\n                var info = surface.getTextInfo(layer, label, font);\n\n                labelWidth = Math.max(labelWidth, info.width);\n                labelHeight = Math.max(labelHeight, info.height);\n            }\n\n            axis.labelWidth = opts.labelWidth || labelWidth;\n            axis.labelHeight = opts.labelHeight || labelHeight;\n        }\n\n        function allocateAxisBoxFirstPhase(axis) {\n            // find the bounding box of the axis by looking at label\n            // widths/heights and ticks, make room by diminishing the\n            // plotOffset; this first phase only looks at one\n            // dimension per axis, the other dimension depends on the\n            // other axes so will have to wait\n\n            // here reserve additional space\n            executeHooks(hooks.axisReserveSpace, [axis]);\n\n            var lw = axis.labelWidth,\n                lh = axis.labelHeight,\n                pos = axis.options.position,\n                isXAxis = axis.direction === \"x\",\n                tickLength = axis.options.tickLength,\n                showTicks = axis.options.showTicks,\n                showMinorTicks = axis.options.showMinorTicks,\n                gridLines = axis.options.gridLines,\n                axisMargin = options.grid.axisMargin,\n                padding = options.grid.labelMargin,\n                innermost = true,\n                outermost = true,\n                found = false;\n\n            // Determine the axis's position in its direction and on its side\n\n            $.each(isXAxis ? xaxes : yaxes, function(i, a) {\n                if (a && (a.show || a.reserveSpace)) {\n                    if (a === axis) {\n                        found = true;\n                    } else if (a.options.position === pos) {\n                        if (found) {\n                            outermost = false;\n                        } else {\n                            innermost = false;\n                        }\n                    }\n                }\n            });\n\n            // The outermost axis on each side has no margin\n            if (outermost) {\n                axisMargin = 0;\n            }\n\n            // Set the default tickLength if necessary\n            if (tickLength == null) {\n                tickLength = TICK_LENGTH_CONSTANT;\n            }\n\n            // By default, major tick marks are visible\n            if (showTicks == null) {\n                showTicks = true;\n            }\n\n            // By default, minor tick marks are visible\n            if (showMinorTicks == null) {\n                showMinorTicks = true;\n            }\n\n            // By default, grid lines are visible\n            if (gridLines == null) {\n                if (innermost) {\n                    gridLines = true;\n                } else {\n                    gridLines = false;\n                }\n            }\n\n            if (!isNaN(+tickLength)) {\n                padding += showTicks ? +tickLength : 0;\n            }\n\n            if (isXAxis) {\n                lh += padding;\n\n                if (pos === \"bottom\") {\n                    plotOffset.bottom += lh + axisMargin;\n                    axis.box = {\n                        top: surface.height - plotOffset.bottom,\n                        height: lh\n                    };\n                } else {\n                    axis.box = {\n                        top: plotOffset.top + axisMargin,\n                        height: lh\n                    };\n                    plotOffset.top += lh + axisMargin;\n                }\n            } else {\n                lw += padding;\n\n                if (pos === \"left\") {\n                    axis.box = {\n                        left: plotOffset.left + axisMargin,\n                        width: lw\n                    };\n                    plotOffset.left += lw + axisMargin;\n                } else {\n                    plotOffset.right += lw + axisMargin;\n                    axis.box = {\n                        left: surface.width - plotOffset.right,\n                        width: lw\n                    };\n                }\n            }\n\n            // save for future reference\n            axis.position = pos;\n            axis.tickLength = tickLength;\n            axis.showMinorTicks = showMinorTicks;\n            axis.showTicks = showTicks;\n            axis.gridLines = gridLines;\n            axis.box.padding = padding;\n            axis.innermost = innermost;\n        }\n\n        function allocateAxisBoxSecondPhase(axis) {\n            // now that all axis boxes have been placed in one\n            // dimension, we can set the remaining dimension coordinates\n            if (axis.direction === \"x\") {\n                axis.box.left = plotOffset.left - axis.labelWidth / 2;\n                axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;\n            } else {\n                axis.box.top = plotOffset.top - axis.labelHeight / 2;\n                axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;\n            }\n        }\n\n        function adjustLayoutForThingsStickingOut() {\n            // possibly adjust plot offset to ensure everything stays\n            // inside the canvas and isn't clipped off\n\n            var minMargin = options.grid.minBorderMargin,\n                i;\n\n            // check stuff from the plot (FIXME: this should just read\n            // a value from the series, otherwise it's impossible to\n            // customize)\n            if (minMargin == null) {\n                minMargin = 0;\n                for (i = 0; i < series.length; ++i) {\n                    minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth / 2));\n                }\n            }\n\n            var a, offset = {},\n                margins = {\n                    left: minMargin,\n                    right: minMargin,\n                    top: minMargin,\n                    bottom: minMargin\n                };\n\n            // check axis labels, note we don't check the actual\n            // labels but instead use the overall width/height to not\n            // jump as much around with replots\n            $.each(allAxes(), function(_, axis) {\n                if (axis.reserveSpace && axis.ticks && axis.ticks.length) {\n                    if (axis.direction === \"x\") {\n                        margins.left = Math.max(margins.left, axis.labelWidth / 2);\n                        margins.right = Math.max(margins.right, axis.labelWidth / 2);\n                    } else {\n                        margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);\n                        margins.top = Math.max(margins.top, axis.labelHeight / 2);\n                    }\n                }\n            });\n\n            for (a in margins) {\n                offset[a] = margins[a] - plotOffset[a];\n            }\n            $.each(xaxes.concat(yaxes), function(_, axis) {\n                alignAxisWithGrid(axis, offset, function (offset) {\n                    return offset > 0;\n                });\n            });\n\n            plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));\n            plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));\n            plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));\n            plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));\n        }\n\n        function alignAxisWithGrid(axis, offset, isValid) {\n            if (axis.direction === \"x\") {\n                if (axis.position === \"bottom\" && isValid(offset.bottom)) {\n                    axis.box.top -= Math.ceil(offset.bottom);\n                }\n                if (axis.position === \"top\" && isValid(offset.top)) {\n                    axis.box.top += Math.ceil(offset.top);\n                }\n            } else {\n                if (axis.position === \"left\" && isValid(offset.left)) {\n                    axis.box.left += Math.ceil(offset.left);\n                }\n                if (axis.position === \"right\" && isValid(offset.right)) {\n                    axis.box.left -= Math.ceil(offset.right);\n                }\n            }\n        }\n\n        function setupGrid(autoScale) {\n            var i, a, axes = allAxes(),\n                showGrid = options.grid.show;\n\n            // Initialize the plot's offset from the edge of the canvas\n\n            for (a in plotOffset) {\n                plotOffset[a] = 0;\n            }\n\n            executeHooks(hooks.processOffset, [plotOffset]);\n\n            // If the grid is visible, add its border width to the offset\n            for (a in plotOffset) {\n                if (typeof (options.grid.borderWidth) === \"object\") {\n                    plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;\n                } else {\n                    plotOffset[a] += showGrid ? options.grid.borderWidth : 0;\n                }\n            }\n\n            $.each(axes, function(_, axis) {\n                var axisOpts = axis.options;\n                axis.show = axisOpts.show == null ? axis.used : axisOpts.show;\n                axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;\n                setupTickFormatter(axis);\n                executeHooks(hooks.setRange, [axis, autoScale]);\n                setRange(axis, autoScale);\n            });\n\n            if (showGrid) {\n                plotWidth = surface.width - plotOffset.left - plotOffset.right;\n                plotHeight = surface.height - plotOffset.bottom - plotOffset.top;\n\n                var allocatedAxes = $.grep(axes, function(axis) {\n                    return axis.show || axis.reserveSpace;\n                });\n\n                $.each(allocatedAxes, function(_, axis) {\n                    // make the ticks\n                    setupTickGeneration(axis);\n                    setMajorTicks(axis);\n                    snapRangeToTicks(axis, axis.ticks, series);\n\n                    //for computing the endpoints precision, transformationHelpers are needed\n                    setTransformationHelpers(axis);\n                    setEndpointTicks(axis, series);\n\n                    // find labelWidth/Height for axis\n                    measureTickLabels(axis);\n                });\n\n                // with all dimensions calculated, we can compute the\n                // axis bounding boxes, start from the outside\n                // (reverse order)\n                for (i = allocatedAxes.length - 1; i >= 0; --i) {\n                    allocateAxisBoxFirstPhase(allocatedAxes[i]);\n                }\n\n                // make sure we've got enough space for things that\n                // might stick out\n                adjustLayoutForThingsStickingOut();\n\n                $.each(allocatedAxes, function(_, axis) {\n                    allocateAxisBoxSecondPhase(axis);\n                });\n            }\n\n            //adjust axis and plotOffset according to grid.margins\n            if (options.grid.margin) {\n                for (a in plotOffset) {\n                    var margin = options.grid.margin || 0;\n                    plotOffset[a] += typeof margin === \"number\" ? margin : (margin[a] || 0);\n                }\n                $.each(xaxes.concat(yaxes), function(_, axis) {\n                    alignAxisWithGrid(axis, options.grid.margin, function(offset) {\n                        return offset !== undefined && offset !== null;\n                    });\n                });\n            }\n\n            //after adjusting the axis, plot width and height will be modified\n            plotWidth = surface.width - plotOffset.left - plotOffset.right;\n            plotHeight = surface.height - plotOffset.bottom - plotOffset.top;\n\n            // now we got the proper plot dimensions, we can compute the scaling\n            $.each(axes, function(_, axis) {\n                setTransformationHelpers(axis);\n            });\n\n            if (showGrid) {\n                drawAxisLabels();\n            }\n\n            executeHooks(hooks.setupGrid, []);\n        }\n\n        function widenMinMax(minimum, maximum) {\n            var min = (minimum === undefined ? null : minimum);\n            var max = (maximum === undefined ? null : maximum);\n            var delta = max - min;\n            if (delta === 0.0) {\n                // degenerate case\n                var widen = max === 0 ? 1 : 0.01;\n                var wmin = null;\n                if (min == null) {\n                    wmin -= widen;\n                }\n\n                // always widen max if we couldn't widen min to ensure we\n                // don't fall into min == max which doesn't work\n                if (max == null || min != null) {\n                    max += widen;\n                }\n\n                if (wmin != null) {\n                    min = wmin;\n                }\n            }\n\n            return {\n                min: min,\n                max: max\n            };\n        }\n\n        function autoScaleAxis(axis) {\n            var opts = axis.options,\n                min = opts.min,\n                max = opts.max,\n                datamin = axis.datamin,\n                datamax = axis.datamax,\n                delta;\n\n            switch (opts.autoScale) {\n                case \"none\":\n                    min = +(opts.min != null ? opts.min : datamin);\n                    max = +(opts.max != null ? opts.max : datamax);\n                    break;\n                case \"loose\":\n                    if (datamin != null && datamax != null) {\n                        min = datamin;\n                        max = datamax;\n                        delta = $.plot.saturated.saturate(max - min);\n                        var margin = ((typeof opts.autoScaleMargin === 'number') ? opts.autoScaleMargin : 0.02);\n                        min = $.plot.saturated.saturate(min - delta * margin);\n                        max = $.plot.saturated.saturate(max + delta * margin);\n\n                        // make sure we don't go below zero if all values are positive\n                        if (min < 0 && datamin >= 0) {\n                            min = 0;\n                        }\n                    } else {\n                        min = opts.min;\n                        max = opts.max;\n                    }\n                    break;\n                case \"exact\":\n                    min = (datamin != null ? datamin : opts.min);\n                    max = (datamax != null ? datamax : opts.max);\n                    break;\n                case \"sliding-window\":\n                    if (datamax > max) {\n                        // move the window to fit the new data,\n                        // keeping the axis range constant\n                        max = datamax;\n                        min = Math.max(datamax - (opts.windowSize || 100), min);\n                    }\n                    break;\n            }\n\n            var widenedMinMax = widenMinMax(min, max);\n            min = widenedMinMax.min;\n            max = widenedMinMax.max;\n\n            // grow loose or grow exact supported\n            if (opts.growOnly === true && opts.autoScale !== \"none\" && opts.autoScale !== \"sliding-window\") {\n                min = (min < datamin) ? min : (datamin !== null ? datamin : min);\n                max = (max > datamax) ? max : (datamax !== null ? datamax : max);\n            }\n\n            axis.autoScaledMin = min;\n            axis.autoScaledMax = max;\n        }\n\n        function setRange(axis, autoScale) {\n            var min = typeof axis.options.min === 'number' ? axis.options.min : axis.min,\n                max = typeof axis.options.max === 'number' ? axis.options.max : axis.max,\n                plotOffset = axis.options.offset;\n\n            if (autoScale) {\n                autoScaleAxis(axis);\n                min = axis.autoScaledMin;\n                max = axis.autoScaledMax;\n            }\n\n            min = (min != null ? min : -1) + (plotOffset.below || 0);\n            max = (max != null ? max : 1) + (plotOffset.above || 0);\n\n            if (min > max) {\n                var tmp = min;\n                min = max;\n                max = tmp;\n                axis.options.offset = { above: 0, below: 0 };\n            }\n\n            axis.min = $.plot.saturated.saturate(min);\n            axis.max = $.plot.saturated.saturate(max);\n        }\n\n        function computeValuePrecision (min, max, direction, ticks, tickDecimals) {\n            var noTicks = fixupNumberOfTicks(direction, surface, ticks);\n\n            var delta = $.plot.saturated.delta(min, max, noTicks),\n                dec = -Math.floor(Math.log(delta) / Math.LN10);\n\n            //if it is called with tickDecimals, then the precision should not be greather then that\n            if (tickDecimals && dec > tickDecimals) {\n                dec = tickDecimals;\n            }\n\n            var magn = parseFloat('1e' + (-dec)),\n                norm = delta / magn;\n\n            if (norm > 2.25 && norm < 3 && (tickDecimals == null || (dec + 1) <= tickDecimals)) {\n                //we need an extra decimals when tickSize is 2.5\n                ++dec;\n            }\n\n            return isFinite(dec) ? dec : 0;\n        };\n\n        function computeTickSize (min, max, noTicks, tickDecimals) {\n            var delta = $.plot.saturated.delta(min, max, noTicks),\n                dec = -Math.floor(Math.log(delta) / Math.LN10);\n\n            //if it is called with tickDecimals, then the precision should not be greather then that\n            if (tickDecimals && dec > tickDecimals) {\n                dec = tickDecimals;\n            }\n\n            var magn = parseFloat('1e' + (-dec)),\n                norm = delta / magn, // norm is between 1.0 and 10.0\n                size;\n\n            if (norm < 1.5) {\n                size = 1;\n            } else if (norm < 3) {\n                size = 2;\n                if (norm > 2.25 && (tickDecimals == null || (dec + 1) <= tickDecimals)) {\n                    size = 2.5;\n                }\n            } else if (norm < 7.5) {\n                size = 5;\n            } else {\n                size = 10;\n            }\n\n            size *= magn;\n            return size;\n        }\n\n        function getAxisTickSize(min, max, direction, options, tickDecimals) {\n            var noTicks;\n\n            if (typeof options.ticks === \"number\" && options.ticks > 0) {\n                noTicks = options.ticks;\n            } else {\n            // heuristic based on the model a*sqrt(x) fitted to\n            // some data points that seemed reasonable\n                noTicks = 0.3 * Math.sqrt(direction === \"x\" ? surface.width : surface.height);\n            }\n\n            var size = computeTickSize(min, max, noTicks, tickDecimals);\n\n            if (options.minTickSize != null && size < options.minTickSize) {\n                size = options.minTickSize;\n            }\n\n            return options.tickSize || size;\n        };\n\n        function fixupNumberOfTicks(direction, surface, ticksOption) {\n            var noTicks;\n\n            if (typeof ticksOption === \"number\" && ticksOption > 0) {\n                noTicks = ticksOption;\n            } else {\n                noTicks = 0.3 * Math.sqrt(direction === \"x\" ? surface.width : surface.height);\n            }\n\n            return noTicks;\n        }\n\n        function setupTickFormatter(axis) {\n            var opts = axis.options;\n            if (!axis.tickFormatter) {\n                if (typeof opts.tickFormatter === 'function') {\n                    axis.tickFormatter = function() {\n                        var args = Array.prototype.slice.call(arguments);\n                        return \"\" + opts.tickFormatter.apply(null, args);\n                    };\n                } else {\n                    axis.tickFormatter = defaultTickFormatter;\n                }\n            }\n        }\n\n        function setupTickGeneration(axis) {\n            var opts = axis.options;\n            var noTicks;\n\n            noTicks = fixupNumberOfTicks(axis.direction, surface, opts.ticks);\n\n            axis.delta = $.plot.saturated.delta(axis.min, axis.max, noTicks);\n            var precision = plot.computeValuePrecision(axis.min, axis.max, axis.direction, noTicks, opts.tickDecimals);\n\n            axis.tickDecimals = Math.max(0, opts.tickDecimals != null ? opts.tickDecimals : precision);\n            axis.tickSize = getAxisTickSize(axis.min, axis.max, axis.direction, opts, opts.tickDecimals);\n\n            // Flot supports base-10 axes; any other mode else is handled by a plug-in,\n            // like flot.time.js.\n\n            if (!axis.tickGenerator) {\n                if (typeof opts.tickGenerator === 'function') {\n                    axis.tickGenerator = opts.tickGenerator;\n                } else {\n                    axis.tickGenerator = defaultTickGenerator;\n                }\n            }\n\n            if (opts.alignTicksWithAxis != null) {\n                var otherAxis = (axis.direction === \"x\" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];\n                if (otherAxis && otherAxis.used && otherAxis !== axis) {\n                    // consider snapping min/max to outermost nice ticks\n                    var niceTicks = axis.tickGenerator(axis, plot);\n                    if (niceTicks.length > 0) {\n                        if (opts.min == null) {\n                            axis.min = Math.min(axis.min, niceTicks[0]);\n                        }\n\n                        if (opts.max == null && niceTicks.length > 1) {\n                            axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);\n                        }\n                    }\n\n                    axis.tickGenerator = function(axis) {\n                        // copy ticks, scaled to this axis\n                        var ticks = [],\n                            v, i;\n                        for (i = 0; i < otherAxis.ticks.length; ++i) {\n                            v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);\n                            v = axis.min + v * (axis.max - axis.min);\n                            ticks.push(v);\n                        }\n                        return ticks;\n                    };\n\n                    // we might need an extra decimal since forced\n                    // ticks don't necessarily fit naturally\n                    if (!axis.mode && opts.tickDecimals == null) {\n                        var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),\n                            ts = axis.tickGenerator(axis, plot);\n\n                        // only proceed if the tick interval rounded\n                        // with an extra decimal doesn't give us a\n                        // zero at end\n                        if (!(ts.length > 1 && /\\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) {\n                            axis.tickDecimals = extraDec;\n                        }\n                    }\n                }\n            }\n        }\n\n        function setMajorTicks(axis) {\n            var oticks = axis.options.ticks,\n                ticks = [];\n            if (oticks == null || (typeof oticks === \"number\" && oticks > 0)) {\n                ticks = axis.tickGenerator(axis, plot);\n            } else if (oticks) {\n                if ($.isFunction(oticks)) {\n                // generate the ticks\n                    ticks = oticks(axis);\n                } else {\n                    ticks = oticks;\n                }\n            }\n\n            // clean up/labelify the supplied ticks, copy them over\n            var i, v;\n            axis.ticks = [];\n            for (i = 0; i < ticks.length; ++i) {\n                var label = null;\n                var t = ticks[i];\n                if (typeof t === \"object\") {\n                    v = +t[0];\n                    if (t.length > 1) {\n                        label = t[1];\n                    }\n                } else {\n                    v = +t;\n                }\n\n                if (!isNaN(v)) {\n                    axis.ticks.push(\n                        newTick(v, label, axis, 'major'));\n                }\n            }\n        }\n\n        function newTick(v, label, axis, type) {\n            if (label === null) {\n                switch (type) {\n                    case 'min':\n                    case 'max':\n                        //improving the precision of endpoints\n                        var precision = getEndpointPrecision(v, axis);\n                        label = isFinite(precision) ? axis.tickFormatter(v, axis, precision, plot) : axis.tickFormatter(v, axis, precision, plot);\n                        break;\n                    case 'major':\n                        label = axis.tickFormatter(v, axis, undefined, plot);\n                }\n            }\n            return {\n                v: v,\n                label: label\n            };\n        }\n\n        function snapRangeToTicks(axis, ticks, series) {\n            var anyDataInSeries = function(series) {\n                return series.some(e => e.datapoints.points.length > 0);\n            }\n\n            if (axis.options.autoScale === \"loose\" && ticks.length > 0 && anyDataInSeries(series)) {\n                // snap to ticks\n                axis.min = Math.min(axis.min, ticks[0].v);\n                axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);\n            }\n        }\n\n        function getEndpointPrecision(value, axis) {\n            var canvas1 = Math.floor(axis.p2c(value)),\n                canvas2 = axis.direction === \"x\" ? canvas1 + 1 : canvas1 - 1,\n                point1 = axis.c2p(canvas1),\n                point2 = axis.c2p(canvas2),\n                precision = computeValuePrecision(point1, point2, axis.direction, 1);\n\n            return precision;\n        }\n\n        function setEndpointTicks(axis, series) {\n            if (isValidEndpointTick(axis, series)) {\n                axis.ticks.unshift(newTick(axis.min, null, axis, 'min'));\n                axis.ticks.push(newTick(axis.max, null, axis, 'max'));\n            }\n        }\n\n        function isValidEndpointTick(axis, series) {\n            if (axis.options.showTickLabels === 'endpoints') {\n                return true;\n            }\n            if (axis.options.showTickLabels === 'all') {\n                var associatedSeries = series.filter(function(s) {\n                        return s.bars.horizontal ? s.yaxis === axis : s.xaxis === axis;\n                    }),\n                    notAllBarSeries = associatedSeries.some(function(s) {\n                        return !s.bars.show;\n                    });\n                return associatedSeries.length === 0 || notAllBarSeries;\n            }\n            if (axis.options.showTickLabels === 'major' || axis.options.showTickLabels === 'none') {\n                return false;\n            }\n        }\n\n        function draw() {\n            surface.clear();\n            executeHooks(hooks.drawBackground, [ctx]);\n\n            var grid = options.grid;\n\n            // draw background, if any\n            if (grid.show && grid.backgroundColor) {\n                drawBackground();\n            }\n\n            if (grid.show && !grid.aboveData) {\n                drawGrid();\n            }\n\n            for (var i = 0; i < series.length; ++i) {\n                executeHooks(hooks.drawSeries, [ctx, series[i], i, getColorOrGradient]);\n                drawSeries(series[i]);\n            }\n\n            executeHooks(hooks.draw, [ctx]);\n\n            if (grid.show && grid.aboveData) {\n                drawGrid();\n            }\n\n            surface.render();\n\n            // A draw implies that either the axes or data have changed, so we\n            // should probably update the overlay highlights as well.\n            triggerRedrawOverlay();\n        }\n\n        function extractRange(ranges, coord) {\n            var axis, from, to, key, axes = allAxes();\n\n            for (var i = 0; i < axes.length; ++i) {\n                axis = axes[i];\n                if (axis.direction === coord) {\n                    key = coord + axis.n + \"axis\";\n                    if (!ranges[key] && axis.n === 1) {\n                        // support x1axis as xaxis\n                        key = coord + \"axis\";\n                    }\n\n                    if (ranges[key]) {\n                        from = ranges[key].from;\n                        to = ranges[key].to;\n                        break;\n                    }\n                }\n            }\n\n            // backwards-compat stuff - to be removed in future\n            if (!ranges[key]) {\n                axis = coord === \"x\" ? xaxes[0] : yaxes[0];\n                from = ranges[coord + \"1\"];\n                to = ranges[coord + \"2\"];\n            }\n\n            // auto-reverse as an added bonus\n            if (from != null && to != null && from > to) {\n                var tmp = from;\n                from = to;\n                to = tmp;\n            }\n\n            return {\n                from: from,\n                to: to,\n                axis: axis\n            };\n        }\n\n        function drawBackground() {\n            ctx.save();\n            ctx.translate(plotOffset.left, plotOffset.top);\n\n            ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, \"rgba(255, 255, 255, 0)\");\n            ctx.fillRect(0, 0, plotWidth, plotHeight);\n            ctx.restore();\n        }\n\n        function drawMarkings() {\n            // draw markings\n            var markings = options.grid.markings,\n                axes;\n\n            if (markings) {\n                if ($.isFunction(markings)) {\n                    axes = plot.getAxes();\n                    // xmin etc. is backwards compatibility, to be\n                    // removed in the future\n                    axes.xmin = axes.xaxis.min;\n                    axes.xmax = axes.xaxis.max;\n                    axes.ymin = axes.yaxis.min;\n                    axes.ymax = axes.yaxis.max;\n\n                    markings = markings(axes);\n                }\n\n                var i;\n                for (i = 0; i < markings.length; ++i) {\n                    var m = markings[i],\n                        xrange = extractRange(m, \"x\"),\n                        yrange = extractRange(m, \"y\");\n\n                    // fill in missing\n                    if (xrange.from == null) {\n                        xrange.from = xrange.axis.min;\n                    }\n\n                    if (xrange.to == null) {\n                        xrange.to = xrange.axis.max;\n                    }\n\n                    if (yrange.from == null) {\n                        yrange.from = yrange.axis.min;\n                    }\n\n                    if (yrange.to == null) {\n                        yrange.to = yrange.axis.max;\n                    }\n\n                    // clip\n                    if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||\n                        yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) {\n                        continue;\n                    }\n\n                    xrange.from = Math.max(xrange.from, xrange.axis.min);\n                    xrange.to = Math.min(xrange.to, xrange.axis.max);\n                    yrange.from = Math.max(yrange.from, yrange.axis.min);\n                    yrange.to = Math.min(yrange.to, yrange.axis.max);\n\n                    var xequal = xrange.from === xrange.to,\n                        yequal = yrange.from === yrange.to;\n\n                    if (xequal && yequal) {\n                        continue;\n                    }\n\n                    // then draw\n                    xrange.from = Math.floor(xrange.axis.p2c(xrange.from));\n                    xrange.to = Math.floor(xrange.axis.p2c(xrange.to));\n                    yrange.from = Math.floor(yrange.axis.p2c(yrange.from));\n                    yrange.to = Math.floor(yrange.axis.p2c(yrange.to));\n\n                    if (xequal || yequal) {\n                        var lineWidth = m.lineWidth || options.grid.markingsLineWidth,\n                            subPixel = lineWidth % 2 ? 0.5 : 0;\n                        ctx.beginPath();\n                        ctx.strokeStyle = m.color || options.grid.markingsColor;\n                        ctx.lineWidth = lineWidth;\n                        if (xequal) {\n                            ctx.moveTo(xrange.to + subPixel, yrange.from);\n                            ctx.lineTo(xrange.to + subPixel, yrange.to);\n                        } else {\n                            ctx.moveTo(xrange.from, yrange.to + subPixel);\n                            ctx.lineTo(xrange.to, yrange.to + subPixel);\n                        }\n                        ctx.stroke();\n                    } else {\n                        ctx.fillStyle = m.color || options.grid.markingsColor;\n                        ctx.fillRect(xrange.from, yrange.to,\n                            xrange.to - xrange.from,\n                            yrange.from - yrange.to);\n                    }\n                }\n            }\n        }\n\n        function findEdges(axis) {\n            var box = axis.box,\n                x = 0,\n                y = 0;\n\n            // find the edges\n            if (axis.direction === \"x\") {\n                x = 0;\n                y = box.top - plotOffset.top + (axis.position === \"top\" ? box.height : 0);\n            } else {\n                y = 0;\n                x = box.left - plotOffset.left + (axis.position === \"left\" ? box.width : 0) + axis.boxPosition.centerX;\n            }\n\n            return {\n                x: x,\n                y: y\n            };\n        };\n\n        function alignPosition(lineWidth, pos) {\n            return ((lineWidth % 2) !== 0) ? Math.floor(pos) + 0.5 : pos;\n        };\n\n        function drawTickBar(axis) {\n            ctx.lineWidth = 1;\n            var edges = findEdges(axis),\n                x = edges.x,\n                y = edges.y;\n\n            // draw tick bar\n            if (axis.show) {\n                var xoff = 0,\n                    yoff = 0;\n\n                ctx.strokeStyle = axis.options.color;\n                ctx.beginPath();\n                if (axis.direction === \"x\") {\n                    xoff = plotWidth + 1;\n                } else {\n                    yoff = plotHeight + 1;\n                }\n\n                if (axis.direction === \"x\") {\n                    y = alignPosition(ctx.lineWidth, y);\n                } else {\n                    x = alignPosition(ctx.lineWidth, x);\n                }\n\n                ctx.moveTo(x, y);\n                ctx.lineTo(x + xoff, y + yoff);\n                ctx.stroke();\n            }\n        };\n\n        function drawTickMarks(axis) {\n            var t = axis.tickLength,\n                minorTicks = axis.showMinorTicks,\n                minorTicksNr = MINOR_TICKS_COUNT_CONSTANT,\n                edges = findEdges(axis),\n                x = edges.x,\n                y = edges.y,\n                i = 0;\n\n            // draw major tick marks\n            ctx.strokeStyle = axis.options.color;\n            ctx.beginPath();\n\n            for (i = 0; i < axis.ticks.length; ++i) {\n                var v = axis.ticks[i].v,\n                    xoff = 0,\n                    yoff = 0,\n                    xminor = 0,\n                    yminor = 0,\n                    j;\n\n                if (!isNaN(v) && v >= axis.min && v <= axis.max) {\n                    if (axis.direction === \"x\") {\n                        x = axis.p2c(v);\n                        yoff = t;\n\n                        if (axis.position === \"top\") {\n                            yoff = -yoff;\n                        }\n                    } else {\n                        y = axis.p2c(v);\n                        xoff = t;\n\n                        if (axis.position === \"left\") {\n                            xoff = -xoff;\n                        }\n                    }\n\n                    if (axis.direction === \"x\") {\n                        x = alignPosition(ctx.lineWidth, x);\n                    } else {\n                        y = alignPosition(ctx.lineWidth, y);\n                    }\n\n                    ctx.moveTo(x, y);\n                    ctx.lineTo(x + xoff, y + yoff);\n                }\n\n                //draw minor tick marks\n                if (minorTicks === true && i < axis.ticks.length - 1) {\n                    var v1 = axis.ticks[i].v,\n                        v2 = axis.ticks[i + 1].v,\n                        step = (v2 - v1) / (minorTicksNr + 1);\n\n                    for (j = 1; j <= minorTicksNr; j++) {\n                        // compute minor tick position\n                        if (axis.direction === \"x\") {\n                            yminor = t / 2; // minor ticks are half length\n                            x = alignPosition(ctx.lineWidth, axis.p2c(v1 + j * step))\n\n                            if (axis.position === \"top\") {\n                                yminor = -yminor;\n                            }\n\n                            // don't go over the plot borders\n                            if ((x < 0) || (x > plotWidth)) {\n                                continue;\n                            }\n                        } else {\n                            xminor = t / 2; // minor ticks are half length\n                            y = alignPosition(ctx.lineWidth, axis.p2c(v1 + j * step));\n\n                            if (axis.position === \"left\") {\n                                xminor = -xminor;\n                            }\n\n                            // don't go over the plot borders\n                            if ((y < 0) || (y > plotHeight)) {\n                                continue;\n                            }\n                        }\n\n                        ctx.moveTo(x, y);\n                        ctx.lineTo(x + xminor, y + yminor);\n                    }\n                }\n            }\n\n            ctx.stroke();\n        };\n\n        function drawGridLines(axis) {\n            // check if the line will be overlapped with a border\n            var overlappedWithBorder = function (value) {\n                var bw = options.grid.borderWidth;\n                return (((typeof bw === \"object\" && bw[axis.position] > 0) || bw > 0) && (value === axis.min || value === axis.max));\n            };\n\n            ctx.strokeStyle = options.grid.tickColor;\n            ctx.beginPath();\n            var i;\n            for (i = 0; i < axis.ticks.length; ++i) {\n                var v = axis.ticks[i].v,\n                    xoff = 0,\n                    yoff = 0,\n                    x = 0,\n                    y = 0;\n\n                if (isNaN(v) || v < axis.min || v > axis.max) continue;\n\n                // skip those lying on the axes if we got a border\n                if (overlappedWithBorder(v)) continue;\n\n                if (axis.direction === \"x\") {\n                    x = axis.p2c(v);\n                    y = plotHeight;\n                    yoff = -plotHeight;\n                } else {\n                    x = 0;\n                    y = axis.p2c(v);\n                    xoff = plotWidth;\n                }\n\n                if (axis.direction === \"x\") {\n                    x = alignPosition(ctx.lineWidth, x);\n                } else {\n                    y = alignPosition(ctx.lineWidth, y);\n                }\n\n                ctx.moveTo(x, y);\n                ctx.lineTo(x + xoff, y + yoff);\n            }\n\n            ctx.stroke();\n        };\n\n        function drawBorder() {\n            // If either borderWidth or borderColor is an object, then draw the border\n            // line by line instead of as one rectangle\n            var bw = options.grid.borderWidth,\n                bc = options.grid.borderColor;\n\n            if (typeof bw === \"object\" || typeof bc === \"object\") {\n                if (typeof bw !== \"object\") {\n                    bw = {\n                        top: bw,\n                        right: bw,\n                        bottom: bw,\n                        left: bw\n                    };\n                }\n                if (typeof bc !== \"object\") {\n                    bc = {\n                        top: bc,\n                        right: bc,\n                        bottom: bc,\n                        left: bc\n                    };\n                }\n\n                if (bw.top > 0) {\n                    ctx.strokeStyle = bc.top;\n                    ctx.lineWidth = bw.top;\n                    ctx.beginPath();\n                    ctx.moveTo(0 - bw.left, 0 - bw.top / 2);\n                    ctx.lineTo(plotWidth, 0 - bw.top / 2);\n                    ctx.stroke();\n                }\n\n                if (bw.right > 0) {\n                    ctx.strokeStyle = bc.right;\n                    ctx.lineWidth = bw.right;\n                    ctx.beginPath();\n                    ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);\n                    ctx.lineTo(plotWidth + bw.right / 2, plotHeight);\n                    ctx.stroke();\n                }\n\n                if (bw.bottom > 0) {\n                    ctx.strokeStyle = bc.bottom;\n                    ctx.lineWidth = bw.bottom;\n                    ctx.beginPath();\n                    ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);\n                    ctx.lineTo(0, plotHeight + bw.bottom / 2);\n                    ctx.stroke();\n                }\n\n                if (bw.left > 0) {\n                    ctx.strokeStyle = bc.left;\n                    ctx.lineWidth = bw.left;\n                    ctx.beginPath();\n                    ctx.moveTo(0 - bw.left / 2, plotHeight + bw.bottom);\n                    ctx.lineTo(0 - bw.left / 2, 0);\n                    ctx.stroke();\n                }\n            } else {\n                ctx.lineWidth = bw;\n                ctx.strokeStyle = options.grid.borderColor;\n                ctx.strokeRect(-bw / 2, -bw / 2, plotWidth + bw, plotHeight + bw);\n            }\n        };\n\n        function drawGrid() {\n            var axes, bw;\n\n            ctx.save();\n            ctx.translate(plotOffset.left, plotOffset.top);\n\n            drawMarkings();\n\n            axes = allAxes();\n            bw = options.grid.borderWidth;\n\n            for (var j = 0; j < axes.length; ++j) {\n                var axis = axes[j];\n\n                if (!axis.show) {\n                    continue;\n                }\n\n                drawTickBar(axis);\n                if (axis.showTicks === true) {\n                    drawTickMarks(axis);\n                }\n\n                if (axis.gridLines === true) {\n                    drawGridLines(axis, bw);\n                }\n            }\n\n            // draw border\n            if (bw) {\n                drawBorder();\n            }\n\n            ctx.restore();\n        }\n\n        function drawAxisLabels() {\n            $.each(allAxes(), function(_, axis) {\n                var box = axis.box,\n                    legacyStyles = axis.direction + \"Axis \" + axis.direction + axis.n + \"Axis\",\n                    layer = \"flot-\" + axis.direction + \"-axis flot-\" + axis.direction + axis.n + \"-axis \" + legacyStyles,\n                    font = axis.options.font || \"flot-tick-label tickLabel\",\n                    i, x, y, halign, valign, info,\n                    margin = 3,\n                    nullBox = {x: NaN, y: NaN, width: NaN, height: NaN}, newLabelBox, labelBoxes = [],\n                    overlapping = function(x11, y11, x12, y12, x21, y21, x22, y22) {\n                        return ((x11 <= x21 && x21 <= x12) || (x21 <= x11 && x11 <= x22)) &&\n                               ((y11 <= y21 && y21 <= y12) || (y21 <= y11 && y11 <= y22));\n                    },\n                    overlapsOtherLabels = function(newLabelBox, previousLabelBoxes) {\n                        return previousLabelBoxes.some(function(labelBox) {\n                            return overlapping(\n                                newLabelBox.x, newLabelBox.y, newLabelBox.x + newLabelBox.width, newLabelBox.y + newLabelBox.height,\n                                labelBox.x, labelBox.y, labelBox.x + labelBox.width, labelBox.y + labelBox.height);\n                        });\n                    },\n                    drawAxisLabel = function (tick, labelBoxes) {\n                        if (!tick || !tick.label || tick.v < axis.min || tick.v > axis.max) {\n                            return nullBox;\n                        }\n\n                        info = surface.getTextInfo(layer, tick.label, font);\n\n                        if (axis.direction === \"x\") {\n                            halign = \"center\";\n                            x = plotOffset.left + axis.p2c(tick.v);\n                            if (axis.position === \"bottom\") {\n                                y = box.top + box.padding - axis.boxPosition.centerY;\n                            } else {\n                                y = box.top + box.height - box.padding + axis.boxPosition.centerY;\n                                valign = \"bottom\";\n                            }\n                            newLabelBox = {x: x - info.width / 2 - margin, y: y - margin, width: info.width + 2 * margin, height: info.height + 2 * margin};\n                        } else {\n                            valign = \"middle\";\n                            y = plotOffset.top + axis.p2c(tick.v);\n                            if (axis.position === \"left\") {\n                                x = box.left + box.width - box.padding - axis.boxPosition.centerX;\n                                halign = \"right\";\n                            } else {\n                                x = box.left + box.padding + axis.boxPosition.centerX;\n                            }\n                            newLabelBox = {x: x - info.width / 2 - margin, y: y - margin, width: info.width + 2 * margin, height: info.height + 2 * margin};\n                        }\n\n                        if (overlapsOtherLabels(newLabelBox, labelBoxes)) {\n                            return nullBox;\n                        }\n\n                        surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);\n\n                        return newLabelBox;\n                    };\n\n                // Remove text before checking for axis.show and ticks.length;\n                // otherwise plugins, like flot-tickrotor, that draw their own\n                // tick labels will end up with both theirs and the defaults.\n\n                surface.removeText(layer);\n\n                executeHooks(hooks.drawAxis, [axis, surface]);\n\n                if (!axis.show) {\n                    return;\n                }\n\n                switch (axis.options.showTickLabels) {\n                    case 'none':\n                        break;\n                    case 'endpoints':\n                        labelBoxes.push(drawAxisLabel(axis.ticks[0], labelBoxes));\n                        labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes));\n                        break;\n                    case 'major':\n                        labelBoxes.push(drawAxisLabel(axis.ticks[0], labelBoxes));\n                        labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes));\n                        for (i = 1; i < axis.ticks.length - 1; ++i) {\n                            labelBoxes.push(drawAxisLabel(axis.ticks[i], labelBoxes));\n                        }\n                        break;\n                    case 'all':\n                        labelBoxes.push(drawAxisLabel(axis.ticks[0], []));\n                        labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes));\n                        for (i = 1; i < axis.ticks.length - 1; ++i) {\n                            labelBoxes.push(drawAxisLabel(axis.ticks[i], labelBoxes));\n                        }\n                        break;\n                }\n            });\n        }\n\n        function drawSeries(series) {\n            if (series.lines.show) {\n                $.plot.drawSeries.drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient);\n            }\n\n            if (series.bars.show) {\n                $.plot.drawSeries.drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient);\n            }\n\n            if (series.points.show) {\n                $.plot.drawSeries.drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient);\n            }\n        }\n\n        function computeRangeForDataSeries(series, force, isValid) {\n            var points = series.datapoints.points,\n                ps = series.datapoints.pointsize,\n                format = series.datapoints.format,\n                topSentry = Number.POSITIVE_INFINITY,\n                bottomSentry = Number.NEGATIVE_INFINITY,\n                range = {\n                    xmin: topSentry,\n                    ymin: topSentry,\n                    xmax: bottomSentry,\n                    ymax: bottomSentry\n                };\n\n            for (var j = 0; j < points.length; j += ps) {\n                if (points[j] === null) {\n                    continue;\n                }\n\n                if (typeof (isValid) === 'function' && !isValid(points[j])) {\n                    continue;\n                }\n\n                for (var m = 0; m < ps; ++m) {\n                    var val = points[j + m],\n                        f = format[m];\n                    if (f === null || f === undefined) {\n                        continue;\n                    }\n\n                    if (typeof (isValid) === 'function' && !isValid(val)) {\n                        continue;\n                    }\n\n                    if ((!force && !f.computeRange) || val === Infinity || val === -Infinity) {\n                        continue;\n                    }\n\n                    if (f.x === true) {\n                        if (val < range.xmin) {\n                            range.xmin = val;\n                        }\n\n                        if (val > range.xmax) {\n                            range.xmax = val;\n                        }\n                    }\n\n                    if (f.y === true) {\n                        if (val < range.ymin) {\n                            range.ymin = val;\n                        }\n\n                        if (val > range.ymax) {\n                            range.ymax = val;\n                        }\n                    }\n                }\n            }\n\n            return range;\n        };\n\n        function adjustSeriesDataRange(series, range) {\n            if (series.bars.show) {\n                // make sure we got room for the bar on the dancing floor\n                var delta;\n\n                // update bar width if needed\n                var useAbsoluteBarWidth = series.bars.barWidth[1];\n                if (series.datapoints && series.datapoints.points && !useAbsoluteBarWidth) {\n                    computeBarWidth(series);\n                }\n\n                var barWidth = series.bars.barWidth[0] || series.bars.barWidth;\n                switch (series.bars.align) {\n                    case \"left\":\n                        delta = 0;\n                        break;\n                    case \"right\":\n                        delta = -barWidth;\n                        break;\n                    default:\n                        delta = -barWidth / 2;\n                }\n\n                if (series.bars.horizontal) {\n                    range.ymin += delta;\n                    range.ymax += delta + barWidth;\n                } else {\n                    range.xmin += delta;\n                    range.xmax += delta + barWidth;\n                }\n            }\n\n            if ((series.bars.show && series.bars.zero) || (series.lines.show && series.lines.zero)) {\n                var ps = series.datapoints.pointsize;\n\n                // make sure the 0 point is included in the computed y range when requested\n                if (ps <= 2) {\n                    /*if ps > 0 the points were already taken into account for autoScale */\n                    range.ymin = Math.min(0, range.ymin);\n                    range.ymax = Math.max(0, range.ymax);\n                }\n            }\n\n            return range;\n        };\n\n        function computeBarWidth(series) {\n            var xValues = [];\n            var pointsize = series.datapoints.pointsize, minDistance = Number.MAX_VALUE;\n\n            if (series.datapoints.points.length <= pointsize) {\n                minDistance = 1;\n            }\n\n            var start = series.bars.horizontal ? 1 : 0;\n            for (let j = start; j < series.datapoints.points.length; j += pointsize) {\n                if (isFinite(series.datapoints.points[j]) && series.datapoints.points[j] !== null) {\n                    xValues.push(series.datapoints.points[j]);\n                }\n            }\n\n            function onlyUnique(value, index, self) {\n                return self.indexOf(value) === index;\n            }\n\n            xValues = xValues.filter(onlyUnique);\n            xValues.sort(function(a, b) { return a - b });\n\n            for (let j = 1; j < xValues.length; j++) {\n                var distance = Math.abs(xValues[j] - xValues[j - 1]);\n                if (distance < minDistance && isFinite(distance)) {\n                    minDistance = distance;\n                }\n            }\n\n            if (typeof series.bars.barWidth === \"number\") {\n                series.bars.barWidth = series.bars.barWidth * minDistance;\n            } else {\n                series.bars.barWidth[0] = series.bars.barWidth[0] * minDistance;\n            }\n        }\n\n        function findNearbyItems(mouseX, mouseY, seriesFilter, radius, computeDistance) {\n            var items = findItems(mouseX, mouseY, seriesFilter, radius, computeDistance);\n            for (var i = 0; i < series.length; ++i) {\n                if (seriesFilter(i)) {\n                    executeHooks(hooks.findNearbyItems, [mouseX, mouseY, series, i, radius, computeDistance, items]);\n                }\n            }\n\n            return items.sort((a, b) => {\n                if (b.distance === undefined) {\n                    return -1;\n                } else if (a.distance === undefined && b.distance !== undefined) {\n                    return 1;\n                }\n\n                return a.distance - b.distance;\n            });\n        }\n\n        function findNearbyItem(mouseX, mouseY, seriesFilter, radius, computeDistance) {\n            var items = findNearbyItems(mouseX, mouseY, seriesFilter, radius, computeDistance);\n            return items[0] !== undefined ? items[0] : null;\n        }\n\n        // returns the data item the mouse is over/ the cursor is closest to, or null if none is found\n        function findItems(mouseX, mouseY, seriesFilter, radius, computeDistance) {\n            var i, foundItems = [],\n                items = [],\n                smallestDistance = radius * radius + 1;\n\n            for (i = series.length - 1; i >= 0; --i) {\n                if (!seriesFilter(i)) continue;\n\n                var s = series[i];\n                if (!s.datapoints) return;\n\n                var foundPoint = false;\n                if (s.lines.show || s.points.show) {\n                    var found = findNearbyPoint(s, mouseX, mouseY, radius, computeDistance);\n                    if (found) {\n                        items.push({ seriesIndex: i, dataIndex: found.dataIndex, distance: found.distance });\n                        foundPoint = true;\n                    }\n                }\n\n                if (s.bars.show && !foundPoint) { // no other point can be nearby\n                    var foundIndex = findNearbyBar(s, mouseX, mouseY);\n                    if (foundIndex >= 0) {\n                        items.push({ seriesIndex: i, dataIndex: foundIndex, distance: smallestDistance });\n                    }\n                }\n            }\n\n            for (i = 0; i < items.length; i++) {\n                var seriesIndex = items[i].seriesIndex;\n                var dataIndex = items[i].dataIndex;\n                var itemDistance = items[i].distance;\n                var ps = series[seriesIndex].datapoints.pointsize;\n\n                foundItems.push({\n                    datapoint: series[seriesIndex].datapoints.points.slice(dataIndex * ps, (dataIndex + 1) * ps),\n                    dataIndex: dataIndex,\n                    series: series[seriesIndex],\n                    seriesIndex: seriesIndex,\n                    distance: Math.sqrt(itemDistance)\n                });\n            }\n\n            return foundItems;\n        }\n\n        function findNearbyPoint (series, mouseX, mouseY, maxDistance, computeDistance) {\n            var mx = series.xaxis.c2p(mouseX),\n                my = series.yaxis.c2p(mouseY),\n                maxx = maxDistance / series.xaxis.scale,\n                maxy = maxDistance / series.yaxis.scale,\n                points = series.datapoints.points,\n                ps = series.datapoints.pointsize,\n                smallestDistance = Number.POSITIVE_INFINITY;\n\n            // with inverse transforms, we can't use the maxx/maxy\n            // optimization, sadly\n            if (series.xaxis.options.inverseTransform) {\n                maxx = Number.MAX_VALUE;\n            }\n\n            if (series.yaxis.options.inverseTransform) {\n                maxy = Number.MAX_VALUE;\n            }\n\n            var found = null;\n            for (var j = 0; j < points.length; j += ps) {\n                var x = points[j];\n                var y = points[j + 1];\n                if (x == null) {\n                    continue;\n                }\n\n                if (x - mx > maxx || x - mx < -maxx ||\n                    y - my > maxy || y - my < -maxy) {\n                    continue;\n                }\n\n                // We have to calculate distances in pixels, not in\n                // data units, because the scales of the axes may be different\n                var dx = Math.abs(series.xaxis.p2c(x) - mouseX);\n                var dy = Math.abs(series.yaxis.p2c(y) - mouseY);\n                var dist = computeDistance ? computeDistance(dx, dy) : dx * dx + dy * dy;\n\n                // use <= to ensure last point takes precedence\n                // (last generally means on top of)\n                if (dist < smallestDistance) {\n                    smallestDistance = dist;\n                    found = { dataIndex: j / ps, distance: dist };\n                }\n            }\n\n            return found;\n        }\n\n        function findNearbyBar (series, mouseX, mouseY) {\n            var barLeft, barRight,\n                barWidth = series.bars.barWidth[0] || series.bars.barWidth,\n                mx = series.xaxis.c2p(mouseX),\n                my = series.yaxis.c2p(mouseY),\n                points = series.datapoints.points,\n                ps = series.datapoints.pointsize;\n\n            switch (series.bars.align) {\n                case \"left\":\n                    barLeft = 0;\n                    break;\n                case \"right\":\n                    barLeft = -barWidth;\n                    break;\n                default:\n                    barLeft = -barWidth / 2;\n            }\n\n            barRight = barLeft + barWidth;\n\n            var fillTowards = series.bars.fillTowards || 0;\n            var defaultBottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min;\n\n            var foundIndex = -1;\n            for (var j = 0; j < points.length; j += ps) {\n                var x = points[j], y = points[j + 1];\n                if (x == null) {\n                    continue;\n                }\n\n                var bottom = ps === 3 ? points[j + 2] : defaultBottom;\n                // for a bar graph, the cursor must be inside the bar\n                if (series.bars.horizontal\n                    ? (mx <= Math.max(bottom, x) && mx >= Math.min(bottom, x) &&\n                        my >= y + barLeft && my <= y + barRight)\n                    : (mx >= x + barLeft && mx <= x + barRight &&\n                        my >= Math.min(bottom, y) && my <= Math.max(bottom, y))) {\n                    foundIndex = j / ps;\n                }\n            }\n\n            return foundIndex;\n        }\n\n        function findNearbyInterpolationPoint(posX, posY, seriesFilter) {\n            var i, j, dist, dx, dy, ps,\n                item,\n                smallestDistance = Number.MAX_VALUE;\n\n            for (i = 0; i < series.length; ++i) {\n                if (!seriesFilter(i)) {\n                    continue;\n                }\n                var points = series[i].datapoints.points;\n                ps = series[i].datapoints.pointsize;\n\n                // if the data is coming from positive -> negative, reverse the comparison\n                const comparer = points[points.length - ps] < points[0]\n                    ? function (x1, x2) { return x1 > x2 }\n                    : function (x1, x2) { return x2 > x1 };\n\n                // do not interpolate outside the bounds of the data.\n                if (comparer(posX, points[0])) {\n                    continue;\n                }\n\n                // Find the nearest points, x-wise\n                for (j = ps; j < points.length; j += ps) {\n                    if (comparer(posX, points[j])) {\n                        break;\n                    }\n                }\n\n                // Now Interpolate\n                var y,\n                    p1x = points[j - ps],\n                    p1y = points[j - ps + 1],\n                    p2x = points[j],\n                    p2y = points[j + 1];\n\n                if ((p1x === undefined) || (p2x === undefined) ||\n                    (p1y === undefined) || (p2y === undefined)) {\n                    continue;\n                }\n\n                if (p1x === p2x) {\n                    y = p2y\n                } else {\n                    y = p1y + (p2y - p1y) * (posX - p1x) / (p2x - p1x);\n                }\n\n                posY = y;\n\n                dx = Math.abs(series[i].xaxis.p2c(p2x) - posX);\n                dy = Math.abs(series[i].yaxis.p2c(p2y) - posY);\n                dist = dx * dx + dy * dy;\n\n                if (dist < smallestDistance) {\n                    smallestDistance = dist;\n                    item = [posX, posY, i, j];\n                }\n            }\n\n            if (item) {\n                i = item[2];\n                j = item[3];\n                ps = series[i].datapoints.pointsize;\n                points = series[i].datapoints.points;\n                p1x = points[j - ps];\n                p1y = points[j - ps + 1];\n                p2x = points[j];\n                p2y = points[j + 1];\n\n                return {\n                    datapoint: [item[0], item[1]],\n                    leftPoint: [p1x, p1y],\n                    rightPoint: [p2x, p2y],\n                    seriesIndex: i\n                };\n            }\n\n            return null;\n        }\n\n        function triggerRedrawOverlay() {\n            var t = options.interaction.redrawOverlayInterval;\n            if (t === -1) { // skip event queue\n                drawOverlay();\n                return;\n            }\n\n            if (!redrawTimeout) {\n                redrawTimeout = setTimeout(function() {\n                    drawOverlay(plot);\n                }, t);\n            }\n        }\n\n        function drawOverlay(plot) {\n            redrawTimeout = null;\n\n            if (!octx) {\n                return;\n            }\n            overlay.clear();\n            executeHooks(hooks.drawOverlay, [octx, overlay]);\n            var event = new CustomEvent('onDrawingDone');\n            plot.getEventHolder().dispatchEvent(event);\n            plot.getPlaceholder().trigger('drawingdone');\n        }\n\n        function getColorOrGradient(spec, bottom, top, defaultColor) {\n            if (typeof spec === \"string\") {\n                return spec;\n            } else {\n                // assume this is a gradient spec; IE currently only\n                // supports a simple vertical gradient properly, so that's\n                // what we support too\n                var gradient = ctx.createLinearGradient(0, top, 0, bottom);\n\n                for (var i = 0, l = spec.colors.length; i < l; ++i) {\n                    var c = spec.colors[i];\n                    if (typeof c !== \"string\") {\n                        var co = $.color.parse(defaultColor);\n                        if (c.brightness != null) {\n                            co = co.scale('rgb', c.brightness);\n                        }\n\n                        if (c.opacity != null) {\n                            co.a *= c.opacity;\n                        }\n\n                        c = co.toString();\n                    }\n                    gradient.addColorStop(i / (l - 1), c);\n                }\n\n                return gradient;\n            }\n        }\n    }\n\n    // Add the plot function to the top level of the jQuery object\n\n    $.plot = function(placeholder, data, options) {\n        var plot = new Plot($(placeholder), data, options, $.plot.plugins);\n        return plot;\n    };\n\n    $.plot.version = \"3.0.0\";\n\n    $.plot.plugins = [];\n\n    // Also add the plot function as a chainable property\n    $.fn.plot = function(data, options) {\n        return this.each(function() {\n            $.plot(this, data, options);\n        });\n    };\n\n    $.plot.linearTickGenerator = defaultTickGenerator;\n    $.plot.defaultTickFormatter = defaultTickFormatter;\n    $.plot.expRepTickFormatter = expRepTickFormatter;\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.legend.js",
    "content": "/* Flot plugin for drawing legends.\n\n*/\n\n(function($) {\n    var defaultOptions = {\n        legend: {\n            show: false,\n            noColumns: 1,\n            labelFormatter: null, // fn: string -> string\n            container: null, // container (as jQuery object) to put legend in, null means default on top of graph\n            position: 'ne', // position of default legend container within plot\n            margin: 5, // distance from grid edge to default legend container within plot\n            sorted: null // default to no legend sorting\n        }\n    };\n\n    function insertLegend(plot, options, placeholder, legendEntries) {\n        // clear before redraw\n        if (options.legend.container != null) {\n            $(options.legend.container).html('');\n        } else {\n            placeholder.find('.legend').remove();\n        }\n\n        if (!options.legend.show) {\n            return;\n        }\n\n        // Save the legend entries in legend options\n        var entries = options.legend.legendEntries = legendEntries,\n            plotOffset = options.legend.plotOffset = plot.getPlotOffset(),\n            html = [],\n            entry, labelHtml, iconHtml,\n            j = 0,\n            i,\n            pos = \"\",\n            p = options.legend.position,\n            m = options.legend.margin,\n            shape = {\n                name: '',\n                label: '',\n                xPos: '',\n                yPos: ''\n            };\n\n        html[j++] = '<svg class=\"legendLayer\" style=\"width:inherit;height:inherit;\">';\n        html[j++] = '<rect class=\"background\" width=\"100%\" height=\"100%\"/>';\n        html[j++] = svgShapeDefs;\n\n        var left = 0;\n        var columnWidths = [];\n        var style = window.getComputedStyle(document.querySelector('body'));\n        for (i = 0; i < entries.length; ++i) {\n            let columnIndex = i % options.legend.noColumns;\n            entry = entries[i];\n            shape.label = entry.label;\n            var info = plot.getSurface().getTextInfo('', shape.label, {\n                style: style.fontStyle,\n                variant: style.fontVariant,\n                weight: style.fontWeight,\n                size: parseInt(style.fontSize),\n                lineHeight: parseInt(style.lineHeight),\n                family: style.fontFamily\n            });\n\n            var labelWidth = info.width;\n            // 36px = 1.5em + 6px margin\n            var iconWidth = 48;\n            if (columnWidths[columnIndex]) {\n                if (labelWidth > columnWidths[columnIndex]) {\n                    columnWidths[columnIndex] = labelWidth + iconWidth;\n                }\n            } else {\n                columnWidths[columnIndex] = labelWidth + iconWidth;\n            }\n        }\n\n        // Generate html for icons and labels from a list of entries\n        for (i = 0; i < entries.length; ++i) {\n            let columnIndex = i % options.legend.noColumns;\n            entry = entries[i];\n            iconHtml = '';\n            shape.label = entry.label;\n            shape.xPos = (left + 3) + 'px';\n            left += columnWidths[columnIndex];\n            if ((i + 1) % options.legend.noColumns === 0) {\n                left = 0;\n            }\n            shape.yPos = Math.floor(i / options.legend.noColumns) * 1.5 + 'em';\n            // area\n            if (entry.options.lines.show && entry.options.lines.fill) {\n                shape.name = 'area';\n                shape.fillColor = entry.color;\n                iconHtml += getEntryIconHtml(shape);\n            }\n            // bars\n            if (entry.options.bars.show) {\n                shape.name = 'bar';\n                shape.fillColor = entry.color;\n                iconHtml += getEntryIconHtml(shape);\n            }\n            // lines\n            if (entry.options.lines.show && !entry.options.lines.fill) {\n                shape.name = 'line';\n                shape.strokeColor = entry.color;\n                shape.strokeWidth = entry.options.lines.lineWidth;\n                iconHtml += getEntryIconHtml(shape);\n            }\n            // points\n            if (entry.options.points.show) {\n                shape.name = entry.options.points.symbol;\n                shape.strokeColor = entry.color;\n                shape.fillColor = entry.options.points.fillColor;\n                shape.strokeWidth = entry.options.points.lineWidth;\n                iconHtml += getEntryIconHtml(shape);\n            }\n\n            labelHtml = '<text x=\"' + shape.xPos + '\" y=\"' + shape.yPos + '\" text-anchor=\"start\"><tspan dx=\"2em\" dy=\"1.2em\">' + shape.label + '</tspan></text>'\n            html[j++] = '<g>' + iconHtml + labelHtml + '</g>';\n        }\n\n        html[j++] = '</svg>';\n        if (m[0] == null) {\n            m = [m, m];\n        }\n\n        if (p.charAt(0) === 'n') {\n            pos += 'top:' + (m[1] + plotOffset.top) + 'px;';\n        } else if (p.charAt(0) === 's') {\n            pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';\n        }\n\n        if (p.charAt(1) === 'e') {\n            pos += 'right:' + (m[0] + plotOffset.right) + 'px;';\n        } else if (p.charAt(1) === 'w') {\n            pos += 'left:' + (m[0] + plotOffset.left) + 'px;';\n        }\n\n        var width = 6;\n        for (i = 0; i < columnWidths.length; ++i) {\n            width += columnWidths[i];\n        }\n\n        var legendEl,\n            height = Math.ceil(entries.length / options.legend.noColumns) * 1.6;\n        if (!options.legend.container) {\n            legendEl = $('<div class=\"legend\" style=\"position:absolute;' + pos + '\">' + html.join('') + '</div>').appendTo(placeholder);\n            legendEl.css('width', width + 'px');\n            legendEl.css('height', height + 'em');\n            legendEl.css('pointerEvents', 'none');\n        } else {\n            legendEl = $(html.join('')).appendTo(options.legend.container)[0];\n            options.legend.container.style.width = width + 'px';\n            options.legend.container.style.height = height + 'em';\n        }\n    }\n\n    // Generate html for a shape\n    function getEntryIconHtml(shape) {\n        var html = '',\n            name = shape.name,\n            x = shape.xPos,\n            y = shape.yPos,\n            fill = shape.fillColor,\n            stroke = shape.strokeColor,\n            width = shape.strokeWidth;\n        switch (name) {\n            case 'circle':\n                html = '<use xlink:href=\"#circle\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    'fill=\"' + fill + '\" ' +\n                    'stroke=\"' + stroke + '\" ' +\n                    'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            case 'diamond':\n                html = '<use xlink:href=\"#diamond\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    'fill=\"' + fill + '\" ' +\n                    'stroke=\"' + stroke + '\" ' +\n                    'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            case 'cross':\n                html = '<use xlink:href=\"#cross\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    // 'fill=\"' + fill + '\" ' +\n                    'stroke=\"' + stroke + '\" ' +\n                    'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            case 'rectangle':\n                html = '<use xlink:href=\"#rectangle\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    'fill=\"' + fill + '\" ' +\n                    'stroke=\"' + stroke + '\" ' +\n                    'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            case 'plus':\n                html = '<use xlink:href=\"#plus\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    // 'fill=\"' + fill + '\" ' +\n                    'stroke=\"' + stroke + '\" ' +\n                    'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            case 'bar':\n                html = '<use xlink:href=\"#bars\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    'fill=\"' + fill + '\" ' +\n                    // 'stroke=\"' + stroke + '\" ' +\n                    // 'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            case 'area':\n                html = '<use xlink:href=\"#area\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    'fill=\"' + fill + '\" ' +\n                    // 'stroke=\"' + stroke + '\" ' +\n                    // 'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            case 'line':\n                html = '<use xlink:href=\"#line\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    // 'fill=\"' + fill + '\" ' +\n                    'stroke=\"' + stroke + '\" ' +\n                    'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n                break;\n            default:\n                // default is circle\n                html = '<use xlink:href=\"#circle\" class=\"legendIcon\" ' +\n                    'x=\"' + x + '\" ' +\n                    'y=\"' + y + '\" ' +\n                    'fill=\"' + fill + '\" ' +\n                    'stroke=\"' + stroke + '\" ' +\n                    'stroke-width=\"' + width + '\" ' +\n                    'width=\"1.5em\" height=\"1.5em\"' +\n                    '/>';\n        }\n\n        return html;\n    }\n\n    // Define svg symbols for shapes\n    var svgShapeDefs = '' +\n        '<defs>' +\n            '<symbol id=\"line\" fill=\"none\" viewBox=\"-5 -5 25 25\">' +\n                '<polyline points=\"0,15 5,5 10,10 15,0\"/>' +\n            '</symbol>' +\n\n            '<symbol id=\"area\" stroke-width=\"1\" viewBox=\"-5 -5 25 25\">' +\n                '<polyline points=\"0,15 5,5 10,10 15,0, 15,15, 0,15\"/>' +\n            '</symbol>' +\n\n            '<symbol id=\"bars\" stroke-width=\"1\" viewBox=\"-5 -5 25 25\">' +\n                '<polyline points=\"1.5,15.5 1.5,12.5, 4.5,12.5 4.5,15.5 6.5,15.5 6.5,3.5, 9.5,3.5 9.5,15.5 11.5,15.5 11.5,7.5 14.5,7.5 14.5,15.5 1.5,15.5\"/>' +\n            '</symbol>' +\n\n            '<symbol id=\"circle\" viewBox=\"-5 -5 25 25\">' +\n                '<circle cx=\"0\" cy=\"15\" r=\"2.5\"/>' +\n                '<circle cx=\"5\" cy=\"5\" r=\"2.5\"/>' +\n                '<circle cx=\"10\" cy=\"10\" r=\"2.5\"/>' +\n                '<circle cx=\"15\" cy=\"0\" r=\"2.5\"/>' +\n            '</symbol>' +\n\n            '<symbol id=\"rectangle\" viewBox=\"-5 -5 25 25\">' +\n                '<rect x=\"-2.1\" y=\"12.9\" width=\"4.2\" height=\"4.2\"/>' +\n                '<rect x=\"2.9\" y=\"2.9\" width=\"4.2\" height=\"4.2\"/>' +\n                '<rect x=\"7.9\" y=\"7.9\" width=\"4.2\" height=\"4.2\"/>' +\n                '<rect x=\"12.9\" y=\"-2.1\" width=\"4.2\" height=\"4.2\"/>' +\n            '</symbol>' +\n\n            '<symbol id=\"diamond\" viewBox=\"-5 -5 25 25\">' +\n                '<path d=\"M-3,15 L0,12 L3,15, L0,18 Z\"/>' +\n                '<path d=\"M2,5 L5,2 L8,5, L5,8 Z\"/>' +\n                '<path d=\"M7,10 L10,7 L13,10, L10,13 Z\"/>' +\n                '<path d=\"M12,0 L15,-3 L18,0, L15,3 Z\"/>' +\n            '</symbol>' +\n\n            '<symbol id=\"cross\" fill=\"none\" viewBox=\"-5 -5 25 25\">' +\n                '<path d=\"M-2.1,12.9 L2.1,17.1, M2.1,12.9 L-2.1,17.1 Z\"/>' +\n                '<path d=\"M2.9,2.9 L7.1,7.1 M7.1,2.9 L2.9,7.1 Z\"/>' +\n                '<path d=\"M7.9,7.9 L12.1,12.1 M12.1,7.9 L7.9,12.1 Z\"/>' +\n                '<path d=\"M12.9,-2.1 L17.1,2.1 M17.1,-2.1 L12.9,2.1 Z\"/>' +\n            '</symbol>' +\n\n            '<symbol id=\"plus\" fill=\"none\" viewBox=\"-5 -5 25 25\">' +\n                '<path d=\"M0,12 L0,18, M-3,15 L3,15 Z\"/>' +\n                '<path d=\"M5,2 L5,8 M2,5 L8,5 Z\"/>' +\n                '<path d=\"M10,7 L10,13 M7,10 L13,10 Z\"/>' +\n                '<path d=\"M15,-3 L15,3 M12,0 L18,0 Z\"/>' +\n            '</symbol>' +\n        '</defs>';\n\n    // Generate a list of legend entries in their final order\n    function getLegendEntries(series, labelFormatter, sorted) {\n        var lf = labelFormatter,\n            legendEntries = series.reduce(function(validEntries, s, i) {\n                var labelEval = (lf ? lf(s.label, s) : s.label)\n                if (s.hasOwnProperty(\"label\") ? labelEval : true) {\n                    var entry = {\n                        label: labelEval || 'Plot ' + (i + 1),\n                        color: s.color,\n                        options: {\n                            lines: s.lines,\n                            points: s.points,\n                            bars: s.bars\n                        }\n                    }\n                    validEntries.push(entry)\n                }\n                return validEntries;\n            }, []);\n\n        // Sort the legend using either the default or a custom comparator\n        if (sorted) {\n            if ($.isFunction(sorted)) {\n                legendEntries.sort(sorted);\n            } else if (sorted === 'reverse') {\n                legendEntries.reverse();\n            } else {\n                var ascending = (sorted !== 'descending');\n                legendEntries.sort(function(a, b) {\n                    return a.label === b.label\n                        ? 0\n                        : ((a.label < b.label) !== ascending ? 1 : -1 // Logical XOR\n                        );\n                });\n            }\n        }\n\n        return legendEntries;\n    }\n\n    // return false if opts1 same as opts2\n    function checkOptions(opts1, opts2) {\n        for (var prop in opts1) {\n            if (opts1.hasOwnProperty(prop)) {\n                if (opts1[prop] !== opts2[prop]) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    // Compare two lists of legend entries\n    function shouldRedraw(oldEntries, newEntries) {\n        if (!oldEntries || !newEntries) {\n            return true;\n        }\n\n        if (oldEntries.length !== newEntries.length) {\n            return true;\n        }\n        var i, newEntry, oldEntry, newOpts, oldOpts;\n        for (i = 0; i < newEntries.length; i++) {\n            newEntry = newEntries[i];\n            oldEntry = oldEntries[i];\n\n            if (newEntry.label !== oldEntry.label) {\n                return true;\n            }\n\n            if (newEntry.color !== oldEntry.color) {\n                return true;\n            }\n\n            // check for changes in lines options\n            newOpts = newEntry.options.lines;\n            oldOpts = oldEntry.options.lines;\n            if (checkOptions(newOpts, oldOpts)) {\n                return true;\n            }\n\n            // check for changes in points options\n            newOpts = newEntry.options.points;\n            oldOpts = oldEntry.options.points;\n            if (checkOptions(newOpts, oldOpts)) {\n                return true;\n            }\n\n            // check for changes in bars options\n            newOpts = newEntry.options.bars;\n            oldOpts = oldEntry.options.bars;\n            if (checkOptions(newOpts, oldOpts)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    function init(plot) {\n        plot.hooks.setupGrid.push(function (plot) {\n            var options = plot.getOptions();\n            var series = plot.getData(),\n                labelFormatter = options.legend.labelFormatter,\n                oldEntries = options.legend.legendEntries,\n                oldPlotOffset = options.legend.plotOffset,\n                newEntries = getLegendEntries(series, labelFormatter, options.legend.sorted),\n                newPlotOffset = plot.getPlotOffset();\n\n            if (shouldRedraw(oldEntries, newEntries) ||\n                checkOptions(oldPlotOffset, newPlotOffset)) {\n                insertLegend(plot, options, plot.getPlaceholder(), newEntries);\n            }\n        });\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: defaultOptions,\n        name: 'legend',\n        version: '1.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.logaxis.js",
    "content": "/* Pretty handling of log axes.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nCopyright (c) 2015 Ciprian Ceteras cipix2000@gmail.com.\nCopyright (c) 2017 Raluca Portase\nLicensed under the MIT license.\n\nSet axis.mode to \"log\" to enable.\n*/\n\n/* global jQuery*/\n\n/**\n## jquery.flot.logaxis\nThis plugin is used to create logarithmic axis. This includes tick generation,\nformatters and transformers to and from logarithmic representation.\n\n### Methods and hooks\n*/\n\n(function ($) {\n    'use strict';\n\n    var options = {\n        xaxis: {}\n    };\n\n    /*tick generators and formatters*/\n    var PREFERRED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 10),\n        EXTENDED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 4);\n\n    function computePreferedLogTickValues(endLimit, rangeStep) {\n        var log10End = Math.floor(Math.log(endLimit) * Math.LOG10E) - 1,\n            log10Start = -log10End,\n            val, range, vals = [];\n\n        for (var power = log10Start; power <= log10End; power++) {\n            range = parseFloat('1e' + power);\n            for (var mult = 1; mult < 9; mult += rangeStep) {\n                val = range * mult;\n                vals.push(val);\n            }\n        }\n        return vals;\n    }\n\n    /**\n    - logTickGenerator(plot, axis, noTicks)\n\n    Generates logarithmic ticks, depending on axis range.\n    In case the number of ticks that can be generated is less than the expected noTicks/4,\n    a linear tick generation is used.\n    */\n    var logTickGenerator = function (plot, axis, noTicks) {\n        var ticks = [],\n            minIdx = -1,\n            maxIdx = -1,\n            surface = plot.getCanvas(),\n            logTickValues = PREFERRED_LOG_TICK_VALUES,\n            min = clampAxis(axis, plot),\n            max = axis.max;\n\n        if (!noTicks) {\n            noTicks = 0.3 * Math.sqrt(axis.direction === \"x\" ? surface.width : surface.height);\n        }\n\n        PREFERRED_LOG_TICK_VALUES.some(function (val, i) {\n            if (val >= min) {\n                minIdx = i;\n                return true;\n            } else {\n                return false;\n            }\n        });\n\n        PREFERRED_LOG_TICK_VALUES.some(function (val, i) {\n            if (val >= max) {\n                maxIdx = i;\n                return true;\n            } else {\n                return false;\n            }\n        });\n\n        if (maxIdx === -1) {\n            maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1;\n        }\n\n        if (maxIdx - minIdx <= noTicks / 4 && logTickValues.length !== EXTENDED_LOG_TICK_VALUES.length) {\n            //try with multiple of 5 for tick values\n            logTickValues = EXTENDED_LOG_TICK_VALUES;\n            minIdx *= 2;\n            maxIdx *= 2;\n        }\n\n        var lastDisplayed = null,\n            inverseNoTicks = 1 / noTicks,\n            tickValue, pixelCoord, tick;\n\n        // Count the number of tick values would appear, if we can get at least\n        // nTicks / 4 accept them.\n        if (maxIdx - minIdx >= noTicks / 4) {\n            for (var idx = maxIdx; idx >= minIdx; idx--) {\n                tickValue = logTickValues[idx];\n                pixelCoord = (Math.log(tickValue) - Math.log(min)) / (Math.log(max) - Math.log(min));\n                tick = tickValue;\n\n                if (lastDisplayed === null) {\n                    lastDisplayed = {\n                        pixelCoord: pixelCoord,\n                        idealPixelCoord: pixelCoord\n                    };\n                } else {\n                    if (Math.abs(pixelCoord - lastDisplayed.pixelCoord) >= inverseNoTicks) {\n                        lastDisplayed = {\n                            pixelCoord: pixelCoord,\n                            idealPixelCoord: lastDisplayed.idealPixelCoord - inverseNoTicks\n                        };\n                    } else {\n                        tick = null;\n                    }\n                }\n\n                if (tick) {\n                    ticks.push(tick);\n                }\n            }\n            // Since we went in backwards order.\n            ticks.reverse();\n        } else {\n            var tickSize = plot.computeTickSize(min, max, noTicks),\n                customAxis = {min: min, max: max, tickSize: tickSize};\n            ticks = $.plot.linearTickGenerator(customAxis);\n        }\n\n        return ticks;\n    };\n\n    var clampAxis = function (axis, plot) {\n        var min = axis.min,\n            max = axis.max;\n\n        if (min <= 0) {\n            //for empty graph if axis.min is not strictly positive make it 0.1\n            if (axis.datamin === null) {\n                min = axis.min = 0.1;\n            } else {\n                min = processAxisOffset(plot, axis);\n            }\n\n            if (max < min) {\n                axis.max = axis.datamax !== null ? axis.datamax : axis.options.max;\n                axis.options.offset.below = 0;\n                axis.options.offset.above = 0;\n            }\n        }\n\n        return min;\n    }\n\n    /**\n    - logTickFormatter(value, axis, precision)\n\n    This is the corresponding tickFormatter of the logaxis.\n    For a number greater that 10^6 or smaller than 10^(-3), this will be drawn\n    with e representation\n    */\n    var logTickFormatter = function (value, axis, precision) {\n        var tenExponent = value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0;\n\n        if (precision) {\n            if ((tenExponent >= -4) && (tenExponent <= 7)) {\n                return $.plot.defaultTickFormatter(value, axis, precision);\n            } else {\n                return $.plot.expRepTickFormatter(value, axis, precision);\n            }\n        }\n        if ((tenExponent >= -4) && (tenExponent <= 7)) {\n            //if we have float numbers, return a limited length string(ex: 0.0009 is represented as 0.000900001)\n            var formattedValue = tenExponent < 0 ? value.toFixed(-tenExponent) : value.toFixed(tenExponent + 2);\n            if (formattedValue.indexOf('.') !== -1) {\n                var lastZero = formattedValue.lastIndexOf('0');\n\n                while (lastZero === formattedValue.length - 1) {\n                    formattedValue = formattedValue.slice(0, -1);\n                    lastZero = formattedValue.lastIndexOf('0');\n                }\n\n                //delete the dot if is last\n                if (formattedValue.indexOf('.') === formattedValue.length - 1) {\n                    formattedValue = formattedValue.slice(0, -1);\n                }\n            }\n            return formattedValue;\n        } else {\n            return $.plot.expRepTickFormatter(value, axis);\n        }\n    };\n\n    /*logaxis caracteristic functions*/\n    var logTransform = function (v) {\n        if (v < PREFERRED_LOG_TICK_VALUES[0]) {\n            v = PREFERRED_LOG_TICK_VALUES[0];\n        }\n\n        return Math.log(v);\n    };\n\n    var logInverseTransform = function (v) {\n        return Math.exp(v);\n    };\n\n    var invertedTransform = function (v) {\n        return -v;\n    }\n\n    var invertedLogTransform = function (v) {\n        return -logTransform(v);\n    }\n\n    var invertedLogInverseTransform = function (v) {\n        return logInverseTransform(-v);\n    }\n\n    /**\n    - setDataminRange(plot, axis)\n\n    It is used for clamping the starting point of a logarithmic axis.\n    This will set the axis datamin range to 0.1 or to the first datapoint greater then 0.\n    The function is usefull since the logarithmic representation can not show\n    values less than or equal to 0.\n    */\n    function setDataminRange(plot, axis) {\n        if (axis.options.mode === 'log' && axis.datamin <= 0) {\n            if (axis.datamin === null) {\n                axis.datamin = 0.1;\n            } else {\n                axis.datamin = processAxisOffset(plot, axis);\n            }\n        }\n    }\n\n    function processAxisOffset(plot, axis) {\n        var series = plot.getData(),\n            range = series\n                .filter(function(series) {\n                    return series.xaxis === axis || series.yaxis === axis;\n                })\n                .map(function(series) {\n                    return plot.computeRangeForDataSeries(series, null, isValid);\n                }),\n            min = axis.direction === 'x'\n                ? Math.min(0.1, range && range[0] ? range[0].xmin : 0.1)\n                : Math.min(0.1, range && range[0] ? range[0].ymin : 0.1);\n\n        axis.min = min;\n\n        return min;\n    }\n\n    function isValid(a) {\n        return a > 0;\n    }\n\n    function init(plot) {\n        plot.hooks.processOptions.push(function (plot) {\n            $.each(plot.getAxes(), function (axisName, axis) {\n                var opts = axis.options;\n                if (opts.mode === 'log') {\n                    axis.tickGenerator = function (axis) {\n                        var noTicks = 11;\n                        return logTickGenerator(plot, axis, noTicks);\n                    };\n                    if (typeof axis.options.tickFormatter !== 'function') {\n                        axis.options.tickFormatter = logTickFormatter;\n                    }\n                    axis.options.transform = opts.inverted ? invertedLogTransform : logTransform;\n                    axis.options.inverseTransform = opts.inverted ? invertedLogInverseTransform : logInverseTransform;\n                    axis.options.autoScaleMargin = 0;\n                    plot.hooks.setRange.push(setDataminRange);\n                } else if (opts.inverted) {\n                    axis.options.transform = invertedTransform;\n                    axis.options.inverseTransform = invertedTransform;\n                }\n            });\n        });\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'log',\n        version: '0.1'\n    });\n\n    $.plot.logTicksGenerator = logTickGenerator;\n    $.plot.logTickFormatter = logTickFormatter;\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.navigate.js",
    "content": "/* Flot plugin for adding the ability to pan and zoom the plot.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nCopyright (c) 2016 Ciprian Ceteras.\nCopyright (c) 2017 Raluca Portase.\nLicensed under the MIT license.\n\n*/\n\n/**\n## jquery.flot.navigate.js\n\nThis flot plugin is used for adding the ability to pan and zoom the plot.\nA higher level overview is available at [interactions](interactions.md) documentation.\n\nThe default behaviour is scrollwheel up/down to zoom in, drag\nto pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and\nplot.pan( offset ) so you easily can add custom controls. It also fires\n\"plotpan\" and \"plotzoom\" events, useful for synchronizing plots.\n\nThe plugin supports these options:\n```js\n    zoom: {\n        interactive: false,\n        active: false,\n        amount: 1.5         // 2 = 200% (zoom in), 0.5 = 50% (zoom out)\n    }\n\n    pan: {\n        interactive: false,\n        active: false,\n        cursor: \"move\",     // CSS mouse cursor value used when dragging, e.g. \"pointer\"\n        frameRate: 60,\n        mode: \"smart\"       // enable smart pan mode\n    }\n\n    xaxis: {\n        axisZoom: true, //zoom axis when mouse over it is allowed\n        plotZoom: true, //zoom axis is allowed for plot zoom\n        axisPan: true, //pan axis when mouse over it is allowed\n        plotPan: true, //pan axis is allowed for plot pan\n        panRange: [undefined, undefined], // no limit on pan range, or [min, max] in axis units\n        zoomRange: [undefined, undefined], // no limit on zoom range, or [closest zoom, furthest zoom] in axis units\n    }\n\n    yaxis: {\n        axisZoom: true, //zoom axis when mouse over it is allowed\n        plotZoom: true, //zoom axis is allowed for plot zoom\n        axisPan: true, //pan axis when mouse over it is allowed\n        plotPan: true //pan axis is allowed for plot pan\n        panRange: [undefined, undefined], // no limit on pan range, or [min, max] in axis units\n        zoomRange: [undefined, undefined], // no limit on zoom range, or [closest zoom, furthest zoom] in axis units\n    }\n```\n**interactive** enables the built-in drag/click behaviour. If you enable\ninteractive for pan, then you'll have a basic plot that supports moving\naround; the same for zoom.\n\n**active** is true after a touch tap on plot. This enables plot navigation.\nOnce activated, zoom and pan cannot be deactivated. When the plot becomes active,\n\"plotactivated\" event is triggered.\n\n**amount** specifies the default amount to zoom in (so 1.5 = 150%) relative to\nthe current viewport.\n\n**cursor** is a standard CSS mouse cursor string used for visual feedback to the\nuser when dragging.\n\n**frameRate** specifies the maximum number of times per second the plot will\nupdate itself while the user is panning around on it (set to null to disable\nintermediate pans, the plot will then not update until the mouse button is\nreleased).\n\n**mode** a string specifies the pan mode for mouse interaction. Accepted values:\n'manual': no pan hint or direction snapping;\n'smart': The graph shows pan hint bar and the pan movement will snap\nto one direction when the drag direction is close to it;\n'smartLock'. The graph shows pan hint bar and the pan movement will always\nsnap to a direction that the drag diorection started with.\n\nExample API usage:\n```js\n    plot = $.plot(...);\n\n    // zoom default amount in on the pixel ( 10, 20 )\n    plot.zoom({ center: { left: 10, top: 20 } });\n\n    // zoom out again\n    plot.zoomOut({ center: { left: 10, top: 20 } });\n\n    // zoom 200% in on the pixel (10, 20)\n    plot.zoom({ amount: 2, center: { left: 10, top: 20 } });\n\n    // pan 100 pixels to the left (changing x-range in a positive way) and 20 down\n    plot.pan({ left: -100, top: 20 })\n```\n\nHere, \"center\" specifies where the center of the zooming should happen. Note\nthat this is defined in pixel space, not the space of the data points (you can\nuse the p2c helpers on the axes in Flot to help you convert between these).\n\n**amount** is the amount to zoom the viewport relative to the current range, so\n1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You\ncan set the default in the options.\n*/\n\n/* eslint-enable */\n(function($) {\n    'use strict';\n\n    var options = {\n        zoom: {\n            interactive: false,\n            active: false,\n            amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)\n        },\n        pan: {\n            interactive: false,\n            active: false,\n            cursor: \"move\",\n            frameRate: 60,\n            mode: 'smart'\n        },\n        recenter: {\n            interactive: true\n        },\n        xaxis: {\n            axisZoom: true, //zoom axis when mouse over it is allowed\n            plotZoom: true, //zoom axis is allowed for plot zoom\n            axisPan: true, //pan axis when mouse over it is allowed\n            plotPan: true, //pan axis is allowed for plot pan\n            panRange: [undefined, undefined], // no limit on pan range, or [min, max] in axis units\n            zoomRange: [undefined, undefined] // no limit on zoom range, or [closest zoom, furthest zoom] in axis units\n        },\n        yaxis: {\n            axisZoom: true,\n            plotZoom: true,\n            axisPan: true,\n            plotPan: true,\n            panRange: [undefined, undefined], // no limit on pan range, or [min, max] in axis units\n            zoomRange: [undefined, undefined] // no limit on zoom range, or [closest zoom, furthest zoom] in axis units\n        }\n    };\n\n    var saturated = $.plot.saturated;\n    var browser = $.plot.browser;\n    var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;\n    var PANHINT_LENGTH_CONSTANT = $.plot.uiConstants.PANHINT_LENGTH_CONSTANT;\n\n    function init(plot) {\n        plot.hooks.processOptions.push(initNevigation);\n    }\n\n    function initNevigation(plot, options) {\n        var panAxes = null;\n        var canDrag = false;\n        var useManualPan = options.pan.mode === 'manual',\n            smartPanLock = options.pan.mode === 'smartLock',\n            useSmartPan = smartPanLock || options.pan.mode === 'smart';\n\n        function onZoomClick(e, zoomOut, amount) {\n            var page = browser.getPageXY(e);\n\n            var c = plot.offset();\n            c.left = page.X - c.left;\n            c.top = page.Y - c.top;\n\n            var ec = plot.getPlaceholder().offset();\n            ec.left = page.X - ec.left;\n            ec.top = page.Y - ec.top;\n\n            var axes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {\n                var box = axis.box;\n                if (box !== undefined) {\n                    return (ec.left > box.left) && (ec.left < box.left + box.width) &&\n                        (ec.top > box.top) && (ec.top < box.top + box.height);\n                }\n            });\n\n            if (axes.length === 0) {\n                axes = undefined;\n            }\n\n            if (zoomOut) {\n                plot.zoomOut({\n                    center: c,\n                    axes: axes,\n                    amount: amount\n                });\n            } else {\n                plot.zoom({\n                    center: c,\n                    axes: axes,\n                    amount: amount\n                });\n            }\n        }\n\n        var prevCursor = 'default',\n            panHint = null,\n            panTimeout = null,\n            plotState,\n            prevDragPosition = { x: 0, y: 0 },\n            isPanAction = false;\n\n        function onMouseWheel(e, delta) {\n            var maxAbsoluteDeltaOnMac = 1,\n                isMacScroll = Math.abs(e.originalEvent.deltaY) <= maxAbsoluteDeltaOnMac,\n                defaultNonMacScrollAmount = null,\n                macMagicRatio = 50,\n                amount = isMacScroll ? 1 + Math.abs(e.originalEvent.deltaY) / macMagicRatio : defaultNonMacScrollAmount;\n\n            if (isPanAction) {\n                onDragEnd(e);\n            }\n\n            if (plot.getOptions().zoom.active) {\n                e.preventDefault();\n                onZoomClick(e, delta < 0, amount);\n                return false;\n            }\n        }\n\n        plot.navigationState = function(startPageX, startPageY) {\n            var axes = this.getAxes();\n            var result = {};\n            Object.keys(axes).forEach(function(axisName) {\n                var axis = axes[axisName];\n                result[axisName] = {\n                    navigationOffset: { below: axis.options.offset.below || 0,\n                        above: axis.options.offset.above || 0},\n                    axisMin: axis.min,\n                    axisMax: axis.max,\n                    diagMode: false\n                }\n            });\n\n            result.startPageX = startPageX || 0;\n            result.startPageY = startPageY || 0;\n            return result;\n        }\n\n        function onMouseDown(e) {\n            canDrag = true;\n        }\n\n        function onMouseUp(e) {\n            canDrag = false;\n        }\n\n        function isLeftMouseButtonPressed(e) {\n            return e.button === 0;\n        }\n\n        function onDragStart(e) {\n            if (!canDrag || !isLeftMouseButtonPressed(e)) {\n                return false;\n            }\n\n            isPanAction = true;\n            var page = browser.getPageXY(e);\n\n            var ec = plot.getPlaceholder().offset();\n            ec.left = page.X - ec.left;\n            ec.top = page.Y - ec.top;\n\n            panAxes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {\n                var box = axis.box;\n                if (box !== undefined) {\n                    return (ec.left > box.left) && (ec.left < box.left + box.width) &&\n                        (ec.top > box.top) && (ec.top < box.top + box.height);\n                }\n            });\n\n            if (panAxes.length === 0) {\n                panAxes = undefined;\n            }\n\n            var c = plot.getPlaceholder().css('cursor');\n            if (c) {\n                prevCursor = c;\n            }\n\n            plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);\n\n            if (useSmartPan) {\n                plotState = plot.navigationState(page.X, page.Y);\n            } else if (useManualPan) {\n                prevDragPosition.x = page.X;\n                prevDragPosition.y = page.Y;\n            }\n        }\n\n        function onDrag(e) {\n            if (!isPanAction) {\n                return;\n            }\n\n            var page = browser.getPageXY(e);\n            var frameRate = plot.getOptions().pan.frameRate;\n\n            if (frameRate === -1) {\n                if (useSmartPan) {\n                    plot.smartPan({\n                        x: plotState.startPageX - page.X,\n                        y: plotState.startPageY - page.Y\n                    }, plotState, panAxes, false, smartPanLock);\n                } else if (useManualPan) {\n                    plot.pan({\n                        left: prevDragPosition.x - page.X,\n                        top: prevDragPosition.y - page.Y,\n                        axes: panAxes\n                    });\n                    prevDragPosition.x = page.X;\n                    prevDragPosition.y = page.Y;\n                }\n                return;\n            }\n\n            if (panTimeout || !frameRate) return;\n\n            panTimeout = setTimeout(function() {\n                if (useSmartPan) {\n                    plot.smartPan({\n                        x: plotState.startPageX - page.X,\n                        y: plotState.startPageY - page.Y\n                    }, plotState, panAxes, false, smartPanLock);\n                } else if (useManualPan) {\n                    plot.pan({\n                        left: prevDragPosition.x - page.X,\n                        top: prevDragPosition.y - page.Y,\n                        axes: panAxes\n                    });\n                    prevDragPosition.x = page.X;\n                    prevDragPosition.y = page.Y;\n                }\n\n                panTimeout = null;\n            }, 1 / frameRate * 1000);\n        }\n\n        function onDragEnd(e) {\n            if (!isPanAction) {\n                return;\n            }\n\n            if (panTimeout) {\n                clearTimeout(panTimeout);\n                panTimeout = null;\n            }\n\n            isPanAction = false;\n            var page = browser.getPageXY(e);\n\n            plot.getPlaceholder().css('cursor', prevCursor);\n\n            if (useSmartPan) {\n                plot.smartPan({\n                    x: plotState.startPageX - page.X,\n                    y: plotState.startPageY - page.Y\n                }, plotState, panAxes, false, smartPanLock);\n                plot.smartPan.end();\n            } else if (useManualPan) {\n                plot.pan({\n                    left: prevDragPosition.x - page.X,\n                    top: prevDragPosition.y - page.Y,\n                    axes: panAxes\n                });\n                prevDragPosition.x = 0;\n                prevDragPosition.y = 0;\n            }\n        }\n\n        function onDblClick(e) {\n            plot.activate();\n            var o = plot.getOptions()\n\n            if (!o.recenter.interactive) { return; }\n\n            var axes = plot.getTouchedAxis(e.clientX, e.clientY),\n                event;\n\n            plot.recenter({ axes: axes[0] ? axes : null });\n\n            if (axes[0]) {\n                event = new $.Event('re-center', { detail: {\n                    axisTouched: axes[0]\n                }});\n            } else {\n                event = new $.Event('re-center', { detail: e });\n            }\n            plot.getPlaceholder().trigger(event);\n        }\n\n        function onClick(e) {\n            plot.activate();\n\n            if (isPanAction) {\n                onDragEnd(e);\n            }\n\n            return false;\n        }\n\n        plot.activate = function() {\n            var o = plot.getOptions();\n            if (!o.pan.active || !o.zoom.active) {\n                o.pan.active = true;\n                o.zoom.active = true;\n                plot.getPlaceholder().trigger(\"plotactivated\", [plot]);\n            }\n        }\n\n        function bindEvents(plot, eventHolder) {\n            var o = plot.getOptions();\n            if (o.zoom.interactive) {\n                eventHolder.mousewheel(onMouseWheel);\n            }\n\n            if (o.pan.interactive) {\n                plot.addEventHandler(\"dragstart\", onDragStart, eventHolder, 0);\n                plot.addEventHandler(\"drag\", onDrag, eventHolder, 0);\n                plot.addEventHandler(\"dragend\", onDragEnd, eventHolder, 0);\n                eventHolder.bind(\"mousedown\", onMouseDown);\n                eventHolder.bind(\"mouseup\", onMouseUp);\n            }\n\n            eventHolder.dblclick(onDblClick);\n            eventHolder.click(onClick);\n        }\n\n        plot.zoomOut = function(args) {\n            if (!args) {\n                args = {};\n            }\n\n            if (!args.amount) {\n                args.amount = plot.getOptions().zoom.amount;\n            }\n\n            args.amount = 1 / args.amount;\n            plot.zoom(args);\n        };\n\n        plot.zoom = function(args) {\n            if (!args) {\n                args = {};\n            }\n\n            var c = args.center,\n                amount = args.amount || plot.getOptions().zoom.amount,\n                w = plot.width(),\n                h = plot.height(),\n                axes = args.axes || plot.getAxes();\n\n            if (!c) {\n                c = {\n                    left: w / 2,\n                    top: h / 2\n                };\n            }\n\n            var xf = c.left / w,\n                yf = c.top / h,\n                minmax = {\n                    x: {\n                        min: c.left - xf * w / amount,\n                        max: c.left + (1 - xf) * w / amount\n                    },\n                    y: {\n                        min: c.top - yf * h / amount,\n                        max: c.top + (1 - yf) * h / amount\n                    }\n                };\n\n            for (var key in axes) {\n                if (!axes.hasOwnProperty(key)) {\n                    continue;\n                }\n\n                var axis = axes[key],\n                    opts = axis.options,\n                    min = minmax[axis.direction].min,\n                    max = minmax[axis.direction].max,\n                    navigationOffset = axis.options.offset;\n\n                //skip axis without axisZoom when zooming only on certain axis or axis without plotZoom for zoom on entire plot\n                if ((!opts.axisZoom && args.axes) || (!args.axes && !opts.plotZoom)) {\n                    continue;\n                }\n\n                min = $.plot.saturated.saturate(axis.c2p(min));\n                max = $.plot.saturated.saturate(axis.c2p(max));\n                if (min > max) {\n                    // make sure min < max\n                    var tmp = min;\n                    min = max;\n                    max = tmp;\n                }\n\n                // test for zoom limits zoomRange: [min,max]\n                if (opts.zoomRange) {\n                    // zoomed in too far\n                    if (max - min < opts.zoomRange[0]) {\n                        continue;\n                    }\n                    // zoomed out to far\n                    if (max - min > opts.zoomRange[1]) {\n                        continue;\n                    }\n                }\n\n                var offsetBelow = $.plot.saturated.saturate(navigationOffset.below - (axis.min - min));\n                var offsetAbove = $.plot.saturated.saturate(navigationOffset.above - (axis.max - max));\n                opts.offset = { below: offsetBelow, above: offsetAbove };\n            };\n\n            plot.setupGrid(true);\n            plot.draw();\n\n            if (!args.preventEvent) {\n                plot.getPlaceholder().trigger(\"plotzoom\", [plot, args]);\n            }\n        };\n\n        plot.pan = function(args) {\n            var delta = {\n                x: +args.left,\n                y: +args.top\n            };\n\n            if (isNaN(delta.x)) delta.x = 0;\n            if (isNaN(delta.y)) delta.y = 0;\n\n            $.each(args.axes || plot.getAxes(), function(_, axis) {\n                var opts = axis.options,\n                    d = delta[axis.direction];\n\n                //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot\n                if ((!opts.axisPan && args.axes) || (!opts.plotPan && !args.axes)) {\n                    return;\n                }\n\n                // calc min delta (revealing left edge of plot)\n                var minD = axis.p2c(opts.panRange[0]) - axis.p2c(axis.min);\n                // calc max delta (revealing right edge of plot)\n                var maxD = axis.p2c(opts.panRange[1]) - axis.p2c(axis.max);\n                // limit delta to min or max if enabled\n                if (opts.panRange[0] !== undefined && d >= maxD) d = maxD;\n                if (opts.panRange[1] !== undefined && d <= minD) d = minD;\n\n                if (d !== 0) {\n                    var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axis.min) + d) - axis.c2p(axis.p2c(axis.min))),\n                        navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axis.max) + d) - axis.c2p(axis.p2c(axis.max)));\n\n                    if (!isFinite(navigationOffsetBelow)) {\n                        navigationOffsetBelow = 0;\n                    }\n\n                    if (!isFinite(navigationOffsetAbove)) {\n                        navigationOffsetAbove = 0;\n                    }\n\n                    opts.offset = {\n                        below: saturated.saturate(navigationOffsetBelow + (opts.offset.below || 0)),\n                        above: saturated.saturate(navigationOffsetAbove + (opts.offset.above || 0))\n                    };\n                }\n            });\n\n            plot.setupGrid(true);\n            plot.draw();\n            if (!args.preventEvent) {\n                plot.getPlaceholder().trigger(\"plotpan\", [plot, args]);\n            }\n        };\n\n        plot.recenter = function(args) {\n            $.each(args.axes || plot.getAxes(), function(_, axis) {\n                if (args.axes) {\n                    if (this.direction === 'x') {\n                        axis.options.offset = { below: 0 };\n                    } else if (this.direction === 'y') {\n                        axis.options.offset = { above: 0 };\n                    }\n                } else {\n                    axis.options.offset = { below: 0, above: 0 };\n                }\n            });\n            plot.setupGrid(true);\n            plot.draw();\n        };\n\n        var shouldSnap = function(delta) {\n            return (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) ||\n                (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT);\n        }\n\n        // adjust delta so the pan action is constrained on the vertical or horizontal direction\n        // it the movements in the other direction are small\n        var adjustDeltaToSnap = function(delta) {\n            if (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT) {\n                return {x: 0, y: delta.y};\n            }\n\n            if (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) {\n                return {x: delta.x, y: 0};\n            }\n\n            return delta;\n        }\n\n        var lockedDirection = null;\n        var lockDeltaDirection = function(delta) {\n            if (!lockedDirection && Math.max(Math.abs(delta.x), Math.abs(delta.y)) >= SNAPPING_CONSTANT) {\n                lockedDirection = Math.abs(delta.x) < Math.abs(delta.y) ? 'y' : 'x';\n            }\n\n            switch (lockedDirection) {\n                case 'x':\n                    return { x: delta.x, y: 0 };\n                case 'y':\n                    return { x: 0, y: delta.y };\n                default:\n                    return { x: 0, y: 0 };\n            }\n        }\n\n        var isDiagonalMode = function(delta) {\n            if (Math.abs(delta.x) > 0 && Math.abs(delta.y) > 0) {\n                return true;\n            }\n            return false;\n        }\n\n        var restoreAxisOffset = function(axes, initialState, delta) {\n            var axis;\n            Object.keys(axes).forEach(function(axisName) {\n                axis = axes[axisName];\n                if (delta[axis.direction] === 0) {\n                    axis.options.offset.below = initialState[axisName].navigationOffset.below;\n                    axis.options.offset.above = initialState[axisName].navigationOffset.above;\n                }\n            });\n        }\n\n        var prevDelta = { x: 0, y: 0 };\n        plot.smartPan = function(delta, initialState, panAxes, preventEvent, smartLock) {\n            var snap = smartLock ? true : shouldSnap(delta),\n                axes = plot.getAxes(),\n                opts;\n            delta = smartLock ? lockDeltaDirection(delta) : adjustDeltaToSnap(delta);\n\n            if (isDiagonalMode(delta)) {\n                initialState.diagMode = true;\n            }\n\n            if (snap && initialState.diagMode === true) {\n                initialState.diagMode = false;\n                restoreAxisOffset(axes, initialState, delta);\n            }\n\n            if (snap) {\n                panHint = {\n                    start: {\n                        x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,\n                        y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top\n                    },\n                    end: {\n                        x: initialState.startPageX - delta.x - plot.offset().left + plot.getPlotOffset().left,\n                        y: initialState.startPageY - delta.y - plot.offset().top + plot.getPlotOffset().top\n                    }\n                }\n            } else {\n                panHint = {\n                    start: {\n                        x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,\n                        y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top\n                    },\n                    end: false\n                }\n            }\n\n            if (isNaN(delta.x)) delta.x = 0;\n            if (isNaN(delta.y)) delta.y = 0;\n\n            if (panAxes) {\n                axes = panAxes;\n            }\n\n            var axis, axisMin, axisMax, p, d;\n            Object.keys(axes).forEach(function(axisName) {\n                axis = axes[axisName];\n                axisMin = axis.min;\n                axisMax = axis.max;\n                opts = axis.options;\n\n                d = delta[axis.direction];\n                p = prevDelta[axis.direction];\n\n                //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot\n                if ((!opts.axisPan && panAxes) || (!panAxes && !opts.plotPan)) {\n                    return;\n                }\n\n                // calc min delta (revealing left edge of plot)\n                var minD = p + axis.p2c(opts.panRange[0]) - axis.p2c(axisMin);\n                // calc max delta (revealing right edge of plot)\n                var maxD = p + axis.p2c(opts.panRange[1]) - axis.p2c(axisMax);\n                // limit delta to min or max if enabled\n                if (opts.panRange[0] !== undefined && d >= maxD) d = maxD;\n                if (opts.panRange[1] !== undefined && d <= minD) d = minD;\n\n                if (d !== 0) {\n                    var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axisMin) - (p - d)) - axis.c2p(axis.p2c(axisMin))),\n                        navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axisMax) - (p - d)) - axis.c2p(axis.p2c(axisMax)));\n\n                    if (!isFinite(navigationOffsetBelow)) {\n                        navigationOffsetBelow = 0;\n                    }\n\n                    if (!isFinite(navigationOffsetAbove)) {\n                        navigationOffsetAbove = 0;\n                    }\n\n                    axis.options.offset.below = saturated.saturate(navigationOffsetBelow + (axis.options.offset.below || 0));\n                    axis.options.offset.above = saturated.saturate(navigationOffsetAbove + (axis.options.offset.above || 0));\n                }\n            });\n\n            prevDelta = delta;\n            plot.setupGrid(true);\n            plot.draw();\n\n            if (!preventEvent) {\n                plot.getPlaceholder().trigger(\"plotpan\", [plot, delta, panAxes, initialState]);\n            }\n        };\n\n        plot.smartPan.end = function() {\n            panHint = null;\n            lockedDirection = null;\n            prevDelta = { x: 0, y: 0 };\n            plot.triggerRedrawOverlay();\n        }\n\n        function shutdown(plot, eventHolder) {\n            eventHolder.unbind(\"mousewheel\", onMouseWheel);\n            eventHolder.unbind(\"mousedown\", onMouseDown);\n            eventHolder.unbind(\"mouseup\", onMouseUp);\n            eventHolder.unbind(\"dragstart\", onDragStart);\n            eventHolder.unbind(\"drag\", onDrag);\n            eventHolder.unbind(\"dragend\", onDragEnd);\n            eventHolder.unbind(\"dblclick\", onDblClick);\n            eventHolder.unbind(\"click\", onClick);\n\n            if (panTimeout) clearTimeout(panTimeout);\n        }\n\n        function drawOverlay(plot, ctx) {\n            if (panHint) {\n                ctx.strokeStyle = 'rgba(96, 160, 208, 0.7)';\n                ctx.lineWidth = 2;\n                ctx.lineJoin = \"round\";\n                var startx = Math.round(panHint.start.x),\n                    starty = Math.round(panHint.start.y),\n                    endx, endy;\n\n                if (panAxes) {\n                    if (panAxes[0].direction === 'x') {\n                        endy = Math.round(panHint.start.y);\n                        endx = Math.round(panHint.end.x);\n                    } else if (panAxes[0].direction === 'y') {\n                        endx = Math.round(panHint.start.x);\n                        endy = Math.round(panHint.end.y);\n                    }\n                } else {\n                    endx = Math.round(panHint.end.x);\n                    endy = Math.round(panHint.end.y);\n                }\n\n                ctx.beginPath();\n\n                if (panHint.end === false) {\n                    ctx.moveTo(startx, starty - PANHINT_LENGTH_CONSTANT);\n                    ctx.lineTo(startx, starty + PANHINT_LENGTH_CONSTANT);\n\n                    ctx.moveTo(startx + PANHINT_LENGTH_CONSTANT, starty);\n                    ctx.lineTo(startx - PANHINT_LENGTH_CONSTANT, starty);\n                } else {\n                    var dirX = starty === endy;\n\n                    ctx.moveTo(startx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty - (dirX ? PANHINT_LENGTH_CONSTANT : 0));\n                    ctx.lineTo(startx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty + (dirX ? PANHINT_LENGTH_CONSTANT : 0));\n\n                    ctx.moveTo(startx, starty);\n                    ctx.lineTo(endx, endy);\n\n                    ctx.moveTo(endx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy - (dirX ? PANHINT_LENGTH_CONSTANT : 0));\n                    ctx.lineTo(endx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy + (dirX ? PANHINT_LENGTH_CONSTANT : 0));\n                }\n\n                ctx.stroke();\n            }\n        }\n\n        plot.getTouchedAxis = function(touchPointX, touchPointY) {\n            var ec = plot.getPlaceholder().offset();\n            ec.left = touchPointX - ec.left;\n            ec.top = touchPointY - ec.top;\n\n            var axis = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {\n                var box = axis.box;\n                if (box !== undefined) {\n                    return (ec.left > box.left) && (ec.left < box.left + box.width) &&\n                            (ec.top > box.top) && (ec.top < box.top + box.height);\n                }\n            });\n\n            return axis;\n        }\n\n        plot.hooks.drawOverlay.push(drawOverlay);\n        plot.hooks.bindEvents.push(bindEvents);\n        plot.hooks.shutdown.push(shutdown);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'navigate',\n        version: '1.3'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.pie.js",
    "content": "/* Flot plugin for rendering pie charts.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe plugin assumes that each series has a single data value, and that each\nvalue is a positive integer or zero.  Negative numbers don't make sense for a\npie chart, and have unpredictable results.  The values do NOT need to be\npassed in as percentages; the plugin will calculate the total and per-slice\npercentages internally.\n\n* Created by Brian Medendorp\n\n* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars\n\nThe plugin supports these options:\n\n    series: {\n        pie: {\n            show: true/false\n            radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'\n            innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect\n            startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result\n            tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)\n            offset: {\n                top: integer value to move the pie up or down\n                left: integer value to move the pie left or right, or 'auto'\n            },\n            stroke: {\n                color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')\n                width: integer pixel width of the stroke\n            },\n            label: {\n                show: true/false, or 'auto'\n                formatter:  a user-defined function that modifies the text/style of the label text\n                radius: 0-1 for percentage of fullsize, or a specified pixel length\n                background: {\n                    color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')\n                    opacity: 0-1\n                },\n                threshold: 0-1 for the percentage value at which to hide labels (if they're too small)\n            },\n            combine: {\n                threshold: 0-1 for the percentage value at which to combine slices (if they're too small)\n                color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined\n                label: any text value of what the combined slice should be labeled\n            }\n            highlight: {\n                opacity: 0-1\n            }\n        }\n    }\n\nMore detail and specific examples can be found in the included HTML file.\n\n*/\n\n(function($) {\n    // Maximum redraw attempts when fitting labels within the plot\n\n    var REDRAW_ATTEMPTS = 10;\n\n    // Factor by which to shrink the pie when fitting labels within the plot\n\n    var REDRAW_SHRINK = 0.95;\n\n    function init(plot) {\n        var canvas = null,\n            target = null,\n            options = null,\n            maxRadius = null,\n            centerLeft = null,\n            centerTop = null,\n            processed = false,\n            ctx = null;\n\n        // interactive variables\n\n        var highlights = [];\n\n        // add hook to determine if pie plugin in enabled, and then perform necessary operations\n\n        plot.hooks.processOptions.push(function(plot, options) {\n            if (options.series.pie.show) {\n                options.grid.show = false;\n\n                // set labels.show\n\n                if (options.series.pie.label.show === \"auto\") {\n                    if (options.legend.show) {\n                        options.series.pie.label.show = false;\n                    } else {\n                        options.series.pie.label.show = true;\n                    }\n                }\n\n                // set radius\n\n                if (options.series.pie.radius === \"auto\") {\n                    if (options.series.pie.label.show) {\n                        options.series.pie.radius = 3 / 4;\n                    } else {\n                        options.series.pie.radius = 1;\n                    }\n                }\n\n                // ensure sane tilt\n\n                if (options.series.pie.tilt > 1) {\n                    options.series.pie.tilt = 1;\n                } else if (options.series.pie.tilt < 0) {\n                    options.series.pie.tilt = 0;\n                }\n            }\n        });\n\n        plot.hooks.bindEvents.push(function(plot, eventHolder) {\n            var options = plot.getOptions();\n            if (options.series.pie.show) {\n                if (options.grid.hoverable) {\n                    eventHolder.unbind(\"mousemove\").mousemove(onMouseMove);\n                    eventHolder.bind(\"mouseleave\", onMouseMove);\n                }\n                if (options.grid.clickable) {\n                    eventHolder.unbind(\"click\").click(onClick);\n                }\n            }\n        });\n\n        plot.hooks.shutdown.push(function (plot, eventHolder) {\n            eventHolder.unbind(\"mousemove\", onMouseMove);\n            eventHolder.unbind(\"mouseleave\", onMouseMove);\n            eventHolder.unbind(\"click\", onClick);\n            highlights = [];\n        });\n\n        plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {\n            var options = plot.getOptions();\n            if (options.series.pie.show) {\n                processDatapoints(plot, series, data, datapoints);\n            }\n        });\n\n        plot.hooks.drawOverlay.push(function(plot, octx) {\n            var options = plot.getOptions();\n            if (options.series.pie.show) {\n                drawOverlay(plot, octx);\n            }\n        });\n\n        plot.hooks.draw.push(function(plot, newCtx) {\n            var options = plot.getOptions();\n            if (options.series.pie.show) {\n                draw(plot, newCtx);\n            }\n        });\n\n        function processDatapoints(plot, series, datapoints) {\n            if (!processed) {\n                processed = true;\n                canvas = plot.getCanvas();\n                target = $(canvas).parent();\n                options = plot.getOptions();\n                plot.setData(combine(plot.getData()));\n            }\n        }\n\n        function combine(data) {\n            var total = 0,\n                combined = 0,\n                numCombined = 0,\n                color = options.series.pie.combine.color,\n                newdata = [],\n                i,\n                value;\n\n            // Fix up the raw data from Flot, ensuring the data is numeric\n\n            for (i = 0; i < data.length; ++i) {\n                value = data[i].data;\n\n                // If the data is an array, we'll assume that it's a standard\n                // Flot x-y pair, and are concerned only with the second value.\n\n                // Note how we use the original array, rather than creating a\n                // new one; this is more efficient and preserves any extra data\n                // that the user may have stored in higher indexes.\n\n                if ($.isArray(value) && value.length === 1) {\n                    value = value[0];\n                }\n\n                if ($.isArray(value)) {\n                    // Equivalent to $.isNumeric() but compatible with jQuery < 1.7\n                    if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {\n                        value[1] = +value[1];\n                    } else {\n                        value[1] = 0;\n                    }\n                } else if (!isNaN(parseFloat(value)) && isFinite(value)) {\n                    value = [1, +value];\n                } else {\n                    value = [1, 0];\n                }\n\n                data[i].data = [value];\n            }\n\n            // Sum up all the slices, so we can calculate percentages for each\n\n            for (i = 0; i < data.length; ++i) {\n                total += data[i].data[0][1];\n            }\n\n            // Count the number of slices with percentages below the combine\n            // threshold; if it turns out to be just one, we won't combine.\n\n            for (i = 0; i < data.length; ++i) {\n                value = data[i].data[0][1];\n                if (value / total <= options.series.pie.combine.threshold) {\n                    combined += value;\n                    numCombined++;\n                    if (!color) {\n                        color = data[i].color;\n                    }\n                }\n            }\n\n            for (i = 0; i < data.length; ++i) {\n                value = data[i].data[0][1];\n                if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {\n                    newdata.push(\n                        $.extend(data[i], {     /* extend to allow keeping all other original data values\n                                                   and using them e.g. in labelFormatter. */\n                            data: [[1, value]],\n                            color: data[i].color,\n                            label: data[i].label,\n                            angle: value * Math.PI * 2 / total,\n                            percent: value / (total / 100)\n                        })\n                    );\n                }\n            }\n\n            if (numCombined > 1) {\n                newdata.push({\n                    data: [[1, combined]],\n                    color: color,\n                    label: options.series.pie.combine.label,\n                    angle: combined * Math.PI * 2 / total,\n                    percent: combined / (total / 100)\n                });\n            }\n\n            return newdata;\n        }\n\n        function draw(plot, newCtx) {\n            if (!target) {\n                return; // if no series were passed\n            }\n\n            var canvasWidth = plot.getPlaceholder().width(),\n                canvasHeight = plot.getPlaceholder().height(),\n                legendWidth = target.children().filter(\".legend\").children().width() || 0;\n\n            ctx = newCtx;\n\n            // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!\n\n            // When combining smaller slices into an 'other' slice, we need to\n            // add a new series.  Since Flot gives plugins no way to modify the\n            // list of series, the pie plugin uses a hack where the first call\n            // to processDatapoints results in a call to setData with the new\n            // list of series, then subsequent processDatapoints do nothing.\n\n            // The plugin-global 'processed' flag is used to control this hack;\n            // it starts out false, and is set to true after the first call to\n            // processDatapoints.\n\n            // Unfortunately this turns future setData calls into no-ops; they\n            // call processDatapoints, the flag is true, and nothing happens.\n\n            // To fix this we'll set the flag back to false here in draw, when\n            // all series have been processed, so the next sequence of calls to\n            // processDatapoints once again starts out with a slice-combine.\n            // This is really a hack; in 0.9 we need to give plugins a proper\n            // way to modify series before any processing begins.\n\n            processed = false;\n\n            // calculate maximum radius and center point\n            maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;\n            centerTop = canvasHeight / 2 + options.series.pie.offset.top;\n            centerLeft = canvasWidth / 2;\n\n            if (options.series.pie.offset.left === \"auto\") {\n                if (options.legend.position.match(\"w\")) {\n                    centerLeft += legendWidth / 2;\n                } else {\n                    centerLeft -= legendWidth / 2;\n                }\n                if (centerLeft < maxRadius) {\n                    centerLeft = maxRadius;\n                } else if (centerLeft > canvasWidth - maxRadius) {\n                    centerLeft = canvasWidth - maxRadius;\n                }\n            } else {\n                centerLeft += options.series.pie.offset.left;\n            }\n\n            var slices = plot.getData(),\n                attempts = 0;\n\n            // Keep shrinking the pie's radius until drawPie returns true,\n            // indicating that all the labels fit, or we try too many times.\n            do {\n                if (attempts > 0) {\n                    maxRadius *= REDRAW_SHRINK;\n                }\n                attempts += 1;\n                clear();\n                if (options.series.pie.tilt <= 0.8) {\n                    drawShadow();\n                }\n            } while (!drawPie() && attempts < REDRAW_ATTEMPTS)\n\n            if (attempts >= REDRAW_ATTEMPTS) {\n                clear();\n                target.prepend(\"<div class='error'>Could not draw pie with labels contained inside canvas</div>\");\n            }\n\n            if (plot.setSeries && plot.insertLegend) {\n                plot.setSeries(slices);\n                plot.insertLegend();\n            }\n\n            // we're actually done at this point, just defining internal functions at this point\n            function clear() {\n                ctx.clearRect(0, 0, canvasWidth, canvasHeight);\n                target.children().filter(\".pieLabel, .pieLabelBackground\").remove();\n            }\n\n            function drawShadow() {\n                var shadowLeft = options.series.pie.shadow.left;\n                var shadowTop = options.series.pie.shadow.top;\n                var edge = 10;\n                var alpha = options.series.pie.shadow.alpha;\n                var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;\n\n                if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {\n                    return;    // shadow would be outside canvas, so don't draw it\n                }\n\n                ctx.save();\n                ctx.translate(shadowLeft, shadowTop);\n                ctx.globalAlpha = alpha;\n                ctx.fillStyle = \"#000\";\n\n                // center and rotate to starting position\n                ctx.translate(centerLeft, centerTop);\n                ctx.scale(1, options.series.pie.tilt);\n\n                //radius -= edge;\n                for (var i = 1; i <= edge; i++) {\n                    ctx.beginPath();\n                    ctx.arc(0, 0, radius, 0, Math.PI * 2, false);\n                    ctx.fill();\n                    radius -= i;\n                }\n\n                ctx.restore();\n            }\n\n            function drawPie() {\n                var startAngle = Math.PI * options.series.pie.startAngle;\n                var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;\n                var i;\n                // center and rotate to starting position\n\n                ctx.save();\n                ctx.translate(centerLeft, centerTop);\n                ctx.scale(1, options.series.pie.tilt);\n                //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera\n\n                // draw slices\n                ctx.save();\n\n                var currentAngle = startAngle;\n                for (i = 0; i < slices.length; ++i) {\n                    slices[i].startAngle = currentAngle;\n                    drawSlice(slices[i].angle, slices[i].color, true);\n                }\n\n                ctx.restore();\n\n                // draw slice outlines\n                if (options.series.pie.stroke.width > 0) {\n                    ctx.save();\n                    ctx.lineWidth = options.series.pie.stroke.width;\n                    currentAngle = startAngle;\n                    for (i = 0; i < slices.length; ++i) {\n                        drawSlice(slices[i].angle, options.series.pie.stroke.color, false);\n                    }\n\n                    ctx.restore();\n                }\n\n                // draw donut hole\n                drawDonutHole(ctx);\n\n                ctx.restore();\n\n                // Draw the labels, returning true if they fit within the plot\n                if (options.series.pie.label.show) {\n                    return drawLabels();\n                } else return true;\n\n                function drawSlice(angle, color, fill) {\n                    if (angle <= 0 || isNaN(angle)) {\n                        return;\n                    }\n\n                    if (fill) {\n                        ctx.fillStyle = color;\n                    } else {\n                        ctx.strokeStyle = color;\n                        ctx.lineJoin = \"round\";\n                    }\n\n                    ctx.beginPath();\n                    if (Math.abs(angle - Math.PI * 2) > 0.000000001) {\n                        ctx.moveTo(0, 0); // Center of the pie\n                    }\n\n                    //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera\n                    ctx.arc(0, 0, radius, currentAngle, currentAngle + angle / 2, false);\n                    ctx.arc(0, 0, radius, currentAngle + angle / 2, currentAngle + angle, false);\n                    ctx.closePath();\n                    //ctx.rotate(angle); // This doesn't work properly in Opera\n                    currentAngle += angle;\n\n                    if (fill) {\n                        ctx.fill();\n                    } else {\n                        ctx.stroke();\n                    }\n                }\n\n                function drawLabels() {\n                    var currentAngle = startAngle;\n                    var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;\n\n                    for (var i = 0; i < slices.length; ++i) {\n                        if (slices[i].percent >= options.series.pie.label.threshold * 100) {\n                            if (!drawLabel(slices[i], currentAngle, i)) {\n                                return false;\n                            }\n                        }\n                        currentAngle += slices[i].angle;\n                    }\n\n                    return true;\n\n                    function drawLabel(slice, startAngle, index) {\n                        if (slice.data[0][1] === 0) {\n                            return true;\n                        }\n\n                        // format label text\n                        var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;\n\n                        if (lf) {\n                            text = lf(slice.label, slice);\n                        } else {\n                            text = slice.label;\n                        }\n\n                        if (plf) {\n                            text = plf(text, slice);\n                        }\n\n                        var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;\n                        var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);\n                        var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;\n\n                        var html = \"<span class='pieLabel' id='pieLabel\" + index + \"' style='position:absolute;top:\" + y + \"px;left:\" + x + \"px;'>\" + text + \"</span>\";\n                        target.append(html);\n\n                        var label = target.children(\"#pieLabel\" + index);\n                        var labelTop = (y - label.height() / 2);\n                        var labelLeft = (x - label.width() / 2);\n\n                        label.css(\"top\", labelTop);\n                        label.css(\"left\", labelLeft);\n\n                        // check to make sure that the label is not outside the canvas\n                        if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {\n                            return false;\n                        }\n\n                        if (options.series.pie.label.background.opacity !== 0) {\n                            // put in the transparent background separately to avoid blended labels and label boxes\n                            var c = options.series.pie.label.background.color;\n                            if (c == null) {\n                                c = slice.color;\n                            }\n\n                            var pos = \"top:\" + labelTop + \"px;left:\" + labelLeft + \"px;\";\n                            $(\"<div class='pieLabelBackground' style='position:absolute;width:\" + label.width() + \"px;height:\" + label.height() + \"px;\" + pos + \"background-color:\" + c + \";'></div>\")\n                                .css(\"opacity\", options.series.pie.label.background.opacity)\n                                .insertBefore(label);\n                        }\n\n                        return true;\n                    } // end individual label function\n                } // end drawLabels function\n            } // end drawPie function\n        } // end draw function\n\n        // Placed here because it needs to be accessed from multiple locations\n\n        function drawDonutHole(layer) {\n            if (options.series.pie.innerRadius > 0) {\n                // subtract the center\n                layer.save();\n                var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;\n                layer.globalCompositeOperation = \"destination-out\"; // this does not work with excanvas, but it will fall back to using the stroke color\n                layer.beginPath();\n                layer.fillStyle = options.series.pie.stroke.color;\n                layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);\n                layer.fill();\n                layer.closePath();\n                layer.restore();\n\n                // add inner stroke\n                layer.save();\n                layer.beginPath();\n                layer.strokeStyle = options.series.pie.stroke.color;\n                layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);\n                layer.stroke();\n                layer.closePath();\n                layer.restore();\n\n                // TODO: add extra shadow inside hole (with a mask) if the pie is tilted.\n            }\n        }\n\n        //-- Additional Interactive related functions --\n\n        function isPointInPoly(poly, pt) {\n            for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) {\n                ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) ||\n                (poly[j][1] <= pt[1] && pt[1] < poly[i][1])) &&\n                (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) &&\n                (c = !c);\n            }\n            return c;\n        }\n\n        function findNearbySlice(mouseX, mouseY) {\n            var slices = plot.getData(),\n                options = plot.getOptions(),\n                radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,\n                x, y;\n\n            for (var i = 0; i < slices.length; ++i) {\n                var s = slices[i];\n                if (s.pie.show) {\n                    ctx.save();\n                    ctx.beginPath();\n                    ctx.moveTo(0, 0); // Center of the pie\n                    //ctx.scale(1, options.series.pie.tilt);    // this actually seems to break everything when here.\n                    ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);\n                    ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);\n                    ctx.closePath();\n                    x = mouseX - centerLeft;\n                    y = mouseY - centerTop;\n\n                    if (ctx.isPointInPath) {\n                        if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {\n                            ctx.restore();\n                            return {\n                                datapoint: [s.percent, s.data],\n                                dataIndex: 0,\n                                series: s,\n                                seriesIndex: i\n                            };\n                        }\n                    } else {\n                        // excanvas for IE doesn;t support isPointInPath, this is a workaround.\n                        var p1X = radius * Math.cos(s.startAngle),\n                            p1Y = radius * Math.sin(s.startAngle),\n                            p2X = radius * Math.cos(s.startAngle + s.angle / 4),\n                            p2Y = radius * Math.sin(s.startAngle + s.angle / 4),\n                            p3X = radius * Math.cos(s.startAngle + s.angle / 2),\n                            p3Y = radius * Math.sin(s.startAngle + s.angle / 2),\n                            p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),\n                            p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),\n                            p5X = radius * Math.cos(s.startAngle + s.angle),\n                            p5Y = radius * Math.sin(s.startAngle + s.angle),\n                            arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],\n                            arrPoint = [x, y];\n\n                        // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?\n\n                        if (isPointInPoly(arrPoly, arrPoint)) {\n                            ctx.restore();\n                            return {\n                                datapoint: [s.percent, s.data],\n                                dataIndex: 0,\n                                series: s,\n                                seriesIndex: i\n                            };\n                        }\n                    }\n\n                    ctx.restore();\n                }\n            }\n\n            return null;\n        }\n\n        function onMouseMove(e) {\n            triggerClickHoverEvent(\"plothover\", e);\n        }\n\n        function onClick(e) {\n            triggerClickHoverEvent(\"plotclick\", e);\n        }\n\n        // trigger click or hover event (they send the same parameters so we share their code)\n\n        function triggerClickHoverEvent(eventname, e) {\n            var offset = plot.offset();\n            var canvasX = parseInt(e.pageX - offset.left);\n            var canvasY = parseInt(e.pageY - offset.top);\n            var item = findNearbySlice(canvasX, canvasY);\n\n            if (options.grid.autoHighlight) {\n                // clear auto-highlights\n                for (var i = 0; i < highlights.length; ++i) {\n                    var h = highlights[i];\n                    if (h.auto === eventname && !(item && h.series === item.series)) {\n                        unhighlight(h.series);\n                    }\n                }\n            }\n\n            // highlight the slice\n\n            if (item) {\n                highlight(item.series, eventname);\n            }\n\n            // trigger any hover bind events\n\n            var pos = { pageX: e.pageX, pageY: e.pageY };\n            target.trigger(eventname, [pos, item]);\n        }\n\n        function highlight(s, auto) {\n            //if (typeof s == \"number\") {\n            //    s = series[s];\n            //}\n\n            var i = indexOfHighlight(s);\n\n            if (i === -1) {\n                highlights.push({ series: s, auto: auto });\n                plot.triggerRedrawOverlay();\n            } else if (!auto) {\n                highlights[i].auto = false;\n            }\n        }\n\n        function unhighlight(s) {\n            if (s == null) {\n                highlights = [];\n                plot.triggerRedrawOverlay();\n            }\n\n            //if (typeof s == \"number\") {\n            //    s = series[s];\n            //}\n\n            var i = indexOfHighlight(s);\n\n            if (i !== -1) {\n                highlights.splice(i, 1);\n                plot.triggerRedrawOverlay();\n            }\n        }\n\n        function indexOfHighlight(s) {\n            for (var i = 0; i < highlights.length; ++i) {\n                var h = highlights[i];\n                if (h.series === s) {\n                    return i;\n                }\n            }\n            return -1;\n        }\n\n        function drawOverlay(plot, octx) {\n            var options = plot.getOptions();\n            var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;\n\n            octx.save();\n            octx.translate(centerLeft, centerTop);\n            octx.scale(1, options.series.pie.tilt);\n\n            for (var i = 0; i < highlights.length; ++i) {\n                drawHighlight(highlights[i].series);\n            }\n\n            drawDonutHole(octx);\n\n            octx.restore();\n\n            function drawHighlight(series) {\n                if (series.angle <= 0 || isNaN(series.angle)) {\n                    return;\n                }\n\n                //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();\n                octx.fillStyle = \"rgba(255, 255, 255, \" + options.series.pie.highlight.opacity + \")\"; // this is temporary until we have access to parseColor\n                octx.beginPath();\n                if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {\n                    octx.moveTo(0, 0); // Center of the pie\n                }\n                octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);\n                octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);\n                octx.closePath();\n                octx.fill();\n            }\n        }\n    } // end init (plugin body)\n\n    // define pie specific options and their default values\n    var options = {\n        series: {\n            pie: {\n                show: false,\n                radius: \"auto\",    // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)\n                innerRadius: 0, /* for donut */\n                startAngle: 3 / 2,\n                tilt: 1,\n                shadow: {\n                    left: 5,    // shadow left offset\n                    top: 15,    // shadow top offset\n                    alpha: 0.02    // shadow alpha\n                },\n                offset: {\n                    top: 0,\n                    left: \"auto\"\n                },\n                stroke: {\n                    color: \"#fff\",\n                    width: 1\n                },\n                label: {\n                    show: \"auto\",\n                    formatter: function(label, slice) {\n                        return \"<div style='font-size:x-small;text-align:center;padding:2px;color:\" + slice.color + \";'>\" + label + \"<br/>\" + Math.round(slice.percent) + \"%</div>\";\n                    },    // formatter function\n                    radius: 1,    // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)\n                    background: {\n                        color: null,\n                        opacity: 0\n                    },\n                    threshold: 0    // percentage at which to hide the label (i.e. the slice is too narrow)\n                },\n                combine: {\n                    threshold: -1,    // percentage at which to combine little slices into one larger slice\n                    color: null,    // color to give the new slice (auto-generated if null)\n                    label: \"Other\"    // label to give the new slice\n                },\n                highlight: {\n                    //color: \"#fff\",        // will add this functionality once parseColor is available\n                    opacity: 0.5\n                }\n            }\n        }\n    };\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: \"pie\",\n        version: \"1.1\"\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.resize.js",
    "content": "/* eslint-disable */\n/* Flot plugin for automatically redrawing plots as the placeholder resizes.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nIt works by listening for changes on the placeholder div (through the jQuery\nresize event plugin) - if the size changes, it will redraw the plot.\n\nThere are no options. If you need to disable the plugin for some plots, you\ncan just fix the size of their placeholders.\n\n*/\n\n/* Inline dependency:\n * jQuery resize event - v1.1 - 3/14/2010\n * http://benalman.com/projects/jquery-resize-plugin/\n *\n * Copyright (c) 2010 \"Cowboy\" Ben Alman\n * Dual licensed under the MIT and GPL licenses.\n * http://benalman.com/about/license/\n */\n(function($,e,t){\"$:nomunge\";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s=\"setTimeout\",u=\"resize\",m=u+\"-special-event\",o=\"pendingDelay\",l=\"activeDelay\",f=\"throttleWindow\";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(\":visible\")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,window);\n\n/* eslint-enable */\n(function ($) {\n    var options = { }; // no options\n\n    function init(plot) {\n        function onResize() {\n            var placeholder = plot.getPlaceholder();\n\n            // somebody might have hidden us and we can't plot\n            // when we don't have the dimensions\n            if (placeholder.width() === 0 || placeholder.height() === 0) return;\n\n            plot.resize();\n            plot.setupGrid();\n            plot.draw();\n        }\n\n        function bindEvents(plot, eventHolder) {\n            plot.getPlaceholder().resize(onResize);\n        }\n\n        function shutdown(plot, eventHolder) {\n            plot.getPlaceholder().unbind(\"resize\", onResize);\n        }\n\n        plot.hooks.bindEvents.push(bindEvents);\n        plot.hooks.shutdown.push(shutdown);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'resize',\n        version: '1.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.saturated.js",
    "content": "(function ($) {\n    'use strict';\n    var saturated = {\n        saturate: function (a) {\n            if (a === Infinity) {\n                return Number.MAX_VALUE;\n            }\n\n            if (a === -Infinity) {\n                return -Number.MAX_VALUE;\n            }\n\n            return a;\n        },\n        delta: function(min, max, noTicks) {\n            return ((max - min) / noTicks) === Infinity ? (max / noTicks - min / noTicks) : (max - min) / noTicks\n        },\n        multiply: function (a, b) {\n            return saturated.saturate(a * b);\n        },\n        // returns c * bInt * a. Beahves properly in the case where c is negative\n        // and bInt * a is bigger that Number.MAX_VALUE (Infinity)\n        multiplyAdd: function (a, bInt, c) {\n            if (isFinite(a * bInt)) {\n                return saturated.saturate(a * bInt + c);\n            } else {\n                var result = c;\n\n                for (var i = 0; i < bInt; i++) {\n                    result += a;\n                }\n\n                return saturated.saturate(result);\n            }\n        },\n        // round to nearby lower multiple of base\n        floorInBase: function(n, base) {\n            return base * Math.floor(n / base);\n        }\n    };\n\n    $.plot.saturated = saturated;\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.selection.js",
    "content": "/* Flot plugin for selecting regions of a plot.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe plugin supports these options:\n\nselection: {\n    mode: null or \"x\" or \"y\" or \"xy\" or \"smart\",\n    color: color,\n    shape: \"round\" or \"miter\" or \"bevel\",\n    visualization: \"fill\" or \"focus\",\n    displaySelectionDecorations: true or false,\n    minSize: number of pixels\n}\n\nSelection support is enabled by setting the mode to one of \"x\", \"y\" or \"xy\".\nIn \"x\" mode, the user will only be able to specify the x range, similarly for\n\"y\" mode. For \"xy\", the selection becomes a rectangle where both ranges can be\nspecified. \"color\" is color of the selection (if you need to change the color\nlater on, you can get to it with plot.getOptions().selection.color). \"shape\"\nis the shape of the corners of the selection.\n\nThe way how the selection is visualized, can be changed by using the option\n\"visualization\". Flot currently supports two modes: \"focus\" and \"fill\". The\noption \"focus\" draws a colored bezel around the selected area while keeping\nthe selected area clear. The option \"fill\" highlights (i.e., fills) the\nselected area with a colored highlight.\n\nThere are optional selection decorations (handles) that are rendered with the\n\"focus\" visualization option. The selection decoration is rendered by default\nbut can be turned off by setting displaySelectionDecorations to false.\n\n\"minSize\" is the minimum size a selection can be in pixels. This value can\nbe customized to determine the smallest size a selection can be and still\nhave the selection rectangle be displayed. When customizing this value, the\nfact that it refers to pixels, not axis units must be taken into account.\nThus, for example, if there is a bar graph in time mode with BarWidth set to 1\nminute, setting \"minSize\" to 1 will not make the minimum selection size 1\nminute, but rather 1 pixel. Note also that setting \"minSize\" to 0 will prevent\n\"plotunselected\" events from being fired when the user clicks the mouse without\ndragging.\n\nWhen selection support is enabled, a \"plotselected\" event will be emitted on\nthe DOM element you passed into the plot function. The event handler gets a\nparameter with the ranges selected on the axes, like this:\n\n    placeholder.bind( \"plotselected\", function( event, ranges ) {\n        alert(\"You selected \" + ranges.xaxis.from + \" to \" + ranges.xaxis.to)\n        // similar for yaxis - with multiple axes, the extra ones are in\n        // x2axis, x3axis, ...\n    });\n\nThe \"plotselected\" event is only fired when the user has finished making the\nselection. A \"plotselecting\" event is fired during the process with the same\nparameters as the \"plotselected\" event, in case you want to know what's\nhappening while it's happening,\n\nA \"plotunselected\" event with no arguments is emitted when the user clicks the\nmouse to remove the selection. As stated above, setting \"minSize\" to 0 will\ndestroy this behavior.\n\nThe plugin allso adds the following methods to the plot object:\n\n- setSelection( ranges, preventEvent )\n\n  Set the selection rectangle. The passed in ranges is on the same form as\n  returned in the \"plotselected\" event. If the selection mode is \"x\", you\n  should put in either an xaxis range, if the mode is \"y\" you need to put in\n  an yaxis range and both xaxis and yaxis if the selection mode is \"xy\", like\n  this:\n\n    setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });\n\n  setSelection will trigger the \"plotselected\" event when called. If you don't\n  want that to happen, e.g. if you're inside a \"plotselected\" handler, pass\n  true as the second parameter. If you are using multiple axes, you can\n  specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of\n  xaxis, the plugin picks the first one it sees.\n\n- clearSelection( preventEvent )\n\n  Clear the selection rectangle. Pass in true to avoid getting a\n  \"plotunselected\" event.\n\n- getSelection()\n\n  Returns the current selection in the same format as the \"plotselected\"\n  event. If there's currently no selection, the function returns null.\n\n*/\n\n(function ($) {\n    function init(plot) {\n        var selection = {\n            first: {x: -1, y: -1},\n            second: {x: -1, y: -1},\n            show: false,\n            currentMode: 'xy',\n            active: false\n        };\n\n        var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;\n\n        // FIXME: The drag handling implemented here should be\n        // abstracted out, there's some similar code from a library in\n        // the navigation plugin, this should be massaged a bit to fit\n        // the Flot cases here better and reused. Doing this would\n        // make this plugin much slimmer.\n        var savedhandlers = {};\n\n        function onDrag(e) {\n            if (selection.active) {\n                updateSelection(e);\n\n                plot.getPlaceholder().trigger(\"plotselecting\", [ getSelection() ]);\n            }\n        }\n\n        function onDragStart(e) {\n            var o = plot.getOptions();\n            // only accept left-click\n            if (e.which !== 1 || o.selection.mode === null) return;\n\n            // reinitialize currentMode\n            selection.currentMode = 'xy';\n\n            // cancel out any text selections\n            document.body.focus();\n\n            // prevent text selection and drag in old-school browsers\n            if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {\n                savedhandlers.onselectstart = document.onselectstart;\n                document.onselectstart = function () { return false; };\n            }\n            if (document.ondrag !== undefined && savedhandlers.ondrag == null) {\n                savedhandlers.ondrag = document.ondrag;\n                document.ondrag = function () { return false; };\n            }\n\n            setSelectionPos(selection.first, e);\n\n            selection.active = true;\n        }\n\n        function onDragEnd(e) {\n            // revert drag stuff for old-school browsers\n            if (document.onselectstart !== undefined) {\n                document.onselectstart = savedhandlers.onselectstart;\n            }\n\n            if (document.ondrag !== undefined) {\n                document.ondrag = savedhandlers.ondrag;\n            }\n\n            // no more dragging\n            selection.active = false;\n            updateSelection(e);\n\n            if (selectionIsSane()) {\n                triggerSelectedEvent();\n            } else {\n                // this counts as a clear\n                plot.getPlaceholder().trigger(\"plotunselected\", [ ]);\n                plot.getPlaceholder().trigger(\"plotselecting\", [ null ]);\n            }\n\n            return false;\n        }\n\n        function getSelection() {\n            if (!selectionIsSane()) return null;\n\n            if (!selection.show) return null;\n\n            var r = {},\n                c1 = {x: selection.first.x, y: selection.first.y},\n                c2 = {x: selection.second.x, y: selection.second.y};\n\n            if (selectionDirection(plot) === 'x') {\n                c1.y = 0;\n                c2.y = plot.height();\n            }\n\n            if (selectionDirection(plot) === 'y') {\n                c1.x = 0;\n                c2.x = plot.width();\n            }\n\n            $.each(plot.getAxes(), function (name, axis) {\n                if (axis.used) {\n                    var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);\n                    r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };\n                }\n            });\n            return r;\n        }\n\n        function triggerSelectedEvent() {\n            var r = getSelection();\n\n            plot.getPlaceholder().trigger(\"plotselected\", [ r ]);\n\n            // backwards-compat stuff, to be removed in future\n            if (r.xaxis && r.yaxis) {\n                plot.getPlaceholder().trigger(\"selected\", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);\n            }\n        }\n\n        function clamp(min, value, max) {\n            return value < min ? min : (value > max ? max : value);\n        }\n\n        function selectionDirection(plot) {\n            var o = plot.getOptions();\n\n            if (o.selection.mode === 'smart') {\n                return selection.currentMode;\n            } else {\n                return o.selection.mode;\n            }\n        }\n\n        function updateMode(pos) {\n            if (selection.first) {\n                var delta = {\n                    x: pos.x - selection.first.x,\n                    y: pos.y - selection.first.y\n                };\n\n                if (Math.abs(delta.x) < SNAPPING_CONSTANT) {\n                    selection.currentMode = 'y';\n                } else if (Math.abs(delta.y) < SNAPPING_CONSTANT) {\n                    selection.currentMode = 'x';\n                } else {\n                    selection.currentMode = 'xy';\n                }\n            }\n        }\n\n        function setSelectionPos(pos, e) {\n            var offset = plot.getPlaceholder().offset();\n            var plotOffset = plot.getPlotOffset();\n            pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());\n            pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());\n\n            if (pos !== selection.first) updateMode(pos);\n\n            if (selectionDirection(plot) === \"y\") {\n                pos.x = pos === selection.first ? 0 : plot.width();\n            }\n\n            if (selectionDirection(plot) === \"x\") {\n                pos.y = pos === selection.first ? 0 : plot.height();\n            }\n        }\n\n        function updateSelection(pos) {\n            if (pos.pageX == null) return;\n\n            setSelectionPos(selection.second, pos);\n            if (selectionIsSane()) {\n                selection.show = true;\n                plot.triggerRedrawOverlay();\n            } else clearSelection(true);\n        }\n\n        function clearSelection(preventEvent) {\n            if (selection.show) {\n                selection.show = false;\n                selection.currentMode = '';\n                plot.triggerRedrawOverlay();\n                if (!preventEvent) {\n                    plot.getPlaceholder().trigger(\"plotunselected\", [ ]);\n                }\n            }\n        }\n\n        // function taken from markings support in Flot\n        function extractRange(ranges, coord) {\n            var axis, from, to, key, axes = plot.getAxes();\n\n            for (var k in axes) {\n                axis = axes[k];\n                if (axis.direction === coord) {\n                    key = coord + axis.n + \"axis\";\n                    if (!ranges[key] && axis.n === 1) {\n                        // support x1axis as xaxis\n                        key = coord + \"axis\";\n                    }\n\n                    if (ranges[key]) {\n                        from = ranges[key].from;\n                        to = ranges[key].to;\n                        break;\n                    }\n                }\n            }\n\n            // backwards-compat stuff - to be removed in future\n            if (!ranges[key]) {\n                axis = coord === \"x\" ? plot.getXAxes()[0] : plot.getYAxes()[0];\n                from = ranges[coord + \"1\"];\n                to = ranges[coord + \"2\"];\n            }\n\n            // auto-reverse as an added bonus\n            if (from != null && to != null && from > to) {\n                var tmp = from;\n                from = to;\n                to = tmp;\n            }\n\n            return { from: from, to: to, axis: axis };\n        }\n\n        function setSelection(ranges, preventEvent) {\n            var range;\n\n            if (selectionDirection(plot) === \"y\") {\n                selection.first.x = 0;\n                selection.second.x = plot.width();\n            } else {\n                range = extractRange(ranges, \"x\");\n                selection.first.x = range.axis.p2c(range.from);\n                selection.second.x = range.axis.p2c(range.to);\n            }\n\n            if (selectionDirection(plot) === \"x\") {\n                selection.first.y = 0;\n                selection.second.y = plot.height();\n            } else {\n                range = extractRange(ranges, \"y\");\n                selection.first.y = range.axis.p2c(range.from);\n                selection.second.y = range.axis.p2c(range.to);\n            }\n\n            selection.show = true;\n            plot.triggerRedrawOverlay();\n            if (!preventEvent && selectionIsSane()) {\n                triggerSelectedEvent();\n            }\n        }\n\n        function selectionIsSane() {\n            var minSize = plot.getOptions().selection.minSize;\n            return Math.abs(selection.second.x - selection.first.x) >= minSize &&\n                Math.abs(selection.second.y - selection.first.y) >= minSize;\n        }\n\n        plot.clearSelection = clearSelection;\n        plot.setSelection = setSelection;\n        plot.getSelection = getSelection;\n\n        plot.hooks.bindEvents.push(function(plot, eventHolder) {\n            var o = plot.getOptions();\n            if (o.selection.mode != null) {\n                plot.addEventHandler(\"dragstart\", onDragStart, eventHolder, 0);\n                plot.addEventHandler(\"drag\", onDrag, eventHolder, 0);\n                plot.addEventHandler(\"dragend\", onDragEnd, eventHolder, 0);\n            }\n        });\n\n        function drawSelectionDecorations(ctx, x, y, w, h, oX, oY, mode) {\n            var spacing = 3;\n            var fullEarWidth = 15;\n            var earWidth = Math.max(0, Math.min(fullEarWidth, w / 2 - 2, h / 2 - 2));\n            ctx.fillStyle = '#ffffff';\n\n            if (mode === 'xy') {\n                ctx.beginPath();\n                ctx.moveTo(x, y + earWidth);\n                ctx.lineTo(x - 3, y + earWidth);\n                ctx.lineTo(x - 3, y - 3);\n                ctx.lineTo(x + earWidth, y - 3);\n                ctx.lineTo(x + earWidth, y);\n                ctx.lineTo(x, y);\n                ctx.closePath();\n\n                ctx.moveTo(x, y + h - earWidth);\n                ctx.lineTo(x - 3, y + h - earWidth);\n                ctx.lineTo(x - 3, y + h + 3);\n                ctx.lineTo(x + earWidth, y + h + 3);\n                ctx.lineTo(x + earWidth, y + h);\n                ctx.lineTo(x, y + h);\n                ctx.closePath();\n\n                ctx.moveTo(x + w, y + earWidth);\n                ctx.lineTo(x + w + 3, y + earWidth);\n                ctx.lineTo(x + w + 3, y - 3);\n                ctx.lineTo(x + w - earWidth, y - 3);\n                ctx.lineTo(x + w - earWidth, y);\n                ctx.lineTo(x + w, y);\n                ctx.closePath();\n\n                ctx.moveTo(x + w, y + h - earWidth);\n                ctx.lineTo(x + w + 3, y + h - earWidth);\n                ctx.lineTo(x + w + 3, y + h + 3);\n                ctx.lineTo(x + w - earWidth, y + h + 3);\n                ctx.lineTo(x + w - earWidth, y + h);\n                ctx.lineTo(x + w, y + h);\n                ctx.closePath();\n\n                ctx.stroke();\n                ctx.fill();\n            }\n\n            x = oX;\n            y = oY;\n\n            if (mode === 'x') {\n                ctx.beginPath();\n                ctx.moveTo(x, y + fullEarWidth);\n                ctx.lineTo(x, y - fullEarWidth);\n                ctx.lineTo(x - spacing, y - fullEarWidth);\n                ctx.lineTo(x - spacing, y + fullEarWidth);\n                ctx.closePath();\n\n                ctx.moveTo(x + w, y + fullEarWidth);\n                ctx.lineTo(x + w, y - fullEarWidth);\n                ctx.lineTo(x + w + spacing, y - fullEarWidth);\n                ctx.lineTo(x + w + spacing, y + fullEarWidth);\n                ctx.closePath();\n                ctx.stroke();\n                ctx.fill();\n            }\n\n            if (mode === 'y') {\n                ctx.beginPath();\n\n                ctx.moveTo(x - fullEarWidth, y);\n                ctx.lineTo(x + fullEarWidth, y);\n                ctx.lineTo(x + fullEarWidth, y - spacing);\n                ctx.lineTo(x - fullEarWidth, y - spacing);\n                ctx.closePath();\n\n                ctx.moveTo(x - fullEarWidth, y + h);\n                ctx.lineTo(x + fullEarWidth, y + h);\n                ctx.lineTo(x + fullEarWidth, y + h + spacing);\n                ctx.lineTo(x - fullEarWidth, y + h + spacing);\n                ctx.closePath();\n                ctx.stroke();\n                ctx.fill();\n            }\n        }\n\n        plot.hooks.drawOverlay.push(function (plot, ctx) {\n            // draw selection\n            if (selection.show && selectionIsSane()) {\n                var plotOffset = plot.getPlotOffset();\n                var o = plot.getOptions();\n\n                ctx.save();\n                ctx.translate(plotOffset.left, plotOffset.top);\n\n                var c = $.color.parse(o.selection.color);\n                var visualization = o.selection.visualization;\n                var displaySelectionDecorations = o.selection.displaySelectionDecorations;\n\n                var scalingFactor = 1;\n\n                // use a dimmer scaling factor if visualization is \"fill\"\n                if (visualization === \"fill\") {\n                    scalingFactor = 0.8;\n                }\n\n                ctx.strokeStyle = c.scale('a', scalingFactor).toString();\n                ctx.lineWidth = 1;\n                ctx.lineJoin = o.selection.shape;\n                ctx.fillStyle = c.scale('a', 0.4).toString();\n\n                var x = Math.min(selection.first.x, selection.second.x) + 0.5,\n                    oX = x,\n                    y = Math.min(selection.first.y, selection.second.y) + 0.5,\n                    oY = y,\n                    w = Math.abs(selection.second.x - selection.first.x) - 1,\n                    h = Math.abs(selection.second.y - selection.first.y) - 1;\n\n                if (selectionDirection(plot) === 'x') {\n                    h += y;\n                    y = 0;\n                }\n\n                if (selectionDirection(plot) === 'y') {\n                    w += x;\n                    x = 0;\n                }\n\n                if (visualization === \"fill\") {\n                    ctx.fillRect(x, y, w, h);\n                    ctx.strokeRect(x, y, w, h);\n                } else {\n                    ctx.fillRect(0, 0, plot.width(), plot.height());\n                    ctx.clearRect(x, y, w, h);\n\n                    if (displaySelectionDecorations) {\n                        drawSelectionDecorations(ctx, x, y, w, h, oX, oY, selectionDirection(plot));\n                    }\n                }\n\n                ctx.restore();\n            }\n        });\n\n        plot.hooks.shutdown.push(function (plot, eventHolder) {\n            eventHolder.unbind(\"dragstart\", onDragStart);\n            eventHolder.unbind(\"drag\", onDrag);\n            eventHolder.unbind(\"dragend\", onDragEnd);\n        });\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: {\n            selection: {\n                mode: null, // one of null, \"x\", \"y\" or \"xy\"\n                visualization: \"focus\", // \"focus\" or \"fill\"\n                displaySelectionDecorations: true, // true or false (currently only relevant for the focus visualization)\n                color: \"#888888\",\n                shape: \"round\", // one of \"round\", \"miter\", or \"bevel\"\n                minSize: 5 // minimum number of pixels\n            }\n        },\n        name: 'selection',\n        version: '1.1'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.stack.js",
    "content": "/* Flot plugin for stacking data sets rather than overlaying them.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe plugin assumes the data is sorted on x (or y if stacking horizontally).\nFor line charts, it is assumed that if a line has an undefined gap (from a\nnull point), then the line above it should have the same gap - insert zeros\ninstead of \"null\" if you want another behaviour. This also holds for the start\nand end of the chart. Note that stacking a mix of positive and negative values\nin most instances doesn't make sense (so it looks weird).\n\nTwo or more series are stacked when their \"stack\" attribute is set to the same\nkey (which can be any number or string or just \"true\"). To specify the default\nstack, you can set the stack option like this:\n\n    series: {\n        stack: null/false, true, or a key (number/string)\n    }\n\nYou can also specify it for a single series, like this:\n\n    $.plot( $(\"#placeholder\"), [{\n        data: [ ... ],\n        stack: true\n    }])\n\nThe stacking order is determined by the order of the data series in the array\n(later series end up on top of the previous).\n\nInternally, the plugin modifies the datapoints in each series, adding an\noffset to the y value. For line series, extra data points are inserted through\ninterpolation. If there's a second y value, it's also adjusted (e.g for bar\ncharts or filled areas).\n\n*/\n\n(function ($) {\n    var options = {\n        series: { stack: null } // or number/string\n    };\n\n    function init(plot) {\n        function findMatchingSeries(s, allseries) {\n            var res = null;\n            for (var i = 0; i < allseries.length; ++i) {\n                if (s === allseries[i]) break;\n\n                if (allseries[i].stack === s.stack) {\n                    res = allseries[i];\n                }\n            }\n\n            return res;\n        }\n\n        function addBottomPoints (s, datapoints) {\n            var formattedPoints = [];\n            for (var i = 0; i < datapoints.points.length; i += 2) {\n                formattedPoints.push(datapoints.points[i]);\n                formattedPoints.push(datapoints.points[i + 1]);\n                formattedPoints.push(0);\n            }\n\n            datapoints.format.push({\n                x: s.bars.horizontal,\n                y: !s.bars.horizontal,\n                number: true,\n                required: false,\n                computeRange: s.yaxis.options.autoScale !== 'none',\n                defaultValue: 0\n            });\n            datapoints.points = formattedPoints;\n            datapoints.pointsize = 3;\n        }\n\n        function stackData(plot, s, datapoints) {\n            if (s.stack == null || s.stack === false) return;\n\n            var needsBottom = s.bars.show || (s.lines.show && s.lines.fill);\n            var hasBottom = datapoints.pointsize > 2 && (s.bars.horizontal ? datapoints.format[2].x : datapoints.format[2].y);\n            // Series data is missing bottom points - need to format\n            if (needsBottom && !hasBottom) {\n                addBottomPoints(s, datapoints);\n            }\n\n            var other = findMatchingSeries(s, plot.getData());\n            if (!other) return;\n\n            var ps = datapoints.pointsize,\n                points = datapoints.points,\n                otherps = other.datapoints.pointsize,\n                otherpoints = other.datapoints.points,\n                newpoints = [],\n                px, py, intery, qx, qy, bottom,\n                withlines = s.lines.show,\n                horizontal = s.bars.horizontal,\n                withsteps = withlines && s.lines.steps,\n                fromgap = true,\n                keyOffset = horizontal ? 1 : 0,\n                accumulateOffset = horizontal ? 0 : 1,\n                i = 0, j = 0, l, m;\n\n            while (true) {\n                if (i >= points.length) break;\n\n                l = newpoints.length;\n\n                if (points[i] == null) {\n                    // copy gaps\n                    for (m = 0; m < ps; ++m) {\n                        newpoints.push(points[i + m]);\n                    }\n\n                    i += ps;\n                } else if (j >= otherpoints.length) {\n                    // for lines, we can't use the rest of the points\n                    if (!withlines) {\n                        for (m = 0; m < ps; ++m) {\n                            newpoints.push(points[i + m]);\n                        }\n                    }\n\n                    i += ps;\n                } else if (otherpoints[j] == null) {\n                    // oops, got a gap\n                    for (m = 0; m < ps; ++m) {\n                        newpoints.push(null);\n                    }\n\n                    fromgap = true;\n                    j += otherps;\n                } else {\n                    // cases where we actually got two points\n                    px = points[i + keyOffset];\n                    py = points[i + accumulateOffset];\n                    qx = otherpoints[j + keyOffset];\n                    qy = otherpoints[j + accumulateOffset];\n                    bottom = 0;\n\n                    if (px === qx) {\n                        for (m = 0; m < ps; ++m) {\n                            newpoints.push(points[i + m]);\n                        }\n\n                        newpoints[l + accumulateOffset] += qy;\n                        bottom = qy;\n\n                        i += ps;\n                        j += otherps;\n                    } else if (px > qx) {\n                        // we got past point below, might need to\n                        // insert interpolated extra point\n                        if (withlines && i > 0 && points[i - ps] != null) {\n                            intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);\n                            newpoints.push(qx);\n                            newpoints.push(intery + qy);\n                            for (m = 2; m < ps; ++m) {\n                                newpoints.push(points[i + m]);\n                            }\n\n                            bottom = qy;\n                        }\n\n                        j += otherps;\n                    } else { // px < qx\n                        if (fromgap && withlines) {\n                            // if we come from a gap, we just skip this point\n                            i += ps;\n                            continue;\n                        }\n\n                        for (m = 0; m < ps; ++m) {\n                            newpoints.push(points[i + m]);\n                        }\n\n                        // we might be able to interpolate a point below,\n                        // this can give us a better y\n                        if (withlines && j > 0 && otherpoints[j - otherps] != null) {\n                            bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);\n                        }\n\n                        newpoints[l + accumulateOffset] += bottom;\n\n                        i += ps;\n                    }\n\n                    fromgap = false;\n\n                    if (l !== newpoints.length && needsBottom) {\n                        newpoints[l + 2] += bottom;\n                    }\n                }\n\n                // maintain the line steps invariant\n                if (withsteps && l !== newpoints.length && l > 0 &&\n                    newpoints[l] !== null &&\n                    newpoints[l] !== newpoints[l - ps] &&\n                    newpoints[l + 1] !== newpoints[l - ps + 1]) {\n                    for (m = 0; m < ps; ++m) {\n                        newpoints[l + ps + m] = newpoints[l + m];\n                    }\n\n                    newpoints[l + 1] = newpoints[l - ps + 1];\n                }\n            }\n\n            datapoints.points = newpoints;\n        }\n\n        plot.hooks.processDatapoints.push(stackData);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'stack',\n        version: '1.2'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.symbol.js",
    "content": "/* Flot plugin that adds some extra symbols for plotting points.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe symbols are accessed as strings through the standard symbol options:\n\n    series: {\n        points: {\n            symbol: \"square\" // or \"diamond\", \"triangle\", \"cross\", \"plus\", \"ellipse\", \"rectangle\"\n        }\n    }\n\n*/\n\n(function ($) {\n    // we normalize the area of each symbol so it is approximately the\n    // same as a circle of the given radius\n\n    var square = function (ctx, x, y, radius, shadow) {\n            // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2\n            var size = radius * Math.sqrt(Math.PI) / 2;\n            ctx.rect(x - size, y - size, size + size, size + size);\n        },\n        rectangle = function (ctx, x, y, radius, shadow) {\n            // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2\n            var size = radius * Math.sqrt(Math.PI) / 2;\n            ctx.rect(x - size, y - size, size + size, size + size);\n        },\n        diamond = function (ctx, x, y, radius, shadow) {\n            // pi * r^2 = 2s^2  =>  s = r * sqrt(pi/2)\n            var size = radius * Math.sqrt(Math.PI / 2);\n            ctx.moveTo(x - size, y);\n            ctx.lineTo(x, y - size);\n            ctx.lineTo(x + size, y);\n            ctx.lineTo(x, y + size);\n            ctx.lineTo(x - size, y);\n            ctx.lineTo(x, y - size);\n        },\n        triangle = function (ctx, x, y, radius, shadow) {\n            // pi * r^2 = 1/2 * s^2 * sin (pi / 3)  =>  s = r * sqrt(2 * pi / sin(pi / 3))\n            var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3));\n            var height = size * Math.sin(Math.PI / 3);\n            ctx.moveTo(x - size / 2, y + height / 2);\n            ctx.lineTo(x + size / 2, y + height / 2);\n            if (!shadow) {\n                ctx.lineTo(x, y - height / 2);\n                ctx.lineTo(x - size / 2, y + height / 2);\n                ctx.lineTo(x + size / 2, y + height / 2);\n            }\n        },\n        cross = function (ctx, x, y, radius, shadow) {\n            // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2\n            var size = radius * Math.sqrt(Math.PI) / 2;\n            ctx.moveTo(x - size, y - size);\n            ctx.lineTo(x + size, y + size);\n            ctx.moveTo(x - size, y + size);\n            ctx.lineTo(x + size, y - size);\n        },\n        ellipse = function(ctx, x, y, radius, shadow, fill) {\n            if (!shadow) {\n                ctx.moveTo(x + radius, y);\n                ctx.arc(x, y, radius, 0, Math.PI * 2, false);\n            }\n        },\n        plus = function (ctx, x, y, radius, shadow) {\n            var size = radius * Math.sqrt(Math.PI / 2);\n            ctx.moveTo(x - size, y);\n            ctx.lineTo(x + size, y);\n            ctx.moveTo(x, y + size);\n            ctx.lineTo(x, y - size);\n        },\n        handlers = {\n            square: square,\n            rectangle: rectangle,\n            diamond: diamond,\n            triangle: triangle,\n            cross: cross,\n            ellipse: ellipse,\n            plus: plus\n        };\n\n    square.fill = true;\n    rectangle.fill = true;\n    diamond.fill = true;\n    triangle.fill = true;\n    ellipse.fill = true;\n\n    function init(plot) {\n        plot.drawSymbol = handlers;\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        name: 'symbols',\n        version: '1.0'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.threshold.js",
    "content": "/* Flot plugin for thresholding data.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe plugin supports these options:\n\n    series: {\n        threshold: {\n            below: number\n            color: colorspec\n        }\n    }\n\nIt can also be applied to a single series, like this:\n\n    $.plot( $(\"#placeholder\"), [{\n        data: [ ... ],\n        threshold: { ... }\n    }])\n\nAn array can be passed for multiple thresholding, like this:\n\n    threshold: [{\n        below: number1\n        color: color1\n    },{\n        below: number2\n        color: color2\n    }]\n\nThese multiple threshold objects can be passed in any order since they are\nsorted by the processing function.\n\nThe data points below \"below\" are drawn with the specified color. This makes\nit easy to mark points below 0, e.g. for budget data.\n\nInternally, the plugin works by splitting the data into two series, above and\nbelow the threshold. The extra series below the threshold will have its label\ncleared and the special \"originSeries\" attribute set to the original series.\nYou may need to check for this in hover events.\n\n*/\n\n(function ($) {\n    var options = {\n        series: { threshold: null } // or { below: number, color: color spec}\n    };\n\n    function init(plot) {\n        function thresholdData(plot, s, datapoints, below, color) {\n            var ps = datapoints.pointsize, i, x, y, p, prevp,\n                thresholded = $.extend({}, s); // note: shallow copy\n\n            thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format };\n            thresholded.label = null;\n            thresholded.color = color;\n            thresholded.threshold = null;\n            thresholded.originSeries = s;\n            thresholded.data = [];\n\n            var origpoints = datapoints.points,\n                addCrossingPoints = s.lines.show;\n\n            var threspoints = [];\n            var newpoints = [];\n            var m;\n\n            for (i = 0; i < origpoints.length; i += ps) {\n                x = origpoints[i];\n                y = origpoints[i + 1];\n\n                prevp = p;\n                if (y < below) p = threspoints;\n                else p = newpoints;\n\n                if (addCrossingPoints && prevp !== p &&\n                    x !== null && i > 0 &&\n                    origpoints[i - ps] != null) {\n                    var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]);\n                    prevp.push(interx);\n                    prevp.push(below);\n                    for (m = 2; m < ps; ++m) {\n                        prevp.push(origpoints[i + m]);\n                    }\n\n                    p.push(null); // start new segment\n                    p.push(null);\n                    for (m = 2; m < ps; ++m) {\n                        p.push(origpoints[i + m]);\n                    }\n\n                    p.push(interx);\n                    p.push(below);\n                    for (m = 2; m < ps; ++m) {\n                        p.push(origpoints[i + m]);\n                    }\n                }\n\n                p.push(x);\n                p.push(y);\n                for (m = 2; m < ps; ++m) {\n                    p.push(origpoints[i + m]);\n                }\n            }\n\n            datapoints.points = newpoints;\n            thresholded.datapoints.points = threspoints;\n\n            if (thresholded.datapoints.points.length > 0) {\n                var origIndex = $.inArray(s, plot.getData());\n                // Insert newly-generated series right after original one (to prevent it from becoming top-most)\n                plot.getData().splice(origIndex + 1, 0, thresholded);\n            }\n\n            // FIXME: there are probably some edge cases left in bars\n        }\n\n        function processThresholds(plot, s, datapoints) {\n            if (!s.threshold) return;\n            if (s.threshold instanceof Array) {\n                s.threshold.sort(function(a, b) {\n                    return a.below - b.below;\n                });\n\n                $(s.threshold).each(function(i, th) {\n                    thresholdData(plot, s, datapoints, th.below, th.color);\n                });\n            } else {\n                thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color);\n            }\n        }\n\n        plot.hooks.processDatapoints.push(processThresholds);\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'threshold',\n        version: '1.2'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.time.js",
    "content": "/* Pretty handling of time axes.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nSet axis.mode to \"time\" to enable. See the section \"Time series data\" in\nAPI.txt for details.\n*/\n\n(function($) {\n    'use strict';\n\n    var options = {\n        xaxis: {\n            timezone: null, // \"browser\" for local to the client or timezone for timezone-js\n            timeformat: null, // format string to use\n            twelveHourClock: false, // 12 or 24 time in time mode\n            monthNames: null, // list of names of months\n            timeBase: 'seconds' // are the values in given in mircoseconds, milliseconds or seconds\n        },\n        yaxis: {\n            timeBase: 'seconds'\n        }\n    };\n\n    var floorInBase = $.plot.saturated.floorInBase;\n\n    // Method to provide microsecond support to Date like classes.\n    var CreateMicroSecondDate = function(DateType, microEpoch) {\n        var newDate = new DateType(microEpoch);\n\n        var oldSetTime = newDate.setTime.bind(newDate);\n        newDate.update = function(microEpoch) {\n            // Round epoch to 3 decimal accuracy\n            microEpoch = Math.round(microEpoch * 1000) / 1000;\n\n            oldSetTime(microEpoch);\n\n            // Microseconds are stored as integers\n            this.microseconds = 1000 * (microEpoch - Math.floor(microEpoch));\n        };\n\n        var oldGetTime = newDate.getTime.bind(newDate);\n        newDate.getTime = function () {\n            var microEpoch = oldGetTime() + this.microseconds / 1000;\n            return microEpoch;\n        };\n\n        newDate.setTime = function (microEpoch) {\n            this.update(microEpoch);\n        };\n\n        newDate.getMicroseconds = function() {\n            return this.microseconds;\n        };\n\n        newDate.setMicroseconds = function(microseconds) {\n            var epochWithoutMicroseconds = oldGetTime();\n            var newEpoch = epochWithoutMicroseconds + microseconds / 1000;\n            this.update(newEpoch);\n        };\n\n        newDate.setUTCMicroseconds = function(microseconds) { this.setMicroseconds(microseconds); }\n\n        newDate.getUTCMicroseconds = function() { return this.getMicroseconds(); }\n\n        newDate.microseconds = null;\n        newDate.microEpoch = null;\n        newDate.update(microEpoch);\n        return newDate;\n    }\n\n    // Returns a string with the date d formatted according to fmt.\n    // A subset of the Open Group's strftime format is supported.\n\n    function formatDate(d, fmt, monthNames, dayNames) {\n        if (typeof d.strftime === \"function\") {\n            return d.strftime(fmt);\n        }\n\n        var leftPad = function(n, pad) {\n            n = \"\" + n;\n            pad = \"\" + (pad == null ? \"0\" : pad);\n            return n.length === 1 ? pad + n : n;\n        };\n\n        var formatSubSeconds = function(milliseconds, microseconds, numberDecimalPlaces) {\n            var totalMicroseconds = milliseconds * 1000 + microseconds;\n            var formattedString;\n            if (numberDecimalPlaces < 6 && numberDecimalPlaces > 0) {\n                var magnitude = parseFloat('1e' + (numberDecimalPlaces - 6));\n                totalMicroseconds = Math.round(Math.round(totalMicroseconds * magnitude) / magnitude);\n                formattedString = ('00000' + totalMicroseconds).slice(-6, -(6 - numberDecimalPlaces));\n            } else {\n                totalMicroseconds = Math.round(totalMicroseconds)\n                formattedString = ('00000' + totalMicroseconds).slice(-6);\n            }\n            return formattedString;\n        };\n\n        var r = [];\n        var escape = false;\n        var hours = d.getHours();\n        var isAM = hours < 12;\n\n        if (!monthNames) {\n            monthNames = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n        }\n\n        if (!dayNames) {\n            dayNames = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n        }\n\n        var hours12;\n        if (hours > 12) {\n            hours12 = hours - 12;\n        } else if (hours === 0) {\n            hours12 = 12;\n        } else {\n            hours12 = hours;\n        }\n\n        var decimals = -1;\n        for (var i = 0; i < fmt.length; ++i) {\n            var c = fmt.charAt(i);\n\n            if (!isNaN(Number(c)) && Number(c) > 0) {\n                decimals = Number(c);\n            } else if (escape) {\n                switch (c) {\n                    case 'a': c = \"\" + dayNames[d.getDay()]; break;\n                    case 'b': c = \"\" + monthNames[d.getMonth()]; break;\n                    case 'd': c = leftPad(d.getDate()); break;\n                    case 'e': c = leftPad(d.getDate(), \" \"); break;\n                    case 'h': // For back-compat with 0.7; remove in 1.0\n                    case 'H': c = leftPad(hours); break;\n                    case 'I': c = leftPad(hours12); break;\n                    case 'l': c = leftPad(hours12, \" \"); break;\n                    case 'm': c = leftPad(d.getMonth() + 1); break;\n                    case 'M': c = leftPad(d.getMinutes()); break;\n                    // quarters not in Open Group's strftime specification\n                    case 'q':\n                        c = \"\" + (Math.floor(d.getMonth() / 3) + 1); break;\n                    case 'S': c = leftPad(d.getSeconds()); break;\n                    case 's': c = \"\" + formatSubSeconds(d.getMilliseconds(), d.getMicroseconds(), decimals); break;\n                    case 'y': c = leftPad(d.getFullYear() % 100); break;\n                    case 'Y': c = \"\" + d.getFullYear(); break;\n                    case 'p': c = (isAM) ? (\"\" + \"am\") : (\"\" + \"pm\"); break;\n                    case 'P': c = (isAM) ? (\"\" + \"AM\") : (\"\" + \"PM\"); break;\n                    case 'w': c = \"\" + d.getDay(); break;\n                }\n                r.push(c);\n                escape = false;\n            } else {\n                if (c === \"%\") {\n                    escape = true;\n                } else {\n                    r.push(c);\n                }\n            }\n        }\n\n        return r.join(\"\");\n    }\n\n    // To have a consistent view of time-based data independent of which time\n    // zone the client happens to be in we need a date-like object independent\n    // of time zones.  This is done through a wrapper that only calls the UTC\n    // versions of the accessor methods.\n\n    function makeUtcWrapper(d) {\n        function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {\n            sourceObj[sourceMethod] = function() {\n                return targetObj[targetMethod].apply(targetObj, arguments);\n            };\n        }\n\n        var utc = {\n            date: d\n        };\n\n        // support strftime, if found\n        if (d.strftime !== undefined) {\n            addProxyMethod(utc, \"strftime\", d, \"strftime\");\n        }\n\n        addProxyMethod(utc, \"getTime\", d, \"getTime\");\n        addProxyMethod(utc, \"setTime\", d, \"setTime\");\n\n        var props = [\"Date\", \"Day\", \"FullYear\", \"Hours\", \"Minutes\", \"Month\", \"Seconds\", \"Milliseconds\", \"Microseconds\"];\n\n        for (var p = 0; p < props.length; p++) {\n            addProxyMethod(utc, \"get\" + props[p], d, \"getUTC\" + props[p]);\n            addProxyMethod(utc, \"set\" + props[p], d, \"setUTC\" + props[p]);\n        }\n\n        return utc;\n    }\n\n    // select time zone strategy.  This returns a date-like object tied to the\n    // desired timezone\n    function dateGenerator(ts, opts) {\n        var maxDateValue = 8640000000000000;\n\n        if (opts && opts.timeBase === 'seconds') {\n            ts *= 1000;\n        } else if (opts.timeBase === 'microseconds') {\n            ts /= 1000;\n        }\n\n        if (ts > maxDateValue) {\n            ts = maxDateValue;\n        } else if (ts < -maxDateValue) {\n            ts = -maxDateValue;\n        }\n\n        if (opts.timezone === \"browser\") {\n            return CreateMicroSecondDate(Date, ts);\n        } else if (!opts.timezone || opts.timezone === \"utc\") {\n            return makeUtcWrapper(CreateMicroSecondDate(Date, ts));\n        } else if (typeof timezoneJS !== \"undefined\" && typeof timezoneJS.Date !== \"undefined\") {\n            var d = CreateMicroSecondDate(timezoneJS.Date, ts);\n            // timezone-js is fickle, so be sure to set the time zone before\n            // setting the time.\n            d.setTimezone(opts.timezone);\n            d.setTime(ts);\n            return d;\n        } else {\n            return makeUtcWrapper(CreateMicroSecondDate(Date, ts));\n        }\n    }\n\n    // map of app. size of time units in seconds\n    var timeUnitSizeSeconds = {\n        \"microsecond\": 0.000001,\n        \"millisecond\": 0.001,\n        \"second\": 1,\n        \"minute\": 60,\n        \"hour\": 60 * 60,\n        \"day\": 24 * 60 * 60,\n        \"month\": 30 * 24 * 60 * 60,\n        \"quarter\": 3 * 30 * 24 * 60 * 60,\n        \"year\": 365.2425 * 24 * 60 * 60\n    };\n\n    // map of app. size of time units in milliseconds\n    var timeUnitSizeMilliseconds = {\n        \"microsecond\": 0.001,\n        \"millisecond\": 1,\n        \"second\": 1000,\n        \"minute\": 60 * 1000,\n        \"hour\": 60 * 60 * 1000,\n        \"day\": 24 * 60 * 60 * 1000,\n        \"month\": 30 * 24 * 60 * 60 * 1000,\n        \"quarter\": 3 * 30 * 24 * 60 * 60 * 1000,\n        \"year\": 365.2425 * 24 * 60 * 60 * 1000\n    };\n\n    // map of app. size of time units in microseconds\n    var timeUnitSizeMicroseconds = {\n        \"microsecond\": 1,\n        \"millisecond\": 1000,\n        \"second\": 1000000,\n        \"minute\": 60 * 1000000,\n        \"hour\": 60 * 60 * 1000000,\n        \"day\": 24 * 60 * 60 * 1000000,\n        \"month\": 30 * 24 * 60 * 60 * 1000000,\n        \"quarter\": 3 * 30 * 24 * 60 * 60 * 1000000,\n        \"year\": 365.2425 * 24 * 60 * 60 * 1000000\n    };\n\n    // the allowed tick sizes, after 1 year we use\n    // an integer algorithm\n\n    var baseSpec = [\n        [1, \"microsecond\"], [2, \"microsecond\"], [5, \"microsecond\"], [10, \"microsecond\"],\n        [25, \"microsecond\"], [50, \"microsecond\"], [100, \"microsecond\"], [250, \"microsecond\"], [500, \"microsecond\"],\n        [1, \"millisecond\"], [2, \"millisecond\"], [5, \"millisecond\"], [10, \"millisecond\"],\n        [25, \"millisecond\"], [50, \"millisecond\"], [100, \"millisecond\"], [250, \"millisecond\"], [500, \"millisecond\"],\n        [1, \"second\"], [2, \"second\"], [5, \"second\"], [10, \"second\"],\n        [30, \"second\"],\n        [1, \"minute\"], [2, \"minute\"], [5, \"minute\"], [10, \"minute\"],\n        [30, \"minute\"],\n        [1, \"hour\"], [2, \"hour\"], [4, \"hour\"],\n        [8, \"hour\"], [12, \"hour\"],\n        [1, \"day\"], [2, \"day\"], [3, \"day\"],\n        [0.25, \"month\"], [0.5, \"month\"], [1, \"month\"],\n        [2, \"month\"]\n    ];\n\n    // we don't know which variant(s) we'll need yet, but generating both is\n    // cheap\n\n    var specMonths = baseSpec.concat([[3, \"month\"], [6, \"month\"],\n        [1, \"year\"]]);\n    var specQuarters = baseSpec.concat([[1, \"quarter\"], [2, \"quarter\"],\n        [1, \"year\"]]);\n\n    function dateTickGenerator(axis) {\n        var opts = axis.options,\n            ticks = [],\n            d = dateGenerator(axis.min, opts),\n            minSize = 0;\n\n        // make quarter use a possibility if quarters are\n        // mentioned in either of these options\n        var spec = (opts.tickSize && opts.tickSize[1] ===\n            \"quarter\") ||\n            (opts.minTickSize && opts.minTickSize[1] ===\n            \"quarter\") ? specQuarters : specMonths;\n\n        var timeUnitSize;\n        if (opts.timeBase === 'seconds') {\n            timeUnitSize = timeUnitSizeSeconds;\n        } else if (opts.timeBase === 'microseconds') {\n            timeUnitSize = timeUnitSizeMicroseconds;\n        } else {\n            timeUnitSize = timeUnitSizeMilliseconds;\n        }\n\n        if (opts.minTickSize !== null && opts.minTickSize !== undefined) {\n            if (typeof opts.tickSize === \"number\") {\n                minSize = opts.tickSize;\n            } else {\n                minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];\n            }\n        }\n\n        for (var i = 0; i < spec.length - 1; ++i) {\n            if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] +\n                spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 &&\n                spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {\n                break;\n            }\n        }\n\n        var size = spec[i][0];\n        var unit = spec[i][1];\n        // special-case the possibility of several years\n        if (unit === \"year\") {\n            // if given a minTickSize in years, just use it,\n            // ensuring that it's an integer\n\n            if (opts.minTickSize !== null && opts.minTickSize !== undefined && opts.minTickSize[1] === \"year\") {\n                size = Math.floor(opts.minTickSize[0]);\n            } else {\n                var magn = parseFloat('1e' + Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));\n                var norm = (axis.delta / timeUnitSize.year) / magn;\n\n                if (norm < 1.5) {\n                    size = 1;\n                } else if (norm < 3) {\n                    size = 2;\n                } else if (norm < 7.5) {\n                    size = 5;\n                } else {\n                    size = 10;\n                }\n\n                size *= magn;\n            }\n\n            // minimum size for years is 1\n\n            if (size < 1) {\n                size = 1;\n            }\n        }\n\n        axis.tickSize = opts.tickSize || [size, unit];\n        var tickSize = axis.tickSize[0];\n        unit = axis.tickSize[1];\n\n        var step = tickSize * timeUnitSize[unit];\n\n        if (unit === \"microsecond\") {\n            d.setMicroseconds(floorInBase(d.getMicroseconds(), tickSize));\n        } else if (unit === \"millisecond\") {\n            d.setMilliseconds(floorInBase(d.getMilliseconds(), tickSize));\n        } else if (unit === \"second\") {\n            d.setSeconds(floorInBase(d.getSeconds(), tickSize));\n        } else if (unit === \"minute\") {\n            d.setMinutes(floorInBase(d.getMinutes(), tickSize));\n        } else if (unit === \"hour\") {\n            d.setHours(floorInBase(d.getHours(), tickSize));\n        } else if (unit === \"month\") {\n            d.setMonth(floorInBase(d.getMonth(), tickSize));\n        } else if (unit === \"quarter\") {\n            d.setMonth(3 * floorInBase(d.getMonth() / 3,\n                tickSize));\n        } else if (unit === \"year\") {\n            d.setFullYear(floorInBase(d.getFullYear(), tickSize));\n        }\n\n        // reset smaller components\n\n        if (step >= timeUnitSize.millisecond) {\n            d.setMicroseconds(0);\n        }\n        if (step >= timeUnitSize.second) {\n            d.setMilliseconds(0);\n        }\n        if (step >= timeUnitSize.minute) {\n            d.setSeconds(0);\n        }\n        if (step >= timeUnitSize.hour) {\n            d.setMinutes(0);\n        }\n        if (step >= timeUnitSize.day) {\n            d.setHours(0);\n        }\n        if (step >= timeUnitSize.day * 4) {\n            d.setDate(1);\n        }\n        if (step >= timeUnitSize.month * 2) {\n            d.setMonth(floorInBase(d.getMonth(), 3));\n        }\n        if (step >= timeUnitSize.quarter * 2) {\n            d.setMonth(floorInBase(d.getMonth(), 6));\n        }\n        if (step >= timeUnitSize.year) {\n            d.setMonth(0);\n        }\n\n        var carry = 0;\n        var v = Number.NaN;\n        var v1000;\n        var prev;\n        do {\n            prev = v;\n            v1000 = d.getTime();\n            if (opts && opts.timeBase === 'seconds') {\n                v = v1000 / 1000;\n            } else if (opts && opts.timeBase === 'microseconds') {\n                v = v1000 * 1000;\n            } else {\n                v = v1000;\n            }\n\n            ticks.push(v);\n\n            if (unit === \"month\" || unit === \"quarter\") {\n                if (tickSize < 1) {\n                    // a bit complicated - we'll divide the\n                    // month/quarter up but we need to take\n                    // care of fractions so we don't end up in\n                    // the middle of a day\n                    d.setDate(1);\n                    var start = d.getTime();\n                    d.setMonth(d.getMonth() +\n                        (unit === \"quarter\" ? 3 : 1));\n                    var end = d.getTime();\n                    d.setTime((v + carry * timeUnitSize.hour + (end - start) * tickSize));\n                    carry = d.getHours();\n                    d.setHours(0);\n                } else {\n                    d.setMonth(d.getMonth() +\n                        tickSize * (unit === \"quarter\" ? 3 : 1));\n                }\n            } else if (unit === \"year\") {\n                d.setFullYear(d.getFullYear() + tickSize);\n            } else {\n                if (opts.timeBase === 'seconds') {\n                    d.setTime((v + step) * 1000);\n                } else if (opts.timeBase === 'microseconds') {\n                    d.setTime((v + step) / 1000);\n                } else {\n                    d.setTime(v + step);\n                }\n            }\n        } while (v < axis.max && v !== prev);\n\n        return ticks;\n    };\n\n    function init(plot) {\n        plot.hooks.processOptions.push(function (plot) {\n            $.each(plot.getAxes(), function(axisName, axis) {\n                var opts = axis.options;\n                if (opts.mode === \"time\") {\n                    axis.tickGenerator = dateTickGenerator;\n\n                    // if a tick formatter is already provided do not overwrite it\n                    if ('tickFormatter' in opts && typeof opts.tickFormatter === 'function') return;\n\n                    axis.tickFormatter = function (v, axis) {\n                        var d = dateGenerator(v, axis.options);\n\n                        // first check global format\n                        if (opts.timeformat != null) {\n                            return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);\n                        }\n\n                        // possibly use quarters if quarters are mentioned in\n                        // any of these places\n                        var useQuarters = (axis.options.tickSize &&\n                                axis.options.tickSize[1] === \"quarter\") ||\n                            (axis.options.minTickSize &&\n                                axis.options.minTickSize[1] === \"quarter\");\n\n                        var timeUnitSize;\n                        if (opts.timeBase === 'seconds') {\n                            timeUnitSize = timeUnitSizeSeconds;\n                        } else if (opts.timeBase === 'microseconds') {\n                            timeUnitSize = timeUnitSizeMicroseconds;\n                        } else {\n                            timeUnitSize = timeUnitSizeMilliseconds;\n                        }\n\n                        var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];\n                        var span = axis.max - axis.min;\n                        var suffix = (opts.twelveHourClock) ? \" %p\" : \"\";\n                        var hourCode = (opts.twelveHourClock) ? \"%I\" : \"%H\";\n                        var factor;\n                        var fmt;\n\n                        if (opts.timeBase === 'seconds') {\n                            factor = 1;\n                        } else if (opts.timeBase === 'microseconds') {\n                            factor = 1000000\n                        } else {\n                            factor = 1000;\n                        }\n\n                        if (t < timeUnitSize.second) {\n                            var decimals = -Math.floor(Math.log10(t / factor))\n\n                            // the two-and-halves require an additional decimal\n                            if (String(t).indexOf('25') > -1) {\n                                decimals++;\n                            }\n\n                            fmt = \"%S.%\" + decimals + \"s\";\n                        } else\n                        if (t < timeUnitSize.minute) {\n                            fmt = hourCode + \":%M:%S\" + suffix;\n                        } else if (t < timeUnitSize.day) {\n                            if (span < 2 * timeUnitSize.day) {\n                                fmt = hourCode + \":%M\" + suffix;\n                            } else {\n                                fmt = \"%b %d \" + hourCode + \":%M\" + suffix;\n                            }\n                        } else if (t < timeUnitSize.month) {\n                            fmt = \"%b %d\";\n                        } else if ((useQuarters && t < timeUnitSize.quarter) ||\n                            (!useQuarters && t < timeUnitSize.year)) {\n                            if (span < timeUnitSize.year) {\n                                fmt = \"%b\";\n                            } else {\n                                fmt = \"%b %Y\";\n                            }\n                        } else if (useQuarters && t < timeUnitSize.year) {\n                            if (span < timeUnitSize.year) {\n                                fmt = \"Q%q\";\n                            } else {\n                                fmt = \"Q%q %Y\";\n                            }\n                        } else {\n                            fmt = \"%Y\";\n                        }\n\n                        var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);\n\n                        return rt;\n                    };\n                }\n            });\n        });\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'time',\n        version: '1.0'\n    });\n\n    // Time-axis support used to be in Flot core, which exposed the\n    // formatDate function on the plot object.  Various plugins depend\n    // on the function, so we need to re-expose it here.\n\n    $.plot.formatDate = formatDate;\n    $.plot.dateGenerator = dateGenerator;\n    $.plot.dateTickGenerator = dateTickGenerator;\n    $.plot.makeUtcWrapper = makeUtcWrapper;\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.touch.js",
    "content": "\n/* global jQuery */\n\n(function($) {\n    'use strict';\n\n    var options = {\n        propagateSupportedGesture: false\n    };\n\n    function init(plot) {\n        plot.hooks.processOptions.push(initTouchNavigation);\n    }\n\n    function initTouchNavigation(plot, options) {\n        var gestureState = {\n                twoTouches: false,\n                currentTapStart: { x: 0, y: 0 },\n                currentTapEnd: { x: 0, y: 0 },\n                prevTap: { x: 0, y: 0 },\n                currentTap: { x: 0, y: 0 },\n                interceptedLongTap: false,\n                isUnsupportedGesture: false,\n                prevTapTime: null,\n                tapStartTime: null,\n                longTapTriggerId: null\n            },\n            maxDistanceBetweenTaps = 20,\n            maxIntervalBetweenTaps = 500,\n            maxLongTapDistance = 20,\n            minLongTapDuration = 1500,\n            pressedTapDuration = 125,\n            mainEventHolder;\n\n        function interpretGestures(e) {\n            var o = plot.getOptions();\n\n            if (!o.pan.active && !o.zoom.active) {\n                return;\n            }\n\n            updateOnMultipleTouches(e);\n            mainEventHolder.dispatchEvent(new CustomEvent('touchevent', { detail: e }));\n\n            if (isPinchEvent(e)) {\n                executeAction(e, 'pinch');\n            } else {\n                executeAction(e, 'pan');\n                if (!wasPinchEvent(e)) {\n                    if (isDoubleTap(e)) {\n                        executeAction(e, 'doubleTap');\n                    }\n                    executeAction(e, 'tap');\n                    executeAction(e, 'longTap');\n                }\n            }\n        }\n\n        function executeAction(e, gesture) {\n            switch (gesture) {\n                case 'pan':\n                    pan[e.type](e);\n                    break;\n                case 'pinch':\n                    pinch[e.type](e);\n                    break;\n                case 'doubleTap':\n                    doubleTap.onDoubleTap(e);\n                    break;\n                case 'longTap':\n                    longTap[e.type](e);\n                    break;\n                case 'tap':\n                    tap[e.type](e);\n                    break;\n            }\n        }\n\n        function bindEvents(plot, eventHolder) {\n            mainEventHolder = eventHolder[0];\n            eventHolder[0].addEventListener('touchstart', interpretGestures, false);\n            eventHolder[0].addEventListener('touchmove', interpretGestures, false);\n            eventHolder[0].addEventListener('touchend', interpretGestures, false);\n        }\n\n        function shutdown(plot, eventHolder) {\n            eventHolder[0].removeEventListener('touchstart', interpretGestures);\n            eventHolder[0].removeEventListener('touchmove', interpretGestures);\n            eventHolder[0].removeEventListener('touchend', interpretGestures);\n            if (gestureState.longTapTriggerId) {\n                clearTimeout(gestureState.longTapTriggerId);\n                gestureState.longTapTriggerId = null;\n            }\n        }\n\n        var pan = {\n            touchstart: function(e) {\n                updatePrevForDoubleTap();\n                updateCurrentForDoubleTap(e);\n                updateStateForLongTapStart(e);\n\n                mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));\n            },\n\n            touchmove: function(e) {\n                preventEventBehaviors(e);\n\n                updateCurrentForDoubleTap(e);\n                updateStateForLongTapEnd(e);\n\n                if (!gestureState.isUnsupportedGesture) {\n                    mainEventHolder.dispatchEvent(new CustomEvent('pandrag', { detail: e }));\n                }\n            },\n\n            touchend: function(e) {\n                preventEventBehaviors(e);\n\n                if (wasPinchEvent(e)) {\n                    mainEventHolder.dispatchEvent(new CustomEvent('pinchend', { detail: e }));\n                    mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));\n                } else if (noTouchActive(e)) {\n                    mainEventHolder.dispatchEvent(new CustomEvent('panend', { detail: e }));\n                }\n            }\n        };\n\n        var pinch = {\n            touchstart: function(e) {\n                mainEventHolder.dispatchEvent(new CustomEvent('pinchstart', { detail: e }));\n            },\n\n            touchmove: function(e) {\n                preventEventBehaviors(e);\n                gestureState.twoTouches = isPinchEvent(e);\n                if (!gestureState.isUnsupportedGesture) {\n                    mainEventHolder.dispatchEvent(new CustomEvent('pinchdrag', { detail: e }));\n                }\n            },\n\n            touchend: function(e) {\n                preventEventBehaviors(e);\n            }\n        };\n\n        var doubleTap = {\n            onDoubleTap: function(e) {\n                preventEventBehaviors(e);\n                mainEventHolder.dispatchEvent(new CustomEvent('doubletap', { detail: e }));\n            }\n        };\n\n        var longTap = {\n            touchstart: function(e) {\n                longTap.waitForLongTap(e);\n            },\n\n            touchmove: function(e) {\n            },\n\n            touchend: function(e) {\n                if (gestureState.longTapTriggerId) {\n                    clearTimeout(gestureState.longTapTriggerId);\n                    gestureState.longTapTriggerId = null;\n                }\n            },\n\n            isLongTap: function(e) {\n                var currentTime = new Date().getTime(),\n                    tapDuration = currentTime - gestureState.tapStartTime;\n                if (tapDuration >= minLongTapDuration && !gestureState.interceptedLongTap) {\n                    if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {\n                        gestureState.interceptedLongTap = true;\n                        return true;\n                    }\n                }\n                return false;\n            },\n\n            waitForLongTap: function(e) {\n                var longTapTrigger = function() {\n                    if (longTap.isLongTap(e)) {\n                        mainEventHolder.dispatchEvent(new CustomEvent('longtap', { detail: e }));\n                    }\n                    gestureState.longTapTriggerId = null;\n                };\n                if (!gestureState.longTapTriggerId) {\n                    gestureState.longTapTriggerId = setTimeout(longTapTrigger, minLongTapDuration);\n                }\n            }\n        };\n\n        var tap = {\n            touchstart: function(e) {\n                gestureState.tapStartTime = new Date().getTime();\n            },\n\n            touchmove: function(e) {\n            },\n\n            touchend: function(e) {\n                if (tap.isTap(e)) {\n                    mainEventHolder.dispatchEvent(new CustomEvent('tap', { detail: e }));\n                    preventEventBehaviors(e);\n                }\n            },\n\n            isTap: function(e) {\n                var currentTime = new Date().getTime(),\n                    tapDuration = currentTime - gestureState.tapStartTime;\n                if (tapDuration <= pressedTapDuration) {\n                    if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n        };\n\n        if (options.pan.enableTouch === true || options.zoom.enableTouch) {\n            plot.hooks.bindEvents.push(bindEvents);\n            plot.hooks.shutdown.push(shutdown);\n        };\n\n        function updatePrevForDoubleTap() {\n            gestureState.prevTap = {\n                x: gestureState.currentTap.x,\n                y: gestureState.currentTap.y\n            };\n        };\n\n        function updateCurrentForDoubleTap(e) {\n            gestureState.currentTap = {\n                x: e.touches[0].pageX,\n                y: e.touches[0].pageY\n            };\n        }\n\n        function updateStateForLongTapStart(e) {\n            gestureState.tapStartTime = new Date().getTime();\n            gestureState.interceptedLongTap = false;\n            gestureState.currentTapStart = {\n                x: e.touches[0].pageX,\n                y: e.touches[0].pageY\n            };\n            gestureState.currentTapEnd = {\n                x: e.touches[0].pageX,\n                y: e.touches[0].pageY\n            };\n        };\n\n        function updateStateForLongTapEnd(e) {\n            gestureState.currentTapEnd = {\n                x: e.touches[0].pageX,\n                y: e.touches[0].pageY\n            };\n        };\n\n        function isDoubleTap(e) {\n            var currentTime = new Date().getTime(),\n                intervalBetweenTaps = currentTime - gestureState.prevTapTime;\n\n            if (intervalBetweenTaps >= 0 && intervalBetweenTaps < maxIntervalBetweenTaps) {\n                if (distance(gestureState.prevTap.x, gestureState.prevTap.y, gestureState.currentTap.x, gestureState.currentTap.y) < maxDistanceBetweenTaps) {\n                    e.firstTouch = gestureState.prevTap;\n                    e.secondTouch = gestureState.currentTap;\n                    return true;\n                }\n            }\n            gestureState.prevTapTime = currentTime;\n            return false;\n        }\n\n        function preventEventBehaviors(e) {\n            if (!gestureState.isUnsupportedGesture) {\n                e.preventDefault();\n                if (!plot.getOptions().propagateSupportedGesture) {\n                    e.stopPropagation();\n                }\n            }\n        }\n\n        function distance(x1, y1, x2, y2) {\n            return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));\n        }\n\n        function noTouchActive(e) {\n            return (e.touches && e.touches.length === 0);\n        }\n\n        function wasPinchEvent(e) {\n            return (gestureState.twoTouches && e.touches.length === 1);\n        }\n\n        function updateOnMultipleTouches(e) {\n            if (e.touches.length >= 3) {\n                gestureState.isUnsupportedGesture = true;\n            } else {\n                gestureState.isUnsupportedGesture = false;\n            }\n        }\n\n        function isPinchEvent(e) {\n            if (e.touches && e.touches.length >= 2) {\n                if (e.touches[0].target === plot.getEventHolder() &&\n                    e.touches[1].target === plot.getEventHolder()) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'navigateTouch',\n        version: '0.3'\n    });\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.touchNavigate.js",
    "content": "/* global jQuery */\n\n(function($) {\n    'use strict';\n\n    var options = {\n        zoom: {\n            enableTouch: false\n        },\n        pan: {\n            enableTouch: false,\n            touchMode: 'manual'\n        },\n        recenter: {\n            enableTouch: true\n        }\n    };\n\n    var ZOOM_DISTANCE_MARGIN = $.plot.uiConstants.ZOOM_DISTANCE_MARGIN;\n\n    function init(plot) {\n        plot.hooks.processOptions.push(initTouchNavigation);\n    }\n\n    function initTouchNavigation(plot, options) {\n        var gestureState = {\n                zoomEnable: false,\n                prevDistance: null,\n                prevTapTime: 0,\n                prevPanPosition: { x: 0, y: 0 },\n                prevTapPosition: { x: 0, y: 0 }\n            },\n            navigationState = {\n                prevTouchedAxis: 'none',\n                currentTouchedAxis: 'none',\n                touchedAxis: null,\n                navigationConstraint: 'unconstrained',\n                initialState: null\n            },\n            useManualPan = options.pan.interactive && options.pan.touchMode === 'manual',\n            smartPanLock = options.pan.touchMode === 'smartLock',\n            useSmartPan = options.pan.interactive && (smartPanLock || options.pan.touchMode === 'smart'),\n            pan, pinch, doubleTap;\n\n        function bindEvents(plot, eventHolder) {\n            var o = plot.getOptions();\n\n            if (o.zoom.interactive && o.zoom.enableTouch) {\n                eventHolder[0].addEventListener('pinchstart', pinch.start, false);\n                eventHolder[0].addEventListener('pinchdrag', pinch.drag, false);\n                eventHolder[0].addEventListener('pinchend', pinch.end, false);\n            }\n\n            if (o.pan.interactive && o.pan.enableTouch) {\n                eventHolder[0].addEventListener('panstart', pan.start, false);\n                eventHolder[0].addEventListener('pandrag', pan.drag, false);\n                eventHolder[0].addEventListener('panend', pan.end, false);\n            }\n\n            if ((o.recenter.interactive && o.recenter.enableTouch)) {\n                eventHolder[0].addEventListener('doubletap', doubleTap.recenterPlot, false);\n            }\n        }\n\n        function shutdown(plot, eventHolder) {\n            eventHolder[0].removeEventListener('panstart', pan.start);\n            eventHolder[0].removeEventListener('pandrag', pan.drag);\n            eventHolder[0].removeEventListener('panend', pan.end);\n            eventHolder[0].removeEventListener('pinchstart', pinch.start);\n            eventHolder[0].removeEventListener('pinchdrag', pinch.drag);\n            eventHolder[0].removeEventListener('pinchend', pinch.end);\n            eventHolder[0].removeEventListener('doubletap', doubleTap.recenterPlot);\n        }\n\n        pan = {\n            start: function(e) {\n                presetNavigationState(e, 'pan', gestureState);\n                updateData(e, 'pan', gestureState, navigationState);\n\n                if (useSmartPan) {\n                    var point = getPoint(e, 'pan');\n                    navigationState.initialState = plot.navigationState(point.x, point.y);\n                }\n            },\n\n            drag: function(e) {\n                presetNavigationState(e, 'pan', gestureState);\n\n                if (useSmartPan) {\n                    var point = getPoint(e, 'pan');\n                    plot.smartPan({\n                        x: navigationState.initialState.startPageX - point.x,\n                        y: navigationState.initialState.startPageY - point.y\n                    }, navigationState.initialState, navigationState.touchedAxis, false, smartPanLock);\n                } else if (useManualPan) {\n                    plot.pan({\n                        left: -delta(e, 'pan', gestureState).x,\n                        top: -delta(e, 'pan', gestureState).y,\n                        axes: navigationState.touchedAxis\n                    });\n                    updatePrevPanPosition(e, 'pan', gestureState, navigationState);\n                }\n            },\n\n            end: function(e) {\n                presetNavigationState(e, 'pan', gestureState);\n\n                if (useSmartPan) {\n                    plot.smartPan.end();\n                }\n\n                if (wasPinchEvent(e, gestureState)) {\n                    updateprevPanPosition(e, 'pan', gestureState, navigationState);\n                }\n            }\n        };\n\n        var pinchDragTimeout;\n        pinch = {\n            start: function(e) {\n                if (pinchDragTimeout) {\n                    clearTimeout(pinchDragTimeout);\n                    pinchDragTimeout = null;\n                }\n                presetNavigationState(e, 'pinch', gestureState);\n                setPrevDistance(e, gestureState);\n                updateData(e, 'pinch', gestureState, navigationState);\n            },\n\n            drag: function(e) {\n                if (pinchDragTimeout) {\n                    return;\n                }\n                pinchDragTimeout = setTimeout(function() {\n                    presetNavigationState(e, 'pinch', gestureState);\n                    plot.pan({\n                        left: -delta(e, 'pinch', gestureState).x,\n                        top: -delta(e, 'pinch', gestureState).y,\n                        axes: navigationState.touchedAxis\n                    });\n                    updatePrevPanPosition(e, 'pinch', gestureState, navigationState);\n\n                    var dist = pinchDistance(e);\n\n                    if (gestureState.zoomEnable || Math.abs(dist - gestureState.prevDistance) > ZOOM_DISTANCE_MARGIN) {\n                        zoomPlot(plot, e, gestureState, navigationState);\n\n                        //activate zoom mode\n                        gestureState.zoomEnable = true;\n                    }\n                    pinchDragTimeout = null;\n                }, 1000 / 60);\n            },\n\n            end: function(e) {\n                if (pinchDragTimeout) {\n                    clearTimeout(pinchDragTimeout);\n                    pinchDragTimeout = null;\n                }\n                presetNavigationState(e, 'pinch', gestureState);\n                gestureState.prevDistance = null;\n            }\n        };\n\n        doubleTap = {\n            recenterPlot: function(e) {\n                if (e && e.detail && e.detail.type === 'touchstart') {\n                    // only do not recenter for touch start;\n                    recenterPlotOnDoubleTap(plot, e, gestureState, navigationState);\n                }\n            }\n        };\n\n        if (options.pan.enableTouch === true || options.zoom.enableTouch === true) {\n            plot.hooks.bindEvents.push(bindEvents);\n            plot.hooks.shutdown.push(shutdown);\n        }\n\n        function presetNavigationState(e, gesture, gestureState) {\n            navigationState.touchedAxis = getAxis(plot, e, gesture, navigationState);\n            if (noAxisTouched(navigationState)) {\n                navigationState.navigationConstraint = 'unconstrained';\n            } else {\n                navigationState.navigationConstraint = 'axisConstrained';\n            }\n        }\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'navigateTouch',\n        version: '0.3'\n    });\n\n    function recenterPlotOnDoubleTap(plot, e, gestureState, navigationState) {\n        checkAxesForDoubleTap(plot, e, navigationState);\n        if ((navigationState.currentTouchedAxis === 'x' && navigationState.prevTouchedAxis === 'x') ||\n            (navigationState.currentTouchedAxis === 'y' && navigationState.prevTouchedAxis === 'y') ||\n            (navigationState.currentTouchedAxis === 'none' && navigationState.prevTouchedAxis === 'none')) {\n            var event;\n\n            plot.recenter({ axes: navigationState.touchedAxis });\n\n            if (navigationState.touchedAxis) {\n                event = new $.Event('re-center', { detail: { axisTouched: navigationState.touchedAxis } });\n            } else {\n                event = new $.Event('re-center', { detail: e });\n            }\n            plot.getPlaceholder().trigger(event);\n        }\n    }\n\n    function checkAxesForDoubleTap(plot, e, navigationState) {\n        var axis = plot.getTouchedAxis(e.detail.firstTouch.x, e.detail.firstTouch.y);\n        if (axis[0] !== undefined) {\n            navigationState.prevTouchedAxis = axis[0].direction;\n        }\n\n        axis = plot.getTouchedAxis(e.detail.secondTouch.x, e.detail.secondTouch.y);\n        if (axis[0] !== undefined) {\n            navigationState.touchedAxis = axis;\n            navigationState.currentTouchedAxis = axis[0].direction;\n        }\n\n        if (noAxisTouched(navigationState)) {\n            navigationState.touchedAxis = null;\n            navigationState.prevTouchedAxis = 'none';\n            navigationState.currentTouchedAxis = 'none';\n        }\n    }\n\n    function zoomPlot(plot, e, gestureState, navigationState) {\n        var offset = plot.offset(),\n            center = {\n                left: 0,\n                top: 0\n            },\n            zoomAmount = pinchDistance(e) / gestureState.prevDistance,\n            dist = pinchDistance(e);\n\n        center.left = getPoint(e, 'pinch').x - offset.left;\n        center.top = getPoint(e, 'pinch').y - offset.top;\n\n        // send the computed touched axis to the zoom function so that it only zooms on that one\n        plot.zoom({\n            center: center,\n            amount: zoomAmount,\n            axes: navigationState.touchedAxis\n        });\n        gestureState.prevDistance = dist;\n    }\n\n    function wasPinchEvent(e, gestureState) {\n        return (gestureState.zoomEnable && e.detail.touches.length === 1);\n    }\n\n    function getAxis(plot, e, gesture, navigationState) {\n        if (e.type === 'pinchstart') {\n            var axisTouch1 = plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);\n            var axisTouch2 = plot.getTouchedAxis(e.detail.touches[1].pageX, e.detail.touches[1].pageY);\n\n            if (axisTouch1.length === axisTouch2.length && axisTouch1.toString() === axisTouch2.toString()) {\n                return axisTouch1;\n            }\n        } else if (e.type === 'panstart') {\n            return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);\n        } else if (e.type === 'pinchend') {\n            //update axis since instead on pinch, a pan event is made\n            return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);\n        } else {\n            return navigationState.touchedAxis;\n        }\n    }\n\n    function noAxisTouched(navigationState) {\n        return (!navigationState.touchedAxis || navigationState.touchedAxis.length === 0);\n    }\n\n    function setPrevDistance(e, gestureState) {\n        gestureState.prevDistance = pinchDistance(e);\n    }\n\n    function updateData(e, gesture, gestureState, navigationState) {\n        var axisDir,\n            point = getPoint(e, gesture);\n\n        switch (navigationState.navigationConstraint) {\n            case 'unconstrained':\n                navigationState.touchedAxis = null;\n                gestureState.prevTapPosition = {\n                    x: gestureState.prevPanPosition.x,\n                    y: gestureState.prevPanPosition.y\n                };\n                gestureState.prevPanPosition = {\n                    x: point.x,\n                    y: point.y\n                };\n                break;\n            case 'axisConstrained':\n                axisDir = navigationState.touchedAxis[0].direction;\n                navigationState.currentTouchedAxis = axisDir;\n                gestureState.prevTapPosition[axisDir] = gestureState.prevPanPosition[axisDir];\n                gestureState.prevPanPosition[axisDir] = point[axisDir];\n                break;\n            default:\n                break;\n        }\n    }\n\n    function distance(x1, y1, x2, y2) {\n        return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));\n    }\n\n    function pinchDistance(e) {\n        var t1 = e.detail.touches[0],\n            t2 = e.detail.touches[1];\n        return distance(t1.pageX, t1.pageY, t2.pageX, t2.pageY);\n    }\n\n    function updatePrevPanPosition(e, gesture, gestureState, navigationState) {\n        var point = getPoint(e, gesture);\n\n        switch (navigationState.navigationConstraint) {\n            case 'unconstrained':\n                gestureState.prevPanPosition.x = point.x;\n                gestureState.prevPanPosition.y = point.y;\n                break;\n            case 'axisConstrained':\n                gestureState.prevPanPosition[navigationState.currentTouchedAxis] =\n                point[navigationState.currentTouchedAxis];\n                break;\n            default:\n                break;\n        }\n    }\n\n    function delta(e, gesture, gestureState) {\n        var point = getPoint(e, gesture);\n\n        return {\n            x: point.x - gestureState.prevPanPosition.x,\n            y: point.y - gestureState.prevPanPosition.y\n        }\n    }\n\n    function getPoint(e, gesture) {\n        if (gesture === 'pinch') {\n            return {\n                x: (e.detail.touches[0].pageX + e.detail.touches[1].pageX) / 2,\n                y: (e.detail.touches[0].pageY + e.detail.touches[1].pageY) / 2\n            }\n        } else {\n            return {\n                x: e.detail.touches[0].pageX,\n                y: e.detail.touches[0].pageY\n            }\n        }\n    }\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.flot.uiConstants.js",
    "content": "(function ($) {\n    'use strict';\n    $.plot.uiConstants = {\n        SNAPPING_CONSTANT: 20,\n        PANHINT_LENGTH_CONSTANT: 10,\n        MINOR_TICKS_COUNT_CONSTANT: 4,\n        TICK_LENGTH_CONSTANT: 10,\n        ZOOM_DISTANCE_MARGIN: 25\n    };\n})(jQuery);\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/flot/source/jquery.js",
    "content": "/*!\n * jQuery JavaScript Library v1.8.3\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2012 jQuery Foundation and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: Tue Nov 13 2012 08:20:33 GMT-0500 (Eastern Standard Time)\n */\n/* eslint-disable */\n(function( window, undefined ) {\nvar\n\t// A central reference to the root jQuery(document)\n\trootjQuery,\n\n\t// The deferred used on DOM ready\n\treadyList,\n\n\t// Use the correct document accordingly with window argument (sandbox)\n\tdocument = window.document,\n\tlocation = window.location,\n\tnavigator = window.navigator,\n\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$,\n\n\t// Save a reference to some core methods\n\tcore_push = Array.prototype.push,\n\tcore_slice = Array.prototype.slice,\n\tcore_indexOf = Array.prototype.indexOf,\n\tcore_toString = Object.prototype.toString,\n\tcore_hasOwn = Object.prototype.hasOwnProperty,\n\tcore_trim = String.prototype.trim,\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\treturn new jQuery.fn.init( selector, context, rootjQuery );\n\t},\n\n\t// Used for matching numbers\n\tcore_pnum = /[\\-+]?(?:\\d*\\.|)\\d+(?:[eE][\\-+]?\\d+|)/.source,\n\n\t// Used for detecting and trimming whitespace\n\tcore_rnotwhite = /\\S/,\n\tcore_rspace = /\\s+/,\n\n\t// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)\n\trtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\trquickExpr = /^(?:[^#<]*(<[\\w\\W]+>)[^>]*$|#([\\w\\-]*)$)/,\n\n\t// Match a standalone tag\n\trsingleTag = /^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/,\n\n\t// JSON RegExp\n\trvalidchars = /^[\\],:{}\\s]*$/,\n\trvalidbraces = /(?:^|:|,)(?:\\s*\\[)+/g,\n\trvalidescape = /\\\\(?:[\"\\\\\\/bfnrt]|u[\\da-fA-F]{4})/g,\n\trvalidtokens = /\"[^\"\\\\\\r\\n]*\"|true|false|null|-?(?:\\d\\d*\\.|)\\d+(?:[eE][\\-+]?\\d+|)/g,\n\n\t// Matches dashed string for camelizing\n\trmsPrefix = /^-ms-/,\n\trdashAlpha = /-([\\da-z])/gi,\n\n\t// Used by jQuery.camelCase as callback to replace()\n\tfcamelCase = function( all, letter ) {\n\t\treturn ( letter + \"\" ).toUpperCase();\n\t},\n\n\t// The ready event handler and self cleanup method\n\tDOMContentLoaded = function() {\n\t\tif ( document.addEventListener ) {\n\t\t\tdocument.removeEventListener( \"DOMContentLoaded\", DOMContentLoaded, false );\n\t\t\tjQuery.ready();\n\t\t} else if ( document.readyState === \"complete\" ) {\n\t\t\t// we're here because readyState === \"complete\" in oldIE\n\t\t\t// which is good enough for us to call the dom ready!\n\t\t\tdocument.detachEvent( \"onreadystatechange\", DOMContentLoaded );\n\t\t\tjQuery.ready();\n\t\t}\n\t},\n\n\t// [[Class]] -> type pairs\n\tclass2type = {};\n\njQuery.fn = jQuery.prototype = {\n\tconstructor: jQuery,\n\tinit: function( selector, context, rootjQuery ) {\n\t\tvar match, elem, ret, doc;\n\n\t\t// Handle $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle $(DOMElement)\n\t\tif ( selector.nodeType ) {\n\t\t\tthis.context = this[0] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector.charAt(0) === \"<\" && selector.charAt( selector.length - 1 ) === \">\" && selector.length >= 3 ) {\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && (match[1] || !context) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[1] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\n\t\t\t\t\tdoc = ( context && context.nodeType ? context.ownerDocument || context : document );\n\n\t\t\t\t\t// scripts is true for back-compat\n\t\t\t\t\tselector = jQuery.parseHTML( match[1], doc, true );\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tthis.attr.call( selector, context, true );\n\t\t\t\t\t}\n\n\t\t\t\t\treturn jQuery.merge( this, selector );\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[2] );\n\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE and Opera return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id !== match[2] ) {\n\t\t\t\t\t\t\treturn rootjQuery.find( selector );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Otherwise, we inject the element directly into the jQuery object\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t\tthis[0] = elem;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.context = document;\n\t\t\t\t\tthis.selector = selector;\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( jQuery.isFunction( selector ) ) {\n\t\t\treturn rootjQuery.ready( selector );\n\t\t}\n\n\t\tif ( selector.selector !== undefined ) {\n\t\t\tthis.selector = selector.selector;\n\t\t\tthis.context = selector.context;\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t},\n\n\t// Start with an empty selector\n\tselector: \"\",\n\n\t// The current version of jQuery being used\n\tjquery: \"1.8.3\",\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\t// The number of elements contained in the matched element set\n\tsize: function() {\n\t\treturn this.length;\n\t},\n\n\ttoArray: function() {\n\t\treturn core_slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\t\treturn num == null ?\n\n\t\t\t// Return a 'clean' array\n\t\t\tthis.toArray() :\n\n\t\t\t// Return just the object\n\t\t\t( num < 0 ? this[ this.length + num ] : this[ num ] );\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems, name, selector ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\n\t\tret.context = this.context;\n\n\t\tif ( name === \"find\" ) {\n\t\t\tret.selector = this.selector + ( this.selector ? \" \" : \"\" ) + selector;\n\t\t} else if ( name ) {\n\t\t\tret.selector = this.selector + \".\" + name + \"(\" + selector + \")\";\n\t\t}\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\t// (You can seed the arguments with an array of args, but this is\n\t// only used internally.)\n\teach: function( callback, args ) {\n\t\treturn jQuery.each( this, callback, args );\n\t},\n\n\tready: function( fn ) {\n\t\t// Add the callback\n\t\tjQuery.ready.promise().done( fn );\n\n\t\treturn this;\n\t},\n\n\teq: function( i ) {\n\t\ti = +i;\n\t\treturn i === -1 ?\n\t\t\tthis.slice( i ) :\n\t\t\tthis.slice( i, i + 1 );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( core_slice.apply( this, arguments ),\n\t\t\t\"slice\", core_slice.call(arguments).join(\",\") );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t}));\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor(null);\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: core_push,\n\tsort: [].sort,\n\tsplice: [].splice\n};\n\n// Give the init function the jQuery prototype for later instantiation\njQuery.fn.init.prototype = jQuery.fn;\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\t\ttarget = arguments[1] || {};\n\t\t// skip the boolean and the target\n\t\ti = 2;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\t// extend jQuery itself if only one argument is passed\n\tif ( length === i ) {\n\t\ttarget = this;\n\t\t--i;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\tnoConflict: function( deep ) {\n\t\tif ( window.$ === jQuery ) {\n\t\t\twindow.$ = _$;\n\t\t}\n\n\t\tif ( deep && window.jQuery === jQuery ) {\n\t\t\twindow.jQuery = _jQuery;\n\t\t}\n\n\t\treturn jQuery;\n\t},\n\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Hold (or release) the ready event\n\tholdReady: function( hold ) {\n\t\tif ( hold ) {\n\t\t\tjQuery.readyWait++;\n\t\t} else {\n\t\t\tjQuery.ready( true );\n\t\t}\n\t},\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).\n\t\tif ( !document.body ) {\n\t\t\treturn setTimeout( jQuery.ready, 1 );\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\n\t\t// Trigger any bound ready events\n\t\tif ( jQuery.fn.trigger ) {\n\t\t\tjQuery( document ).trigger(\"ready\").off(\"ready\");\n\t\t}\n\t},\n\n\t// See test/unit/core.js for details concerning isFunction.\n\t// Since version 1.3, DOM methods and functions like alert\n\t// aren't supported. They return false on IE (#2968).\n\tisFunction: function( obj ) {\n\t\treturn jQuery.type(obj) === \"function\";\n\t},\n\n\tisArray: Array.isArray || function( obj ) {\n\t\treturn jQuery.type(obj) === \"array\";\n\t},\n\n\tisWindow: function( obj ) {\n\t\treturn obj != null && obj == obj.window;\n\t},\n\n\tisNumeric: function( obj ) {\n\t\treturn !isNaN( parseFloat(obj) ) && isFinite( obj );\n\t},\n\n\ttype: function( obj ) {\n\t\treturn obj == null ?\n\t\t\tString( obj ) :\n\t\t\tclass2type[ core_toString.call(obj) ] || \"object\";\n\t},\n\n\tisPlainObject: function( obj ) {\n\t\t// Must be an Object.\n\t\t// Because of IE, we also have to check the presence of the constructor property.\n\t\t// Make sure that DOM nodes and window objects don't pass through, as well\n\t\tif ( !obj || jQuery.type(obj) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\t// Not own constructor property must be Object\n\t\t\tif ( obj.constructor &&\n\t\t\t\t!core_hasOwn.call(obj, \"constructor\") &&\n\t\t\t\t!core_hasOwn.call(obj.constructor.prototype, \"isPrototypeOf\") ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\t// IE8,9 Will throw exceptions on certain host objects #9897\n\t\t\treturn false;\n\t\t}\n\n\t\t// Own properties are enumerated firstly, so to speed up,\n\t\t// if last one is own, then all properties are own.\n\n\t\tvar key;\n\t\tfor ( key in obj ) {}\n\n\t\treturn key === undefined || core_hasOwn.call( obj, key );\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\t// data: string of html\n\t// context (optional): If specified, the fragment will be created in this context, defaults to document\n\t// scripts (optional): If true, will include scripts passed in the html string\n\tparseHTML: function( data, context, scripts ) {\n\t\tvar parsed;\n\t\tif ( !data || typeof data !== \"string\" ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( typeof context === \"boolean\" ) {\n\t\t\tscripts = context;\n\t\t\tcontext = 0;\n\t\t}\n\t\tcontext = context || document;\n\n\t\t// Single tag\n\t\tif ( (parsed = rsingleTag.exec( data )) ) {\n\t\t\treturn [ context.createElement( parsed[1] ) ];\n\t\t}\n\n\t\tparsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );\n\t\treturn jQuery.merge( [],\n\t\t\t(parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );\n\t},\n\n\tparseJSON: function( data ) {\n\t\tif ( !data || typeof data !== \"string\") {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Make sure leading/trailing whitespace is removed (IE can't handle it)\n\t\tdata = jQuery.trim( data );\n\n\t\t// Attempt to parse using the native JSON parser first\n\t\tif ( window.JSON && window.JSON.parse ) {\n\t\t\treturn window.JSON.parse( data );\n\t\t}\n\n\t\t// Make sure the incoming data is actual JSON\n\t\t// Logic borrowed from http://json.org/json2.js\n\t\tif ( rvalidchars.test( data.replace( rvalidescape, \"@\" )\n\t\t\t.replace( rvalidtokens, \"]\" )\n\t\t\t.replace( rvalidbraces, \"\")) ) {\n\n\t\t\treturn ( new Function( \"return \" + data ) )();\n\n\t\t}\n\t\tjQuery.error( \"Invalid JSON: \" + data );\n\t},\n\n\t// Cross-browser xml parsing\n\tparseXML: function( data ) {\n\t\tvar xml, tmp;\n\t\tif ( !data || typeof data !== \"string\" ) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tif ( window.DOMParser ) { // Standard\n\t\t\t\ttmp = new DOMParser();\n\t\t\t\txml = tmp.parseFromString( data , \"text/xml\" );\n\t\t\t} else { // IE\n\t\t\t\txml = new ActiveXObject( \"Microsoft.XMLDOM\" );\n\t\t\t\txml.async = \"false\";\n\t\t\t\txml.loadXML( data );\n\t\t\t}\n\t\t} catch( e ) {\n\t\t\txml = undefined;\n\t\t}\n\t\tif ( !xml || !xml.documentElement || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\t\tjQuery.error( \"Invalid XML: \" + data );\n\t\t}\n\t\treturn xml;\n\t},\n\n\tnoop: function() {},\n\n\t// Evaluates a script in a global context\n\t// Workarounds based on findings by Jim Driscoll\n\t// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context\n\tglobalEval: function( data ) {\n\t\tif ( data && core_rnotwhite.test( data ) ) {\n\t\t\t// We use execScript on Internet Explorer\n\t\t\t// We use an anonymous function so that context is window\n\t\t\t// rather than jQuery in Firefox\n\t\t\t( window.execScript || function( data ) {\n\t\t\t\twindow[ \"eval\" ].call( window, data );\n\t\t\t} )( data );\n\t\t}\n\t},\n\n\t// Convert dashed to camelCase; used by the css and data modules\n\t// Microsoft forgot to hump their vendor prefix (#9572)\n\tcamelCase: function( string ) {\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n\t},\n\n\tnodeName: function( elem, name ) {\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\t},\n\n\t// args is for internal usage only\n\teach: function( obj, callback, args ) {\n\t\tvar name,\n\t\t\ti = 0,\n\t\t\tlength = obj.length,\n\t\t\tisObj = length === undefined || jQuery.isFunction( obj );\n\n\t\tif ( args ) {\n\t\t\tif ( isObj ) {\n\t\t\t\tfor ( name in obj ) {\n\t\t\t\t\tif ( callback.apply( obj[ name ], args ) === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( ; i < length; ) {\n\t\t\t\t\tif ( callback.apply( obj[ i++ ], args ) === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// A special, fast, case for the most common use of each\n\t\t} else {\n\t\t\tif ( isObj ) {\n\t\t\t\tfor ( name in obj ) {\n\t\t\t\t\tif ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( ; i < length; ) {\n\t\t\t\t\tif ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t// Use native String.trim function wherever possible\n\ttrim: core_trim && !core_trim.call(\"\\uFEFF\\xA0\") ?\n\t\tfunction( text ) {\n\t\t\treturn text == null ?\n\t\t\t\t\"\" :\n\t\t\t\tcore_trim.call( text );\n\t\t} :\n\n\t\t// Otherwise use our own trimming functionality\n\t\tfunction( text ) {\n\t\t\treturn text == null ?\n\t\t\t\t\"\" :\n\t\t\t\t( text + \"\" ).replace( rtrim, \"\" );\n\t\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar type,\n\t\t\tret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\t// The window, strings (and functions) also have 'length'\n\t\t\t// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930\n\t\t\ttype = jQuery.type( arr );\n\n\t\t\tif ( arr.length == null || type === \"string\" || type === \"function\" || type === \"regexp\" || jQuery.isWindow( arr ) ) {\n\t\t\t\tcore_push.call( ret, arr );\n\t\t\t} else {\n\t\t\t\tjQuery.merge( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\tvar len;\n\n\t\tif ( arr ) {\n\t\t\tif ( core_indexOf ) {\n\t\t\t\treturn core_indexOf.call( arr, elem, i );\n\t\t\t}\n\n\t\t\tlen = arr.length;\n\t\t\ti = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t// Skip accessing in sparse arrays\n\t\t\t\tif ( i in arr && arr[ i ] === elem ) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn -1;\n\t},\n\n\tmerge: function( first, second ) {\n\t\tvar l = second.length,\n\t\t\ti = first.length,\n\t\t\tj = 0;\n\n\t\tif ( typeof l === \"number\" ) {\n\t\t\tfor ( ; j < l; j++ ) {\n\t\t\t\tfirst[ i++ ] = second[ j ];\n\t\t\t}\n\n\t\t} else {\n\t\t\twhile ( second[j] !== undefined ) {\n\t\t\t\tfirst[ i++ ] = second[ j++ ];\n\t\t\t}\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, inv ) {\n\t\tvar retVal,\n\t\t\tret = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length;\n\t\tinv = !!inv;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tretVal = !!callback( elems[ i ], i );\n\t\t\tif ( inv !== retVal ) {\n\t\t\t\tret.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar value, key,\n\t\t\tret = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\t// jquery objects are treated as arrays\n\t\t\tisArray = elems instanceof jQuery || length !== undefined && typeof length === \"number\" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;\n\n\t\t// Go through the array, translating each of the items to their\n\t\tif ( isArray ) {\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret[ ret.length ] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( key in elems ) {\n\t\t\t\tvalue = callback( elems[ key ], key, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret[ ret.length ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn ret.concat.apply( [], ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// Bind a function to a context, optionally partially applying any\n\t// arguments.\n\tproxy: function( fn, context ) {\n\t\tvar tmp, args, proxy;\n\n\t\tif ( typeof context === \"string\" ) {\n\t\t\ttmp = fn[ context ];\n\t\t\tcontext = fn;\n\t\t\tfn = tmp;\n\t\t}\n\n\t\t// Quick check to determine if target is callable, in the spec\n\t\t// this throws a TypeError, but we will just return undefined.\n\t\tif ( !jQuery.isFunction( fn ) ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Simulated bind\n\t\targs = core_slice.call( arguments, 2 );\n\t\tproxy = function() {\n\t\t\treturn fn.apply( context, args.concat( core_slice.call( arguments ) ) );\n\t\t};\n\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\t\treturn proxy;\n\t},\n\n\t// Multifunctional method to get and set values of a collection\n\t// The value/s can optionally be executed if it's a function\n\taccess: function( elems, fn, key, value, chainable, emptyGet, pass ) {\n\t\tvar exec,\n\t\t\tbulk = key == null,\n\t\t\ti = 0,\n\t\t\tlength = elems.length;\n\n\t\t// Sets many values\n\t\tif ( key && typeof key === \"object\" ) {\n\t\t\tfor ( i in key ) {\n\t\t\t\tjQuery.access( elems, fn, i, key[i], 1, emptyGet, value );\n\t\t\t}\n\t\t\tchainable = 1;\n\n\t\t// Sets one value\n\t\t} else if ( value !== undefined ) {\n\t\t\t// Optionally, function values get executed if exec is true\n\t\t\texec = pass === undefined && jQuery.isFunction( value );\n\n\t\t\tif ( bulk ) {\n\t\t\t\t// Bulk operations only iterate when executing function values\n\t\t\t\tif ( exec ) {\n\t\t\t\t\texec = fn;\n\t\t\t\t\tfn = function( elem, key, value ) {\n\t\t\t\t\t\treturn exec.call( jQuery( elem ), value );\n\t\t\t\t\t};\n\n\t\t\t\t// Otherwise they run against the entire set\n\t\t\t\t} else {\n\t\t\t\t\tfn.call( elems, value );\n\t\t\t\t\tfn = null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( fn ) {\n\t\t\t\tfor (; i < length; i++ ) {\n\t\t\t\t\tfn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tchainable = 1;\n\t\t}\n\n\t\treturn chainable ?\n\t\t\telems :\n\n\t\t\t// Gets\n\t\t\tbulk ?\n\t\t\t\tfn.call( elems ) :\n\t\t\t\tlength ? fn( elems[0], key ) : emptyGet;\n\t},\n\n\tnow: function() {\n\t\treturn ( new Date() ).getTime();\n\t}\n});\n\njQuery.ready.promise = function( obj ) {\n\tif ( !readyList ) {\n\n\t\treadyList = jQuery.Deferred();\n\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\n\t\t// we once tried to use readyState \"interactive\" here, but it caused issues like the one\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n\t\tif ( document.readyState === \"complete\" ) {\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\t\t\tsetTimeout( jQuery.ready, 1 );\n\n\t\t// Standards-based browsers support DOMContentLoaded\n\t\t} else if ( document.addEventListener ) {\n\t\t\t// Use the handy event callback\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", DOMContentLoaded, false );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.addEventListener( \"load\", jQuery.ready, false );\n\n\t\t// If IE event model is used\n\t\t} else {\n\t\t\t// Ensure firing before onload, maybe late but safe also for iframes\n\t\t\tdocument.attachEvent( \"onreadystatechange\", DOMContentLoaded );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.attachEvent( \"onload\", jQuery.ready );\n\n\t\t\t// If IE and not a frame\n\t\t\t// continually check to see if the document is ready\n\t\t\tvar top = false;\n\n\t\t\ttry {\n\t\t\t\ttop = window.frameElement == null && document.documentElement;\n\t\t\t} catch(e) {}\n\n\t\t\tif ( top && top.doScroll ) {\n\t\t\t\t(function doScrollCheck() {\n\t\t\t\t\tif ( !jQuery.isReady ) {\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Use the trick by Diego Perini\n\t\t\t\t\t\t\t// http://javascript.nwbox.com/IEContentLoaded/\n\t\t\t\t\t\t\ttop.doScroll(\"left\");\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\treturn setTimeout( doScrollCheck, 50 );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// and execute any waiting functions\n\t\t\t\t\t\tjQuery.ready();\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t}\n\t}\n\treturn readyList.promise( obj );\n};\n\n// Populate the class2type map\njQuery.each(\"Boolean Number String Function Array Date RegExp Object\".split(\" \"), function(i, name) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n});\n\n// All jQuery objects should point back to these\nrootjQuery = jQuery(document);\n// String to Object options format cache\nvar optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\nfunction createOptions( options ) {\n\tvar object = optionsCache[ options ] = {};\n\tjQuery.each( options.split( core_rspace ), function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t});\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Last fire value (for non-forgettable lists)\n\t\tmemory,\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\t\t// Flag to know if list is currently firing\n\t\tfiring,\n\t\t// First callback to fire (used internally by add and fireWith)\n\t\tfiringStart,\n\t\t// End of the loop when firing\n\t\tfiringLength,\n\t\t// Index of currently firing callback (modified by remove if needed)\n\t\tfiringIndex,\n\t\t// Actual callback list\n\t\tlist = [],\n\t\t// Stack of fire calls for repeatable lists\n\t\tstack = !options.once && [],\n\t\t// Fire callbacks\n\t\tfire = function( data ) {\n\t\t\tmemory = options.memory && data;\n\t\t\tfired = true;\n\t\t\tfiringIndex = firingStart || 0;\n\t\t\tfiringStart = 0;\n\t\t\tfiringLength = list.length;\n\t\t\tfiring = true;\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\n\t\t\t\t\tmemory = false; // To prevent further calls using add\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiring = false;\n\t\t\tif ( list ) {\n\t\t\t\tif ( stack ) {\n\t\t\t\t\tif ( stack.length ) {\n\t\t\t\t\t\tfire( stack.shift() );\n\t\t\t\t\t}\n\t\t\t\t} else if ( memory ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t} else {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Actual Callbacks object\n\t\tself = {\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\t// First, we save the current length\n\t\t\t\t\tvar start = list.length;\n\t\t\t\t\t(function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})( arguments );\n\t\t\t\t\t// Do we need to add the callbacks to the\n\t\t\t\t\t// current firing batch?\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tfiringLength = list.length;\n\t\t\t\t\t// With memory, if we're not firing then\n\t\t\t\t\t// we should call right away\n\t\t\t\t\t} else if ( memory ) {\n\t\t\t\t\t\tfiringStart = start;\n\t\t\t\t\t\tfire( memory );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\t\tvar index;\n\t\t\t\t\t\twhile( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\t\tlist.splice( index, 1 );\n\t\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\n\t\t\t\t\t\t\t\t\tfiringLength--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Control if a given callback is in the list\n\t\t\thas: function( fn ) {\n\t\t\t\treturn jQuery.inArray( fn, list ) > -1;\n\t\t\t},\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tlist = [];\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Have the list do nothing anymore\n\t\t\tdisable: function() {\n\t\t\t\tlist = stack = memory = undefined;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it disabled?\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\t\t\t// Lock the list in its current state\n\t\t\tlock: function() {\n\t\t\t\tstack = undefined;\n\t\t\t\tif ( !memory ) {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it locked?\n\t\t\tlocked: function() {\n\t\t\t\treturn !stack;\n\t\t\t},\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\targs = args || [];\n\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\tif ( list && ( !fired || stack ) ) {\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tstack.push( args );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire( args );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\njQuery.extend({\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\t\t\t\t// action, add listener, listener list, final state\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\t\t\t\t\tvar action = tuple[ 0 ],\n\t\t\t\t\t\t\t\tfn = fns[ i ];\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\n\t\t\t\t\t\t\tdeferred[ tuple[1] ]( jQuery.isFunction( fn ) ?\n\t\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\t\tvar returned = fn.apply( this, arguments );\n\t\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\n\t\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tnewDefer[ action + \"With\" ]( this === deferred ? newDefer : this, [ returned ] );\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} :\n\t\t\t\t\t\t\t\tnewDefer[ action ]\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Keep pipe for back-compat\n\t\tpromise.pipe = promise.then;\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 3 ];\n\n\t\t\t// promise[ done | fail | progress ] = list.add\n\t\t\tpromise[ tuple[1] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(function() {\n\t\t\t\t\t// state = [ resolved | rejected ]\n\t\t\t\t\tstate = stateString;\n\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\n\t\t\t}\n\n\t\t\t// deferred[ resolve | reject | notify ] = list.fire\n\t\t\tdeferred[ tuple[0] ] = list.fire;\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\n\t\tvar i = 0,\n\t\t\tresolveValues = core_slice.call( arguments ),\n\t\t\tlength = resolveValues.length,\n\n\t\t\t// the count of uncompleted subordinates\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\n\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n\t\t\t// Update function for both resolve and progress values\n\t\t\tupdateFunc = function( i, contexts, values ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tcontexts[ i ] = this;\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;\n\t\t\t\t\tif( values === progressValues ) {\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\n\t\t\t\t\t} else if ( !( --remaining ) ) {\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\t\t\tprogressValues, progressContexts, resolveContexts;\n\n\t\t// add listeners to Deferred subordinates; treat others as resolved\n\t\tif ( length > 1 ) {\n\t\t\tprogressValues = new Array( length );\n\t\t\tprogressContexts = new Array( length );\n\t\t\tresolveContexts = new Array( length );\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\n\t\t\t\t\tresolveValues[ i ].promise()\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\n\t\t\t\t} else {\n\t\t\t\t\t--remaining;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// if we're not waiting on anything, resolve the master\n\t\tif ( !remaining ) {\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\n\t\t}\n\n\t\treturn deferred.promise();\n\t}\n});\njQuery.support = (function() {\n\n\tvar support,\n\t\tall,\n\t\ta,\n\t\tselect,\n\t\topt,\n\t\tinput,\n\t\tfragment,\n\t\teventName,\n\t\ti,\n\t\tisSupported,\n\t\tclickFn,\n\t\tdiv = document.createElement(\"div\");\n\n\t// Setup\n\tdiv.setAttribute( \"className\", \"t\" );\n\tdiv.innerHTML = \"  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>\";\n\n\t// Support tests won't run in some limited or non-browser environments\n\tall = div.getElementsByTagName(\"*\");\n\ta = div.getElementsByTagName(\"a\")[ 0 ];\n\tif ( !all || !a || !all.length ) {\n\t\treturn {};\n\t}\n\n\t// First batch of tests\n\tselect = document.createElement(\"select\");\n\topt = select.appendChild( document.createElement(\"option\") );\n\tinput = div.getElementsByTagName(\"input\")[ 0 ];\n\n\ta.style.cssText = \"top:1px;float:left;opacity:.5\";\n\tsupport = {\n\t\t// IE strips leading whitespace when .innerHTML is used\n\t\tleadingWhitespace: ( div.firstChild.nodeType === 3 ),\n\n\t\t// Make sure that tbody elements aren't automatically inserted\n\t\t// IE will insert them into empty tables\n\t\ttbody: !div.getElementsByTagName(\"tbody\").length,\n\n\t\t// Make sure that link elements get serialized correctly by innerHTML\n\t\t// This requires a wrapper element in IE\n\t\thtmlSerialize: !!div.getElementsByTagName(\"link\").length,\n\n\t\t// Get the style information from getAttribute\n\t\t// (IE uses .cssText instead)\n\t\tstyle: /top/.test( a.getAttribute(\"style\") ),\n\n\t\t// Make sure that URLs aren't manipulated\n\t\t// (IE normalizes it by default)\n\t\threfNormalized: ( a.getAttribute(\"href\") === \"/a\" ),\n\n\t\t// Make sure that element opacity exists\n\t\t// (IE uses filter instead)\n\t\t// Use a regex to work around a WebKit issue. See #5145\n\t\topacity: /^0.5/.test( a.style.opacity ),\n\n\t\t// Verify style float existence\n\t\t// (IE uses styleFloat instead of cssFloat)\n\t\tcssFloat: !!a.style.cssFloat,\n\n\t\t// Make sure that if no value is specified for a checkbox\n\t\t// that it defaults to \"on\".\n\t\t// (WebKit defaults to \"\" instead)\n\t\tcheckOn: ( input.value === \"on\" ),\n\n\t\t// Make sure that a selected-by-default option has a working selected property.\n\t\t// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)\n\t\toptSelected: opt.selected,\n\n\t\t// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)\n\t\tgetSetAttribute: div.className !== \"t\",\n\n\t\t// Tests for enctype support on a form (#6743)\n\t\tenctype: !!document.createElement(\"form\").enctype,\n\n\t\t// Makes sure cloning an html5 element does not cause problems\n\t\t// Where outerHTML is undefined, this still works\n\t\thtml5Clone: document.createElement(\"nav\").cloneNode( true ).outerHTML !== \"<:nav></:nav>\",\n\n\t\t// jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode\n\t\tboxModel: ( document.compatMode === \"CSS1Compat\" ),\n\n\t\t// Will be defined later\n\t\tsubmitBubbles: true,\n\t\tchangeBubbles: true,\n\t\tfocusinBubbles: false,\n\t\tdeleteExpando: true,\n\t\tnoCloneEvent: true,\n\t\tinlineBlockNeedsLayout: false,\n\t\tshrinkWrapBlocks: false,\n\t\treliableMarginRight: true,\n\t\tboxSizingReliable: true,\n\t\tpixelPosition: false\n\t};\n\n\t// Make sure checked status is properly cloned\n\tinput.checked = true;\n\tsupport.noCloneChecked = input.cloneNode( true ).checked;\n\n\t// Make sure that the options inside disabled selects aren't marked as disabled\n\t// (WebKit marks them as disabled)\n\tselect.disabled = true;\n\tsupport.optDisabled = !opt.disabled;\n\n\t// Test to see if it's possible to delete an expando from an element\n\t// Fails in Internet Explorer\n\ttry {\n\t\tdelete div.test;\n\t} catch( e ) {\n\t\tsupport.deleteExpando = false;\n\t}\n\n\tif ( !div.addEventListener && div.attachEvent && div.fireEvent ) {\n\t\tdiv.attachEvent( \"onclick\", clickFn = function() {\n\t\t\t// Cloning a node shouldn't copy over any\n\t\t\t// bound event handlers (IE does this)\n\t\t\tsupport.noCloneEvent = false;\n\t\t});\n\t\tdiv.cloneNode( true ).fireEvent(\"onclick\");\n\t\tdiv.detachEvent( \"onclick\", clickFn );\n\t}\n\n\t// Check if a radio maintains its value\n\t// after being appended to the DOM\n\tinput = document.createElement(\"input\");\n\tinput.value = \"t\";\n\tinput.setAttribute( \"type\", \"radio\" );\n\tsupport.radioValue = input.value === \"t\";\n\n\tinput.setAttribute( \"checked\", \"checked\" );\n\n\t// #11217 - WebKit loses check when the name is after the checked attribute\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tdiv.appendChild( input );\n\tfragment = document.createDocumentFragment();\n\tfragment.appendChild( div.lastChild );\n\n\t// WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Check if a disconnected checkbox will retain its checked\n\t// value of true after appended to the DOM (IE6/7)\n\tsupport.appendChecked = input.checked;\n\n\tfragment.removeChild( input );\n\tfragment.appendChild( div );\n\n\t// Technique from Juriy Zaytsev\n\t// http://perfectionkills.com/detecting-event-support-without-browser-sniffing/\n\t// We only care about the case where non-standard event systems\n\t// are used, namely in IE. Short-circuiting here helps us to\n\t// avoid an eval call (in setAttribute) which can cause CSP\n\t// to go haywire. See: https://developer.mozilla.org/en/Security/CSP\n\tif ( div.attachEvent ) {\n\t\tfor ( i in {\n\t\t\tsubmit: true,\n\t\t\tchange: true,\n\t\t\tfocusin: true\n\t\t}) {\n\t\t\teventName = \"on\" + i;\n\t\t\tisSupported = ( eventName in div );\n\t\t\tif ( !isSupported ) {\n\t\t\t\tdiv.setAttribute( eventName, \"return;\" );\n\t\t\t\tisSupported = ( typeof div[ eventName ] === \"function\" );\n\t\t\t}\n\t\t\tsupport[ i + \"Bubbles\" ] = isSupported;\n\t\t}\n\t}\n\n\t// Run tests that need a body at doc ready\n\tjQuery(function() {\n\t\tvar container, div, tds, marginDiv,\n\t\t\tdivReset = \"padding:0;margin:0;border:0;display:block;overflow:hidden;\",\n\t\t\tbody = document.getElementsByTagName(\"body\")[0];\n\n\t\tif ( !body ) {\n\t\t\t// Return for frameset docs that don't have a body\n\t\t\treturn;\n\t\t}\n\n\t\tcontainer = document.createElement(\"div\");\n\t\tcontainer.style.cssText = \"visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px\";\n\t\tbody.insertBefore( container, body.firstChild );\n\n\t\t// Construct the test element\n\t\tdiv = document.createElement(\"div\");\n\t\tcontainer.appendChild( div );\n\n\t\t// Check if table cells still have offsetWidth/Height when they are set\n\t\t// to display:none and there are still other visible table cells in a\n\t\t// table row; if so, offsetWidth/Height are not reliable for use when\n\t\t// determining if an element has been hidden directly using\n\t\t// display:none (it is still safe to use offsets if a parent element is\n\t\t// hidden; don safety goggles and see bug #4512 for more information).\n\t\t// (only IE 8 fails this test)\n\t\tdiv.innerHTML = \"<table><tr><td></td><td>t</td></tr></table>\";\n\t\ttds = div.getElementsByTagName(\"td\");\n\t\ttds[ 0 ].style.cssText = \"padding:0;margin:0;border:0;display:none\";\n\t\tisSupported = ( tds[ 0 ].offsetHeight === 0 );\n\n\t\ttds[ 0 ].style.display = \"\";\n\t\ttds[ 1 ].style.display = \"none\";\n\n\t\t// Check if empty table cells still have offsetWidth/Height\n\t\t// (IE <= 8 fail this test)\n\t\tsupport.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );\n\n\t\t// Check box-sizing and margin behavior\n\t\tdiv.innerHTML = \"\";\n\t\tdiv.style.cssText = \"box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;\";\n\t\tsupport.boxSizing = ( div.offsetWidth === 4 );\n\t\tsupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );\n\n\t\t// NOTE: To any future maintainer, we've window.getComputedStyle\n\t\t// because jsdom on node.js will break without it.\n\t\tif ( window.getComputedStyle ) {\n\t\t\tsupport.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== \"1%\";\n\t\t\tsupport.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: \"4px\" } ).width === \"4px\";\n\n\t\t\t// Check if div with explicit width and no margin-right incorrectly\n\t\t\t// gets computed margin-right based on width of container. For more\n\t\t\t// info see bug #3333\n\t\t\t// Fails in WebKit before Feb 2011 nightlies\n\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\tmarginDiv = document.createElement(\"div\");\n\t\t\tmarginDiv.style.cssText = div.style.cssText = divReset;\n\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\n\t\t\tdiv.style.width = \"1px\";\n\t\t\tdiv.appendChild( marginDiv );\n\t\t\tsupport.reliableMarginRight =\n\t\t\t\t!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );\n\t\t}\n\n\t\tif ( typeof div.style.zoom !== \"undefined\" ) {\n\t\t\t// Check if natively block-level elements act like inline-block\n\t\t\t// elements when setting their display to 'inline' and giving\n\t\t\t// them layout\n\t\t\t// (IE < 8 does this)\n\t\t\tdiv.innerHTML = \"\";\n\t\t\tdiv.style.cssText = divReset + \"width:1px;padding:1px;display:inline;zoom:1\";\n\t\t\tsupport.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );\n\n\t\t\t// Check if elements with layout shrink-wrap their children\n\t\t\t// (IE 6 does this)\n\t\t\tdiv.style.display = \"block\";\n\t\t\tdiv.style.overflow = \"visible\";\n\t\t\tdiv.innerHTML = \"<div></div>\";\n\t\t\tdiv.firstChild.style.width = \"5px\";\n\t\t\tsupport.shrinkWrapBlocks = ( div.offsetWidth !== 3 );\n\n\t\t\tcontainer.style.zoom = 1;\n\t\t}\n\n\t\t// Null elements to avoid leaks in IE\n\t\tbody.removeChild( container );\n\t\tcontainer = div = tds = marginDiv = null;\n\t});\n\n\t// Null elements to avoid leaks in IE\n\tfragment.removeChild( div );\n\tall = a = select = opt = input = fragment = div = null;\n\n\treturn support;\n})();\nvar rbrace = /(?:\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])$/,\n\trmultiDash = /([A-Z])/g;\n\njQuery.extend({\n\tcache: {},\n\n\tdeletedIds: [],\n\n\t// Remove at next major release (1.9/2.0)\n\tuuid: 0,\n\n\t// Unique for each copy of jQuery on the page\n\t// Non-digits removed to match rinlinejQuery\n\texpando: \"jQuery\" + ( jQuery.fn.jquery + Math.random() ).replace( /\\D/g, \"\" ),\n\n\t// The following elements throw uncatchable exceptions if you\n\t// attempt to add expando properties to them.\n\tnoData: {\n\t\t\"embed\": true,\n\t\t// Ban all objects except for Flash (which handle expandos)\n\t\t\"object\": \"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\",\n\t\t\"applet\": true\n\t},\n\n\thasData: function( elem ) {\n\t\telem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];\n\t\treturn !!elem && !isEmptyDataObject( elem );\n\t},\n\n\tdata: function( elem, name, data, pvt /* Internal Use Only */ ) {\n\t\tif ( !jQuery.acceptData( elem ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar thisCache, ret,\n\t\t\tinternalKey = jQuery.expando,\n\t\t\tgetByName = typeof name === \"string\",\n\n\t\t\t// We have to handle DOM nodes and JS objects differently because IE6-7\n\t\t\t// can't GC object references properly across the DOM-JS boundary\n\t\t\tisNode = elem.nodeType,\n\n\t\t\t// Only DOM nodes need the global jQuery cache; JS object data is\n\t\t\t// attached directly to the object so GC can occur automatically\n\t\t\tcache = isNode ? jQuery.cache : elem,\n\n\t\t\t// Only defining an ID for JS objects if its cache already exists allows\n\t\t\t// the code to shortcut on the same path as a DOM node with no cache\n\t\t\tid = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;\n\n\t\t// Avoid doing any more work than we need to when trying to get data on an\n\t\t// object that has no data at all\n\t\tif ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !id ) {\n\t\t\t// Only DOM nodes need a new unique ID for each element since their data\n\t\t\t// ends up in the global cache\n\t\t\tif ( isNode ) {\n\t\t\t\telem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;\n\t\t\t} else {\n\t\t\t\tid = internalKey;\n\t\t\t}\n\t\t}\n\n\t\tif ( !cache[ id ] ) {\n\t\t\tcache[ id ] = {};\n\n\t\t\t// Avoids exposing jQuery metadata on plain JS objects when the object\n\t\t\t// is serialized using JSON.stringify\n\t\t\tif ( !isNode ) {\n\t\t\t\tcache[ id ].toJSON = jQuery.noop;\n\t\t\t}\n\t\t}\n\n\t\t// An object can be passed to jQuery.data instead of a key/value pair; this gets\n\t\t// shallow copied over onto the existing cache\n\t\tif ( typeof name === \"object\" || typeof name === \"function\" ) {\n\t\t\tif ( pvt ) {\n\t\t\t\tcache[ id ] = jQuery.extend( cache[ id ], name );\n\t\t\t} else {\n\t\t\t\tcache[ id ].data = jQuery.extend( cache[ id ].data, name );\n\t\t\t}\n\t\t}\n\n\t\tthisCache = cache[ id ];\n\n\t\t// jQuery data() is stored in a separate object inside the object's internal data\n\t\t// cache in order to avoid key collisions between internal data and user-defined\n\t\t// data.\n\t\tif ( !pvt ) {\n\t\t\tif ( !thisCache.data ) {\n\t\t\t\tthisCache.data = {};\n\t\t\t}\n\n\t\t\tthisCache = thisCache.data;\n\t\t}\n\n\t\tif ( data !== undefined ) {\n\t\t\tthisCache[ jQuery.camelCase( name ) ] = data;\n\t\t}\n\n\t\t// Check for both converted-to-camel and non-converted data property names\n\t\t// If a data property was specified\n\t\tif ( getByName ) {\n\n\t\t\t// First Try to find as-is property data\n\t\t\tret = thisCache[ name ];\n\n\t\t\t// Test for null|undefined property data\n\t\t\tif ( ret == null ) {\n\n\t\t\t\t// Try to find the camelCased property\n\t\t\t\tret = thisCache[ jQuery.camelCase( name ) ];\n\t\t\t}\n\t\t} else {\n\t\t\tret = thisCache;\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tremoveData: function( elem, name, pvt /* Internal Use Only */ ) {\n\t\tif ( !jQuery.acceptData( elem ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar thisCache, i, l,\n\n\t\t\tisNode = elem.nodeType,\n\n\t\t\t// See jQuery.data for more information\n\t\t\tcache = isNode ? jQuery.cache : elem,\n\t\t\tid = isNode ? elem[ jQuery.expando ] : jQuery.expando;\n\n\t\t// If there is already no cache entry for this object, there is no\n\t\t// purpose in continuing\n\t\tif ( !cache[ id ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( name ) {\n\n\t\t\tthisCache = pvt ? cache[ id ] : cache[ id ].data;\n\n\t\t\tif ( thisCache ) {\n\n\t\t\t\t// Support array or space separated string names for data keys\n\t\t\t\tif ( !jQuery.isArray( name ) ) {\n\n\t\t\t\t\t// try the string as a key before any manipulation\n\t\t\t\t\tif ( name in thisCache ) {\n\t\t\t\t\t\tname = [ name ];\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\t// split the camel cased version by spaces unless a key with the spaces exists\n\t\t\t\t\t\tname = jQuery.camelCase( name );\n\t\t\t\t\t\tif ( name in thisCache ) {\n\t\t\t\t\t\t\tname = [ name ];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tname = name.split(\" \");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor ( i = 0, l = name.length; i < l; i++ ) {\n\t\t\t\t\tdelete thisCache[ name[i] ];\n\t\t\t\t}\n\n\t\t\t\t// If there is no data left in the cache, we want to continue\n\t\t\t\t// and let the cache object itself get destroyed\n\t\t\t\tif ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// See jQuery.data for more information\n\t\tif ( !pvt ) {\n\t\t\tdelete cache[ id ].data;\n\n\t\t\t// Don't destroy the parent cache unless the internal data object\n\t\t\t// had been the only thing left in it\n\t\t\tif ( !isEmptyDataObject( cache[ id ] ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Destroy the cache\n\t\tif ( isNode ) {\n\t\t\tjQuery.cleanData( [ elem ], true );\n\n\t\t// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)\n\t\t} else if ( jQuery.support.deleteExpando || cache != cache.window ) {\n\t\t\tdelete cache[ id ];\n\n\t\t// When all else fails, null\n\t\t} else {\n\t\t\tcache[ id ] = null;\n\t\t}\n\t},\n\n\t// For internal use only.\n\t_data: function( elem, name, data ) {\n\t\treturn jQuery.data( elem, name, data, true );\n\t},\n\n\t// A method for determining if a DOM node can handle the data expando\n\tacceptData: function( elem ) {\n\t\tvar noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];\n\n\t\t// nodes accept data unless otherwise specified; rejection can be conditional\n\t\treturn !noData || noData !== true && elem.getAttribute(\"classid\") === noData;\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function( key, value ) {\n\t\tvar parts, part, attr, name, l,\n\t\t\telem = this[0],\n\t\t\ti = 0,\n\t\t\tdata = null;\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = jQuery.data( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !jQuery._data( elem, \"parsedAttrs\" ) ) {\n\t\t\t\t\tattr = elem.attributes;\n\t\t\t\t\tfor ( l = attr.length; i < l; i++ ) {\n\t\t\t\t\t\tname = attr[i].name;\n\n\t\t\t\t\t\tif ( !name.indexOf( \"data-\" ) ) {\n\t\t\t\t\t\t\tname = jQuery.camelCase( name.substring(5) );\n\n\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tjQuery._data( elem, \"parsedAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tjQuery.data( this, key );\n\t\t\t});\n\t\t}\n\n\t\tparts = key.split( \".\", 2 );\n\t\tparts[1] = parts[1] ? \".\" + parts[1] : \"\";\n\t\tpart = parts[1] + \"!\";\n\n\t\treturn jQuery.access( this, function( value ) {\n\n\t\t\tif ( value === undefined ) {\n\t\t\t\tdata = this.triggerHandler( \"getData\" + part, [ parts[0] ] );\n\n\t\t\t\t// Try to fetch any internally stored data first\n\t\t\t\tif ( data === undefined && elem ) {\n\t\t\t\t\tdata = jQuery.data( elem, key );\n\t\t\t\t\tdata = dataAttr( elem, key, data );\n\t\t\t\t}\n\n\t\t\t\treturn data === undefined && parts[1] ?\n\t\t\t\t\tthis.data( parts[0] ) :\n\t\t\t\t\tdata;\n\t\t\t}\n\n\t\t\tparts[1] = value;\n\t\t\tthis.each(function() {\n\t\t\t\tvar self = jQuery( this );\n\n\t\t\t\tself.triggerHandler( \"setData\" + part, parts );\n\t\t\t\tjQuery.data( this, key, value );\n\t\t\t\tself.triggerHandler( \"changeData\" + part, parts );\n\t\t\t});\n\t\t}, null, value, arguments.length > 1, null, false );\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeData( this, key );\n\t\t});\n\t}\n});\n\nfunction dataAttr( elem, key, data ) {\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\n\t\tvar name = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\n\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = data === \"true\" ? true :\n\t\t\t\tdata === \"false\" ? false :\n\t\t\t\tdata === \"null\" ? null :\n\t\t\t\t// Only convert to a number if it doesn't change the string\n\t\t\t\t+data + \"\" === data ? +data :\n\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\n\t\t\t\t\tdata;\n\t\t\t} catch( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tjQuery.data( elem, key, data );\n\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\n\treturn data;\n}\n\n// checks a cache object for emptiness\nfunction isEmptyDataObject( obj ) {\n\tvar name;\n\tfor ( name in obj ) {\n\n\t\t// if the public data object is empty, the private is still empty\n\t\tif ( name === \"data\" && jQuery.isEmptyObject( obj[name] ) ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( name !== \"toJSON\" ) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\njQuery.extend({\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = jQuery._data( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || jQuery.isArray(data) ) {\n\t\t\t\t\tqueue = jQuery._data( elem, type, jQuery.makeArray(data) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// not intended for public consumption - generates a queueHooks object, or returns the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn jQuery._data( elem, key ) || jQuery._data( elem, key, {\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\n\t\t\t\tjQuery.removeData( elem, type + \"queue\", true );\n\t\t\t\tjQuery.removeData( elem, key, true );\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[0], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t});\n\t},\n\t// Based off of the plugin by Clint Helfers, with permission.\n\t// http://blindsignals.com/index.php/2009/07/jquery-delay/\n\tdelay: function( time, type ) {\n\t\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\t\ttype = type || \"fx\";\n\n\t\treturn this.queue( type, function( next, hooks ) {\n\t\t\tvar timeout = setTimeout( next, time );\n\t\t\thooks.stop = function() {\n\t\t\t\tclearTimeout( timeout );\n\t\t\t};\n\t\t});\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile( i-- ) {\n\t\t\ttmp = jQuery._data( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n});\nvar nodeHook, boolHook, fixSpecified,\n\trclass = /[\\t\\r\\n]/g,\n\trreturn = /\\r/g,\n\trtype = /^(?:button|input)$/i,\n\trfocusable = /^(?:button|input|object|select|textarea)$/i,\n\trclickable = /^a(?:rea|)$/i,\n\trboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,\n\tgetSetAttribute = jQuery.support.getSetAttribute;\n\njQuery.fn.extend({\n\tattr: function( name, value ) {\n\t\treturn jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t});\n\t},\n\n\tprop: function( name, value ) {\n\t\treturn jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\tname = jQuery.propFix[ name ] || name;\n\t\treturn this.each(function() {\n\t\t\t// try/catch handles cases where IE balks (such as removing a property on window)\n\t\t\ttry {\n\t\t\t\tthis[ name ] = undefined;\n\t\t\t\tdelete this[ name ];\n\t\t\t} catch( e ) {}\n\t\t});\n\t},\n\n\taddClass: function( value ) {\n\t\tvar classNames, i, l, elem,\n\t\t\tsetClass, c, cl;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call(this, j, this.className) );\n\t\t\t});\n\t\t}\n\n\t\tif ( value && typeof value === \"string\" ) {\n\t\t\tclassNames = value.split( core_rspace );\n\n\t\t\tfor ( i = 0, l = this.length; i < l; i++ ) {\n\t\t\t\telem = this[ i ];\n\n\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\tif ( !elem.className && classNames.length === 1 ) {\n\t\t\t\t\t\telem.className = value;\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsetClass = \" \" + elem.className + \" \";\n\n\t\t\t\t\t\tfor ( c = 0, cl = classNames.length; c < cl; c++ ) {\n\t\t\t\t\t\t\tif ( setClass.indexOf( \" \" + classNames[ c ] + \" \" ) < 0 ) {\n\t\t\t\t\t\t\t\tsetClass += classNames[ c ] + \" \";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telem.className = jQuery.trim( setClass );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar removes, className, elem, c, cl, i, l;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call(this, j, this.className) );\n\t\t\t});\n\t\t}\n\t\tif ( (value && typeof value === \"string\") || value === undefined ) {\n\t\t\tremoves = ( value || \"\" ).split( core_rspace );\n\n\t\t\tfor ( i = 0, l = this.length; i < l; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\tif ( elem.nodeType === 1 && elem.className ) {\n\n\t\t\t\t\tclassName = (\" \" + elem.className + \" \").replace( rclass, \" \" );\n\n\t\t\t\t\t// loop over each item in the removal list\n\t\t\t\t\tfor ( c = 0, cl = removes.length; c < cl; c++ ) {\n\t\t\t\t\t\t// Remove until there is nothing to remove,\n\t\t\t\t\t\twhile ( className.indexOf(\" \" + removes[ c ] + \" \") >= 0 ) {\n\t\t\t\t\t\t\tclassName = className.replace( \" \" + removes[ c ] + \" \" , \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telem.className = value ? jQuery.trim( className ) : \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value,\n\t\t\tisBool = typeof stateVal === \"boolean\";\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( type === \"string\" ) {\n\t\t\t\t// toggle individual class names\n\t\t\t\tvar className,\n\t\t\t\t\ti = 0,\n\t\t\t\t\tself = jQuery( this ),\n\t\t\t\t\tstate = stateVal,\n\t\t\t\t\tclassNames = value.split( core_rspace );\n\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\n\t\t\t\t\t// check each className given, space separated list\n\t\t\t\t\tstate = isBool ? state : !self.hasClass( className );\n\t\t\t\t\tself[ state ? \"addClass\" : \"removeClass\" ]( className );\n\t\t\t\t}\n\n\t\t\t} else if ( type === \"undefined\" || type === \"boolean\" ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\t// store className if set\n\t\t\t\t\tjQuery._data( this, \"__className__\", this.className );\n\t\t\t\t}\n\n\t\t\t\t// toggle whole className\n\t\t\t\tthis.className = this.className || value === false ? \"\" : jQuery._data( this, \"__className__\" ) || \"\";\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className = \" \" + selector + \" \",\n\t\t\ti = 0,\n\t\t\tl = this.length;\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\tval: function( value ) {\n\t\tvar hooks, ret, isFunction,\n\t\t\telem = this[0];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\treturn typeof ret === \"string\" ?\n\t\t\t\t\t// handle most common string cases\n\t\t\t\t\tret.replace(rreturn, \"\") :\n\t\t\t\t\t// handle cases where value is null/undef or number\n\t\t\t\t\tret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tisFunction = jQuery.isFunction( value );\n\n\t\treturn this.each(function( i ) {\n\t\t\tvar val,\n\t\t\t\tself = jQuery(this);\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( isFunction ) {\n\t\t\t\tval = value.call( this, i, self.val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\t\t\t} else if ( jQuery.isArray( val ) ) {\n\t\t\t\tval = jQuery.map(val, function ( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function( elem ) {\n\t\t\t\t// attributes.value is undefined in Blackberry 4.7 but\n\t\t\t\t// uses .value. See #6932\n\t\t\t\tvar val = elem.attributes.value;\n\t\t\t\treturn !val || val.specified ? elem.value : elem.text;\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length,\n\t\t\t\t\ti = index < 0 ?\n\t\t\t\t\t\tmax :\n\t\t\t\t\t\tone ? index : 0;\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// oldIE doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t( jQuery.support.optDisabled ? !option.disabled : option.getAttribute(\"disabled\") === null ) &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar values = jQuery.makeArray( value );\n\n\t\t\t\tjQuery(elem).find(\"option\").each(function() {\n\t\t\t\t\tthis.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;\n\t\t\t\t});\n\n\t\t\t\tif ( !values.length ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t},\n\n\t// Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9\n\tattrFn: {},\n\n\tattr: function( elem, name, value, pass ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set attributes on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {\n\t\t\treturn jQuery( elem )[ name ]( value );\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === \"undefined\" ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\t// All attributes are lowercase\n\t\t// Grab necessary hook if one is defined\n\t\tif ( notxml ) {\n\t\t\tname = name.toLowerCase();\n\t\t\thooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\t\t\t\treturn;\n\n\t\t\t} else if ( hooks && \"set\" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t} else if ( hooks && \"get\" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\treturn ret;\n\n\t\t} else {\n\n\t\t\tret = elem.getAttribute( name );\n\n\t\t\t// Non-existent attributes return null, we normalize to undefined\n\t\t\treturn ret === null ?\n\t\t\t\tundefined :\n\t\t\t\tret;\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar propName, attrNames, name, isBool,\n\t\t\ti = 0;\n\n\t\tif ( value && elem.nodeType === 1 ) {\n\n\t\t\tattrNames = value.split( core_rspace );\n\n\t\t\tfor ( ; i < attrNames.length; i++ ) {\n\t\t\t\tname = attrNames[ i ];\n\n\t\t\t\tif ( name ) {\n\t\t\t\t\tpropName = jQuery.propFix[ name ] || name;\n\t\t\t\t\tisBool = rboolean.test( name );\n\n\t\t\t\t\t// See #9699 for explanation of this approach (setting first, then removal)\n\t\t\t\t\t// Do not do this for boolean attributes (see #10870)\n\t\t\t\t\tif ( !isBool ) {\n\t\t\t\t\t\tjQuery.attr( elem, name, \"\" );\n\t\t\t\t\t}\n\t\t\t\t\telem.removeAttribute( getSetAttribute ? name : propName );\n\n\t\t\t\t\t// Set corresponding property to false for boolean attributes\n\t\t\t\t\tif ( isBool && propName in elem ) {\n\t\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\t// We can't allow the type property to be changed (since it causes problems in IE)\n\t\t\t\tif ( rtype.test( elem.nodeName ) && elem.parentNode ) {\n\t\t\t\t\tjQuery.error( \"type property can't be changed\" );\n\t\t\t\t} else if ( !jQuery.support.radioValue && value === \"radio\" && jQuery.nodeName(elem, \"input\") ) {\n\t\t\t\t\t// Setting the type on a radio button after the value resets the value in IE6-9\n\t\t\t\t\t// Reset value to it's default in case type is set after value\n\t\t\t\t\t// This is for element creation\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Use the value property for back compat\n\t\t// Use the nodeHook for button elements in IE6/7 (#1954)\n\t\tvalue: {\n\t\t\tget: function( elem, name ) {\n\t\t\t\tif ( nodeHook && jQuery.nodeName( elem, \"button\" ) ) {\n\t\t\t\t\treturn nodeHook.get( elem, name );\n\t\t\t\t}\n\t\t\t\treturn name in elem ?\n\t\t\t\t\telem.value :\n\t\t\t\t\tnull;\n\t\t\t},\n\t\t\tset: function( elem, value, name ) {\n\t\t\t\tif ( nodeHook && jQuery.nodeName( elem, \"button\" ) ) {\n\t\t\t\t\treturn nodeHook.set( elem, value, name );\n\t\t\t\t}\n\t\t\t\t// Does not return so that setAttribute is also used\n\t\t\t\telem.value = value;\n\t\t\t}\n\t\t}\n\t},\n\n\tpropFix: {\n\t\ttabindex: \"tabIndex\",\n\t\treadonly: \"readOnly\",\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\",\n\t\tmaxlength: \"maxLength\",\n\t\tcellspacing: \"cellSpacing\",\n\t\tcellpadding: \"cellPadding\",\n\t\trowspan: \"rowSpan\",\n\t\tcolspan: \"colSpan\",\n\t\tusemap: \"useMap\",\n\t\tframeborder: \"frameBorder\",\n\t\tcontenteditable: \"contentEditable\"\n\t},\n\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set properties on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\tif ( notxml ) {\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\tif ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\treturn ( elem[ name ] = value );\n\t\t\t}\n\n\t\t} else {\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\treturn elem[ name ];\n\t\t\t}\n\t\t}\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\t\t\t\t// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set\n\t\t\t\t// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/\n\t\t\t\tvar attributeNode = elem.getAttributeNode(\"tabindex\");\n\n\t\t\t\treturn attributeNode && attributeNode.specified ?\n\t\t\t\t\tparseInt( attributeNode.value, 10 ) :\n\t\t\t\t\trfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?\n\t\t\t\t\t\t0 :\n\t\t\t\t\t\tundefined;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hook for boolean attributes\nboolHook = {\n\tget: function( elem, name ) {\n\t\t// Align boolean attributes with corresponding properties\n\t\t// Fall back to attribute presence where some booleans are not supported\n\t\tvar attrNode,\n\t\t\tproperty = jQuery.prop( elem, name );\n\t\treturn property === true || typeof property !== \"boolean\" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?\n\t\t\tname.toLowerCase() :\n\t\t\tundefined;\n\t},\n\tset: function( elem, value, name ) {\n\t\tvar propName;\n\t\tif ( value === false ) {\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else {\n\t\t\t// value is true since we know at this point it's type boolean and not false\n\t\t\t// Set boolean attributes to the same name and set the DOM property\n\t\t\tpropName = jQuery.propFix[ name ] || name;\n\t\t\tif ( propName in elem ) {\n\t\t\t\t// Only set the IDL specifically if it already exists on the element\n\t\t\t\telem[ propName ] = true;\n\t\t\t}\n\n\t\t\telem.setAttribute( name, name.toLowerCase() );\n\t\t}\n\t\treturn name;\n\t}\n};\n\n// IE6/7 do not support getting/setting some attributes with get/setAttribute\nif ( !getSetAttribute ) {\n\n\tfixSpecified = {\n\t\tname: true,\n\t\tid: true,\n\t\tcoords: true\n\t};\n\n\t// Use this for any attribute in IE6/7\n\t// This fixes almost every IE6/7 issue\n\tnodeHook = jQuery.valHooks.button = {\n\t\tget: function( elem, name ) {\n\t\t\tvar ret;\n\t\t\tret = elem.getAttributeNode( name );\n\t\t\treturn ret && ( fixSpecified[ name ] ? ret.value !== \"\" : ret.specified ) ?\n\t\t\t\tret.value :\n\t\t\t\tundefined;\n\t\t},\n\t\tset: function( elem, value, name ) {\n\t\t\t// Set the existing or create a new attribute node\n\t\t\tvar ret = elem.getAttributeNode( name );\n\t\t\tif ( !ret ) {\n\t\t\t\tret = document.createAttribute( name );\n\t\t\t\telem.setAttributeNode( ret );\n\t\t\t}\n\t\t\treturn ( ret.value = value + \"\" );\n\t\t}\n\t};\n\n\t// Set width and height to auto instead of 0 on empty string( Bug #8150 )\n\t// This is for removals\n\tjQuery.each([ \"width\", \"height\" ], function( i, name ) {\n\t\tjQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( value === \"\" ) {\n\t\t\t\t\telem.setAttribute( name, \"auto\" );\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n\n\t// Set contenteditable to false on removals(#10429)\n\t// Setting to empty string throws an error as an invalid value\n\tjQuery.attrHooks.contenteditable = {\n\t\tget: nodeHook.get,\n\t\tset: function( elem, value, name ) {\n\t\t\tif ( value === \"\" ) {\n\t\t\t\tvalue = \"false\";\n\t\t\t}\n\t\t\tnodeHook.set( elem, value, name );\n\t\t}\n\t};\n}\n\n\n// Some attributes require a special call on IE\nif ( !jQuery.support.hrefNormalized ) {\n\tjQuery.each([ \"href\", \"src\", \"width\", \"height\" ], function( i, name ) {\n\t\tjQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar ret = elem.getAttribute( name, 2 );\n\t\t\t\treturn ret === null ? undefined : ret;\n\t\t\t}\n\t\t});\n\t});\n}\n\nif ( !jQuery.support.style ) {\n\tjQuery.attrHooks.style = {\n\t\tget: function( elem ) {\n\t\t\t// Return undefined in the case of empty string\n\t\t\t// Normalize to lowercase since IE uppercases css property names\n\t\t\treturn elem.style.cssText.toLowerCase() || undefined;\n\t\t},\n\t\tset: function( elem, value ) {\n\t\t\treturn ( elem.style.cssText = value + \"\" );\n\t\t}\n\t};\n}\n\n// Safari mis-reports the default selected property of an option\n// Accessing the parent's selectedIndex property fixes it\nif ( !jQuery.support.optSelected ) {\n\tjQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {\n\t\tget: function( elem ) {\n\t\t\tvar parent = elem.parentNode;\n\n\t\t\tif ( parent ) {\n\t\t\t\tparent.selectedIndex;\n\n\t\t\t\t// Make sure that it also works with optgroups, see #5701\n\t\t\t\tif ( parent.parentNode ) {\n\t\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t});\n}\n\n// IE6/7 call enctype encoding\nif ( !jQuery.support.enctype ) {\n\tjQuery.propFix.enctype = \"encoding\";\n}\n\n// Radios and checkboxes getter/setter\nif ( !jQuery.support.checkOn ) {\n\tjQuery.each([ \"radio\", \"checkbox\" ], function() {\n\t\tjQuery.valHooks[ this ] = {\n\t\t\tget: function( elem ) {\n\t\t\t\t// Handle the case where in Webkit \"\" is returned instead of \"on\" if a value isn't specified\n\t\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n\t\t\t}\n\t\t};\n\t});\n}\njQuery.each([ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {\n\t\tset: function( elem, value ) {\n\t\t\tif ( jQuery.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\n\t\t\t}\n\t\t}\n\t});\n});\nvar rformElems = /^(?:textarea|input|select)$/i,\n\trtypenamespace = /^([^\\.]*|)(?:\\.(.+)|)$/,\n\trhoverHack = /(?:^|\\s)hover(\\.\\S+|)\\b/,\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|contextmenu)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\thoverHack = function( events ) {\n\t\treturn jQuery.event.special.hover ? events : events.replace( rhoverHack, \"mouseenter$1 mouseleave$1\" );\n\t};\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tadd: function( elem, types, handler, data, selector ) {\n\n\t\tvar elemData, eventHandle, events,\n\t\t\tt, tns, type, namespaces, handleObj,\n\t\t\thandleObjIn, handlers, special;\n\n\t\t// Don't attach events to noData or text/comment nodes (allow plain objects tho)\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tevents = elemData.events;\n\t\tif ( !events ) {\n\t\t\telemData.events = events = {};\n\t\t}\n\t\teventHandle = elemData.handle;\n\t\tif ( !eventHandle ) {\n\t\t\telemData.handle = eventHandle = function( e ) {\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== \"undefined\" && (!e || jQuery.event.triggered !== e.type) ?\n\t\t\t\t\tjQuery.event.dispatch.apply( eventHandle.elem, arguments ) :\n\t\t\t\t\tundefined;\n\t\t\t};\n\t\t\t// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events\n\t\t\teventHandle.elem = elem;\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\t// jQuery(...).bind(\"mouseover mouseout\", fn);\n\t\ttypes = jQuery.trim( hoverHack(types) ).split( \" \" );\n\t\tfor ( t = 0; t < types.length; t++ ) {\n\n\t\t\ttns = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = tns[1];\n\t\t\tnamespaces = ( tns[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: tns[1],\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join(\".\")\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\thandlers = events[ type ];\n\t\t\tif ( !handlers ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener/attachEvent if the special events handler returns false\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\t\t\t\t\t// Bind the global event handler to the element\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\n\t\t\t\t\t} else if ( elem.attachEvent ) {\n\t\t\t\t\t\telem.attachEvent( \"on\" + type, eventHandle );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t\t// Nullify elem to prevent memory leaks in IE\n\t\telem = null;\n\t},\n\n\tglobal: {},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\n\t\tvar t, tns, type, origType, namespaces, origCount,\n\t\t\tj, events, special, eventType, handleObj,\n\t\t\telemData = jQuery.hasData( elem ) && jQuery._data( elem );\n\n\t\tif ( !elemData || !(events = elemData.events) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = jQuery.trim( hoverHack( types || \"\" ) ).split(\" \");\n\t\tfor ( t = 0; t < types.length; t++ ) {\n\t\t\ttns = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tns[1];\n\t\t\tnamespaces = tns[2];\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector? special.delegateType : special.bindType ) || type;\n\t\t\teventType = events[ type ] || [];\n\t\t\torigCount = eventType.length;\n\t\t\tnamespaces = namespaces ? new RegExp(\"(^|\\\\.)\" + namespaces.split(\".\").sort().join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\") : null;\n\n\t\t\t// Remove matching events\n\t\t\tfor ( j = 0; j < eventType.length; j++ ) {\n\t\t\t\thandleObj = eventType[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t ( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t ( !namespaces || namespaces.test( handleObj.namespace ) ) &&\n\t\t\t\t\t ( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\teventType.splice( j--, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\teventType.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( eventType.length === 0 && origCount !== eventType.length ) {\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\n\t\t\t// removeData also checks for emptiness and clears the expando if empty\n\t\t\t// so use it instead of delete\n\t\t\tjQuery.removeData( elem, \"events\", true );\n\t\t}\n\t},\n\n\t// Events that are safe to short-circuit if no handlers are attached.\n\t// Native DOM events should not be added, they may have inline handlers.\n\tcustomEvent: {\n\t\t\"getData\": true,\n\t\t\"setData\": true,\n\t\t\"changeData\": true\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Event object or event type\n\t\tvar cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,\n\t\t\ttype = event.type || event,\n\t\t\tnamespaces = [];\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf( \"!\" ) >= 0 ) {\n\t\t\t// Exclusive events trigger only for the exact event (no namespaces)\n\t\t\ttype = type.slice(0, -1);\n\t\t\texclusive = true;\n\t\t}\n\n\t\tif ( type.indexOf( \".\" ) >= 0 ) {\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split(\".\");\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\n\t\tif ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {\n\t\t\t// No jQuery handlers for this event type, and it can't have inline handlers\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an Event, Object, or just an event type string\n\t\tevent = typeof event === \"object\" ?\n\t\t\t// jQuery.Event object\n\t\t\tevent[ jQuery.expando ] ? event :\n\t\t\t// Object literal\n\t\t\tnew jQuery.Event( type, event ) :\n\t\t\t// Just the event type (string)\n\t\t\tnew jQuery.Event( type );\n\n\t\tevent.type = type;\n\t\tevent.isTrigger = true;\n\t\tevent.exclusive = exclusive;\n\t\tevent.namespace = namespaces.join( \".\" );\n\t\tevent.namespace_re = event.namespace? new RegExp(\"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\") : null;\n\t\tontype = type.indexOf( \":\" ) < 0 ? \"on\" + type : \"\";\n\n\t\t// Handle a global trigger\n\t\tif ( !elem ) {\n\n\t\t\t// TODO: Stop taunting the data cache; remove global events and always attach to document\n\t\t\tcache = jQuery.cache;\n\t\t\tfor ( i in cache ) {\n\t\t\t\tif ( cache[ i ].events && cache[ i ].events[ type ] ) {\n\t\t\t\t\tjQuery.event.trigger( event, data, cache[ i ].handle.elem, true );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data != null ? jQuery.makeArray( data ) : [];\n\t\tdata.unshift( event );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\teventPath = [[ elem, special.bindType || type ]];\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tcur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;\n\t\t\tfor ( old = elem; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push([ cur, bubbleType ]);\n\t\t\t\told = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( old === (elem.ownerDocument || document) ) {\n\t\t\t\teventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\tfor ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {\n\n\t\t\tcur = eventPath[i][0];\n\t\t\tevent.type = eventPath[i][1];\n\n\t\t\thandle = ( jQuery._data( cur, \"events\" ) || {} )[ event.type ] && jQuery._data( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\t\t\t// Note that this is a bare JS function and not a jQuery handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&\n\t\t\t\t!(type === \"click\" && jQuery.nodeName( elem, \"a\" )) && jQuery.acceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Can't use an .isFunction() check here because IE6/7 fails that test.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\t// IE<9 dies on focus/blur to hidden element (#1486)\n\t\t\t\tif ( ontype && elem[ type ] && ((type !== \"focus\" && type !== \"blur\") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\told = elem[ ontype ];\n\n\t\t\t\t\tif ( old ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\telem[ type ]();\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( old ) {\n\t\t\t\t\t\telem[ ontype ] = old;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event || window.event );\n\n\t\tvar i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,\n\t\t\thandlers = ( (jQuery._data( this, \"events\" ) || {} )[ event.type ] || []),\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\targs = core_slice.call( arguments ),\n\t\t\trun_all = !event.exclusive && !event.namespace,\n\t\t\tspecial = jQuery.event.special[ event.type ] || {},\n\t\t\thandlerQueue = [];\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[0] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers that should run if there are delegated events\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\n\t\tif ( delegateCount && !(event.button && event.type === \"click\") ) {\n\n\t\t\tfor ( cur = event.target; cur != this; cur = cur.parentNode || this ) {\n\n\t\t\t\t// Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.disabled !== true || event.type !== \"click\" ) {\n\t\t\t\t\tselMatch = {};\n\t\t\t\t\tmatches = [];\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\t\t\t\t\t\tsel = handleObj.selector;\n\n\t\t\t\t\t\tif ( selMatch[ sel ] === undefined ) {\n\t\t\t\t\t\t\tselMatch[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( selMatch[ sel ] ) {\n\t\t\t\t\t\t\tmatches.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matches.length ) {\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, matches: matches });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tif ( handlers.length > delegateCount ) {\n\t\t\thandlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });\n\t\t}\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\tfor ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {\n\t\t\tmatched = handlerQueue[ i ];\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tfor ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {\n\t\t\t\thandleObj = matched.matches[ j ];\n\n\t\t\t\t// Triggered event must either 1) be non-exclusive and have no namespace, or\n\t\t\t\t// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.data = handleObj.data;\n\t\t\t\t\tevent.handleObj = handleObj;\n\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n\t\t\t\t\t\t\t.apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tevent.result = ret;\n\t\t\t\t\t\tif ( ret === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\t// Includes some event props shared by KeyEvent and MouseEvent\n\t// *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***\n\tprops: \"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n\tfixHooks: {},\n\n\tkeyHooks: {\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\n\t\t\t// Add which for key events\n\t\t\tif ( event.which == null ) {\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tmouseHooks: {\n\t\tprops: \"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\t\t\tvar eventDoc, doc, body,\n\t\t\t\tbutton = original.button,\n\t\t\t\tfromElement = original.fromElement;\n\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\n\t\t\t\teventDoc = event.target.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\n\t\t\t}\n\n\t\t\t// Add relatedTarget, if necessary\n\t\t\tif ( !event.relatedTarget && fromElement ) {\n\t\t\t\tevent.relatedTarget = fromElement === event.target ? original.toElement : fromElement;\n\t\t\t}\n\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\t\t// Note: button is not normalized, so don't use it\n\t\t\tif ( !event.which && button !== undefined ) {\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tfix: function( event ) {\n\t\tif ( event[ jQuery.expando ] ) {\n\t\t\treturn event;\n\t\t}\n\n\t\t// Create a writable copy of the event object and normalize some properties\n\t\tvar i, prop,\n\t\t\toriginalEvent = event,\n\t\t\tfixHook = jQuery.event.fixHooks[ event.type ] || {},\n\t\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\n\n\t\tevent = jQuery.Event( originalEvent );\n\n\t\tfor ( i = copy.length; i; ) {\n\t\t\tprop = copy[ --i ];\n\t\t\tevent[ prop ] = originalEvent[ prop ];\n\t\t}\n\n\t\t// Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)\n\t\tif ( !event.target ) {\n\t\t\tevent.target = originalEvent.srcElement || document;\n\t\t}\n\n\t\t// Target should not be a text node (#504, Safari)\n\t\tif ( event.target.nodeType === 3 ) {\n\t\t\tevent.target = event.target.parentNode;\n\t\t}\n\n\t\t// For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)\n\t\tevent.metaKey = !!event.metaKey;\n\n\t\treturn fixHook.filter? fixHook.filter( event, originalEvent ) : event;\n\t},\n\n\tspecial: {\n\t\tload: {\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\n\t\tfocus: {\n\t\t\tdelegateType: \"focusin\"\n\t\t},\n\t\tblur: {\n\t\t\tdelegateType: \"focusout\"\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tsetup: function( data, namespaces, eventHandle ) {\n\t\t\t\t// We only want to do this special case on windows\n\t\t\t\tif ( jQuery.isWindow( this ) ) {\n\t\t\t\t\tthis.onbeforeunload = eventHandle;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tteardown: function( namespaces, eventHandle ) {\n\t\t\t\tif ( this.onbeforeunload === eventHandle ) {\n\t\t\t\t\tthis.onbeforeunload = null;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tsimulate: function( type, elem, event, bubble ) {\n\t\t// Piggyback on a donor event to simulate a different one.\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\n\t\t// simulated event prevents default then we do the same on the donor.\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{ type: type,\n\t\t\t\tisSimulated: true,\n\t\t\t\toriginalEvent: {}\n\t\t\t}\n\t\t);\n\t\tif ( bubble ) {\n\t\t\tjQuery.event.trigger( e, null, elem );\n\t\t} else {\n\t\t\tjQuery.event.dispatch.call( elem, e );\n\t\t}\n\t\tif ( e.isDefaultPrevented() ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n};\n\n// Some plugins are using, but it's undocumented/deprecated and will be removed.\n// The 1.7 special event interface should provide all the hooks needed now.\njQuery.event.handle = jQuery.event.dispatch;\n\njQuery.removeEvent = document.removeEventListener ?\n\tfunction( elem, type, handle ) {\n\t\tif ( elem.removeEventListener ) {\n\t\t\telem.removeEventListener( type, handle, false );\n\t\t}\n\t} :\n\tfunction( elem, type, handle ) {\n\t\tvar name = \"on\" + type;\n\n\t\tif ( elem.detachEvent ) {\n\n\t\t\t// #8545, #7054, preventing memory leaks for custom events in IE6-8\n\t\t\t// detachEvent needed property on element, by name of that event, to properly expose it to GC\n\t\t\tif ( typeof elem[ name ] === \"undefined\" ) {\n\t\t\t\telem[ name ] = null;\n\t\t\t}\n\n\t\t\telem.detachEvent( name, handle );\n\t\t}\n\t};\n\njQuery.Event = function( src, props ) {\n\t// Allow instantiation without the 'new' keyword\n\tif ( !(this instanceof jQuery.Event) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||\n\t\t\tsrc.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\nfunction returnFalse() {\n\treturn false;\n}\nfunction returnTrue() {\n\treturn true;\n}\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tpreventDefault: function() {\n\t\tthis.isDefaultPrevented = returnTrue;\n\n\t\tvar e = this.originalEvent;\n\t\tif ( !e ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// if preventDefault exists run it on the original event\n\t\tif ( e.preventDefault ) {\n\t\t\te.preventDefault();\n\n\t\t// otherwise set the returnValue property of the original event to false (IE)\n\t\t} else {\n\t\t\te.returnValue = false;\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tthis.isPropagationStopped = returnTrue;\n\n\t\tvar e = this.originalEvent;\n\t\tif ( !e ) {\n\t\t\treturn;\n\t\t}\n\t\t// if stopPropagation exists run it on the original event\n\t\tif ( e.stopPropagation ) {\n\t\t\te.stopPropagation();\n\t\t}\n\t\t// otherwise set the cancelBubble property of the original event to true (IE)\n\t\te.cancelBubble = true;\n\t},\n\tstopImmediatePropagation: function() {\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\t\tthis.stopPropagation();\n\t},\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse\n};\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\njQuery.each({\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj,\n\t\t\t\tselector = handleObj.selector;\n\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\n// IE submit delegation\nif ( !jQuery.support.submitBubbles ) {\n\n\tjQuery.event.special.submit = {\n\t\tsetup: function() {\n\t\t\t// Only need this for delegated form submit events\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Lazy-add a submit handler when a descendant form may potentially be submitted\n\t\t\tjQuery.event.add( this, \"click._submit keypress._submit\", function( e ) {\n\t\t\t\t// Node name check avoids a VML-related crash in IE (#9807)\n\t\t\t\tvar elem = e.target,\n\t\t\t\t\tform = jQuery.nodeName( elem, \"input\" ) || jQuery.nodeName( elem, \"button\" ) ? elem.form : undefined;\n\t\t\t\tif ( form && !jQuery._data( form, \"_submit_attached\" ) ) {\n\t\t\t\t\tjQuery.event.add( form, \"submit._submit\", function( event ) {\n\t\t\t\t\t\tevent._submit_bubble = true;\n\t\t\t\t\t});\n\t\t\t\t\tjQuery._data( form, \"_submit_attached\", true );\n\t\t\t\t}\n\t\t\t});\n\t\t\t// return undefined since we don't need an event listener\n\t\t},\n\n\t\tpostDispatch: function( event ) {\n\t\t\t// If form was submitted by the user, bubble the event up the tree\n\t\t\tif ( event._submit_bubble ) {\n\t\t\t\tdelete event._submit_bubble;\n\t\t\t\tif ( this.parentNode && !event.isTrigger ) {\n\t\t\t\t\tjQuery.event.simulate( \"submit\", this.parentNode, event, true );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tteardown: function() {\n\t\t\t// Only need this for delegated form submit events\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Remove delegated handlers; cleanData eventually reaps submit handlers attached above\n\t\t\tjQuery.event.remove( this, \"._submit\" );\n\t\t}\n\t};\n}\n\n// IE change delegation and checkbox/radio fix\nif ( !jQuery.support.changeBubbles ) {\n\n\tjQuery.event.special.change = {\n\n\t\tsetup: function() {\n\n\t\t\tif ( rformElems.test( this.nodeName ) ) {\n\t\t\t\t// IE doesn't fire change on a check/radio until blur; trigger it on click\n\t\t\t\t// after a propertychange. Eat the blur-change in special.change.handle.\n\t\t\t\t// This still fires onchange a second time for check/radio after blur.\n\t\t\t\tif ( this.type === \"checkbox\" || this.type === \"radio\" ) {\n\t\t\t\t\tjQuery.event.add( this, \"propertychange._change\", function( event ) {\n\t\t\t\t\t\tif ( event.originalEvent.propertyName === \"checked\" ) {\n\t\t\t\t\t\t\tthis._just_changed = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tjQuery.event.add( this, \"click._change\", function( event ) {\n\t\t\t\t\t\tif ( this._just_changed && !event.isTrigger ) {\n\t\t\t\t\t\t\tthis._just_changed = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Allow triggered, simulated change events (#11500)\n\t\t\t\t\t\tjQuery.event.simulate( \"change\", this, event, true );\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// Delegated event; lazy-add a change handler on descendant inputs\n\t\t\tjQuery.event.add( this, \"beforeactivate._change\", function( e ) {\n\t\t\t\tvar elem = e.target;\n\n\t\t\t\tif ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, \"_change_attached\" ) ) {\n\t\t\t\t\tjQuery.event.add( elem, \"change._change\", function( event ) {\n\t\t\t\t\t\tif ( this.parentNode && !event.isSimulated && !event.isTrigger ) {\n\t\t\t\t\t\t\tjQuery.event.simulate( \"change\", this.parentNode, event, true );\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tjQuery._data( elem, \"_change_attached\", true );\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\thandle: function( event ) {\n\t\t\tvar elem = event.target;\n\n\t\t\t// Swallow native change events from checkbox/radio, we already triggered them above\n\t\t\tif ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== \"radio\" && elem.type !== \"checkbox\") ) {\n\t\t\t\treturn event.handleObj.handler.apply( this, arguments );\n\t\t\t}\n\t\t},\n\n\t\tteardown: function() {\n\t\t\tjQuery.event.remove( this, \"._change\" );\n\n\t\t\treturn !rformElems.test( this.nodeName );\n\t\t}\n\t};\n}\n\n// Create \"bubbling\" focus and blur events\nif ( !jQuery.support.focusinBubbles ) {\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler while someone wants focusin/focusout\n\t\tvar attaches = 0,\n\t\t\thandler = function( event ) {\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\n\t\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\t\t\t\tif ( attaches++ === 0 ) {\n\t\t\t\t\tdocument.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tif ( --attaches === 0 ) {\n\t\t\t\t\tdocument.removeEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\njQuery.fn.extend({\n\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\n\t\tvar origFn, type;\n\n\t\t// Types can be a map of types/handlers\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-Object, selector, data )\n\t\t\tif ( typeof selector !== \"string\" ) { // && selector != null\n\t\t\t\t// ( types-Object, data )\n\t\t\t\tdata = data || selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( data == null && fn == null ) {\n\t\t\t// ( types, fn )\n\t\t\tfn = selector;\n\t\t\tdata = selector = undefined;\n\t\t} else if ( fn == null ) {\n\t\t\tif ( typeof selector === \"string\" ) {\n\t\t\t\t// ( types, selector, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = undefined;\n\t\t\t} else {\n\t\t\t\t// ( types, data, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t} else if ( !fn ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( one === 1 ) {\n\t\t\torigFn = fn;\n\t\t\tfn = function( event ) {\n\t\t\t\t// Can use an empty set, since event contains the info\n\t\t\t\tjQuery().off( event );\n\t\t\t\treturn origFn.apply( this, arguments );\n\t\t\t};\n\t\t\t// Use same guid so caller can remove using origFn\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\n\t\t});\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn this.on( types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t});\n\t},\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tlive: function( types, data, fn ) {\n\t\tjQuery( this.context ).on( types, this.selector, data, fn );\n\t\treturn this;\n\t},\n\tdie: function( types, fn ) {\n\t\tjQuery( this.context ).off( types, this.selector || \"**\", fn );\n\t\treturn this;\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\n\t},\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t});\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tif ( this[0] ) {\n\t\t\treturn jQuery.event.trigger( type, data, this[0], true );\n\t\t}\n\t},\n\n\ttoggle: function( fn ) {\n\t\t// Save reference to arguments for access in closure\n\t\tvar args = arguments,\n\t\t\tguid = fn.guid || jQuery.guid++,\n\t\t\ti = 0,\n\t\t\ttoggler = function( event ) {\n\t\t\t\t// Figure out which function to execute\n\t\t\t\tvar lastToggle = ( jQuery._data( this, \"lastToggle\" + fn.guid ) || 0 ) % i;\n\t\t\t\tjQuery._data( this, \"lastToggle\" + fn.guid, lastToggle + 1 );\n\n\t\t\t\t// Make sure that clicks stop\n\t\t\t\tevent.preventDefault();\n\n\t\t\t\t// and execute the function\n\t\t\t\treturn args[ lastToggle ].apply( this, arguments ) || false;\n\t\t\t};\n\n\t\t// link all the functions, so any of them can unbind this click handler\n\t\ttoggler.guid = guid;\n\t\twhile ( i < args.length ) {\n\t\t\targs[ i++ ].guid = guid;\n\t\t}\n\n\t\treturn this.click( toggler );\n\t},\n\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t}\n});\n\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\n\n\t// Handle event binding\n\tjQuery.fn[ name ] = function( data, fn ) {\n\t\tif ( fn == null ) {\n\t\t\tfn = data;\n\t\t\tdata = null;\n\t\t}\n\n\t\treturn arguments.length > 0 ?\n\t\t\tthis.on( name, null, data, fn ) :\n\t\t\tthis.trigger( name );\n\t};\n\n\tif ( rkeyEvent.test( name ) ) {\n\t\tjQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;\n\t}\n\n\tif ( rmouseEvent.test( name ) ) {\n\t\tjQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;\n\t}\n});\n/*!\n * Sizzle CSS Selector Engine\n * Copyright 2012 jQuery Foundation and other contributors\n * Released under the MIT license\n * http://sizzlejs.com/\n */\n(function( window, undefined ) {\n\nvar cachedruns,\n\tassertGetIdNotName,\n\tExpr,\n\tgetText,\n\tisXML,\n\tcontains,\n\tcompile,\n\tsortOrder,\n\thasDuplicate,\n\toutermostContext,\n\n\tbaseHasDuplicate = true,\n\tstrundefined = \"undefined\",\n\n\texpando = ( \"sizcache\" + Math.random() ).replace( \".\", \"\" ),\n\n\tToken = String,\n\tdocument = window.document,\n\tdocElem = document.documentElement,\n\tdirruns = 0,\n\tdone = 0,\n\tpop = [].pop,\n\tpush = [].push,\n\tslice = [].slice,\n\t// Use a stripped-down indexOf if a native one is unavailable\n\tindexOf = [].indexOf || function( elem ) {\n\t\tvar i = 0,\n\t\t\tlen = this.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( this[i] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\t// Augment a function for special use by Sizzle\n\tmarkFunction = function( fn, value ) {\n\t\tfn[ expando ] = value == null || value;\n\t\treturn fn;\n\t},\n\n\tcreateCache = function() {\n\t\tvar cache = {},\n\t\t\tkeys = [];\n\n\t\treturn markFunction(function( key, value ) {\n\t\t\t// Only keep the most recent entries\n\t\t\tif ( keys.push( key ) > Expr.cacheLength ) {\n\t\t\t\tdelete cache[ keys.shift() ];\n\t\t\t}\n\n\t\t\t// Retrieve with (key + \" \") to avoid collision with native Object.prototype properties (see Issue #157)\n\t\t\treturn (cache[ key + \" \" ] = value);\n\t\t}, cache );\n\t},\n\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\n\t// Regex\n\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\t// http://www.w3.org/TR/css3-syntax/#characters\n\tcharacterEncoding = \"(?:\\\\\\\\.|[-\\\\w]|[^\\\\x00-\\\\xa0])+\",\n\n\t// Loosely modeled on CSS identifier characters\n\t// An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\n\n\t// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\n\toperators = \"([*^$|!~]?=)\",\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")\" + whitespace +\n\t\t\"*(?:\" + operators + whitespace + \"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\" + identifier + \")|)|)\" + whitespace + \"*\\\\]\",\n\n\t// Prefer arguments not in parens/brackets,\n\t//   then attribute selectors and non-pseudos (denoted by :),\n\t//   then anything else\n\t// These preferences are here to reduce the number of selectors\n\t//   needing tokenize in the PSEUDO preFilter\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\((?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\2|([^()[\\\\]]*|(?:(?:\" + attributes + \")|[^:]|\\\\\\\\.)*|.*))\\\\)|)\",\n\n\t// For matchExpr.POS and matchExpr.needsContext\n\tpos = \":(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" + whitespace +\n\t\t\"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([\\\\x20\\\\t\\\\r\\\\n\\\\f>+~])\" + whitespace + \"*\" ),\n\trpseudo = new RegExp( pseudos ),\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w\\-]+)|(\\w+)|\\.([\\w\\-]+))$/,\n\n\trnot = /^:not/,\n\trsibling = /[\\x20\\t\\r\\n\\f]*[+~]/,\n\trendsWithNot = /:not\\($/,\n\n\trheader = /h\\d/i,\n\trinputs = /input|select|textarea|button/i,\n\n\trbackslash = /\\\\(?!\\\\)/g,\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\n\t\t\"NAME\": new RegExp( \"^\\\\[name=['\\\"]?(\" + characterEncoding + \")['\\\"]?\\\\]\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"POS\": new RegExp( pos, \"i\" ),\n\t\t\"CHILD\": new RegExp( \"^:(only|nth|first|last)-child(?:\\\\(\" + whitespace +\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t// For use in libraries implementing .is()\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|\" + pos, \"i\" )\n\t},\n\n\t// Support\n\n\t// Used for testing something on an element\n\tassert = function( fn ) {\n\t\tvar div = document.createElement(\"div\");\n\n\t\ttry {\n\t\t\treturn fn( div );\n\t\t} catch (e) {\n\t\t\treturn false;\n\t\t} finally {\n\t\t\t// release memory in IE\n\t\t\tdiv = null;\n\t\t}\n\t},\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tassertTagNameNoComments = assert(function( div ) {\n\t\tdiv.appendChild( document.createComment(\"\") );\n\t\treturn !div.getElementsByTagName(\"*\").length;\n\t}),\n\n\t// Check if getAttribute returns normalized href attributes\n\tassertHrefNotNormalized = assert(function( div ) {\n\t\tdiv.innerHTML = \"<a href='#'></a>\";\n\t\treturn div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&\n\t\t\tdiv.firstChild.getAttribute(\"href\") === \"#\";\n\t}),\n\n\t// Check if attributes should be retrieved by attribute nodes\n\tassertAttributes = assert(function( div ) {\n\t\tdiv.innerHTML = \"<select></select>\";\n\t\tvar type = typeof div.lastChild.getAttribute(\"multiple\");\n\t\t// IE8 returns a string for some attributes even when not present\n\t\treturn type !== \"boolean\" && type !== \"string\";\n\t}),\n\n\t// Check if getElementsByClassName can be trusted\n\tassertUsableClassName = assert(function( div ) {\n\t\t// Opera can't find a second classname (in 9.6)\n\t\tdiv.innerHTML = \"<div class='hidden e'></div><div class='hidden'></div>\";\n\t\tif ( !div.getElementsByClassName || !div.getElementsByClassName(\"e\").length ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Safari 3.2 caches class attributes and doesn't catch changes\n\t\tdiv.lastChild.className = \"e\";\n\t\treturn div.getElementsByClassName(\"e\").length === 2;\n\t}),\n\n\t// Check if getElementById returns elements by name\n\t// Check if getElementsByName privileges form controls or returns elements by ID\n\tassertUsableName = assert(function( div ) {\n\t\t// Inject content\n\t\tdiv.id = expando + 0;\n\t\tdiv.innerHTML = \"<a name='\" + expando + \"'></a><div name='\" + expando + \"'></div>\";\n\t\tdocElem.insertBefore( div, docElem.firstChild );\n\n\t\t// Test\n\t\tvar pass = document.getElementsByName &&\n\t\t\t// buggy browsers will return fewer than the correct 2\n\t\t\tdocument.getElementsByName( expando ).length === 2 +\n\t\t\t// buggy browsers will return more than the correct 0\n\t\t\tdocument.getElementsByName( expando + 0 ).length;\n\t\tassertGetIdNotName = !document.getElementById( expando );\n\n\t\t// Cleanup\n\t\tdocElem.removeChild( div );\n\n\t\treturn pass;\n\t});\n\n// If slice is not available, provide a backup\ntry {\n\tslice.call( docElem.childNodes, 0 )[0].nodeType;\n} catch ( e ) {\n\tslice = function( i ) {\n\t\tvar elem,\n\t\t\tresults = [];\n\t\tfor ( ; (elem = this[i]); i++ ) {\n\t\t\tresults.push( elem );\n\t\t}\n\t\treturn results;\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tresults = results || [];\n\tcontext = context || document;\n\tvar match, elem, xml, m,\n\t\tnodeType = context.nodeType;\n\n\tif ( !selector || typeof selector !== \"string\" ) {\n\t\treturn results;\n\t}\n\n\tif ( nodeType !== 1 && nodeType !== 9 ) {\n\t\treturn [];\n\t}\n\n\txml = isXML( context );\n\n\tif ( !xml && !seed ) {\n\t\tif ( (match = rquickExpr.exec( selector )) ) {\n\t\t\t// Speed-up: Sizzle(\"#ID\")\n\t\t\tif ( (m = match[1]) ) {\n\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\telem = context.getElementById( m );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Context is not a document\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\n\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Speed-up: Sizzle(\"TAG\")\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tpush.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );\n\t\t\t\treturn results;\n\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\n\t\t\t} else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {\n\t\t\t\tpush.apply( results, slice.call(context.getElementsByClassName( m ), 0) );\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed, xml );\n}\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\treturn Sizzle( expr, null, null, [ elem ] ).length > 0;\n};\n\n// Returns a function to use in pseudos for input types\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n// Returns a function to use in pseudos for buttons\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\n\t};\n}\n\n// Returns a function to use in pseudos for positionals\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction(function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction(function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( nodeType ) {\n\t\tif ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t\t// Use textContent for elements\n\t\t\t// innerText usage removed for consistency of new lines (see #11153)\n\t\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\t\treturn elem.textContent;\n\t\t\t} else {\n\t\t\t\t// Traverse its children\n\t\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\t\tret += getText( elem );\n\t\t\t\t}\n\t\t\t}\n\t\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\t\treturn elem.nodeValue;\n\t\t}\n\t\t// Do not include comment or processing instruction nodes\n\t} else {\n\n\t\t// If no nodeType, this is expected to be an array\n\t\tfor ( ; (node = elem[i]); i++ ) {\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t}\n\treturn ret;\n};\n\nisXML = Sizzle.isXML = function( elem ) {\n\t// documentElement is verified for cases where it doesn't yet exist\n\t// (such as loading iframes in IE - #4833)\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\n};\n\n// Element contains another\ncontains = Sizzle.contains = docElem.contains ?\n\tfunction( a, b ) {\n\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\tbup = b && b.parentNode;\n\t\treturn a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );\n\t} :\n\tdocElem.compareDocumentPosition ?\n\tfunction( a, b ) {\n\t\treturn b && !!( a.compareDocumentPosition( b ) & 16 );\n\t} :\n\tfunction( a, b ) {\n\t\twhile ( (b = b.parentNode) ) {\n\t\t\tif ( b === a ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t};\n\nSizzle.attr = function( elem, name ) {\n\tvar val,\n\t\txml = isXML( elem );\n\n\tif ( !xml ) {\n\t\tname = name.toLowerCase();\n\t}\n\tif ( (val = Expr.attrHandle[ name ]) ) {\n\t\treturn val( elem );\n\t}\n\tif ( xml || assertAttributes ) {\n\t\treturn elem.getAttribute( name );\n\t}\n\tval = elem.getAttributeNode( name );\n\treturn val ?\n\t\ttypeof elem[ name ] === \"boolean\" ?\n\t\t\telem[ name ] ? name : null :\n\t\t\tval.specified ? val.value : null :\n\t\tnull;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\t// IE6/7 return a modified href\n\tattrHandle: assertHrefNotNormalized ?\n\t\t{} :\n\t\t{\n\t\t\t\"href\": function( elem ) {\n\t\t\t\treturn elem.getAttribute( \"href\", 2 );\n\t\t\t},\n\t\t\t\"type\": function( elem ) {\n\t\t\t\treturn elem.getAttribute(\"type\");\n\t\t\t}\n\t\t},\n\n\tfind: {\n\t\t\"ID\": assertGetIdNotName ?\n\t\t\tfunction( id, context, xml ) {\n\t\t\t\tif ( typeof context.getElementById !== strundefined && !xml ) {\n\t\t\t\t\tvar m = context.getElementById( id );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\treturn m && m.parentNode ? [m] : [];\n\t\t\t\t}\n\t\t\t} :\n\t\t\tfunction( id, context, xml ) {\n\t\t\t\tif ( typeof context.getElementById !== strundefined && !xml ) {\n\t\t\t\t\tvar m = context.getElementById( id );\n\n\t\t\t\t\treturn m ?\n\t\t\t\t\t\tm.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode(\"id\").value === id ?\n\t\t\t\t\t\t\t[m] :\n\t\t\t\t\t\t\tundefined :\n\t\t\t\t\t\t[];\n\t\t\t\t}\n\t\t\t},\n\n\t\t\"TAG\": assertTagNameNoComments ?\n\t\t\tfunction( tag, context ) {\n\t\t\t\tif ( typeof context.getElementsByTagName !== strundefined ) {\n\t\t\t\t\treturn context.getElementsByTagName( tag );\n\t\t\t\t}\n\t\t\t} :\n\t\t\tfunction( tag, context ) {\n\t\t\t\tvar results = context.getElementsByTagName( tag );\n\n\t\t\t\t// Filter out possible comments\n\t\t\t\tif ( tag === \"*\" ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\ttmp = [],\n\t\t\t\t\t\ti = 0;\n\n\t\t\t\t\tfor ( ; (elem = results[i]); i++ ) {\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn tmp;\n\t\t\t\t}\n\t\t\t\treturn results;\n\t\t\t},\n\n\t\t\"NAME\": assertUsableName && function( tag, context ) {\n\t\t\tif ( typeof context.getElementsByName !== strundefined ) {\n\t\t\t\treturn context.getElementsByName( name );\n\t\t\t}\n\t\t},\n\n\t\t\"CLASS\": assertUsableClassName && function( className, context, xml ) {\n\t\t\tif ( typeof context.getElementsByClassName !== strundefined && !xml ) {\n\t\t\t\treturn context.getElementsByClassName( className );\n\t\t\t}\n\t\t}\n\t},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[1] = match[1].replace( rbackslash, \"\" );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[3] = ( match[4] || match[5] || \"\" ).replace( rbackslash, \"\" );\n\n\t\t\tif ( match[2] === \"~=\" ) {\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t3 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t4 sign of xn-component\n\t\t\t\t5 x of xn-component\n\t\t\t\t6 sign of y-component\n\t\t\t\t7 y of y-component\n\t\t\t*/\n\t\t\tmatch[1] = match[1].toLowerCase();\n\n\t\t\tif ( match[1] === \"nth\" ) {\n\t\t\t\t// nth-child requires argument\n\t\t\t\tif ( !match[2] ) {\n\t\t\t\t\tSizzle.error( match[0] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === \"even\" || match[2] === \"odd\" ) );\n\t\t\t\tmatch[4] = +( ( match[6] + match[7] ) || match[2] === \"odd\" );\n\n\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tSizzle.error( match[0] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar unquoted, excess;\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif ( match[3] ) {\n\t\t\t\tmatch[2] = match[3];\n\t\t\t} else if ( (unquoted = match[4]) ) {\n\t\t\t\t// Only check arguments that contain a pseudo\n\t\t\t\tif ( rpseudo.test(unquoted) &&\n\t\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t\t(excess = tokenize( unquoted, true )) &&\n\t\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\n\n\t\t\t\t\t// excess is a negative index\n\t\t\t\t\tunquoted = unquoted.slice( 0, excess );\n\t\t\t\t\tmatch[0] = match[0].slice( 0, excess );\n\t\t\t\t}\n\t\t\t\tmatch[2] = unquoted;\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\t\t\"ID\": assertGetIdNotName ?\n\t\t\tfunction( id ) {\n\t\t\t\tid = id.replace( rbackslash, \"\" );\n\t\t\t\treturn function( elem ) {\n\t\t\t\t\treturn elem.getAttribute(\"id\") === id;\n\t\t\t\t};\n\t\t\t} :\n\t\t\tfunction( id ) {\n\t\t\t\tid = id.replace( rbackslash, \"\" );\n\t\t\t\treturn function( elem ) {\n\t\t\t\t\tvar node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode(\"id\");\n\t\t\t\t\treturn node && node.value === id;\n\t\t\t\t};\n\t\t\t},\n\n\t\t\"TAG\": function( nodeName ) {\n\t\t\tif ( nodeName === \"*\" ) {\n\t\t\t\treturn function() { return true; };\n\t\t\t}\n\t\t\tnodeName = nodeName.replace( rbackslash, \"\" ).toLowerCase();\n\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ expando ][ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\n\t\t\t\tclassCache( className, function( elem ) {\n\t\t\t\t\treturn pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute(\"class\")) || \"\" );\n\t\t\t\t});\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem, context ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.substr( result.length - check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.substr( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, argument, first, last ) {\n\n\t\t\tif ( type === \"nth\" ) {\n\t\t\t\treturn function( elem ) {\n\t\t\t\t\tvar node, diff,\n\t\t\t\t\t\tparent = elem.parentNode;\n\n\t\t\t\t\tif ( first === 1 && last === 0 ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( parent ) {\n\t\t\t\t\t\tdiff = 0;\n\t\t\t\t\t\tfor ( node = parent.firstChild; node; node = node.nextSibling ) {\n\t\t\t\t\t\t\tif ( node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\tdiff++;\n\t\t\t\t\t\t\t\tif ( elem === node ) {\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Incorporate the offset (or cast to NaN), then check against cycle size\n\t\t\t\t\tdiff -= last;\n\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = elem;\n\n\t\t\t\tswitch ( type ) {\n\t\t\t\t\tcase \"only\":\n\t\t\t\t\tcase \"first\":\n\t\t\t\t\t\twhile ( (node = node.previousSibling) ) {\n\t\t\t\t\t\t\tif ( node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ( type === \"first\" ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tnode = elem;\n\n\t\t\t\t\t\t/* falls through */\n\t\t\t\t\tcase \"last\":\n\t\t\t\t\t\twhile ( (node = node.nextSibling) ) {\n\t\t\t\t\t\t\tif ( node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf.call( seed, matched[i] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\t\t\"not\": markFunction(function( selector ) {\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tinput[0] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\t\"has\": markFunction(function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\t\"contains\": markFunction(function( text ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t\"enabled\": function( elem ) {\n\t\t\treturn elem.disabled === false;\n\t\t},\n\n\t\t\"disabled\": function( elem ) {\n\t\t\treturn elem.disabled === true;\n\t\t},\n\n\t\t\"checked\": function( elem ) {\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\n\t\t},\n\n\t\t\"empty\": function( elem ) {\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),\n\t\t\t//   not comment, processing instructions, or others\n\t\t\t// Thanks to Diego Perini for the nodeName shortcut\n\t\t\t//   Greater than \"@\" means alpha characters (specifically not starting with \"#\" or \"?\")\n\t\t\tvar nodeType;\n\t\t\telem = elem.firstChild;\n\t\t\twhile ( elem ) {\n\t\t\t\tif ( elem.nodeName > \"@\" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\telem = elem.nextSibling;\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar type, attr;\n\t\t\t// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)\n\t\t\t// use getAttribute instead to test this case\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\t(type = elem.type) === \"text\" &&\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === type );\n\t\t},\n\n\t\t// Input types\n\t\t\"radio\": createInputPseudo(\"radio\"),\n\t\t\"checkbox\": createInputPseudo(\"checkbox\"),\n\t\t\"file\": createInputPseudo(\"file\"),\n\t\t\"password\": createInputPseudo(\"password\"),\n\t\t\"image\": createInputPseudo(\"image\"),\n\n\t\t\"submit\": createButtonPseudo(\"submit\"),\n\t\t\"reset\": createButtonPseudo(\"reset\"),\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\tvar doc = elem.ownerDocument;\n\t\t\treturn elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n\t\t},\n\n\t\t\"active\": function( elem ) {\n\t\t\treturn elem === elem.ownerDocument.activeElement;\n\t\t},\n\n\t\t// Positional types\n\t\t\"first\": createPositionalPseudo(function() {\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t}),\n\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tfor ( var i = 0; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tfor ( var i = 1; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tfor ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tfor ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\nfunction siblingCheck( a, b, ret ) {\n\tif ( a === b ) {\n\t\treturn ret;\n\t}\n\n\tvar cur = a.nextSibling;\n\n\twhile ( cur ) {\n\t\tif ( cur === b ) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tcur = cur.nextSibling;\n\t}\n\n\treturn 1;\n}\n\nsortOrder = docElem.compareDocumentPosition ?\n\tfunction( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn ( !a.compareDocumentPosition || !b.compareDocumentPosition ?\n\t\t\ta.compareDocumentPosition :\n\t\t\ta.compareDocumentPosition(b) & 4\n\t\t) ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\t\t// The nodes are identical, we can exit early\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\n\t\t// Fallback to using sourceIndex (in IE) if it's available on both nodes\n\t\t} else if ( a.sourceIndex && b.sourceIndex ) {\n\t\t\treturn a.sourceIndex - b.sourceIndex;\n\t\t}\n\n\t\tvar al, bl,\n\t\t\tap = [],\n\t\t\tbp = [],\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tcur = aup;\n\n\t\t// If the nodes are siblings (or identical) we can do a quick check\n\t\tif ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\n\t\t// If no parents were found then the nodes are disconnected\n\t\t} else if ( !aup ) {\n\t\t\treturn -1;\n\n\t\t} else if ( !bup ) {\n\t\t\treturn 1;\n\t\t}\n\n\t\t// Otherwise they're somewhere else in the tree so we need\n\t\t// to build up a full list of the parentNodes for comparison\n\t\twhile ( cur ) {\n\t\t\tap.unshift( cur );\n\t\t\tcur = cur.parentNode;\n\t\t}\n\n\t\tcur = bup;\n\n\t\twhile ( cur ) {\n\t\t\tbp.unshift( cur );\n\t\t\tcur = cur.parentNode;\n\t\t}\n\n\t\tal = ap.length;\n\t\tbl = bp.length;\n\n\t\t// Start walking down the tree looking for a discrepancy\n\t\tfor ( var i = 0; i < al && i < bl; i++ ) {\n\t\t\tif ( ap[i] !== bp[i] ) {\n\t\t\t\treturn siblingCheck( ap[i], bp[i] );\n\t\t\t}\n\t\t}\n\n\t\t// We ended someplace up the tree so do a sibling check\n\t\treturn i === al ?\n\t\t\tsiblingCheck( a, bp[i], -1 ) :\n\t\t\tsiblingCheck( ap[i], b, 1 );\n\t};\n\n// Always assume the presence of duplicates if sort doesn't\n// pass them to our comparison function (as in Google Chrome).\n[0, 0].sort( sortOrder );\nbaseHasDuplicate = !hasDuplicate;\n\n// Document sorting and removing duplicates\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\ti = 1,\n\t\tj = 0;\n\n\thasDuplicate = baseHasDuplicate;\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\tfor ( ; (elem = results[i]); i++ ) {\n\t\t\tif ( elem === results[ i - 1 ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\treturn results;\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\nfunction tokenize( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ expando ][ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\n\t\t\tif ( match ) {\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( tokens = [] );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\n\t\t\ttokens.push( matched = new Token( match.shift() ) );\n\t\t\tsoFar = soFar.slice( matched.length );\n\n\t\t\t// Cast descendant combinators to space\n\t\t\tmatched.type = match[0].replace( rtrim, \" \" );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\n\n\t\t\t\ttokens.push( matched = new Token( match.shift() ) );\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t\tmatched.type = type;\n\t\t\t\tmatched.matches = match;\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tcheckNonElements = base && combinator.dir === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\tif ( checkNonElements || elem.nodeType === 1  ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n\t\t\tif ( !xml ) {\n\t\t\t\tvar cache,\n\t\t\t\t\tdirkey = dirruns + \" \" + doneName + \" \",\n\t\t\t\t\tcachedkey = dirkey + cachedruns;\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( checkNonElements || elem.nodeType === 1 ) {\n\t\t\t\t\t\tif ( (cache = elem[ expando ]) === cachedkey ) {\n\t\t\t\t\t\t\treturn elem.sizset;\n\t\t\t\t\t\t} else if ( typeof cache === \"string\" && cache.indexOf(dirkey) === 0 ) {\n\t\t\t\t\t\t\tif ( elem.sizset ) {\n\t\t\t\t\t\t\t\treturn elem;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\telem[ expando ] = cachedkey;\n\t\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\t\telem.sizset = true;\n\t\t\t\t\t\t\t\treturn elem;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telem.sizset = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( checkNonElements || elem.nodeType === 1 ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn elem;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[0];\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (elem = unmatched[i]) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction(function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( (elem = temp[i]) ) {\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\n\t\t\t\t\t\t(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {\n\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf.call( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\treturn ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t(checkContext = context).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\n\t\t\tmatchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && tokens.slice( 0, i - 1 ).join(\"\").replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\n\t\t\t\t\tj < len && tokens.join(\"\")\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, expandContext ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tsetMatched = [],\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\toutermost = expandContext != null,\n\t\t\t\tcontextBackup = outermostContext,\n\t\t\t\t// We must always have either seed elements or context\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", expandContext && context.parentNode || context ),\n\t\t\t\t// Nested matchers should use non-integer dirruns\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);\n\n\t\t\tif ( outermost ) {\n\t\t\t\toutermostContext = context !== document && context;\n\t\t\t\tcachedruns = superMatcher.el;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tfor ( j = 0; (matcher = elementMatchers[j]); j++ ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t\tcachedruns = ++superMatcher.el;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\tmatchedCount += i;\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tfor ( j = 0; (matcher = setMatchers[j]); j++ ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\tsuperMatcher.el = 0;\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ expando ][ selector + \" \" ];\n\n\tif ( !cached ) {\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !group ) {\n\t\t\tgroup = tokenize( selector );\n\t\t}\n\t\ti = group.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( group[i] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\n\t}\n\treturn cached;\n};\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[i], results );\n\t}\n\treturn results;\n}\n\nfunction select( selector, context, results, seed, xml ) {\n\tvar i, tokens, token, type, find,\n\t\tmatch = tokenize( selector ),\n\t\tj = match.length;\n\n\tif ( !seed ) {\n\t\t// Try to minimize operations if there is only one group\n\t\tif ( match.length === 1 ) {\n\n\t\t\t// Take a shortcut and set the context if the root selector is an ID\n\t\t\ttokens = match[0] = match[0].slice( 0 );\n\t\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n\t\t\t\t\tcontext.nodeType === 9 && !xml &&\n\t\t\t\t\tExpr.relative[ tokens[1].type ] ) {\n\n\t\t\t\tcontext = Expr.find[\"ID\"]( token.matches[0].replace( rbackslash, \"\" ), context, xml )[0];\n\t\t\t\tif ( !context ) {\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\n\t\t\t\tselector = selector.slice( tokens.shift().length );\n\t\t\t}\n\n\t\t\t// Fetch a seed set for right-to-left matching\n\t\t\tfor ( i = matchExpr[\"POS\"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {\n\t\t\t\ttoken = tokens[i];\n\n\t\t\t\t// Abort if we hit a combinator\n\t\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif ( (find = Expr.find[ type ]) ) {\n\t\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\t\tif ( (seed = find(\n\t\t\t\t\t\ttoken.matches[0].replace( rbackslash, \"\" ),\n\t\t\t\t\t\trsibling.test( tokens[0].type ) && context.parentNode || context,\n\t\t\t\t\t\txml\n\t\t\t\t\t)) ) {\n\n\t\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\t\tselector = seed.length && tokens.join(\"\");\n\t\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\t\tpush.apply( results, slice.call( seed, 0 ) );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\tcompile( selector, match )(\n\t\tseed,\n\t\tcontext,\n\t\txml,\n\t\tresults,\n\t\trsibling.test( selector )\n\t);\n\treturn results;\n}\n\nif ( document.querySelectorAll ) {\n\t(function() {\n\t\tvar disconnectedMatch,\n\t\t\toldSelect = select,\n\t\t\trescape = /'|\\\\/g,\n\t\t\trattributeQuotes = /\\=[\\x20\\t\\r\\n\\f]*([^'\"\\]]*)[\\x20\\t\\r\\n\\f]*\\]/g,\n\n\t\t\t// qSa(:focus) reports false when true (Chrome 21), no need to also add to buggyMatches since matches checks buggyQSA\n\t\t\t// A support test would require too much code (would include document ready)\n\t\t\trbuggyQSA = [ \":focus\" ],\n\n\t\t\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\t\t\t// A support test would require too much code (would include document ready)\n\t\t\t// just skip matchesSelector for :active\n\t\t\trbuggyMatches = [ \":active\" ],\n\t\t\tmatches = docElem.matchesSelector ||\n\t\t\t\tdocElem.mozMatchesSelector ||\n\t\t\t\tdocElem.webkitMatchesSelector ||\n\t\t\t\tdocElem.oMatchesSelector ||\n\t\t\t\tdocElem.msMatchesSelector;\n\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert(function( div ) {\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explictly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// http://bugs.jquery.com/ticket/12359\n\t\t\tdiv.innerHTML = \"<select><option selected=''></option></select>\";\n\n\t\t\t// IE8 - Some boolean attributes are not treated correctly\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:checked|disabled|ismap|multiple|readonly|selected|value)\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here (do not put tests after this one)\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\n\t\t\t\trbuggyQSA.push(\":checked\");\n\t\t\t}\n\t\t});\n\n\t\tassert(function( div ) {\n\n\t\t\t// Opera 10-12/IE9 - ^= $= *= and empty values\n\t\t\t// Should not select anything\n\t\t\tdiv.innerHTML = \"<p test=''></p>\";\n\t\t\tif ( div.querySelectorAll(\"[test^='']\").length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:\\\"\\\"|'')\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here (do not put tests after this one)\n\t\t\tdiv.innerHTML = \"<input type='hidden'/>\";\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\n\t\t\t\trbuggyQSA.push(\":enabled\", \":disabled\");\n\t\t\t}\n\t\t});\n\n\t\t// rbuggyQSA always contains :focus, so no need for a length check\n\t\trbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join(\"|\") );\n\n\t\tselect = function( selector, context, results, seed, xml ) {\n\t\t\t// Only use querySelectorAll when not filtering,\n\t\t\t// when this is not xml,\n\t\t\t// and when no QSA bugs apply\n\t\t\tif ( !seed && !xml && !rbuggyQSA.test( selector ) ) {\n\t\t\t\tvar groups, i,\n\t\t\t\t\told = true,\n\t\t\t\t\tnid = expando,\n\t\t\t\t\tnewContext = context,\n\t\t\t\t\tnewSelector = context.nodeType === 9 && selector;\n\n\t\t\t\t// qSA works strangely on Element-rooted queries\n\t\t\t\t// We can work around this by specifying an extra ID on the root\n\t\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\n\t\t\t\t// IE 8 doesn't work on object elements\n\t\t\t\tif ( context.nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\n\t\t\t\t\tgroups = tokenize( selector );\n\n\t\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\n\t\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontext.setAttribute( \"id\", nid );\n\t\t\t\t\t}\n\t\t\t\t\tnid = \"[id='\" + nid + \"'] \";\n\n\t\t\t\t\ti = groups.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tgroups[i] = nid + groups[i].join(\"\");\n\t\t\t\t\t}\n\t\t\t\t\tnewContext = rsibling.test( selector ) && context.parentNode || context;\n\t\t\t\t\tnewSelector = groups.join(\",\");\n\t\t\t\t}\n\n\t\t\t\tif ( newSelector ) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tpush.apply( results, slice.call( newContext.querySelectorAll(\n\t\t\t\t\t\t\tnewSelector\n\t\t\t\t\t\t), 0 ) );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t} catch(qsaError) {\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif ( !old ) {\n\t\t\t\t\t\t\tcontext.removeAttribute(\"id\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn oldSelect( selector, context, results, seed, xml );\n\t\t};\n\n\t\tif ( matches ) {\n\t\t\tassert(function( div ) {\n\t\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t\t// on a disconnected node (IE 9)\n\t\t\t\tdisconnectedMatch = matches.call( div, \"div\" );\n\n\t\t\t\t// This should fail with an exception\n\t\t\t\t// Gecko does not error, returns false instead\n\t\t\t\ttry {\n\t\t\t\t\tmatches.call( div, \"[test!='']:sizzle\" );\n\t\t\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t\t\t} catch ( e ) {}\n\t\t\t});\n\n\t\t\t// rbuggyMatches always contains :active and :focus, so no need for a length check\n\t\t\trbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join(\"|\") );\n\n\t\t\tSizzle.matchesSelector = function( elem, expr ) {\n\t\t\t\t// Make sure that attribute selectors are quoted\n\t\t\t\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\n\n\t\t\t\t// rbuggyMatches always contains :active, so no need for an existence check\n\t\t\t\tif ( !isXML( elem ) && !rbuggyMatches.test( expr ) && !rbuggyQSA.test( expr ) ) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\t\t\t\tif ( ret || disconnectedMatch ||\n\t\t\t\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\t\t\t\treturn ret;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(e) {}\n\t\t\t\t}\n\n\t\t\t\treturn Sizzle( expr, null, null, [ elem ] ).length > 0;\n\t\t\t};\n\t\t}\n\t})();\n}\n\n// Deprecated\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Back-compat\nfunction setFilters() {}\nExpr.filters = setFilters.prototype = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\n// Override sizzle attribute retrieval\nSizzle.attr = jQuery.attr;\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\njQuery.expr[\":\"] = jQuery.expr.pseudos;\njQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\n\n\n})( window );\nvar runtil = /Until$/,\n\trparentsprev = /^(?:parents|prev(?:Until|All))/,\n\tisSimple = /^.[^:#\\[\\.,]*$/,\n\trneedsContext = jQuery.expr.match.needsContext,\n\t// methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.fn.extend({\n\tfind: function( selector ) {\n\t\tvar i, l, length, n, r, ret,\n\t\t\tself = this;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn jQuery( selector ).filter(function() {\n\t\t\t\tfor ( i = 0, l = self.length; i < l; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tret = this.pushStack( \"\", \"find\", selector );\n\n\t\tfor ( i = 0, l = this.length; i < l; i++ ) {\n\t\t\tlength = ret.length;\n\t\t\tjQuery.find( selector, this[i], ret );\n\n\t\t\tif ( i > 0 ) {\n\t\t\t\t// Make sure that the results are unique\n\t\t\t\tfor ( n = length; n < ret.length; n++ ) {\n\t\t\t\t\tfor ( r = 0; r < length; r++ ) {\n\t\t\t\t\t\tif ( ret[r] === ret[n] ) {\n\t\t\t\t\t\t\tret.splice(n--, 1);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\thas: function( target ) {\n\t\tvar i,\n\t\t\ttargets = jQuery( target, this ),\n\t\t\tlen = targets.length;\n\n\t\treturn this.filter(function() {\n\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector, false), \"not\", selector);\n\t},\n\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector, true), \"filter\", selector );\n\t},\n\n\tis: function( selector ) {\n\t\treturn !!selector && (\n\t\t\ttypeof selector === \"string\" ?\n\t\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\t\trneedsContext.test( selector ) ?\n\t\t\t\t\tjQuery( selector, this.context ).index( this[0] ) >= 0 :\n\t\t\t\t\tjQuery.filter( selector, this ).length > 0 :\n\t\t\t\tthis.filter( selector ).length > 0 );\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tret = [],\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\n\t\t\t\tjQuery( selectors, context || this.context ) :\n\t\t\t\t0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tcur = this[i];\n\n\t\t\twhile ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {\n\t\t\t\tif ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {\n\t\t\t\t\tret.push( cur );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t}\n\n\t\tret = ret.length > 1 ? jQuery.unique( ret ) : ret;\n\n\t\treturn this.pushStack( ret, \"closest\", selectors );\n\t},\n\n\t// Determine the position of an element within\n\t// the matched set of elements\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;\n\t\t}\n\n\t\t// index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn jQuery.inArray( this[0], jQuery( elem ) );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn jQuery.inArray(\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[0] : elem, this );\n\t},\n\n\tadd: function( selector, context ) {\n\t\tvar set = typeof selector === \"string\" ?\n\t\t\t\tjQuery( selector, context ) :\n\t\t\t\tjQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),\n\t\t\tall = jQuery.merge( this.get(), set );\n\n\t\treturn this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?\n\t\t\tall :\n\t\t\tjQuery.unique( all ) );\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\njQuery.fn.andSelf = jQuery.fn.addBack;\n\n// A painfully simple check to see if an element is disconnected\n// from a document (should be improved, where feasible).\nfunction isDisconnected( node ) {\n\treturn !node || !node.parentNode || node.parentNode.nodeType === 11;\n}\n\nfunction sibling( cur, dir ) {\n\tdo {\n\t\tcur = cur[ dir ];\n\t} while ( cur && cur.nodeType !== 1 );\n\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn jQuery.dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn jQuery.sibling( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\treturn jQuery.nodeName( elem, \"iframe\" ) ?\n\t\t\telem.contentDocument || elem.contentWindow.document :\n\t\t\tjQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar ret = jQuery.map( this, fn, until );\n\n\t\tif ( !runtil.test( name ) ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tret = jQuery.filter( selector, ret );\n\t\t}\n\n\t\tret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;\n\n\t\tif ( this.length > 1 && rparentsprev.test( name ) ) {\n\t\t\tret = ret.reverse();\n\t\t}\n\n\t\treturn this.pushStack( ret, name, core_slice.call( arguments ).join(\",\") );\n\t};\n});\n\njQuery.extend({\n\tfilter: function( expr, elems, not ) {\n\t\tif ( not ) {\n\t\t\texpr = \":not(\" + expr + \")\";\n\t\t}\n\n\t\treturn elems.length === 1 ?\n\t\t\tjQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :\n\t\t\tjQuery.find.matches(expr, elems);\n\t},\n\n\tdir: function( elem, dir, until ) {\n\t\tvar matched = [],\n\t\t\tcur = elem[ dir ];\n\n\t\twhile ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {\n\t\t\tif ( cur.nodeType === 1 ) {\n\t\t\t\tmatched.push( cur );\n\t\t\t}\n\t\t\tcur = cur[dir];\n\t\t}\n\t\treturn matched;\n\t},\n\n\tsibling: function( n, elem ) {\n\t\tvar r = [];\n\n\t\tfor ( ; n; n = n.nextSibling ) {\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\t\tr.push( n );\n\t\t\t}\n\t\t}\n\n\t\treturn r;\n\t}\n});\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, keep ) {\n\n\t// Can't pass null or undefined to indexOf in Firefox 4\n\t// Set to 0 to skip string check\n\tqualifier = qualifier || 0;\n\n\tif ( jQuery.isFunction( qualifier ) ) {\n\t\treturn jQuery.grep(elements, function( elem, i ) {\n\t\t\tvar retVal = !!qualifier.call( elem, i, elem );\n\t\t\treturn retVal === keep;\n\t\t});\n\n\t} else if ( qualifier.nodeType ) {\n\t\treturn jQuery.grep(elements, function( elem, i ) {\n\t\t\treturn ( elem === qualifier ) === keep;\n\t\t});\n\n\t} else if ( typeof qualifier === \"string\" ) {\n\t\tvar filtered = jQuery.grep(elements, function( elem ) {\n\t\t\treturn elem.nodeType === 1;\n\t\t});\n\n\t\tif ( isSimple.test( qualifier ) ) {\n\t\t\treturn jQuery.filter(qualifier, filtered, !keep);\n\t\t} else {\n\t\t\tqualifier = jQuery.filter( qualifier, filtered );\n\t\t}\n\t}\n\n\treturn jQuery.grep(elements, function( elem, i ) {\n\t\treturn ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;\n\t});\n}\nfunction createSafeFragment( document ) {\n\tvar list = nodeNames.split( \"|\" ),\n\tsafeFrag = document.createDocumentFragment();\n\n\tif ( safeFrag.createElement ) {\n\t\twhile ( list.length ) {\n\t\t\tsafeFrag.createElement(\n\t\t\t\tlist.pop()\n\t\t\t);\n\t\t}\n\t}\n\treturn safeFrag;\n}\n\nvar nodeNames = \"abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|\" +\n\t\t\"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video\",\n\trinlinejQuery = / jQuery\\d+=\"(?:null|\\d+)\"/g,\n\trleadingWhitespace = /^\\s+/,\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n\trtagName = /<([\\w:]+)/,\n\trtbody = /<tbody/i,\n\trhtml = /<|&#?\\w+;/,\n\trnoInnerhtml = /<(?:script|style|link)/i,\n\trnocache = /<(?:script|object|embed|option|style)/i,\n\trnoshimcache = new RegExp(\"<(?:\" + nodeNames + \")[\\\\s/>]\", \"i\"),\n\trcheckableType = /^(?:checkbox|radio)$/,\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trscriptType = /\\/(java|ecma)script/i,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|\\-\\-)|[\\]\\-]{2}>\\s*$/g,\n\twrapMap = {\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\t\tlegend: [ 1, \"<fieldset>\", \"</fieldset>\" ],\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\t\tcol: [ 2, \"<table><tbody></tbody><colgroup>\", \"</colgroup></table>\" ],\n\t\tarea: [ 1, \"<map>\", \"</map>\" ],\n\t\t_default: [ 0, \"\", \"\" ]\n\t},\n\tsafeFragment = createSafeFragment( document ),\n\tfragmentDiv = safeFragment.appendChild( document.createElement(\"div\") );\n\nwrapMap.optgroup = wrapMap.option;\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,\n// unless wrapped in a div with non-breaking characters in front of it.\nif ( !jQuery.support.htmlSerialize ) {\n\twrapMap._default = [ 1, \"X<div>\", \"</div>\" ];\n}\n\njQuery.fn.extend({\n\ttext: function( value ) {\n\t\treturn jQuery.access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );\n\t\t}, null, value, arguments.length );\n\t},\n\n\twrapAll: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function(i) {\n\t\t\t\tjQuery(this).wrapAll( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[0] ) {\n\t\t\t// The elements to wrap the target around\n\t\t\tvar wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);\n\n\t\t\tif ( this[0].parentNode ) {\n\t\t\t\twrap.insertBefore( this[0] );\n\t\t\t}\n\n\t\t\twrap.map(function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstChild && elem.firstChild.nodeType === 1 ) {\n\t\t\t\t\telem = elem.firstChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function(i) {\n\t\t\t\tjQuery(this).wrapInner( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function( html ) {\n\t\tvar isFunction = jQuery.isFunction( html );\n\n\t\treturn this.each(function(i) {\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\n\t\t});\n\t},\n\n\tunwrap: function() {\n\t\treturn this.parent().each(function() {\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t\t}\n\t\t}).end();\n\t},\n\n\tappend: function() {\n\t\treturn this.domManip(arguments, true, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 ) {\n\t\t\t\tthis.appendChild( elem );\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function() {\n\t\treturn this.domManip(arguments, true, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 ) {\n\t\t\t\tthis.insertBefore( elem, this.firstChild );\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function() {\n\t\tif ( !isDisconnected( this[0] ) ) {\n\t\t\treturn this.domManip(arguments, false, function( elem ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t});\n\t\t}\n\n\t\tif ( arguments.length ) {\n\t\t\tvar set = jQuery.clean( arguments );\n\t\t\treturn this.pushStack( jQuery.merge( set, this ), \"before\", this.selector );\n\t\t}\n\t},\n\n\tafter: function() {\n\t\tif ( !isDisconnected( this[0] ) ) {\n\t\t\treturn this.domManip(arguments, false, function( elem ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t});\n\t\t}\n\n\t\tif ( arguments.length ) {\n\t\t\tvar set = jQuery.clean( arguments );\n\t\t\treturn this.pushStack( jQuery.merge( this, set ), \"after\", this.selector );\n\t\t}\n\t},\n\n\t// keepData is for internal use only--do not document\n\tremove: function( selector, keepData ) {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\tif ( !selector || jQuery.filter( selector, [ elem ] ).length ) {\n\t\t\t\tif ( !keepData && elem.nodeType === 1 ) {\n\t\t\t\t\tjQuery.cleanData( elem.getElementsByTagName(\"*\") );\n\t\t\t\t\tjQuery.cleanData( [ elem ] );\n\t\t\t\t}\n\n\t\t\t\tif ( elem.parentNode ) {\n\t\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( elem.getElementsByTagName(\"*\") );\n\t\t\t}\n\n\t\t\t// Remove any remaining nodes\n\t\t\twhile ( elem.firstChild ) {\n\t\t\t\telem.removeChild( elem.firstChild );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map( function () {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t});\n\t},\n\n\thtml: function( value ) {\n\t\treturn jQuery.access( this, function( value ) {\n\t\t\tvar elem = this[0] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined ) {\n\t\t\t\treturn elem.nodeType === 1 ?\n\t\t\t\t\telem.innerHTML.replace( rinlinejQuery, \"\" ) :\n\t\t\t\t\tundefined;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&\n\t\t\t\t( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [\"\", \"\"] )[1].toLowerCase() ] ) {\n\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor (; i < l; i++ ) {\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\telem = this[i] || {};\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( elem.getElementsByTagName( \"*\" ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function( value ) {\n\t\tif ( !isDisconnected( this[0] ) ) {\n\t\t\t// Make sure that the elements are removed from the DOM before they are inserted\n\t\t\t// this can help fix replacing a parent with child elements\n\t\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\t\treturn this.each(function(i) {\n\t\t\t\t\tvar self = jQuery(this), old = self.html();\n\t\t\t\t\tself.replaceWith( value.call( this, i, old ) );\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif ( typeof value !== \"string\" ) {\n\t\t\t\tvalue = jQuery( value ).detach();\n\t\t\t}\n\n\t\t\treturn this.each(function() {\n\t\t\t\tvar next = this.nextSibling,\n\t\t\t\t\tparent = this.parentNode;\n\n\t\t\t\tjQuery( this ).remove();\n\n\t\t\t\tif ( next ) {\n\t\t\t\t\tjQuery(next).before( value );\n\t\t\t\t} else {\n\t\t\t\t\tjQuery(parent).append( value );\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn this.length ?\n\t\t\tthis.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), \"replaceWith\", value ) :\n\t\t\tthis;\n\t},\n\n\tdetach: function( selector ) {\n\t\treturn this.remove( selector, true );\n\t},\n\n\tdomManip: function( args, table, callback ) {\n\n\t\t// Flatten any nested arrays\n\t\targs = [].concat.apply( [], args );\n\n\t\tvar results, first, fragment, iNoClone,\n\t\t\ti = 0,\n\t\t\tvalue = args[0],\n\t\t\tscripts = [],\n\t\t\tl = this.length;\n\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\n\t\tif ( !jQuery.support.checkClone && l > 1 && typeof value === \"string\" && rchecked.test( value ) ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tjQuery(this).domManip( args, table, callback );\n\t\t\t});\n\t\t}\n\n\t\tif ( jQuery.isFunction(value) ) {\n\t\t\treturn this.each(function(i) {\n\t\t\t\tvar self = jQuery(this);\n\t\t\t\targs[0] = value.call( this, i, table ? self.html() : undefined );\n\t\t\t\tself.domManip( args, table, callback );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[0] ) {\n\t\t\tresults = jQuery.buildFragment( args, this, scripts );\n\t\t\tfragment = results.fragment;\n\t\t\tfirst = fragment.firstChild;\n\n\t\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\t\tfragment = first;\n\t\t\t}\n\n\t\t\tif ( first ) {\n\t\t\t\ttable = table && jQuery.nodeName( first, \"tr\" );\n\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\t\t// Fragments from the fragment cache must always be cloned and never used in place.\n\t\t\t\tfor ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {\n\t\t\t\t\tcallback.call(\n\t\t\t\t\t\ttable && jQuery.nodeName( this[i], \"table\" ) ?\n\t\t\t\t\t\t\tfindOrAppend( this[i], \"tbody\" ) :\n\t\t\t\t\t\t\tthis[i],\n\t\t\t\t\t\ti === iNoClone ?\n\t\t\t\t\t\t\tfragment :\n\t\t\t\t\t\t\tjQuery.clone( fragment, true, true )\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fix #11809: Avoid leaking memory\n\t\t\tfragment = first = null;\n\n\t\t\tif ( scripts.length ) {\n\t\t\t\tjQuery.each( scripts, function( i, elem ) {\n\t\t\t\t\tif ( elem.src ) {\n\t\t\t\t\t\tif ( jQuery.ajax ) {\n\t\t\t\t\t\t\tjQuery.ajax({\n\t\t\t\t\t\t\t\turl: elem.src,\n\t\t\t\t\t\t\t\ttype: \"GET\",\n\t\t\t\t\t\t\t\tdataType: \"script\",\n\t\t\t\t\t\t\t\tasync: false,\n\t\t\t\t\t\t\t\tglobal: false,\n\t\t\t\t\t\t\t\t\"throws\": true\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tjQuery.error(\"no ajax\");\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tjQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || \"\" ).replace( rcleanScript, \"\" ) );\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( elem.parentNode ) {\n\t\t\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n});\n\nfunction findOrAppend( elem, tag ) {\n\treturn elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\n\tif ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {\n\t\treturn;\n\t}\n\n\tvar type, i, l,\n\t\toldData = jQuery._data( src ),\n\t\tcurData = jQuery._data( dest, oldData ),\n\t\tevents = oldData.events;\n\n\tif ( events ) {\n\t\tdelete curData.handle;\n\t\tcurData.events = {};\n\n\t\tfor ( type in events ) {\n\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t}\n\t\t}\n\t}\n\n\t// make the cloned public data object a copy from the original\n\tif ( curData.data ) {\n\t\tcurData.data = jQuery.extend( {}, curData.data );\n\t}\n}\n\nfunction cloneFixAttributes( src, dest ) {\n\tvar nodeName;\n\n\t// We do not need to do anything for non-Elements\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\t// clearAttributes removes the attributes, which we don't want,\n\t// but also removes the attachEvent events, which we *do* want\n\tif ( dest.clearAttributes ) {\n\t\tdest.clearAttributes();\n\t}\n\n\t// mergeAttributes, in contrast, only merges back on the\n\t// original attributes, not the events\n\tif ( dest.mergeAttributes ) {\n\t\tdest.mergeAttributes( src );\n\t}\n\n\tnodeName = dest.nodeName.toLowerCase();\n\n\tif ( nodeName === \"object\" ) {\n\t\t// IE6-10 improperly clones children of object elements using classid.\n\t\t// IE10 throws NoModificationAllowedError if parent is null, #12132.\n\t\tif ( dest.parentNode ) {\n\t\t\tdest.outerHTML = src.outerHTML;\n\t\t}\n\n\t\t// This path appears unavoidable for IE9. When cloning an object\n\t\t// element in IE9, the outerHTML strategy above is not sufficient.\n\t\t// If the src has innerHTML and the destination does not,\n\t\t// copy the src.innerHTML into the dest.innerHTML. #10324\n\t\tif ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {\n\t\t\tdest.innerHTML = src.innerHTML;\n\t\t}\n\n\t} else if ( nodeName === \"input\" && rcheckableType.test( src.type ) ) {\n\t\t// IE6-8 fails to persist the checked state of a cloned checkbox\n\t\t// or radio button. Worse, IE6-7 fail to give the cloned element\n\t\t// a checked appearance if the defaultChecked value isn't also set\n\n\t\tdest.defaultChecked = dest.checked = src.checked;\n\n\t\t// IE6-7 get confused and end up setting the value of a cloned\n\t\t// checkbox/radio button to an empty string instead of \"on\"\n\t\tif ( dest.value !== src.value ) {\n\t\t\tdest.value = src.value;\n\t\t}\n\n\t// IE6-8 fails to return the selected option to the default selected\n\t// state when cloning options\n\t} else if ( nodeName === \"option\" ) {\n\t\tdest.selected = src.defaultSelected;\n\n\t// IE6-8 fails to set the defaultValue to the correct value when\n\t// cloning other types of input fields\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\n\t// IE blanks contents when cloning scripts\n\t} else if ( nodeName === \"script\" && dest.text !== src.text ) {\n\t\tdest.text = src.text;\n\t}\n\n\t// Event data gets referenced instead of copied if the expando\n\t// gets copied too\n\tdest.removeAttribute( jQuery.expando );\n}\n\njQuery.buildFragment = function( args, context, scripts ) {\n\tvar fragment, cacheable, cachehit,\n\t\tfirst = args[ 0 ];\n\n\t// Set context from what may come in as undefined or a jQuery collection or a node\n\t// Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 &\n\t// also doubles as fix for #8950 where plain objects caused createDocumentFragment exception\n\tcontext = context || document;\n\tcontext = !context.nodeType && context[0] || context;\n\tcontext = context.ownerDocument || context;\n\n\t// Only cache \"small\" (1/2 KB) HTML strings that are associated with the main document\n\t// Cloning options loses the selected state, so don't cache them\n\t// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment\n\t// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache\n\t// Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501\n\tif ( args.length === 1 && typeof first === \"string\" && first.length < 512 && context === document &&\n\t\tfirst.charAt(0) === \"<\" && !rnocache.test( first ) &&\n\t\t(jQuery.support.checkClone || !rchecked.test( first )) &&\n\t\t(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {\n\n\t\t// Mark cacheable and look for a hit\n\t\tcacheable = true;\n\t\tfragment = jQuery.fragments[ first ];\n\t\tcachehit = fragment !== undefined;\n\t}\n\n\tif ( !fragment ) {\n\t\tfragment = context.createDocumentFragment();\n\t\tjQuery.clean( args, context, fragment, scripts );\n\n\t\t// Update the cache, but only store false\n\t\t// unless this is a second parsing of the same content\n\t\tif ( cacheable ) {\n\t\t\tjQuery.fragments[ first ] = cachehit && fragment;\n\t\t}\n\t}\n\n\treturn { fragment: fragment, cacheable: cacheable };\n};\n\njQuery.fragments = {};\n\njQuery.each({\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\ti = 0,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tl = insert.length,\n\t\t\tparent = this.length === 1 && this[0].parentNode;\n\n\t\tif ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {\n\t\t\tinsert[ original ]( this[0] );\n\t\t\treturn this;\n\t\t} else {\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\telems = ( i > 0 ? this.clone(true) : this ).get();\n\t\t\t\tjQuery( insert[i] )[ original ]( elems );\n\t\t\t\tret = ret.concat( elems );\n\t\t\t}\n\n\t\t\treturn this.pushStack( ret, name, insert.selector );\n\t\t}\n\t};\n});\n\nfunction getAll( elem ) {\n\tif ( typeof elem.getElementsByTagName !== \"undefined\" ) {\n\t\treturn elem.getElementsByTagName( \"*\" );\n\n\t} else if ( typeof elem.querySelectorAll !== \"undefined\" ) {\n\t\treturn elem.querySelectorAll( \"*\" );\n\n\t} else {\n\t\treturn [];\n\t}\n}\n\n// Used in clean, fixes the defaultChecked property\nfunction fixDefaultChecked( elem ) {\n\tif ( rcheckableType.test( elem.type ) ) {\n\t\telem.defaultChecked = elem.checked;\n\t}\n}\n\njQuery.extend({\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar srcElements,\n\t\t\tdestElements,\n\t\t\ti,\n\t\t\tclone;\n\n\t\tif ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( \"<\" + elem.nodeName + \">\" ) ) {\n\t\t\tclone = elem.cloneNode( true );\n\n\t\t// IE<=8 does not properly clone detached, unknown element nodes\n\t\t} else {\n\t\t\tfragmentDiv.innerHTML = elem.outerHTML;\n\t\t\tfragmentDiv.removeChild( clone = fragmentDiv.firstChild );\n\t\t}\n\n\t\tif ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&\n\t\t\t\t(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {\n\t\t\t// IE copies events bound via attachEvent when using cloneNode.\n\t\t\t// Calling detachEvent on the clone will also remove the events\n\t\t\t// from the original. In order to get around this, we use some\n\t\t\t// proprietary methods to clear the events. Thanks to MooTools\n\t\t\t// guys for this hotness.\n\n\t\t\tcloneFixAttributes( elem, clone );\n\n\t\t\t// Using Sizzle here is crazy slow, so we use getElementsByTagName instead\n\t\t\tsrcElements = getAll( elem );\n\t\t\tdestElements = getAll( clone );\n\n\t\t\t// Weird iteration because IE will replace the length property\n\t\t\t// with an element if you are cloning the body and one of the\n\t\t\t// elements on the page has a name or id of \"length\"\n\t\t\tfor ( i = 0; srcElements[i]; ++i ) {\n\t\t\t\t// Ensure that the destination node is not null; Fixes #9587\n\t\t\t\tif ( destElements[i] ) {\n\t\t\t\t\tcloneFixAttributes( srcElements[i], destElements[i] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tcloneCopyEvent( elem, clone );\n\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = getAll( elem );\n\t\t\t\tdestElements = getAll( clone );\n\n\t\t\t\tfor ( i = 0; srcElements[i]; ++i ) {\n\t\t\t\t\tcloneCopyEvent( srcElements[i], destElements[i] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsrcElements = destElements = null;\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tclean: function( elems, context, fragment, scripts ) {\n\t\tvar i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,\n\t\t\tsafe = context === document && safeFragment,\n\t\t\tret = [];\n\n\t\t// Ensure that context is a document\n\t\tif ( !context || typeof context.createDocumentFragment === \"undefined\" ) {\n\t\t\tcontext = document;\n\t\t}\n\n\t\t// Use the already-created safe fragment if context permits\n\t\tfor ( i = 0; (elem = elems[i]) != null; i++ ) {\n\t\t\tif ( typeof elem === \"number\" ) {\n\t\t\t\telem += \"\";\n\t\t\t}\n\n\t\t\tif ( !elem ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Convert html string into DOM nodes\n\t\t\tif ( typeof elem === \"string\" ) {\n\t\t\t\tif ( !rhtml.test( elem ) ) {\n\t\t\t\t\telem = context.createTextNode( elem );\n\t\t\t\t} else {\n\t\t\t\t\t// Ensure a safe container in which to render the html\n\t\t\t\t\tsafe = safe || createSafeFragment( context );\n\t\t\t\t\tdiv = context.createElement(\"div\");\n\t\t\t\t\tsafe.appendChild( div );\n\n\t\t\t\t\t// Fix \"XHTML\"-style tags in all browsers\n\t\t\t\t\telem = elem.replace(rxhtmlTag, \"<$1></$2>\");\n\n\t\t\t\t\t// Go to html and back, then peel off extra wrappers\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [\"\", \"\"] )[1].toLowerCase();\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\t\t\t\t\tdepth = wrap[0];\n\t\t\t\t\tdiv.innerHTML = wrap[1] + elem + wrap[2];\n\n\t\t\t\t\t// Move to the right depth\n\t\t\t\t\twhile ( depth-- ) {\n\t\t\t\t\t\tdiv = div.lastChild;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove IE's autoinserted <tbody> from table fragments\n\t\t\t\t\tif ( !jQuery.support.tbody ) {\n\n\t\t\t\t\t\t// String was a <table>, *may* have spurious <tbody>\n\t\t\t\t\t\thasBody = rtbody.test(elem);\n\t\t\t\t\t\t\ttbody = tag === \"table\" && !hasBody ?\n\t\t\t\t\t\t\t\tdiv.firstChild && div.firstChild.childNodes :\n\n\t\t\t\t\t\t\t\t// String was a bare <thead> or <tfoot>\n\t\t\t\t\t\t\t\twrap[1] === \"<table>\" && !hasBody ?\n\t\t\t\t\t\t\t\t\tdiv.childNodes :\n\t\t\t\t\t\t\t\t\t[];\n\n\t\t\t\t\t\tfor ( j = tbody.length - 1; j >= 0 ; --j ) {\n\t\t\t\t\t\t\tif ( jQuery.nodeName( tbody[ j ], \"tbody\" ) && !tbody[ j ].childNodes.length ) {\n\t\t\t\t\t\t\t\ttbody[ j ].parentNode.removeChild( tbody[ j ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// IE completely kills leading whitespace when innerHTML is used\n\t\t\t\t\tif ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {\n\t\t\t\t\t\tdiv.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );\n\t\t\t\t\t}\n\n\t\t\t\t\telem = div.childNodes;\n\n\t\t\t\t\t// Take out of fragment container (we need a fresh div each time)\n\t\t\t\t\tdiv.parentNode.removeChild( div );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( elem.nodeType ) {\n\t\t\t\tret.push( elem );\n\t\t\t} else {\n\t\t\t\tjQuery.merge( ret, elem );\n\t\t\t}\n\t\t}\n\n\t\t// Fix #11356: Clear elements from safeFragment\n\t\tif ( div ) {\n\t\t\telem = div = safe = null;\n\t\t}\n\n\t\t// Reset defaultChecked for any radios and checkboxes\n\t\t// about to be appended to the DOM in IE 6/7 (#8060)\n\t\tif ( !jQuery.support.appendChecked ) {\n\t\t\tfor ( i = 0; (elem = ret[i]) != null; i++ ) {\n\t\t\t\tif ( jQuery.nodeName( elem, \"input\" ) ) {\n\t\t\t\t\tfixDefaultChecked( elem );\n\t\t\t\t} else if ( typeof elem.getElementsByTagName !== \"undefined\" ) {\n\t\t\t\t\tjQuery.grep( elem.getElementsByTagName(\"input\"), fixDefaultChecked );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Append elements to a provided document fragment\n\t\tif ( fragment ) {\n\t\t\t// Special handling of each script element\n\t\t\thandleScript = function( elem ) {\n\t\t\t\t// Check if we consider it executable\n\t\t\t\tif ( !elem.type || rscriptType.test( elem.type ) ) {\n\t\t\t\t\t// Detach the script and store it in the scripts array (if provided) or the fragment\n\t\t\t\t\t// Return truthy to indicate that it has been handled\n\t\t\t\t\treturn scripts ?\n\t\t\t\t\t\tscripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :\n\t\t\t\t\t\tfragment.appendChild( elem );\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tfor ( i = 0; (elem = ret[i]) != null; i++ ) {\n\t\t\t\t// Check if we're done after handling an executable script\n\t\t\t\tif ( !( jQuery.nodeName( elem, \"script\" ) && handleScript( elem ) ) ) {\n\t\t\t\t\t// Append to fragment and handle embedded scripts\n\t\t\t\t\tfragment.appendChild( elem );\n\t\t\t\t\tif ( typeof elem.getElementsByTagName !== \"undefined\" ) {\n\t\t\t\t\t\t// handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration\n\t\t\t\t\t\tjsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName(\"script\") ), handleScript );\n\n\t\t\t\t\t\t// Splice the scripts into ret after their former ancestor and advance our index beyond them\n\t\t\t\t\t\tret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );\n\t\t\t\t\t\ti += jsTags.length;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tcleanData: function( elems, /* internal */ acceptData ) {\n\t\tvar data, id, elem, type,\n\t\t\ti = 0,\n\t\t\tinternalKey = jQuery.expando,\n\t\t\tcache = jQuery.cache,\n\t\t\tdeleteExpando = jQuery.support.deleteExpando,\n\t\t\tspecial = jQuery.event.special;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\n\t\t\tif ( acceptData || jQuery.acceptData( elem ) ) {\n\n\t\t\t\tid = elem[ internalKey ];\n\t\t\t\tdata = id && cache[ id ];\n\n\t\t\t\tif ( data ) {\n\t\t\t\t\tif ( data.events ) {\n\t\t\t\t\t\tfor ( type in data.events ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove cache only if it was not already removed by jQuery.event.remove\n\t\t\t\t\tif ( cache[ id ] ) {\n\n\t\t\t\t\t\tdelete cache[ id ];\n\n\t\t\t\t\t\t// IE does not allow us to delete expando properties from nodes,\n\t\t\t\t\t\t// nor does it have a removeAttribute function on Document nodes;\n\t\t\t\t\t\t// we must handle all of these cases\n\t\t\t\t\t\tif ( deleteExpando ) {\n\t\t\t\t\t\t\tdelete elem[ internalKey ];\n\n\t\t\t\t\t\t} else if ( elem.removeAttribute ) {\n\t\t\t\t\t\t\telem.removeAttribute( internalKey );\n\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\telem[ internalKey ] = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tjQuery.deletedIds.push( id );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});\n// Limit scope pollution from any deprecated API\n(function() {\n\nvar matched, browser;\n\n// Use of jQuery.browser is frowned upon.\n// More details: http://api.jquery.com/jQuery.browser\n// jQuery.uaMatch maintained for back-compat\njQuery.uaMatch = function( ua ) {\n\tua = ua.toLowerCase();\n\n\tvar match = /(chrome)[ \\/]([\\w.]+)/.exec( ua ) ||\n\t\t/(webkit)[ \\/]([\\w.]+)/.exec( ua ) ||\n\t\t/(opera)(?:.*version|)[ \\/]([\\w.]+)/.exec( ua ) ||\n\t\t/(msie) ([\\w.]+)/.exec( ua ) ||\n\t\tua.indexOf(\"compatible\") < 0 && /(mozilla)(?:.*? rv:([\\w.]+)|)/.exec( ua ) ||\n\t\t[];\n\n\treturn {\n\t\tbrowser: match[ 1 ] || \"\",\n\t\tversion: match[ 2 ] || \"0\"\n\t};\n};\n\nmatched = jQuery.uaMatch( navigator.userAgent );\nbrowser = {};\n\nif ( matched.browser ) {\n\tbrowser[ matched.browser ] = true;\n\tbrowser.version = matched.version;\n}\n\n// Chrome is Webkit, but Webkit is also Safari.\nif ( browser.chrome ) {\n\tbrowser.webkit = true;\n} else if ( browser.webkit ) {\n\tbrowser.safari = true;\n}\n\njQuery.browser = browser;\n\njQuery.sub = function() {\n\tfunction jQuerySub( selector, context ) {\n\t\treturn new jQuerySub.fn.init( selector, context );\n\t}\n\tjQuery.extend( true, jQuerySub, this );\n\tjQuerySub.superclass = this;\n\tjQuerySub.fn = jQuerySub.prototype = this();\n\tjQuerySub.fn.constructor = jQuerySub;\n\tjQuerySub.sub = this.sub;\n\tjQuerySub.fn.init = function init( selector, context ) {\n\t\tif ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {\n\t\t\tcontext = jQuerySub( context );\n\t\t}\n\n\t\treturn jQuery.fn.init.call( this, selector, context, rootjQuerySub );\n\t};\n\tjQuerySub.fn.init.prototype = jQuerySub.fn;\n\tvar rootjQuerySub = jQuerySub(document);\n\treturn jQuerySub;\n};\n\n})();\nvar curCSS, iframe, iframeDoc,\n\tralpha = /alpha\\([^)]*\\)/i,\n\tropacity = /opacity=([^)]*)/,\n\trposition = /^(top|right|bottom|left)$/,\n\t// swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n\t// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trmargin = /^margin/,\n\trnumsplit = new RegExp( \"^(\" + core_pnum + \")(.*)$\", \"i\" ),\n\trnumnonpx = new RegExp( \"^(\" + core_pnum + \")(?!px)[a-z%]+$\", \"i\" ),\n\trrelNum = new RegExp( \"^([-+])=(\" + core_pnum + \")\", \"i\" ),\n\telemdisplay = { BODY: \"block\" },\n\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: 0,\n\t\tfontWeight: 400\n\t},\n\n\tcssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ],\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ],\n\n\teventsToggle = jQuery.fn.toggle;\n\n// return a css property mapped to a potentially vendor prefixed property\nfunction vendorPropName( style, name ) {\n\n\t// shortcut for names that are not vendor prefixed\n\tif ( name in style ) {\n\t\treturn name;\n\t}\n\n\t// check for vendor prefixed names\n\tvar capName = name.charAt(0).toUpperCase() + name.slice(1),\n\t\torigName = name,\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in style ) {\n\t\t\treturn name;\n\t\t}\n\t}\n\n\treturn origName;\n}\n\nfunction isHidden( elem, el ) {\n\telem = el || elem;\n\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\n}\n\nfunction showHide( elements, show ) {\n\tvar elem, display,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\" );\n\t\tif ( show ) {\n\t\t\t// Reset the inline display of this element to learn if it is\n\t\t\t// being hidden by cascaded rules or not\n\t\t\tif ( !values[ index ] && elem.style.display === \"none\" ) {\n\t\t\t\telem.style.display = \"\";\n\t\t\t}\n\n\t\t\t// Set elements which have been overridden with display: none\n\t\t\t// in a stylesheet to whatever the default browser style is\n\t\t\t// for such an element\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\n\t\t\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\", css_defaultDisplay(elem.nodeName) );\n\t\t\t}\n\t\t} else {\n\t\t\tdisplay = curCSS( elem, \"display\" );\n\n\t\t\tif ( !values[ index ] && display !== \"none\" ) {\n\t\t\t\tjQuery._data( elem, \"olddisplay\", display );\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of most of the elements in a second loop\n\t// to avoid the constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.fn.extend({\n\tcss: function( name, value ) {\n\t\treturn jQuery.access( this, function( elem, name, value ) {\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t},\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state, fn2 ) {\n\t\tvar bool = typeof state === \"boolean\";\n\n\t\tif ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {\n\t\t\treturn eventsToggle.apply( this, arguments );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( bool ? state : isHidden( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Exclude the following css properties to add px\n\tcssNumber: {\n\t\t\"fillOpacity\": true,\n\t\t\"fontWeight\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {\n\t\t// normalize float css property\n\t\t\"float\": jQuery.support.cssFloat ? \"cssFloat\" : \"styleFloat\"\n\t},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = jQuery.camelCase( name ),\n\t\t\tstyle = elem.style;\n\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// convert relative number strings (+= or -=) to relative numbers. #7345\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that NaN and null values aren't set. See: #7116\n\t\t\tif ( value == null || type === \"number\" && isNaN( value ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add 'px' to the (except for certain CSS properties)\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\n\t\t\t\tvalue += \"px\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\n\t\t\t\t// Wrapped to prevent IE from throwing errors when 'invalid' values are provided\n\t\t\t\t// Fixes bug #5509\n\t\t\t\ttry {\n\t\t\t\t\tstyle[ name ] = value;\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, numeric, extra ) {\n\t\tvar val, num, hooks,\n\t\t\torigName = jQuery.camelCase( name );\n\n\t\t// Make sure that we're working with the right name\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name );\n\t\t}\n\n\t\t//convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Return, converting to number if forced or a qualifier was provided and val looks numeric\n\t\tif ( numeric || extra !== undefined ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn numeric || jQuery.isNumeric( num ) ? num || 0 : val;\n\t\t}\n\t\treturn val;\n\t},\n\n\t// A method for quickly swapping in/out CSS properties to get correct calculations\n\tswap: function( elem, options, callback ) {\n\t\tvar ret, name,\n\t\t\told = {};\n\n\t\t// Remember the old values, and insert the new ones\n\t\tfor ( name in options ) {\n\t\t\told[ name ] = elem.style[ name ];\n\t\t\telem.style[ name ] = options[ name ];\n\t\t}\n\n\t\tret = callback.call( elem );\n\n\t\t// Revert the old values\n\t\tfor ( name in options ) {\n\t\t\telem.style[ name ] = old[ name ];\n\t\t}\n\n\t\treturn ret;\n\t}\n});\n\n// NOTE: To any future maintainer, we've window.getComputedStyle\n// because jsdom on node.js will break without it.\nif ( window.getComputedStyle ) {\n\tcurCSS = function( elem, name ) {\n\t\tvar ret, width, minWidth, maxWidth,\n\t\t\tcomputed = window.getComputedStyle( elem, null ),\n\t\t\tstyle = elem.style;\n\n\t\tif ( computed ) {\n\n\t\t\t// getPropertyValue is only needed for .css('filter') in IE9, see #12537\n\t\t\tret = computed.getPropertyValue( name ) || computed[ name ];\n\n\t\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\tret = jQuery.style( elem, name );\n\t\t\t}\n\n\t\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t\t// Chrome < 17 and Safari 5.0 uses \"computed value\" instead of \"used value\" for margin-right\n\t\t\t// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n\t\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n\t\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\n\t\t\t\twidth = style.width;\n\t\t\t\tminWidth = style.minWidth;\n\t\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\t\tret = computed.width;\n\n\t\t\t\tstyle.width = width;\n\t\t\t\tstyle.minWidth = minWidth;\n\t\t\t\tstyle.maxWidth = maxWidth;\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t};\n} else if ( document.documentElement.currentStyle ) {\n\tcurCSS = function( elem, name ) {\n\t\tvar left, rsLeft,\n\t\t\tret = elem.currentStyle && elem.currentStyle[ name ],\n\t\t\tstyle = elem.style;\n\n\t\t// Avoid setting ret to empty string here\n\t\t// so we don't default to auto\n\t\tif ( ret == null && style && style[ name ] ) {\n\t\t\tret = style[ name ];\n\t\t}\n\n\t\t// From the awesome hack by Dean Edwards\n\t\t// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291\n\n\t\t// If we're not dealing with a regular pixel number\n\t\t// but a number that has a weird ending, we need to convert it to pixels\n\t\t// but not position css attributes, as those are proportional to the parent element instead\n\t\t// and we can't measure the parent instead because it might trigger a \"stacking dolls\" problem\n\t\tif ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\tleft = style.left;\n\t\t\trsLeft = elem.runtimeStyle && elem.runtimeStyle.left;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tif ( rsLeft ) {\n\t\t\t\telem.runtimeStyle.left = elem.currentStyle.left;\n\t\t\t}\n\t\t\tstyle.left = name === \"fontSize\" ? \"1em\" : ret;\n\t\t\tret = style.pixelLeft + \"px\";\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.left = left;\n\t\t\tif ( rsLeft ) {\n\t\t\t\telem.runtimeStyle.left = rsLeft;\n\t\t\t}\n\t\t}\n\n\t\treturn ret === \"\" ? \"auto\" : ret;\n\t};\n}\n\nfunction setPositiveNumber( elem, value, subtract ) {\n\tvar matches = rnumsplit.exec( value );\n\treturn matches ?\n\t\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\n\t\t\tvalue;\n}\n\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox ) {\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n\t\t// If we already have the right measurement, avoid augmentation\n\t\t4 :\n\t\t// Otherwise initialize for horizontal or vertical properties\n\t\tname === \"width\" ? 1 : 0,\n\n\t\tval = 0;\n\n\tfor ( ; i < 4; i += 2 ) {\n\t\t// both box models exclude margin, so add it if we want it\n\t\tif ( extra === \"margin\" ) {\n\t\t\t// we use jQuery.css instead of curCSS here\n\t\t\t// because of the reliableMarginRight CSS hook!\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true );\n\t\t}\n\n\t\t// From this point on we use curCSS for maximum performance (relevant in animations)\n\t\tif ( isBorderBox ) {\n\t\t\t// border-box includes padding, so remove it if we want content\n\t\t\tif ( extra === \"content\" ) {\n\t\t\t\tval -= parseFloat( curCSS( elem, \"padding\" + cssExpand[ i ] ) ) || 0;\n\t\t\t}\n\n\t\t\t// at this point, extra isn't border nor margin, so remove border\n\t\t\tif ( extra !== \"margin\" ) {\n\t\t\t\tval -= parseFloat( curCSS( elem, \"border\" + cssExpand[ i ] + \"Width\" ) ) || 0;\n\t\t\t}\n\t\t} else {\n\t\t\t// at this point, extra isn't content, so add padding\n\t\t\tval += parseFloat( curCSS( elem, \"padding\" + cssExpand[ i ] ) ) || 0;\n\n\t\t\t// at this point, extra isn't content nor padding, so add border\n\t\t\tif ( extra !== \"padding\" ) {\n\t\t\t\tval += parseFloat( curCSS( elem, \"border\" + cssExpand[ i ] + \"Width\" ) ) || 0;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn val;\n}\n\nfunction getWidthOrHeight( elem, name, extra ) {\n\n\t// Start with offset property, which is equivalent to the border-box value\n\tvar val = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n\t\tvalueIsBorderBox = true,\n\t\tisBorderBox = jQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\" ) === \"border-box\";\n\n\t// some non-html elements return undefined for offsetWidth, so check for null/undefined\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n\tif ( val <= 0 || val == null ) {\n\t\t// Fall back to computed then uncomputed css if necessary\n\t\tval = curCSS( elem, name );\n\t\tif ( val < 0 || val == null ) {\n\t\t\tval = elem.style[ name ];\n\t\t}\n\n\t\t// Computed unit is not pixels. Stop here and return.\n\t\tif ( rnumnonpx.test(val) ) {\n\t\t\treturn val;\n\t\t}\n\n\t\t// we need the check for style in case a browser which returns unreliable values\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\n\t\tvalueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );\n\n\t\t// Normalize \"\", auto, and prepare for extra\n\t\tval = parseFloat( val ) || 0;\n\t}\n\n\t// use the active box-sizing model to add/subtract irrelevant styles\n\treturn ( val +\n\t\taugmentWidthOrHeight(\n\t\t\telem,\n\t\t\tname,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox\n\t\t)\n\t) + \"px\";\n}\n\n\n// Try to determine the default display value of an element\nfunction css_defaultDisplay( nodeName ) {\n\tif ( elemdisplay[ nodeName ] ) {\n\t\treturn elemdisplay[ nodeName ];\n\t}\n\n\tvar elem = jQuery( \"<\" + nodeName + \">\" ).appendTo( document.body ),\n\t\tdisplay = elem.css(\"display\");\n\telem.remove();\n\n\t// If the simple way fails,\n\t// get element's real default display by attaching it to a temp iframe\n\tif ( display === \"none\" || display === \"\" ) {\n\t\t// Use the already-created iframe if possible\n\t\tiframe = document.body.appendChild(\n\t\t\tiframe || jQuery.extend( document.createElement(\"iframe\"), {\n\t\t\t\tframeBorder: 0,\n\t\t\t\twidth: 0,\n\t\t\t\theight: 0\n\t\t\t})\n\t\t);\n\n\t\t// Create a cacheable copy of the iframe document on first call.\n\t\t// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML\n\t\t// document to it; WebKit & Firefox won't allow reusing the iframe document.\n\t\tif ( !iframeDoc || !iframe.createElement ) {\n\t\t\tiframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;\n\t\t\tiframeDoc.write(\"<!doctype html><html><body>\");\n\t\t\tiframeDoc.close();\n\t\t}\n\n\t\telem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );\n\n\t\tdisplay = curCSS( elem, \"display\" );\n\t\tdocument.body.removeChild( iframe );\n\t}\n\n\t// Store the correct default display\n\telemdisplay[ nodeName ] = display;\n\n\treturn display;\n}\n\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\n\tjQuery.cssHooks[ name ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\t\t\t\t// certain elements can have dimension info if we invisibly show them\n\t\t\t\t// however, it must have a current display style that would benefit from this\n\t\t\t\tif ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, \"display\" ) ) ) {\n\t\t\t\t\treturn jQuery.swap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\treturn setPositiveNumber( elem, value, extra ?\n\t\t\t\taugmentWidthOrHeight(\n\t\t\t\t\telem,\n\t\t\t\t\tname,\n\t\t\t\t\textra,\n\t\t\t\t\tjQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\" ) === \"border-box\"\n\t\t\t\t) : 0\n\t\t\t);\n\t\t}\n\t};\n});\n\nif ( !jQuery.support.opacity ) {\n\tjQuery.cssHooks.opacity = {\n\t\tget: function( elem, computed ) {\n\t\t\t// IE uses filters for opacity\n\t\t\treturn ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || \"\" ) ?\n\t\t\t\t( 0.01 * parseFloat( RegExp.$1 ) ) + \"\" :\n\t\t\t\tcomputed ? \"1\" : \"\";\n\t\t},\n\n\t\tset: function( elem, value ) {\n\t\t\tvar style = elem.style,\n\t\t\t\tcurrentStyle = elem.currentStyle,\n\t\t\t\topacity = jQuery.isNumeric( value ) ? \"alpha(opacity=\" + value * 100 + \")\" : \"\",\n\t\t\t\tfilter = currentStyle && currentStyle.filter || style.filter || \"\";\n\n\t\t\t// IE has trouble with opacity if it does not have layout\n\t\t\t// Force it by setting the zoom level\n\t\t\tstyle.zoom = 1;\n\n\t\t\t// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652\n\t\t\tif ( value >= 1 && jQuery.trim( filter.replace( ralpha, \"\" ) ) === \"\" &&\n\t\t\t\tstyle.removeAttribute ) {\n\n\t\t\t\t// Setting style.filter to null, \"\" & \" \" still leave \"filter:\" in the cssText\n\t\t\t\t// if \"filter:\" is present at all, clearType is disabled, we want to avoid this\n\t\t\t\t// style.removeAttribute is IE Only, but so apparently is this code path...\n\t\t\t\tstyle.removeAttribute( \"filter\" );\n\n\t\t\t\t// if there there is no filter style applied in a css rule, we are done\n\t\t\t\tif ( currentStyle && !currentStyle.filter ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// otherwise, set new filter values\n\t\t\tstyle.filter = ralpha.test( filter ) ?\n\t\t\t\tfilter.replace( ralpha, opacity ) :\n\t\t\t\tfilter + \" \" + opacity;\n\t\t}\n\t};\n}\n\n// These hooks cannot be added until DOM ready because the support test\n// for it is not run until after DOM ready\njQuery(function() {\n\tif ( !jQuery.support.reliableMarginRight ) {\n\t\tjQuery.cssHooks.marginRight = {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t\t// Work around by temporarily setting element display to inline-block\n\t\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" }, function() {\n\t\t\t\t\tif ( computed ) {\n\t\t\t\t\t\treturn curCSS( elem, \"marginRight\" );\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t}\n\n\t// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n\t// getComputedStyle returns percent when specified for top/left/bottom/right\n\t// rather than make the css module depend on the offset module, we just check for it here\n\tif ( !jQuery.support.pixelPosition && jQuery.fn.position ) {\n\t\tjQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\n\t\t\tjQuery.cssHooks[ prop ] = {\n\t\t\t\tget: function( elem, computed ) {\n\t\t\t\t\tif ( computed ) {\n\t\t\t\t\t\tvar ret = curCSS( elem, prop );\n\t\t\t\t\t\t// if curCSS returns percentage, fallback to offset\n\t\t\t\t\t\treturn rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + \"px\" : ret;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n});\n\nif ( jQuery.expr && jQuery.expr.filters ) {\n\tjQuery.expr.filters.hidden = function( elem ) {\n\t\treturn ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, \"display\" )) === \"none\");\n\t};\n\n\tjQuery.expr.filters.visible = function( elem ) {\n\t\treturn !jQuery.expr.filters.hidden( elem );\n\t};\n}\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i,\n\n\t\t\t\t// assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ],\n\t\t\t\texpanded = {};\n\n\t\t\tfor ( i = 0; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( !rmargin.test( prefix ) ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n});\nvar r20 = /%20/g,\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,\n\trselectTextarea = /^(?:select|textarea)/i;\n\njQuery.fn.extend({\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map(function(){\n\t\t\treturn this.elements ? jQuery.makeArray( this.elements ) : this;\n\t\t})\n\t\t.filter(function(){\n\t\t\treturn this.name && !this.disabled &&\n\t\t\t\t( this.checked || rselectTextarea.test( this.nodeName ) ||\n\t\t\t\t\trinput.test( this.type ) );\n\t\t})\n\t\t.map(function( i, elem ){\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\treturn val == null ?\n\t\t\t\tnull :\n\t\t\t\tjQuery.isArray( val ) ?\n\t\t\t\t\tjQuery.map( val, function( val, i ){\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t\t}) :\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t}).get();\n\t}\n});\n\n//Serialize an array of form elements or a set of\n//key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, value ) {\n\t\t\t// If value is a function, invoke it and return its value\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\n\t\t};\n\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\n\tif ( traditional === undefined ) {\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t});\n\n\t} else {\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\n};\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( jQuery.isArray( obj ) ) {\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\t\t\t\t// If array item is non-scalar (array or object), encode its\n\t\t\t\t// numeric index to resolve deserialization ambiguity issues.\n\t\t\t\t// Note that rack (as of 1.0.0) can't currently deserialize\n\t\t\t\t// nested arrays properly, and attempting to do so may cause\n\t\t\t\t// a server error. Possible fixes are to modify rack's\n\t\t\t\t// deserialization algorithm or to provide an option or flag\n\t\t\t\t// to force array serialization to be shallow.\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\n\t\t\t}\n\t\t});\n\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\nvar\n\t// Document location\n\tajaxLocParts,\n\tajaxLocation,\n\n\trhash = /#.*$/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)\\r?$/mg, // IE leaves an \\r character at EOL\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app\\-storage|.+\\-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\trquery = /\\?/,\n\trscript = /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n\trts = /([?&])_=[^&]*/,\n\trurl = /^([\\w\\+\\.\\-]+:)(?:\\/\\/([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n\t// Keep a copy of the old load method\n\t_load = jQuery.fn.load,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = [\"*/\"] + [\"*\"];\n\n// #8138, IE may throw an exception when accessing\n// a field from window.location if document.domain has been set\ntry {\n\tajaxLocation = location.href;\n} catch( e ) {\n\t// Use the href attribute of an A element\n\t// since IE will modify it given document.location\n\tajaxLocation = document.createElement( \"a\" );\n\tajaxLocation.href = \"\";\n\tajaxLocation = ajaxLocation.href;\n}\n\n// Segment location into parts\najaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType, list, placeBefore,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),\n\t\t\ti = 0,\n\t\t\tlength = dataTypes.length;\n\n\t\tif ( jQuery.isFunction( func ) ) {\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tdataType = dataTypes[ i ];\n\t\t\t\t// We control if we're asked to add before\n\t\t\t\t// any existing element\n\t\t\t\tplaceBefore = /^\\+/.test( dataType );\n\t\t\t\tif ( placeBefore ) {\n\t\t\t\t\tdataType = dataType.substr( 1 ) || \"*\";\n\t\t\t\t}\n\t\t\t\tlist = structure[ dataType ] = structure[ dataType ] || [];\n\t\t\t\t// then we add to the structure accordingly\n\t\t\t\tlist[ placeBefore ? \"unshift\" : \"push\" ]( func );\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,\n\t\tdataType /* internal */, inspected /* internal */ ) {\n\n\tdataType = dataType || options.dataTypes[ 0 ];\n\tinspected = inspected || {};\n\n\tinspected[ dataType ] = true;\n\n\tvar selection,\n\t\tlist = structure[ dataType ],\n\t\ti = 0,\n\t\tlength = list ? list.length : 0,\n\t\texecuteOnly = ( structure === prefilters );\n\n\tfor ( ; i < length && ( executeOnly || !selection ); i++ ) {\n\t\tselection = list[ i ]( options, originalOptions, jqXHR );\n\t\t// If we got redirected to another dataType\n\t\t// we try there if executing only and not done already\n\t\tif ( typeof selection === \"string\" ) {\n\t\t\tif ( !executeOnly || inspected[ selection ] ) {\n\t\t\t\tselection = undefined;\n\t\t\t} else {\n\t\t\t\toptions.dataTypes.unshift( selection );\n\t\t\t\tselection = inspectPrefiltersOrTransports(\n\t\t\t\t\t\tstructure, options, originalOptions, jqXHR, selection, inspected );\n\t\t\t}\n\t\t}\n\t}\n\t// If we're only executing or nothing was selected\n\t// we try the catchall dataType if not done already\n\tif ( ( executeOnly || !selection ) && !inspected[ \"*\" ] ) {\n\t\tselection = inspectPrefiltersOrTransports(\n\t\t\t\tstructure, options, originalOptions, jqXHR, \"*\", inspected );\n\t}\n\t// unnecessary when only executing (prefilters)\n\t// but it'll be ignored by the caller in that case\n\treturn selection;\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar key, deep,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n}\n\njQuery.fn.load = function( url, params, callback ) {\n\tif ( typeof url !== \"string\" && _load ) {\n\t\treturn _load.apply( this, arguments );\n\t}\n\n\t// Don't do a request if no elements are being requested\n\tif ( !this.length ) {\n\t\treturn this;\n\t}\n\n\tvar selector, type, response,\n\t\tself = this,\n\t\toff = url.indexOf(\" \");\n\n\tif ( off >= 0 ) {\n\t\tselector = url.slice( off, url.length );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( jQuery.isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// Request the remote document\n\tjQuery.ajax({\n\t\turl: url,\n\n\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\n\t\ttype: type,\n\t\tdataType: \"html\",\n\t\tdata: params,\n\t\tcomplete: function( jqXHR, status ) {\n\t\t\tif ( callback ) {\n\t\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t\t}\n\t\t}\n\t}).done(function( responseText ) {\n\n\t\t// Save response for use in complete callback\n\t\tresponse = arguments;\n\n\t\t// See if a selector was specified\n\t\tself.html( selector ?\n\n\t\t\t// Create a dummy div to hold the results\n\t\t\tjQuery(\"<div>\")\n\n\t\t\t\t// inject the contents of the document in, removing the scripts\n\t\t\t\t// to avoid any 'Permission Denied' errors in IE\n\t\t\t\t.append( responseText.replace( rscript, \"\" ) )\n\n\t\t\t\t// Locate the specified elements\n\t\t\t\t.find( selector ) :\n\n\t\t\t// If not, just inject the full result\n\t\t\tresponseText );\n\n\t});\n\n\treturn this;\n};\n\n// Attach a bunch of functions for handling common AJAX events\njQuery.each( \"ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend\".split( \" \" ), function( i, o ){\n\tjQuery.fn[ o ] = function( f ){\n\t\treturn this.on( o, f );\n\t};\n});\n\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\t\t// shift arguments if data argument was omitted\n\t\tif ( jQuery.isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\treturn jQuery.ajax({\n\t\t\ttype: method,\n\t\t\turl: url,\n\t\t\tdata: data,\n\t\t\tsuccess: callback,\n\t\t\tdataType: type\n\t\t});\n\t};\n});\n\njQuery.extend({\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\tif ( settings ) {\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( target, jQuery.ajaxSettings );\n\t\t} else {\n\t\t\t// Extending ajaxSettings\n\t\t\tsettings = target;\n\t\t\ttarget = jQuery.ajaxSettings;\n\t\t}\n\t\tajaxExtend( target, settings );\n\t\treturn target;\n\t},\n\n\tajaxSettings: {\n\t\turl: ajaxLocation,\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\n\t\tglobal: true,\n\t\ttype: \"GET\",\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t\tprocessData: true,\n\t\tasync: true,\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\thtml: \"text/html\",\n\t\t\ttext: \"text/plain\",\n\t\t\tjson: \"application/json, text/javascript\",\n\t\t\t\"*\": allTypes\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /xml/,\n\t\t\thtml: /html/,\n\t\t\tjson: /json/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\"\n\t\t},\n\n\t\t// List of data converters\n\t\t// 1) key format is \"source_type destination_type\" (a single space in-between)\n\t\t// 2) the catchall symbol \"*\" can be used for source_type\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": window.String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": jQuery.parseJSON,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\tcontext: true,\n\t\t\turl: true\n\t\t}\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar // ifModified key\n\t\t\tifModifiedKey,\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\t\t\t// transport\n\t\t\ttransport,\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\t\t\t// Cross-domain detection vars\n\t\t\tparts,\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\t\t\t// Loop variable\n\t\t\ti,\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\t\t\t// Context for global events\n\t\t\t// It's the callbackContext if one was provided in the options\n\t\t\t// and if it's a DOM node or a jQuery collection\n\t\t\tglobalEventContext = callbackContext !== s &&\n\t\t\t\t( callbackContext.nodeType || callbackContext instanceof jQuery ) ?\n\t\t\t\t\t\tjQuery( callbackContext ) : jQuery.event,\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks( \"once memory\" ),\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\t\t\t// The jqXHR state\n\t\t\tstate = 0,\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\tvar lname = name.toLowerCase();\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( state === 2 ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile( ( match = rheaders.exec( responseHeadersString ) ) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match === undefined ? null : match;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tstatusText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( statusText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, statusText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Callback for when everything is done\n\t\t// It is defined here because jslint complains if it is declared\n\t\t// at the end of the function (which would be more logical and readable)\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Called once\n\t\t\tif ( state === 2 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State is \"done\" now\n\t\t\tstate = 2;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\tclearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( status >= 200 && status < 300 || status === 304 ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ ifModifiedKey ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Etag\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ ifModifiedKey ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If not modified\n\t\t\t\tif ( status === 304 ) {\n\n\t\t\t\t\tstatusText = \"notmodified\";\n\t\t\t\t\tisSuccess = true;\n\n\t\t\t\t// If we have data\n\t\t\t\t} else {\n\n\t\t\t\t\tisSuccess = ajaxConvert( s, response );\n\t\t\t\t\tstatusText = isSuccess.state;\n\t\t\t\t\tsuccess = isSuccess.data;\n\t\t\t\t\terror = isSuccess.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// We extract error from statusText\n\t\t\t\t// then normalize statusText and status for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( !statusText || status ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajax\" + ( isSuccess ? \"Success\" : \"Error\" ),\n\t\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger( \"ajaxStop\" );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR );\n\t\tjqXHR.success = jqXHR.done;\n\t\tjqXHR.error = jqXHR.fail;\n\t\tjqXHR.complete = completeDeferred.add;\n\n\t\t// Status-dependent callbacks\n\t\tjqXHR.statusCode = function( map ) {\n\t\t\tif ( map ) {\n\t\t\t\tvar tmp;\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tfor ( tmp in map ) {\n\t\t\t\t\t\tstatusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttmp = map[ jqXHR.status ];\n\t\t\t\t\tjqXHR.always( tmp );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this;\n\t\t};\n\n\t\t// Remove hash character (#7531: and string promotion)\n\t\t// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url ) + \"\" ).replace( rhash, \"\" ).replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().split( core_rspace );\n\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\n\t\tif ( s.crossDomain == null ) {\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\n\t\t\ts.crossDomain = !!( parts &&\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? 80 : 443 ) ) !=\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? 80 : 443 ) ) )\n\t\t\t);\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( state === 2 ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\tfireGlobals = s.global;\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger( \"ajaxStart\" );\n\t\t}\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// If data is available, append data to url\n\t\t\tif ( s.data ) {\n\t\t\t\ts.url += ( rquery.test( s.url ) ? \"&\" : \"?\" ) + s.data;\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Get ifModifiedKey before adding the anti-cache parameter\n\t\t\tifModifiedKey = s.url;\n\n\t\t\t// Add anti-cache in url if needed\n\t\t\tif ( s.cache === false ) {\n\n\t\t\t\tvar ts = jQuery.now(),\n\t\t\t\t\t// try replacing _= if it is there\n\t\t\t\t\tret = s.url.replace( rts, \"$1_=\" + ts );\n\n\t\t\t\t// if nothing was replaced, add timestamp to the end\n\t\t\t\ts.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? \"&\" : \"?\" ) + \"_=\" + ts : \"\" );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tifModifiedKey = ifModifiedKey || s.url;\n\t\t\tif ( jQuery.lastModified[ ifModifiedKey ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ ifModifiedKey ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ ifModifiedKey ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ ifModifiedKey ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\n\t\t\t\t// Abort if not done already and return\n\t\t\t\treturn jqXHR.abort();\n\n\t\t}\n\n\t\t// aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\n\t\t\tjqXHR[ i ]( s[ i ] );\n\t\t}\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = setTimeout( function(){\n\t\t\t\t\tjqXHR.abort( \"timeout\" );\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tstate = 1;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch (e) {\n\t\t\t\t// Propagate exception as error if not done\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tdone( -1, e );\n\t\t\t\t// Simply rethrow otherwise\n\t\t\t\t} else {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {}\n\n});\n\n/* Handles responses to an ajax request:\n * - sets all responseXXX fields accordingly\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes,\n\t\tresponseFields = s.responseFields;\n\n\t// Fill responseXXX fields\n\tfor ( type in responseFields ) {\n\t\tif ( type in responses ) {\n\t\t\tjqXHR[ responseFields[type] ] = responses[ type ];\n\t\t}\n\t}\n\n\t// Remove auto dataType and get content-type in the process\n\twhile( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader( \"content-type\" );\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n// Chain conversions given the request and the original response\nfunction ajaxConvert( s, response ) {\n\n\tvar conv, conv2, current, tmp,\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice(),\n\t\tprev = dataTypes[ 0 ],\n\t\tconverters = {},\n\t\ti = 0;\n\n\t// Apply the dataFilter if provided\n\tif ( s.dataFilter ) {\n\t\tresponse = s.dataFilter( response, s.dataType );\n\t}\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\t// Convert to each sequential dataType, tolerating list modification\n\tfor ( ; (current = dataTypes[++i]); ) {\n\n\t\t// There's only work to do if current dataType is non-auto\n\t\tif ( current !== \"*\" ) {\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\tif ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split(\" \");\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.splice( i--, 0, current );\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s[\"throws\"] ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update prev for next iteration\n\t\t\tprev = current;\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\nvar oldCallbacks = [],\n\trquestion = /\\?/,\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/,\n\tnonce = jQuery.now();\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tdata = s.data,\n\t\turl = s.url,\n\t\thasCallback = s.jsonp !== false,\n\t\treplaceInUrl = hasCallback && rjsonp.test( url ),\n\t\treplaceInData = hasCallback && !replaceInUrl && typeof data === \"string\" &&\n\t\t\t!( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") &&\n\t\t\trjsonp.test( data );\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( s.dataTypes[ 0 ] === \"jsonp\" || replaceInUrl || replaceInData ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\t\toverwritten = window[ callbackName ];\n\n\t\t// Insert callback into url or form data\n\t\tif ( replaceInUrl ) {\n\t\t\ts.url = url.replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( replaceInData ) {\n\t\t\ts.data = data.replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( hasCallback ) {\n\t\t\ts.url += ( rquestion.test( url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[\"script json\"] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function() {\n\t\t\t// Restore preexisting value\n\t\t\twindow[ callbackName ] = overwritten;\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\t\t\t\t// make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n});\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /javascript|ecmascript/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache's special case and global\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t\ts.global = false;\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function(s) {\n\n\t// This transport only deals with cross domain requests\n\tif ( s.crossDomain ) {\n\n\t\tvar script,\n\t\t\thead = document.head || document.getElementsByTagName( \"head\" )[0] || document.documentElement;\n\n\t\treturn {\n\n\t\t\tsend: function( _, callback ) {\n\n\t\t\t\tscript = document.createElement( \"script\" );\n\n\t\t\t\tscript.async = \"async\";\n\n\t\t\t\tif ( s.scriptCharset ) {\n\t\t\t\t\tscript.charset = s.scriptCharset;\n\t\t\t\t}\n\n\t\t\t\tscript.src = s.url;\n\n\t\t\t\t// Attach handlers for all browsers\n\t\t\t\tscript.onload = script.onreadystatechange = function( _, isAbort ) {\n\n\t\t\t\t\tif ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {\n\n\t\t\t\t\t\t// Handle memory leak in IE\n\t\t\t\t\t\tscript.onload = script.onreadystatechange = null;\n\n\t\t\t\t\t\t// Remove the script\n\t\t\t\t\t\tif ( head && script.parentNode ) {\n\t\t\t\t\t\t\thead.removeChild( script );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Dereference the script\n\t\t\t\t\t\tscript = undefined;\n\n\t\t\t\t\t\t// Callback if not abort\n\t\t\t\t\t\tif ( !isAbort ) {\n\t\t\t\t\t\t\tcallback( 200, \"success\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t// Use insertBefore instead of appendChild  to circumvent an IE6 bug.\n\t\t\t\t// This arises when a base node is used (#2709 and #4378).\n\t\t\t\thead.insertBefore( script, head.firstChild );\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( script ) {\n\t\t\t\t\tscript.onload( 0, 1 );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\nvar xhrCallbacks,\n\t// #5280: Internet Explorer will keep connections alive if we don't abort on unload\n\txhrOnUnloadAbort = window.ActiveXObject ? function() {\n\t\t// Abort all pending requests\n\t\tfor ( var key in xhrCallbacks ) {\n\t\t\txhrCallbacks[ key ]( 0, 1 );\n\t\t}\n\t} : false,\n\txhrId = 0;\n\n// Functions to create xhrs\nfunction createStandardXHR() {\n\ttry {\n\t\treturn new window.XMLHttpRequest();\n\t} catch( e ) {}\n}\n\nfunction createActiveXHR() {\n\ttry {\n\t\treturn new window.ActiveXObject( \"Microsoft.XMLHTTP\" );\n\t} catch( e ) {}\n}\n\n// Create the request object\n// (This is still attached to ajaxSettings for backward compatibility)\njQuery.ajaxSettings.xhr = window.ActiveXObject ?\n\t/* Microsoft failed to properly\n\t * implement the XMLHttpRequest in IE7 (can't request local files),\n\t * so we use the ActiveXObject when it is available\n\t * Additionally XMLHttpRequest can be disabled in IE7/IE8 so\n\t * we need a fallback.\n\t */\n\tfunction() {\n\t\treturn !this.isLocal && createStandardXHR() || createActiveXHR();\n\t} :\n\t// For all other browsers, use the standard XMLHttpRequest object\n\tcreateStandardXHR;\n\n// Determine support properties\n(function( xhr ) {\n\tjQuery.extend( jQuery.support, {\n\t\tajax: !!xhr,\n\t\tcors: !!xhr && ( \"withCredentials\" in xhr )\n\t});\n})( jQuery.ajaxSettings.xhr() );\n\n// Create transport if the browser can provide an xhr\nif ( jQuery.support.ajax ) {\n\n\tjQuery.ajaxTransport(function( s ) {\n\t\t// Cross domain only allowed if supported through XMLHttpRequest\n\t\tif ( !s.crossDomain || jQuery.support.cors ) {\n\n\t\t\tvar callback;\n\n\t\t\treturn {\n\t\t\t\tsend: function( headers, complete ) {\n\n\t\t\t\t\t// Get a new xhr\n\t\t\t\t\tvar handle, i,\n\t\t\t\t\t\txhr = s.xhr();\n\n\t\t\t\t\t// Open the socket\n\t\t\t\t\t// Passing null username, generates a login popup on Opera (#2865)\n\t\t\t\t\tif ( s.username ) {\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async, s.username, s.password );\n\t\t\t\t\t} else {\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply custom fields if provided\n\t\t\t\t\tif ( s.xhrFields ) {\n\t\t\t\t\t\tfor ( i in s.xhrFields ) {\n\t\t\t\t\t\t\txhr[ i ] = s.xhrFields[ i ];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Override mime type if needed\n\t\t\t\t\tif ( s.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\t\txhr.overrideMimeType( s.mimeType );\n\t\t\t\t\t}\n\n\t\t\t\t\t// X-Requested-With header\n\t\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\t\tif ( !s.crossDomain && !headers[\"X-Requested-With\"] ) {\n\t\t\t\t\t\theaders[ \"X-Requested-With\" ] = \"XMLHttpRequest\";\n\t\t\t\t\t}\n\n\t\t\t\t\t// Need an extra try/catch for cross domain requests in Firefox 3\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch( _ ) {}\n\n\t\t\t\t\t// Do send the request\n\t\t\t\t\t// This may raise an exception which is actually\n\t\t\t\t\t// handled in jQuery.ajax (so no try/catch here)\n\t\t\t\t\txhr.send( ( s.hasContent && s.data ) || null );\n\n\t\t\t\t\t// Listener\n\t\t\t\t\tcallback = function( _, isAbort ) {\n\n\t\t\t\t\t\tvar status,\n\t\t\t\t\t\t\tstatusText,\n\t\t\t\t\t\t\tresponseHeaders,\n\t\t\t\t\t\t\tresponses,\n\t\t\t\t\t\t\txml;\n\n\t\t\t\t\t\t// Firefox throws exceptions when accessing properties\n\t\t\t\t\t\t// of an xhr when a network error occurred\n\t\t\t\t\t\t// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)\n\t\t\t\t\t\ttry {\n\n\t\t\t\t\t\t\t// Was never called and is aborted or complete\n\t\t\t\t\t\t\tif ( callback && ( isAbort || xhr.readyState === 4 ) ) {\n\n\t\t\t\t\t\t\t\t// Only called once\n\t\t\t\t\t\t\t\tcallback = undefined;\n\n\t\t\t\t\t\t\t\t// Do not keep as active anymore\n\t\t\t\t\t\t\t\tif ( handle ) {\n\t\t\t\t\t\t\t\t\txhr.onreadystatechange = jQuery.noop;\n\t\t\t\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\n\t\t\t\t\t\t\t\t\t\tdelete xhrCallbacks[ handle ];\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// If it's an abort\n\t\t\t\t\t\t\t\tif ( isAbort ) {\n\t\t\t\t\t\t\t\t\t// Abort it manually if needed\n\t\t\t\t\t\t\t\t\tif ( xhr.readyState !== 4 ) {\n\t\t\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tstatus = xhr.status;\n\t\t\t\t\t\t\t\t\tresponseHeaders = xhr.getAllResponseHeaders();\n\t\t\t\t\t\t\t\t\tresponses = {};\n\t\t\t\t\t\t\t\t\txml = xhr.responseXML;\n\n\t\t\t\t\t\t\t\t\t// Construct response list\n\t\t\t\t\t\t\t\t\tif ( xml && xml.documentElement /* #4958 */ ) {\n\t\t\t\t\t\t\t\t\t\tresponses.xml = xml;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// When requesting binary data, IE6-9 will throw an exception\n\t\t\t\t\t\t\t\t\t// on any attempt to access responseText (#11426)\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tresponses.text = xhr.responseText;\n\t\t\t\t\t\t\t\t\t} catch( e ) {\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Firefox throws an exception when accessing\n\t\t\t\t\t\t\t\t\t// statusText for faulty cross-domain requests\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tstatusText = xhr.statusText;\n\t\t\t\t\t\t\t\t\t} catch( e ) {\n\t\t\t\t\t\t\t\t\t\t// We normalize with Webkit giving an empty statusText\n\t\t\t\t\t\t\t\t\t\tstatusText = \"\";\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Filter status for non standard behaviors\n\n\t\t\t\t\t\t\t\t\t// If the request is local and we have data: assume a success\n\t\t\t\t\t\t\t\t\t// (success with no data won't get notified, that's the best we\n\t\t\t\t\t\t\t\t\t// can do given current implementations)\n\t\t\t\t\t\t\t\t\tif ( !status && s.isLocal && !s.crossDomain ) {\n\t\t\t\t\t\t\t\t\t\tstatus = responses.text ? 200 : 404;\n\t\t\t\t\t\t\t\t\t// IE - #1450: sometimes returns 1223 when it should be 204\n\t\t\t\t\t\t\t\t\t} else if ( status === 1223 ) {\n\t\t\t\t\t\t\t\t\t\tstatus = 204;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch( firefoxAccessException ) {\n\t\t\t\t\t\t\tif ( !isAbort ) {\n\t\t\t\t\t\t\t\tcomplete( -1, firefoxAccessException );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Call complete if needed\n\t\t\t\t\t\tif ( responses ) {\n\t\t\t\t\t\t\tcomplete( status, statusText, responses, responseHeaders );\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif ( !s.async ) {\n\t\t\t\t\t\t// if we're in sync mode we fire the callback\n\t\t\t\t\t\tcallback();\n\t\t\t\t\t} else if ( xhr.readyState === 4 ) {\n\t\t\t\t\t\t// (IE6 & IE7) if it's in cache and has been\n\t\t\t\t\t\t// retrieved directly we need to fire the callback\n\t\t\t\t\t\tsetTimeout( callback, 0 );\n\t\t\t\t\t} else {\n\t\t\t\t\t\thandle = ++xhrId;\n\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\n\t\t\t\t\t\t\t// Create the active xhrs callbacks list if needed\n\t\t\t\t\t\t\t// and attach the unload handler\n\t\t\t\t\t\t\tif ( !xhrCallbacks ) {\n\t\t\t\t\t\t\t\txhrCallbacks = {};\n\t\t\t\t\t\t\t\tjQuery( window ).unload( xhrOnUnloadAbort );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Add to list of active xhrs callbacks\n\t\t\t\t\t\t\txhrCallbacks[ handle ] = callback;\n\t\t\t\t\t\t}\n\t\t\t\t\t\txhr.onreadystatechange = callback;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tabort: function() {\n\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\tcallback(0,1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t});\n}\nvar fxNow, timerId,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trfxnum = new RegExp( \"^(?:([-+])=|)(\" + core_pnum + \")([a-z%]*)$\", \"i\" ),\n\trrun = /queueHooks$/,\n\tanimationPrefilters = [ defaultPrefilter ],\n\ttweeners = {\n\t\t\"*\": [function( prop, value ) {\n\t\t\tvar end, unit,\n\t\t\t\ttween = this.createTween( prop, value ),\n\t\t\t\tparts = rfxnum.exec( value ),\n\t\t\t\ttarget = tween.cur(),\n\t\t\t\tstart = +target || 0,\n\t\t\t\tscale = 1,\n\t\t\t\tmaxIterations = 20;\n\n\t\t\tif ( parts ) {\n\t\t\t\tend = +parts[2];\n\t\t\t\tunit = parts[3] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\n\t\t\t\t// We need to compute starting value\n\t\t\t\tif ( unit !== \"px\" && start ) {\n\t\t\t\t\t// Iteratively approximate from a nonzero starting point\n\t\t\t\t\t// Prefer the current property, because this process will be trivial if it uses the same units\n\t\t\t\t\t// Fallback to end or a simple constant\n\t\t\t\t\tstart = jQuery.css( tween.elem, prop, true ) || end || 1;\n\n\t\t\t\t\tdo {\n\t\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*\n\t\t\t\t\t\t// Use a string for doubling factor so we don't accidentally see scale as unchanged below\n\t\t\t\t\t\tscale = scale || \".5\";\n\n\t\t\t\t\t\t// Adjust and apply\n\t\t\t\t\t\tstart = start / scale;\n\t\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\n\n\t\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur()\n\t\t\t\t\t// And breaking the loop if scale is unchanged or perfect, or if we've just had enough\n\t\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\n\t\t\t\t}\n\n\t\t\t\ttween.unit = unit;\n\t\t\t\ttween.start = start;\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\n\t\t\t\ttween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;\n\t\t\t}\n\t\t\treturn tween;\n\t\t}]\n\t};\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\tsetTimeout(function() {\n\t\tfxNow = undefined;\n\t}, 0 );\n\treturn ( fxNow = jQuery.now() );\n}\n\nfunction createTweens( animation, props ) {\n\tjQuery.each( props, function( prop, value ) {\n\t\tvar collection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\n\t\t\tindex = 0,\n\t\t\tlength = collection.length;\n\t\tfor ( ; index < length; index++ ) {\n\t\t\tif ( collection[ index ].call( animation, prop, value ) ) {\n\n\t\t\t\t// we're done with this property\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tindex = 0,\n\t\ttweenerIndex = 0,\n\t\tlength = animationPrefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\t\t\t// don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick = function() {\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\t\t\t\t// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\n\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t} else {\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tanimation = deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end, easing ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\t\t\t\t\t// if we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\n\t\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// resolve when we played the last frame\n\t\t\t\t// otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length ; index++ ) {\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tcreateTweens( animation, props );\n\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue,\n\t\t\telem: elem\n\t\t})\n\t);\n\n\t// attach callbacks from options\n\treturn animation.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = jQuery.camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( jQuery.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// not quite $.extend, this wont overwrite keys already present.\n\t\t\t// also - reusing 'index' from above because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweener: function( props, callback ) {\n\t\tif ( jQuery.isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.split(\" \");\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length ; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\n\t\t\ttweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tanimationPrefilters.unshift( callback );\n\t\t} else {\n\t\t\tanimationPrefilters.push( callback );\n\t\t}\n\t}\n});\n\nfunction defaultPrefilter( elem, props, opts ) {\n\tvar index, prop, value, length, dataShow, toggle, tween, hooks, oldfire,\n\t\tanim = this,\n\t\tstyle = elem.style,\n\t\torig = {},\n\t\thandled = [],\n\t\thidden = elem.nodeType && isHidden( elem );\n\n\t// handle queue: false promises\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function() {\n\t\t\t// doing this makes sure that the complete handler will be called\n\t\t\t// before this completes\n\t\t\tanim.always(function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// height/width overflow pass\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\n\t\t// Make sure that nothing sneaks out\n\t\t// Record all 3 overflow attributes because IE does not\n\t\t// change the overflow attribute when overflowX and\n\t\t// overflowY are set to the same value\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Set display property to inline-block for height/width\n\t\t// animations on inline elements that are having width/height animated\n\t\tif ( jQuery.css( elem, \"display\" ) === \"inline\" &&\n\t\t\t\tjQuery.css( elem, \"float\" ) === \"none\" ) {\n\n\t\t\t// inline-level elements accept inline-block;\n\t\t\t// block-level elements need to be inline with layout\n\t\t\tif ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === \"inline\" ) {\n\t\t\t\tstyle.display = \"inline-block\";\n\n\t\t\t} else {\n\t\t\t\tstyle.zoom = 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tif ( !jQuery.support.shrinkWrapBlocks ) {\n\t\t\tanim.done(function() {\n\t\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t\t});\n\t\t}\n\t}\n\n\n\t// show/hide pass\n\tfor ( index in props ) {\n\t\tvalue = props[ index ];\n\t\tif ( rfxtypes.exec( value ) ) {\n\t\t\tdelete props[ index ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thandled.push( index );\n\t\t}\n\t}\n\n\tlength = handled.length;\n\tif ( length ) {\n\t\tdataShow = jQuery._data( elem, \"fxshow\" ) || jQuery._data( elem, \"fxshow\", {} );\n\t\tif ( \"hidden\" in dataShow ) {\n\t\t\thidden = dataShow.hidden;\n\t\t}\n\n\t\t// store state if its toggle - enables .stop().toggle() to \"reverse\"\n\t\tif ( toggle ) {\n\t\t\tdataShow.hidden = !hidden;\n\t\t}\n\t\tif ( hidden ) {\n\t\t\tjQuery( elem ).show();\n\t\t} else {\n\t\t\tanim.done(function() {\n\t\t\t\tjQuery( elem ).hide();\n\t\t\t});\n\t\t}\n\t\tanim.done(function() {\n\t\t\tvar prop;\n\t\t\tjQuery.removeData( elem, \"fxshow\", true );\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t}\n\t\t});\n\t\tfor ( index = 0 ; index < length ; index++ ) {\n\t\t\tprop = handled[ index ];\n\t\t\ttween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );\n\t\t\torig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );\n\n\t\t\tif ( !( prop in dataShow ) ) {\n\t\t\t\tdataShow[ prop ] = tween.start;\n\t\t\t\tif ( hidden ) {\n\t\t\t\t\ttween.end = tween.start;\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || \"swing\";\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// passing any value as a 4th parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails\n\t\t\t// so, simple values such as \"10px\" are parsed to Float.\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, false, \"\" );\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\t\t\t// use step hook for back compat - use cssHook if its there - use .style if its\n\t\t\t// available and use plain properties where available\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Remove in 2.0 - this supports IE8's panic based approach\n// to setting things on disconnected nodes\n\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ||\n\t\t\t// special check for .toggle( handler, handler, ... )\n\t\t\t( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n});\n\njQuery.fn.extend({\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\n\n\t\t\t// animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations resolve immediately\n\t\t\t\tif ( empty ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue && type !== false ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = jQuery._data( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// start the next in the queue if the last step wasn't forced\n\t\t\t// timers currently will call their complete callbacks, which will dequeue\n\t\t\t// but only if they were gotoEnd\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t});\n\t}\n});\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\tattrs = { height: type },\n\t\ti = 0;\n\n\t// if we include width, step value is 1 to do all cssExpand values,\n\t// if we don't include width, step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth? 1 : 0;\n\tfor( ; i < 4 ; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx(\"show\"),\n\tslideUp: genFx(\"hide\"),\n\tslideToggle: genFx(\"toggle\"),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n});\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tjQuery.isFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\n\t};\n\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n\t// normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( jQuery.isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p*Math.PI ) / 2;\n\t}\n};\n\njQuery.timers = [];\njQuery.fx = Tween.prototype.init;\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ttimers = jQuery.timers,\n\t\ti = 0;\n\n\tfxNow = jQuery.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\t\t// Checks the timer has not already been removed\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tif ( timer() && jQuery.timers.push( timer ) && !timerId ) {\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\n\t}\n};\n\njQuery.fx.interval = 13;\n\njQuery.fx.stop = function() {\n\tclearInterval( timerId );\n\ttimerId = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\t// Default speed\n\t_default: 400\n};\n\n// Back Compat <1.8 extension point\njQuery.fx.step = {};\n\nif ( jQuery.expr && jQuery.expr.filters ) {\n\tjQuery.expr.filters.animated = function( elem ) {\n\t\treturn jQuery.grep(jQuery.timers, function( fn ) {\n\t\t\treturn elem === fn.elem;\n\t\t}).length;\n\t};\n}\nvar rroot = /^(?:body|html)$/i;\n\njQuery.fn.offset = function( options ) {\n\tif ( arguments.length ) {\n\t\treturn options === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function( i ) {\n\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t});\n\t}\n\n\tvar docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft,\n\t\tbox = { top: 0, left: 0 },\n\t\telem = this[ 0 ],\n\t\tdoc = elem && elem.ownerDocument;\n\n\tif ( !doc ) {\n\t\treturn;\n\t}\n\n\tif ( (body = doc.body) === elem ) {\n\t\treturn jQuery.offset.bodyOffset( elem );\n\t}\n\n\tdocElem = doc.documentElement;\n\n\t// Make sure it's not a disconnected DOM node\n\tif ( !jQuery.contains( docElem, elem ) ) {\n\t\treturn box;\n\t}\n\n\t// If we don't have gBCR, just use 0,0 rather than error\n\t// BlackBerry 5, iOS 3 (original iPhone)\n\tif ( typeof elem.getBoundingClientRect !== \"undefined\" ) {\n\t\tbox = elem.getBoundingClientRect();\n\t}\n\twin = getWindow( doc );\n\tclientTop  = docElem.clientTop  || body.clientTop  || 0;\n\tclientLeft = docElem.clientLeft || body.clientLeft || 0;\n\tscrollTop  = win.pageYOffset || docElem.scrollTop;\n\tscrollLeft = win.pageXOffset || docElem.scrollLeft;\n\treturn {\n\t\ttop: box.top  + scrollTop  - clientTop,\n\t\tleft: box.left + scrollLeft - clientLeft\n\t};\n};\n\njQuery.offset = {\n\n\tbodyOffset: function( body ) {\n\t\tvar top = body.offsetTop,\n\t\t\tleft = body.offsetLeft;\n\n\t\tif ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {\n\t\t\ttop  += parseFloat( jQuery.css(body, \"marginTop\") ) || 0;\n\t\t\tleft += parseFloat( jQuery.css(body, \"marginLeft\") ) || 0;\n\t\t}\n\n\t\treturn { top: top, left: left };\n\t},\n\n\tsetOffset: function( elem, options, i ) {\n\t\tvar position = jQuery.css( elem, \"position\" );\n\n\t\t// set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tvar curElem = jQuery( elem ),\n\t\t\tcurOffset = curElem.offset(),\n\t\t\tcurCSSTop = jQuery.css( elem, \"top\" ),\n\t\t\tcurCSSLeft = jQuery.css( elem, \"left\" ),\n\t\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) && jQuery.inArray(\"auto\", [curCSSTop, curCSSLeft]) > -1,\n\t\t\tprops = {}, curPosition = {}, curTop, curLeft;\n\n\t\t// need to be able to calculate position if either top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( jQuery.isFunction( options ) ) {\n\t\t\toptions = options.call( elem, i, curOffset );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\n\njQuery.fn.extend({\n\n\tposition: function() {\n\t\tif ( !this[0] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar elem = this[0],\n\n\t\t// Get *real* offsetParent\n\t\toffsetParent = this.offsetParent(),\n\n\t\t// Get correct offsets\n\t\toffset       = this.offset(),\n\t\tparentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();\n\n\t\t// Subtract element margins\n\t\t// note: when an element has margin: auto the offsetLeft and marginLeft\n\t\t// are the same in Safari causing offset.left to incorrectly be 0\n\t\toffset.top  -= parseFloat( jQuery.css(elem, \"marginTop\") ) || 0;\n\t\toffset.left -= parseFloat( jQuery.css(elem, \"marginLeft\") ) || 0;\n\n\t\t// Add offsetParent borders\n\t\tparentOffset.top  += parseFloat( jQuery.css(offsetParent[0], \"borderTopWidth\") ) || 0;\n\t\tparentOffset.left += parseFloat( jQuery.css(offsetParent[0], \"borderLeftWidth\") ) || 0;\n\n\t\t// Subtract the two offsets\n\t\treturn {\n\t\t\ttop:  offset.top  - parentOffset.top,\n\t\t\tleft: offset.left - parentOffset.left\n\t\t};\n\t},\n\n\toffsetParent: function() {\n\t\treturn this.map(function() {\n\t\t\tvar offsetParent = this.offsetParent || document.body;\n\t\t\twhile ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, \"position\") === \"static\") ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\t\t\treturn offsetParent || document.body;\n\t\t});\n\t}\n});\n\n\n// Create scrollLeft and scrollTop methods\njQuery.each( {scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\"}, function( method, prop ) {\n\tvar top = /Y/.test( prop );\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn jQuery.access( this, function( elem, method, val ) {\n\t\t\tvar win = getWindow( elem );\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? (prop in win) ? win[ prop ] :\n\t\t\t\t\twin.document.documentElement[ method ] :\n\t\t\t\t\telem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : jQuery( win ).scrollLeft(),\n\t\t\t\t\t top ? val : jQuery( win ).scrollTop()\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length, null );\n\t};\n});\n\nfunction getWindow( elem ) {\n\treturn jQuery.isWindow( elem ) ?\n\t\telem :\n\t\telem.nodeType === 9 ?\n\t\t\telem.defaultView || elem.parentWindow :\n\t\t\tfalse;\n}\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\n\t\t// margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn jQuery.access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest\n\t\t\t\t\t// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, value, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\n\t\t};\n\t});\n});\n// Expose jQuery to the global object\nwindow.jQuery = window.$ = jQuery;\n\n// Expose jQuery as an AMD module, but only for AMD loaders that\n// understand the issues with loading multiple versions of jQuery\n// in a page that all might call define(). The loader will indicate\n// they have special allowances for multiple jQuery versions by\n// specifying define.amd.jQuery = true. Register as a named module,\n// since jQuery can be concatenated with other files that may use define,\n// but not use a proper concatenation script that understands anonymous\n// AMD modules. A named AMD is safest and most robust way to register.\n// Lowercase jquery is used because AMD module names are derived from\n// file names, and jQuery is normally delivered in a lowercase file name.\n// Do this after creating the global so that if an AMD module wants to call\n// noConflict to hide this version of jQuery, it will work.\nif ( typeof define === \"function\" && define.amd && define.amd.jQuery ) {\n\tdefine( \"jquery\", [], function () { return jQuery; } );\n}\n\n})( window );\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/jquery/jquery.cookie.js",
    "content": "/*!\n * jQuery Cookie Plugin v1.4.0\n * https://github.com/carhartl/jquery-cookie\n *\n * Copyright 2013 Klaus Hartl\n * Released under the MIT license\n */\n(function (factory) {\n\tif (typeof define === 'function' && define.amd) {\n\t\t// AMD. Register as anonymous module.\n\t\tdefine(['jquery'], factory);\n\t} else {\n\t\t// Browser globals.\n\t\tfactory(jQuery);\n\t}\n}(function ($) {\n\n\tvar pluses = /\\+/g;\n\n\tfunction encode(s) {\n\t\treturn config.raw ? s : encodeURIComponent(s);\n\t}\n\n\tfunction decode(s) {\n\t\treturn config.raw ? s : decodeURIComponent(s);\n\t}\n\n\tfunction stringifyCookieValue(value) {\n\t\treturn encode(config.json ? JSON.stringify(value) : String(value));\n\t}\n\n\tfunction parseCookieValue(s) {\n\t\tif (s.indexOf('\"') === 0) {\n\t\t\t// This is a quoted cookie as according to RFC2068, unescape...\n\t\t\ts = s.slice(1, -1).replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, '\\\\');\n\t\t}\n\n\t\ttry {\n\t\t\t// Replace server-side written pluses with spaces.\n\t\t\t// If we can't decode the cookie, ignore it, it's unusable.\n\t\t\ts = decodeURIComponent(s.replace(pluses, ' '));\n\t\t} catch(e) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// If we can't parse the cookie, ignore it, it's unusable.\n\t\t\treturn config.json ? JSON.parse(s) : s;\n\t\t} catch(e) {}\n\t}\n\n\tfunction read(s, converter) {\n\t\tvar value = config.raw ? s : parseCookieValue(s);\n\t\treturn $.isFunction(converter) ? converter(value) : value;\n\t}\n\n\tvar config = $.cookie = function (key, value, options) {\n\n\t\t// Write\n\t\tif (value !== undefined && !$.isFunction(value)) {\n\t\t\toptions = $.extend({}, config.defaults, options);\n\n\t\t\tif (typeof options.expires === 'number') {\n\t\t\t\tvar days = options.expires, t = options.expires = new Date();\n\t\t\t\tt.setDate(t.getDate() + days);\n\t\t\t}\n\n\t\t\treturn (document.cookie = [\n\t\t\t\tencode(key), '=', stringifyCookieValue(value),\n\t\t\t\toptions.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE\n\t\t\t\toptions.path    ? '; path=' + options.path : '',\n\t\t\t\toptions.domain  ? '; domain=' + options.domain : '',\n\t\t\t\toptions.secure  ? '; secure' : ''\n\t\t\t].join(''));\n\t\t}\n\n\t\t// Read\n\n\t\tvar result = key ? undefined : {};\n\n\t\t// To prevent the for loop in the first place assign an empty array\n\t\t// in case there are no cookies at all. Also prevents odd result when\n\t\t// calling $.cookie().\n\t\tvar cookies = document.cookie ? document.cookie.split('; ') : [];\n\n\t\tfor (var i = 0, l = cookies.length; i < l; i++) {\n\t\t\tvar parts = cookies[i].split('=');\n\t\t\tvar name = decode(parts.shift());\n\t\t\tvar cookie = parts.join('=');\n\n\t\t\tif (key && key === name) {\n\t\t\t\t// If second argument (value) is a function it's a converter...\n\t\t\t\tresult = read(cookie, value);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Prevent storing a cookie that we couldn't decode.\n\t\t\tif (!key && (cookie = read(cookie)) !== undefined) {\n\t\t\t\tresult[name] = cookie;\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tconfig.defaults = {};\n\n\t$.removeCookie = function (key, options) {\n\t\tif ($.cookie(key) !== undefined) {\n\t\t\t// Must not alter options, thus extending a fresh object...\n\t\t\t$.cookie(key, '', $.extend({}, options, { expires: -1 }));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t};\n\n}));\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/jquery/parser-input-select.js",
    "content": "/*! Parser: input & select - updated 2018-07-10 (v2.30.7) *//*\n * for jQuery 1.7+ & tablesorter 2.7.11+\n * Demo: http://mottie.github.com/tablesorter/docs/example-widget-grouping.html\n */\n/*jshint browser: true, jquery:true, unused:false */\n;( function( $ ) {\n\t'use strict';\n\n\tvar updateServer = function( /* event, $table, $input */ ) {\n\t\t// do something here to update your server, if needed\n\t\t// event = change event object\n\t\t// $table = jQuery object of the table that was just updated\n\t\t// $input = jQuery object(s) of the input or select that was modified; in v2.24.6,\n\t\t//   if the thead has a checkbox, $input may include multiple elements\n\t};\n\n\t// Custom parser for parsing input values\n\t// updated dynamically using the 'change' function below\n\t$.tablesorter.addParser({\n\t\tid : 'inputs',\n\t\tis : function() {\n\t\t\treturn false;\n\t\t},\n\t\tformat : function( txt, table, cell ) {\n\t\t\tvar $input = $( cell ).find( 'input' );\n\t\t\treturn $input.length ? $input.val() : txt;\n\t\t},\n\t\tparsed : true, // filter widget flag\n\t\ttype : 'text'\n\t});\n\n\t$.tablesorter.addParser({\n\t\tid : 'inputs-numeric',\n\t\tis : function() {\n\t\t\treturn false;\n\t\t},\n\t\tformat : function( txt, table, cell ) {\n\t\t\tvar $input = $( cell ).find( 'input' );\n\t\t\tvar val = $input.length ? $input.val() : txt,\n\t\t\t\tnum = $.tablesorter.formatFloat( ( val || '' ).replace( /[^\\w,. \\-()]/g, '' ), table );\n\t\t\treturn txt && typeof num === 'number' ? num :\n\t\t\t\ttxt ? $.trim( txt && table.config.ignoreCase ? txt.toLocaleLowerCase() : txt ) : txt;\n\t\t},\n\t\tparsed : true, // filter widget flag\n\t\ttype : 'numeric'\n\t});\n\n\t// Custom parser for including checkbox status if using the grouping widget\n\t// updated dynamically using the 'change' function below\n\t$.tablesorter.addParser({\n\t\tid : 'checkbox',\n\t\tis : function() {\n\t\t\treturn false;\n\t\t},\n\t\tformat : function( txt, table, cell ) {\n\t\t\tvar $cell = $( cell ),\n\t\t\t\two = table.config.widgetOptions,\n\t\t\t\t// returning plain language here because this is what is shown in the\n\t\t\t\t// group headers - change it as desired\n\t\t\t\tstatus = wo.group_checkbox ? wo.group_checkbox : [ 'checked', 'unchecked' ],\n\t\t\t\t$input = $cell.find( 'input[type=\"checkbox\"]' ),\n\t\t\t\tisChecked = $input.length ? $input[ 0 ].checked : '';\n\t\t\treturn $input.length ? status[ isChecked ? 0 : 1 ] : txt;\n\t\t},\n\t\tparsed : true, // filter widget flag\n\t\ttype : 'text'\n\t});\n\n\t$.tablesorter.addParser({\n\t\tid: 'radio',\n\t\tis: function() {\n\t\t\treturn false;\n\t\t},\n\t\tformat: function(txt, table, cell) {\n\t\t\tvar $input = $(cell).find('input:checked');\n\t\t\treturn $input.length ? $input.val() : txt;\n\t\t},\n\t\tparsed: true,\n\t\ttype: 'text'\n\t});\n\n\t// Custom parser which returns the currently selected options\n\t// updated dynamically using the 'change' function below\n\t$.tablesorter.addParser({\n\t\tid : 'select',\n\t\tis : function() {\n\t\t\treturn false;\n\t\t},\n\t\tformat : function( txt, table, cell ) {\n\t\t\tvar $select = $( cell ).find( 'select' );\n\t\t\treturn $select.length ? $select.val() : txt;\n\t\t},\n\t\tparsed : true, // filter widget flag\n\t\ttype : 'text'\n\t});\n\n\t// Select parser to get the selected text\n\t$.tablesorter.addParser({\n\t\tid : 'select-text',\n\t\tis : function() {\n\t\t\treturn false;\n\t\t},\n\t\tformat : function( txt, table, cell ) {\n\t\t\tvar $select = $( cell ).find( 'select' );\n\t\t\treturn $select.length ? $select.find( 'option:selected' ).text() || '' : txt;\n\t\t},\n\t\tparsed : true, // filter widget flag\n\t\ttype : 'text'\n\t});\n\n\t// Custom parser for parsing textarea values\n\t// updated dynamically using the 'change' function below\n\t$.tablesorter.addParser({\n\t\tid : 'textarea',\n\t\tis : function() {\n\t\t\treturn false;\n\t\t},\n\t\tformat : function( txt, table, cell ) {\n\t\t\tvar $textarea = $( cell ).find( 'textarea' );\n\t\t\treturn $textarea.length ? $textarea.val() : txt;\n\t\t},\n\t\tparsed : true, // filter widget flag\n\t\ttype : 'text'\n\t});\n\n\t// update defaults for validator; values must be falsy\n\t$.tablesorter.defaults.checkboxClass = '';\n\t$.tablesorter.defaults.checkboxVisible = '';\n\n\t// update select and all input types in the tablesorter cache when the change event fires.\n\t// This method only works with jQuery 1.7+\n\t// you can change it to use delegate (v1.4.3+) or live (v1.3+) as desired\n\t// if this code interferes somehow, target the specific table $('#mytable'), instead of $('table')\n\t$( function() {\n\t\tif ( !$.fn.on ) { return; }\n\t\tvar toggleRowClass = function( $row, checkboxClass, indx, isChecked ) {\n\t\t\t// adding class to row, indicating that a checkbox is checked; includes\n\t\t\t// a column index in case more than one checkbox happens to be in a row\n\t\t\t$row.toggleClass( checkboxClass + '-' + indx, isChecked );\n\t\t\t// don't remove checked class if other columns have a check\n\t\t\tif ( ( $row[0].className || '' ).match( checkboxClass + '-' ) ) {\n\t\t\t\t$row.addClass( checkboxClass );\n\t\t\t} else {\n\t\t\t\t$row.removeClass( checkboxClass );\n\t\t\t}\n\t\t},\n\t\tupdateCheckbox = function($el, state) {\n\t\t\tif ($el.length && $el[0].nodeName !== 'INPUT') {\n\t\t\t\t$el = $el.find( 'input[type=\"checkbox\"]' );\n\t\t\t}\n\t\t\tif ($el.length) {\n\t\t\t\tvar ua = window.navigator.userAgent;\n\t\t\t\tif (state === 'indeterminate') {\n\t\t\t\t\t// needed for IE\n\t\t\t\t\t$el.prop('checked', !(ua.indexOf('Trident/') > -1 || ua.indexOf('Edge/') > -1));\n\t\t\t\t\t$el.prop('indeterminate', true);\n\t\t\t\t} else {\n\t\t\t\t\t$el.prop('checked', state);\n\t\t\t\t\t$el.prop('indeterminate', false);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tupdateHeaderCheckbox = function( $table, checkboxClass ) {\n\t\t\tvar $sticky,\n\t\t\t\t$rows = $table.children( 'tbody' ).children( ':visible' ), // (include child rows?)\n\t\t\t\tlen = $rows.length,\n\t\t\t\tc = $table[0].config,\n\t\t\t\two = c && c.widgetOptions,\n\t\t\t\t$headers = c && c.$headers.add( $( c.namespace + '_extra_headers' ) ) || $table.children( 'thead' ),\n\t\t\t\thasSticky = wo && wo.$sticky;\n\t\t\t// set indeterminate state on header checkbox\n\t\t\t$headers.find( 'input[type=\"checkbox\"]' ).each( function() {\n\t\t\t\tif (hasSticky) {\n\t\t\t\t\t$sticky = hasSticky.find( '[data-column=\"' + column + '\"]' );\n\t\t\t\t}\n\t\t\t\tvar column = $( this ).closest( 'td, th' ).attr( 'data-column' ),\n\t\t\t\t\tvis = $rows.filter( '.' + checkboxClass + '-' + column ).length,\n\t\t\t\t\tallChecked = vis === len && len > 0;\n\t\t\t\tif ( vis === 0 || allChecked ) {\n\t\t\t\t\tupdateCheckbox($(this), allChecked);\n\t\t\t\t\tif ($sticky) {\n\t\t\t\t\t\tupdateCheckbox($sticky, allChecked);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tupdateCheckbox($(this), 'indeterminate');\n\t\t\t\t\tif ($sticky) {\n\t\t\t\t\t\tupdateCheckbox($sticky, 'indeterminate');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t};\n\n\t\t$( 'table' ).on( 'tablesorter-initialized updateComplete', function() {\n\t\t\tthis.tablesorterBusy = false;\n\t\t\tvar namespace = '.parser-forms';\n\t\t\t$( this )\n\t\t\t// add namespace to table in case of version mismatch (v2.28.10)\n\t\t\t.addClass( this.config.namespace.slice(1) )\n\t\t\t.children( 'tbody' )\n\t\t\t.off( namespace )\n\t\t\t.on( 'mouseleave' + namespace, function( event ) {\n\t\t\t\t// make sure we restore original values (trigger blur)\n\t\t\t\t// isTbody is needed to prevent the select from closing in IE\n\t\t\t\t// see https://connect.microsoft.com/IE/feedbackdetail/view/962618/\n\t\t\t\tif ( event.target.nodeName === 'TBODY' ) {\n\t\t\t\t\t$( ':focus' ).blur();\n\t\t\t\t}\n\t\t\t})\n\t\t\t.on( 'focus' + namespace, 'select, input:not([type=checkbox]), textarea', function( event ) {\n\t\t\t\tvar $row = $( event.target ).closest( 'tr' ),\n\t\t\t\t\tc = $row.closest( 'table' )[0].config;\n\t\t\t\tif ( !c || c && c.ignoreChildRow && $row.hasClass( c.cssChildRow ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t$( this ).data( 'ts-original-value', this.value );\n\t\t\t})\n\t\t\t.on( 'blur' + namespace, 'input:not([type=checkbox]), textarea', function( event ) {\n\t\t\t\tvar $row = $( event.target ).closest( 'tr' ),\n\t\t\t\t\tc = $row.closest( 'table' )[0].config;\n\t\t\t\tif ( !c || c && c.ignoreChildRow && $row.hasClass( c.cssChildRow ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// restore input value;\n\t\t\t\t// 'change' is triggered before 'blur' so this doesn't replace the new update with the original\n\t\t\t\tthis.value = $( this ).data( 'ts-original-value' );\n\t\t\t})\n\t\t\t.on( 'change keyup '.split( ' ' ).join( namespace + ' ' ), 'select, input, textarea', function( event ) {\n\t\t\t\t// don't use updateCell if this (input) is set to 'parser-false'\n\t\t\t\tif (this.classList.contains('parser-false')) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvar $row = $( this ).closest( 'tr' ),\n\t\t\t\t\tc = $row.closest( 'table' )[0].config;\n\t\t\t\tif ( !c || c && c.ignoreChildRow && $row.hasClass( c.cssChildRow ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif ( event.which === 27 && !( this.nodeName === 'INPUT' && this.type === 'checkbox' ) ) {\n\t\t\t\t\t// escape: restore original value\n\t\t\t\t\tthis.value = $( this ).data( 'ts-original-value' );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Update cell cache using... select: change, input: enter or textarea: alt + enter\n\t\t\t\tif ( event.type === 'change' ||\n\t\t\t\t\t( event.type === 'keyup' && event.which === 13 &&\n\t\t\t\t\t( event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA' && event.altKey ) ) ) {\n\t\t\t\t\tvar undef, checkboxClass,\n\t\t\t\t\t\t$target = $( event.target ),\n\t\t\t\t\t\tisCheckbox = event.target.type === 'checkbox',\n\t\t\t\t\t\t$cell = $target.closest( 'td' ),\n\t\t\t\t\t\tindx = $cell[ 0 ].cellIndex,\n\t\t\t\t\t\tbusy = c.table.tablesorterBusy,\n\t\t\t\t\t\t$hdr = c.$headerIndexed && c.$headerIndexed[ indx ] || [],\n\t\t\t\t\t\tval = isCheckbox ? event.target.checked : $target.val();\n\t\t\t\t\t// abort if not a tablesorter table, or busy\n\t\t\t\t\tif ( $.isEmptyObject( c ) || busy !== false ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif ( isCheckbox ) {\n\t\t\t\t\t\tcheckboxClass = c.checkboxClass || 'checked';\n\t\t\t\t\t\ttoggleRowClass( $cell.closest( 'tr' ), checkboxClass, indx, val );\n\t\t\t\t\t\tupdateHeaderCheckbox( c.$table, checkboxClass );\n\t\t\t\t\t}\n\t\t\t\t\t// don't use updateCell if column is set to 'sorter-false' and 'filter-false',\n\t\t\t\t\t// or column is set to 'parser-false'\n\t\t\t\t\tif ( $hdr.length && ( $hdr.hasClass( 'parser-false' ) ||\n\t\t\t\t\t\t( $hdr.hasClass( 'sorter-false' ) && $hdr.hasClass( 'filter-false' ) ) ) ||\n\t\t\t\t\t\t// table already updating; get out of here, we might be in an endless loop (in IE)! See #971\n\t\t\t\t\t\t( event.type === 'change' && c.table.isUpdating ) ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// ignore change event if nothing changed\n\t\t\t\t\tif ( c && val !== $target.data( 'ts-original-value' ) || isCheckbox ) {\n\t\t\t\t\t\t$target.data( 'ts-original-value', val );\n\t\t\t\t\t\tc.table.tablesorterBusy = true;\n\t\t\t\t\t\t// pass undefined resort value so it falls back to config.resort setting\n\t\t\t\t\t\t$.tablesorter.updateCell( c, $cell, undef, function() {\n\t\t\t\t\t\t\tupdateServer( event, c.$table, $target );\n\t\t\t\t\t\t\tc.table.tablesorterBusy = false;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// add code for a checkbox in the header to set/unset all checkboxes in a column\n\t\t\tif ( $( this ).children( 'thead' ).find( 'input[type=\"checkbox\"]' ) ) {\n\t\t\t\t$( this )\n\t\t\t\t.off( namespace )\n\t\t\t\t.on( 'tablesorter-ready' + namespace, function() {\n\t\t\t\t\tvar checkboxClass,\n\t\t\t\t\t\t$table = $( this ),\n\t\t\t\t\t\tc = $table.length && $table[ 0 ].config;\n\t\t\t\t\tif ( !$.isEmptyObject( c ) ) {\n\t\t\t\t\t\tthis.tablesorterBusy = true;\n\t\t\t\t\t\tcheckboxClass = c && c.checkboxClass || 'checked';\n\t\t\t\t\t\t// set indeterminate state on header checkbox\n\t\t\t\t\t\tupdateHeaderCheckbox( $table, checkboxClass );\n\t\t\t\t\t\tthis.tablesorterBusy = false;\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.children( 'thead' )\n\t\t\t\t.add( this.config.widgetOptions.$sticky )\n\t\t\t\t.off( namespace )\n\t\t\t\t// modified from http://jsfiddle.net/abkNM/6163/\n\t\t\t\t// click needed for IE; a change isn't fired when going from an indeterminate checkbox to\n\t\t\t\t// either checked or unchecked\n\t\t\t\t.on( 'click' + namespace + ' change' + namespace, 'input[type=\"checkbox\"]', function( event ) {\n\t\t\t\t\tvar c, undef, onlyVisible, column, $target, isParsed, checkboxClass,\n\t\t\t\t\t\t$checkbox = $( this ),\n\t\t\t\t\t\tisChecked = this.checked,\n\t\t\t\t\t\t$table = $checkbox.closest( 'table' ),\n\t\t\t\t\t\tisSticky = $table.length && $table[0].className.match(/(tablesorter\\w+)_extra_table/);\n\t\t\t\t\tif (isSticky) {\n\t\t\t\t\t\tisSticky = isSticky[1];\n\t\t\t\t\t\t$table = $('.' + isSticky + ':not(.' + isSticky + '_extra_table)');\n\t\t\t\t\t}\n\t\t\t\t\tc = $table.length && $table[ 0 ].config;\n\t\t\t\t\tif ( $table.length && c && !$table[ 0 ].tablesorterBusy ) {\n\t\t\t\t\t\tcolumn = parseInt( $checkbox.closest( 'td, th' ).attr( 'data-column' ), 10 );\n\t\t\t\t\t\tisParsed = c.parsers[ column ].id === 'checkbox';\n\t\t\t\t\t\tonlyVisible = c.checkboxVisible;\n\t\t\t\t\t\t$table[ 0 ].tablesorterBusy = true; // prevent \"change\" event from calling updateCell numerous times (see #971)\n\t\t\t\t\t\tupdateCheckbox(\n\t\t\t\t\t\t\t$target = $table\n\t\t\t\t\t\t\t\t.children( 'tbody' )\n\t\t\t\t\t\t\t\t.children( 'tr' + ( typeof onlyVisible === 'undefined' || onlyVisible === true ? ':visible' : '' ) )\n\t\t\t\t\t\t\t\t.children( ':nth-child(' + ( column + 1 ) + ')' ),\n\t\t\t\t\t\t\tisChecked\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// add checkbox class names to row\n\t\t\t\t\t\tcheckboxClass = c.checkboxClass || 'checked';\n\t\t\t\t\t\t$target.each( function() {\n\t\t\t\t\t\t\ttoggleRowClass( $( this ).closest( 'tr' ), checkboxClass, column, isChecked );\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (isSticky) {\n\t\t\t\t\t\t\t// make main table checkbox match sticky header checkbox\n\t\t\t\t\t\t\tupdateCheckbox($table.children( 'thead' ).find( '[data-column=\"' + column + '\"]' ), isChecked);\n\t\t\t\t\t\t} else if (c.widgetOptions.$sticky) {\n\t\t\t\t\t\t\tupdateCheckbox(c.widgetOptions.$sticky.find( 'thead' ).find( '[data-column=\"' + column + '\"]' ), isChecked);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tupdateHeaderCheckbox( $table, checkboxClass );\n\t\t\t\t\t\tif ( isParsed ) {\n\t\t\t\t\t\t\t// only update cache if checkboxes are being sorted\n\t\t\t\t\t\t\t$.tablesorter.update( c, undef, function() {\n\t\t\t\t\t\t\t\tupdateServer( event, $table, $target );\n\t\t\t\t\t\t\t\t$table[ 0 ].tablesorterBusy = false;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tupdateServer( event, $table, $target );\n\t\t\t\t\t\t\t$table[ 0 ].tablesorterBusy = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// needed for IE8\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\t// update already going on, don't do anything!\n\t\t\t\t\treturn false;\n\t\t\t\t});\n\t\t\t}\n\n\t\t});\n\t});\n\n})( jQuery );\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/jquery-ui/AUTHORS.txt",
    "content": "Authors ordered by first contribution\nA list of current team members is available at http://jqueryui.com/about\n\nPaul Bakaus <paul.bakaus@gmail.com>\nRichard Worth <rdworth@gmail.com>\nYehuda Katz <wycats@gmail.com>\nSean Catchpole <sean@sunsean.com>\nJohn Resig <jeresig@gmail.com>\nTane Piper <piper.tane@gmail.com>\nDmitri Gaskin <dmitrig01@gmail.com>\nKlaus Hartl <klaus.hartl@gmail.com>\nStefan Petre <stefan.petre@gmail.com>\nGilles van den Hoven <gilles@webunity.nl>\nMicheil Bryan Smith <micheil@brandedcode.com>\nJörn Zaefferer <joern.zaefferer@gmail.com>\nMarc Grabanski <m@marcgrabanski.com>\nKeith Wood <kbwood@iinet.com.au>\nBrandon Aaron <brandon.aaron@gmail.com>\nScott González <scott.gonzalez@gmail.com>\nEduardo Lundgren <eduardolundgren@gmail.com>\nAaron Eisenberger <aaronchi@gmail.com>\nJoan Piedra <theneojp@gmail.com>\nBruno Basto <b.basto@gmail.com>\nRemy Sharp <remy@leftlogic.com>\nBohdan Ganicky <bohdan.ganicky@gmail.com>\nDavid Bolter <david.bolter@gmail.com>\nChi Cheng <cloudream@gmail.com>\nCa-Phun Ung <pazu2k@gmail.com>\nAriel Flesler <aflesler@gmail.com>\nMaggie Wachs <maggie@filamentgroup.com>\nScott Jehl <scottjehl@gmail.com>\nTodd Parker <todd@filamentgroup.com>\nAndrew Powell <andrew@shellscape.org>\nBrant Burnett <btburnett3@gmail.com>\nDouglas Neiner <doug@dougneiner.com>\nPaul Irish <paul.irish@gmail.com>\nRalph Whitbeck <ralph.whitbeck@gmail.com>\nThibault Duplessis <thibault.duplessis@gmail.com>\nDominique Vincent <dominique.vincent@toitl.com>\nJack Hsu <jack.hsu@gmail.com>\nAdam Sontag <ajpiano@ajpiano.com>\nCarl Fürstenberg <carl@excito.com>\nKevin Dalman <development@allpro.net>\nAlberto Fernández Capel <afcapel@gmail.com>\nJacek Jędrzejewski (http://jacek.jedrzejewski.name)\nTing Kuei <ting@kuei.com>\nSamuel Cormier-Iijima <sam@chide.it>\nJon Palmer <jonspalmer@gmail.com>\nBen Hollis <bhollis@amazon.com>\nJustin MacCarthy <Justin@Rubystars.biz>\nEyal Kobrigo <kobrigo@hotmail.com>\nTiago Freire <tiago.freire@gmail.com>\nDiego Tres <diegotres@gmail.com>\nHolger Rüprich <holger@rueprich.de>\nZiling Zhao <zilingzhao@gmail.com>\nMike Alsup <malsup@gmail.com>\nRobson Braga Araujo <robsonbraga@gmail.com>\nPierre-Henri Ausseil <ph.ausseil@gmail.com>\nChristopher McCulloh <cmcculloh@gmail.com>\nAndrew Newcomb <ext.github@preceptsoftware.co.uk>\nLim Chee Aun <cheeaun@gmail.com>\nJorge Barreiro <yortx.barry@gmail.com>\nDaniel Steigerwald <daniel@steigerwald.cz>\nJohn Firebaugh <john_firebaugh@bigfix.com>\nJohn Enters <github@darkdark.net>\nAndrey Kapitcyn <ru.m157y@gmail.com>\nDmitry Petrov <dpetroff@gmail.com>\nEric Hynds <eric@hynds.net>\nChairat Sunthornwiphat <pipo@sixhead.com>\nJosh Varner <josh.varner@gmail.com>\nStéphane Raimbault <stephane.raimbault@gmail.com>\nJay Merrifield <fracmak@gmail.com>\nJ. Ryan Stinnett <jryans@gmail.com>\nPeter Heiberg <peter@heiberg.se>\nAlex Dovenmuehle <adovenmuehle@gmail.com>\nJamie Gegerson <git@jamiegegerson.com>\nRaymond Schwartz <skeetergraphics@gmail.com>\nPhillip Barnes <philbar@gmail.com>\nKyle Wilkinson <kai@wikyd.org>\nKhaled AlHourani <me@khaledalhourani.com>\nMarian Rudzynski <mr@impaled.org>\nJean-Francois Remy <jeff@melix.org>\nDoug Blood <dougblood@gmail.com>\nFilippo Cavallarin <filippo.cavallarin@codseq.it>\nHeiko Henning <heiko@thehennings.ch>\nAliaksandr Rahalevich <saksmlz@gmail.com>\nMario Visic <mario@mariovisic.com>\nXavi Ramirez <xavi.rmz@gmail.com>\nMax Schnur <max.schnur@gmail.com>\nSaji Nediyanchath <saji89@gmail.com>\nCorey Frang <gnarf37@gmail.com>\nAaron Peterson <aaronp123@yahoo.com>\nIvan Peters <ivan@ivanpeters.com>\nMohamed Cherif Bouchelaghem <cherifbouchelaghem@yahoo.fr>\nMarcos Sousa <falecomigo@marcossousa.com>\nMichael DellaNoce <mdellanoce@mailtrust.com>\nGeorge Marshall <echosx@gmail.com>\nTobias Brunner <tobias@strongswan.org>\nMartin Solli <msolli@gmail.com>\nDavid Petersen <public@petersendidit.com>\nDan Heberden <danheberden@gmail.com>\nWilliam Kevin Manire <williamkmanire@gmail.com>\nGilmore Davidson <gilmoreorless@gmail.com>\nMichael Wu <michaelmwu@gmail.com>\nAdam Parod <mystic414@gmail.com>\nGuillaume Gautreau <guillaume+github@ghusse.com>\nMarcel Toele <EleotleCram@gmail.com>\nDan Streetman <ddstreet@ieee.org>\nMatt Hoskins <matt@nipltd.com>\nGiovanni Giacobbi <giovanni@giacobbi.net>\nKyle Florence <kyle.florence@gmail.com>\nPavol Hluchý <lopo@losys.sk>\nHans Hillen <hans.hillen@gmail.com>\nMark Johnson <virgofx@live.com>\nTrey Hunner <treyhunner@gmail.com>\nShane Whittet <whittet@gmail.com>\nEdward A Faulkner <ef@alum.mit.edu>\nAdam Baratz <adam@adambaratz.com>\nKato Kazuyoshi <kato.kazuyoshi@gmail.com>\nEike Send <eike.send@gmail.com>\nKris Borchers <kris.borchers@gmail.com>\nEddie Monge <eddie@eddiemonge.com>\nIsrael Tsadok <itsadok@gmail.com>\nCarson McDonald <carson@ioncannon.net>\nJason Davies <jason@jasondavies.com>\nGarrison Locke <gplocke@gmail.com>\nDavid Murdoch <david@davidmurdoch.com>\nBenjamin Scott Boyle <benjamins.boyle@gmail.com>\nJesse Baird <jebaird@gmail.com>\nJonathan Vingiano <jvingiano@gmail.com>\nDylan Just <dev@ephox.com>\nHiroshi Tomita <tomykaira@gmail.com>\nGlenn Goodrich <glenn.goodrich@gmail.com>\nTarafder Ashek-E-Elahi <mail.ashek@gmail.com>\nRyan Neufeld <ryan@neufeldmail.com>\nMarc Neuwirth <marc.neuwirth@gmail.com>\nPhilip Graham <philip.robert.graham@gmail.com>\nBenjamin Sterling <benjamin.sterling@kenzomedia.com>\nWesley Walser <waw325@gmail.com>\nKouhei Sutou <kou@clear-code.com>\nKarl Kirch <karlkrch@gmail.com>\nChris Kelly <ckdake@ckdake.com>\nJason Oster <jay@kodewerx.org>\nFelix Nagel <info@felixnagel.com>\nAlexander Polomoshnov <alex.polomoshnov@gmail.com>\nDavid Leal <dgleal@gmail.com>\nIgor Milla <igor.fsp.milla@gmail.com>\nDave Methvin <dave.methvin@gmail.com>\nFlorian Gutmann <f.gutmann@chronimo.com>\nMarwan Al Jubeh <marwan.aljubeh@gmail.com>\nMilan Broum <midlis@googlemail.com>\nSebastian Sauer <info@dynpages.de>\nGaëtan Muller <m.gaetan89@gmail.com>\nMichel Weimerskirch <michel@weimerskirch.net>\nWilliam Griffiths <william@ycymro.com>\nStojce Slavkovski <stojce@gmail.com>\nDavid Soms <david.soms@gmail.com>\nDavid De Sloovere <david.desloovere@outlook.com>\nMichael P. Jung <michael.jung@terreon.de>\nShannon Pekary <spekary@gmail.com>\nDan Wellman <danwellman@hotmail.com>\nMatthew Edward Hutton <meh@corefiling.co.uk>\nJames Khoury <james@jameskhoury.com>\nRob Loach <robloach@gmail.com>\nAlberto Monteiro <betimbrasil@gmail.com>\nAlex Rhea <alex.rhea@gmail.com>\nKrzysztof Rosiński <rozwell69@gmail.com>\nRyan Olton <oltonr@gmail.com>\nGenie <386@mail.com>\nRick Waldron <waldron.rick@gmail.com>\nIan Simpson <spoonlikesham@gmail.com>\nLev Kitsis <spam4lev@gmail.com>\nTJ VanToll <tj.vantoll@gmail.com>\nJustin Domnitz <jdomnitz@gmail.com>\nDouglas Cerna <douglascerna@yahoo.com>\nBert ter Heide <bertjh@hotmail.com>\nJasvir Nagra <jasvir@gmail.com>\nYuriy Khabarov <13real008@gmail.com>\nHarri Kilpiö <harri.kilpio@gmail.com>\nLado Lomidze <lado.lomidze@gmail.com>\nAmir E. Aharoni <amir.aharoni@mail.huji.ac.il>\nSimon Sattes <simon.sattes@gmail.com>\nJo Liss <joliss42@gmail.com>\nGuntupalli Karunakar <karunakarg@yahoo.com>\nShahyar Ghobadpour <shahyar@gmail.com>\nLukasz Lipinski <uzza17@gmail.com>\nTimo Tijhof <krinklemail@gmail.com>\nJason Moon <jmoon@socialcast.com>\nMartin Frost <martinf55@hotmail.com>\nEneko Illarramendi <eneko@illarra.com>\nEungJun Yi <semtlenori@gmail.com>\nCourtland Allen <courtlandallen@gmail.com>\nViktar Varvanovich <non4eg@gmail.com>\nDanny Trunk <dtrunk90@gmail.com>\nPavel Stetina <pavel.stetina@nangu.tv>\nMichael Stay <metaweta@gmail.com>\nSteven Roussey <sroussey@gmail.com>\nMichael Hollis <hollis21@gmail.com>\nLee Rowlands <lee.rowlands@previousnext.com.au>\nTimmy Willison <timmywillisn@gmail.com>\nKarl Swedberg <kswedberg@gmail.com>\nBaoju Yuan <the_guy_1987@hotmail.com>\nMaciej Mroziński <maciej.k.mrozinski@gmail.com>\nLuis Dalmolin <luis.nh@gmail.com>\nMark Aaron Shirley <maspwr@gmail.com>\nMartin Hoch <martin@fidion.de>\nJiayi Yang <tr870829@gmail.com>\nPhilipp Benjamin Köppchen <xgxtpbk@gws.ms>\nSindre Sorhus <sindresorhus@gmail.com>\nBernhard Sirlinger <bernhard.sirlinger@tele2.de>\nJared A. Scheel <jared@jaredscheel.com>\nRafael Xavier de Souza <rxaviers@gmail.com>\nJohn Chen <zhang.z.chen@intel.com>\nRobert Beuligmann <robertbeuligmann@gmail.com>\nDale Kocian <dale.kocian@gmail.com>\nMike Sherov <mike.sherov@gmail.com>\nAndrew Couch <andy@couchand.com>\nMarc-Andre Lafortune <github@marc-andre.ca>\nNate Eagle <nate.eagle@teamaol.com>\nDavid Souther <davidsouther@gmail.com>\nMathias Stenbom <mathias@stenbom.com>\nSergey Kartashov <ebishkek@yandex.ru>\nAvinash R <nashpapa@gmail.com>\nEthan Romba <ethanromba@gmail.com>\nCory Gackenheimer <cory.gack@gmail.com>\nJuan Pablo Kaniefsky <jpkaniefsky@gmail.com>\nRoman Salnikov <bardt.dz@gmail.com>\nAnika Henke <anika@selfthinker.org>\nSamuel Bovée <samycookie2000@yahoo.fr>\nFabrício Matté <ult_combo@hotmail.com>\nViktor Kojouharov <vkojouharov@gmail.com>\nPawel Maruszczyk (http://hrabstwo.net)\nPavel Selitskas <p.selitskas@gmail.com>\nBjørn Johansen <post@bjornjohansen.no>\nMatthieu Penant <thieum22@hotmail.com>\nDominic Barnes <dominic@dbarnes.info>\nDavid Sullivan <david.sullivan@gmail.com>\nThomas Jaggi <thomas@responsive.ch>\nVahid Sohrabloo <vahid4134@gmail.com>\nTravis Carden <travis.carden@gmail.com>\nBruno M. Custódio <bruno@brunomcustodio.com>\nNathanael Silverman <nathanael.silverman@gmail.com>\nChristian Wenz <christian@wenz.org>\nSteve Urmston <steve@urm.st>\nZaven Muradyan <megalivoithos@gmail.com>\nWoody Gilk <shadowhand@deviantart.com>\nZbigniew Motyka <zbigniew.motyka@gmail.com>\nSuhail Alkowaileet <xsoh.k7@gmail.com>\nToshi MARUYAMA <marutosijp2@yahoo.co.jp>\nDavid Hansen <hansede@gmail.com>\nBrian Grinstead <briangrinstead@gmail.com>\nChristian Klammer <christian314159@gmail.com>\nSteven Luscher <jquerycla@steveluscher.com>\nGan Eng Chin <engchin.gan@gmail.com>\nGabriel Schulhof <gabriel.schulhof@intel.com>\nAlexander Schmitz <arschmitz@gmail.com>\nVilhjálmur Skúlason <vis@dmm.is>\nSiebrand Mazeland <siebrand@kitano.nl>\nMohsen Ekhtiari <mohsenekhtiari@yahoo.com>\nPere Orga <gotrunks@gmail.com>\nJasper de Groot <mail@ugomobi.com>\nStephane Deschamps <stephane.deschamps@gmail.com>\nJyoti Deka <dekajp@gmail.com>\nAndrei Picus <office.nightcrawler@gmail.com>\nOndrej Novy <novy@ondrej.org>\nJacob McCutcheon <jacob.mccutcheon@gmail.com>\nMonika Piotrowicz <monika.piotrowicz@gmail.com>\nImants Horsts <imants.horsts@inbox.lv>\nEric Dahl <eric.c.dahl@gmail.com>\nDave Stein <dave@behance.com>\nDylan Barrell <dylan@barrell.com>\nDaniel DeGroff <djdegroff@gmail.com>\nMichael Wiencek <mwtuea@gmail.com>\nThomas Meyer <meyertee@gmail.com>\nRuslan Yakhyaev <ruslan@ruslan.io>\nBrian J. Dowling <bjd-dev@simplicity.net>\nBen Higgins <ben@extrahop.com>\nYermo Lamers <yml@yml.com>\nPatrick Stapleton <github@gdi2290.com>\nTrisha Crowley <trisha.crowley@gmail.com>\nUsman Akeju <akeju00+github@gmail.com>\nRodrigo Menezes <rod333@gmail.com>\nJacques Perrault <jacques_perrault@us.ibm.com>\nFrederik Elvhage <frederik.elvhage@googlemail.com>\nWill Holley <willholley@gmail.com>\nUri Gilad <antishok@gmail.com>\nRichard Gibson <richard.gibson@gmail.com>\nSimen Bekkhus <sbekkhus91@gmail.com>\nChen Eshchar <eshcharc@gmail.com>\nBruno Pérel <brunoperel@gmail.com>\nMohammed Alshehri <m@dralshehri.com>\nLisa Seacat DeLuca <ldeluca@us.ibm.com>\nAnne-Gaelle Colom <coloma@westminster.ac.uk>\nAdam Foster <slimfoster@gmail.com>\nLuke Page <luke.a.page@gmail.com>\nDaniel Owens <daniel@matchstickmixup.com>\nMichael Orchard <morchard@scottlogic.co.uk>\nMarcus Warren <marcus@envoke.com>\nNils Heuermann <nils@world-of-scripts.de>\nMarco Ziech <marco@ziech.net>\nPatricia Juarez <patrixd@gmail.com>\nBen Mosher <me@benmosher.com>\nAblay Keldibek <atomio.ak@gmail.com>\nThomas Applencourt <thomas.applencourt@irsamc.ups-tlse.fr>\nJiabao Wu <jiabao.foss@gmail.com>\nEric Lee Carraway <github@ericcarraway.com>\nVictor Homyakov <vkhomyackov@gmail.com>\nMyeongjin Lee <aranet100@gmail.com>\nLiran Sharir <lsharir@gmail.com>\nWeston Ruter <weston@xwp.co>\nMani Mishra <manimishra902@gmail.com>\nHannah Methvin <hannahmethvin@gmail.com>\nLeonardo Balter <leonardo.balter@gmail.com>\nBenjamin Albert <benjamin_a5@yahoo.com>\nMichał Gołębiowski <m.goleb@gmail.com>\nAlyosha Pushak <alyosha.pushak@gmail.com>\nFahad Ahmad <fahadahmad41@hotmail.com>\nMatt Brundage <github@mattbrundage.com>\nFrancesc Baeta <francesc.baeta@gmail.com>\nPiotr Baran <piotros@wp.pl>\nMukul Hase <mukulhase@gmail.com>\nKonstantin Dinev <kdinev@mail.bw.edu>\nRand Scullard <rand@randscullard.com>\nDan Strohl <dan@wjcg.net>\nMaksim Ryzhikov <rv.maksim@gmail.com>\nAmine HADDAD <haddad@allegorie.tv>\nAmanpreet Singh <apsdehal@gmail.com>\nAlexey Balchunas <bleshik@gmail.com>\nPeter Kehl <peter.kehl@gmail.com>\nPeter Dave Hello <hsu@peterdavehello.org>\nJohannes Schäfer <johnschaefer@gmx.de>\nVille Skyttä <ville.skytta@iki.fi>\nRyan Oriecuia <ryan.oriecuia@visioncritical.com>\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/jquery-ui/LICENSE.txt",
    "content": "Copyright jQuery Foundation and other contributors, https://jquery.org/\n\nThis software consists of voluntary contributions made by many\nindividuals. For exact contribution history, see the revision history\navailable at https://github.com/jquery/jquery-ui\n\nThe following license applies to all parts of this software except as\ndocumented below:\n\n====\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n====\n\nCopyright and related rights for sample code are waived via CC0. Sample\ncode is defined as all source code contained within the demos directory.\n\nCC0: http://creativecommons.org/publicdomain/zero/1.0/\n\n====\n\nAll files located in the node_modules and external directories are\nexternally maintained libraries used by this software which have their\nown licenses; we recommend you read them, as their terms may differ from\nthe terms above.\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/jquery-ui/jquery-ui.js",
    "content": "/*! jQuery UI - v1.12.1 - 2018-03-11\n* http://jqueryui.com\n* Includes: widget.js, position.js, data.js, disable-selection.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/selectable.js, widgets/sortable.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/selectmenu.js, widgets/slider.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js\n* Copyright jQuery Foundation and other contributors; Licensed MIT */\n\n(function( factory ) {\n\tif ( typeof define === \"function\" && define.amd ) {\n\n\t\t// AMD. Register as an anonymous module.\n\t\tdefine([ \"jquery\" ], factory );\n\t} else {\n\n\t\t// Browser globals\n\t\tfactory( jQuery );\n\t}\n}(function( $ ) {\n\n$.ui = $.ui || {};\n\nvar version = $.ui.version = \"1.12.1\";\n\n\n/*!\n * jQuery UI Widget 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Widget\n//>>group: Core\n//>>description: Provides a factory for creating stateful widgets with a common API.\n//>>docs: http://api.jqueryui.com/jQuery.widget/\n//>>demos: http://jqueryui.com/widget/\n\n\n\nvar widgetUuid = 0;\nvar widgetSlice = Array.prototype.slice;\n\n$.cleanData = ( function( orig ) {\n\treturn function( elems ) {\n\t\tvar events, elem, i;\n\t\tfor ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {\n\t\t\ttry {\n\n\t\t\t\t// Only trigger remove when necessary to save time\n\t\t\t\tevents = $._data( elem, \"events\" );\n\t\t\t\tif ( events && events.remove ) {\n\t\t\t\t\t$( elem ).triggerHandler( \"remove\" );\n\t\t\t\t}\n\n\t\t\t// Http://bugs.jquery.com/ticket/8235\n\t\t\t} catch ( e ) {}\n\t\t}\n\t\torig( elems );\n\t};\n} )( $.cleanData );\n\n$.widget = function( name, base, prototype ) {\n\tvar existingConstructor, constructor, basePrototype;\n\n\t// ProxiedPrototype allows the provided prototype to remain unmodified\n\t// so that it can be used as a mixin for multiple widgets (#8876)\n\tvar proxiedPrototype = {};\n\n\tvar namespace = name.split( \".\" )[ 0 ];\n\tname = name.split( \".\" )[ 1 ];\n\tvar fullName = namespace + \"-\" + name;\n\n\tif ( !prototype ) {\n\t\tprototype = base;\n\t\tbase = $.Widget;\n\t}\n\n\tif ( $.isArray( prototype ) ) {\n\t\tprototype = $.extend.apply( null, [ {} ].concat( prototype ) );\n\t}\n\n\t// Create selector for plugin\n\t$.expr[ \":\" ][ fullName.toLowerCase() ] = function( elem ) {\n\t\treturn !!$.data( elem, fullName );\n\t};\n\n\t$[ namespace ] = $[ namespace ] || {};\n\texistingConstructor = $[ namespace ][ name ];\n\tconstructor = $[ namespace ][ name ] = function( options, element ) {\n\n\t\t// Allow instantiation without \"new\" keyword\n\t\tif ( !this._createWidget ) {\n\t\t\treturn new constructor( options, element );\n\t\t}\n\n\t\t// Allow instantiation without initializing for simple inheritance\n\t\t// must use \"new\" keyword (the code above always passes args)\n\t\tif ( arguments.length ) {\n\t\t\tthis._createWidget( options, element );\n\t\t}\n\t};\n\n\t// Extend with the existing constructor to carry over any static properties\n\t$.extend( constructor, existingConstructor, {\n\t\tversion: prototype.version,\n\n\t\t// Copy the object used to create the prototype in case we need to\n\t\t// redefine the widget later\n\t\t_proto: $.extend( {}, prototype ),\n\n\t\t// Track widgets that inherit from this widget in case this widget is\n\t\t// redefined after a widget inherits from it\n\t\t_childConstructors: []\n\t} );\n\n\tbasePrototype = new base();\n\n\t// We need to make the options hash a property directly on the new instance\n\t// otherwise we'll modify the options hash on the prototype that we're\n\t// inheriting from\n\tbasePrototype.options = $.widget.extend( {}, basePrototype.options );\n\t$.each( prototype, function( prop, value ) {\n\t\tif ( !$.isFunction( value ) ) {\n\t\t\tproxiedPrototype[ prop ] = value;\n\t\t\treturn;\n\t\t}\n\t\tproxiedPrototype[ prop ] = ( function() {\n\t\t\tfunction _super() {\n\t\t\t\treturn base.prototype[ prop ].apply( this, arguments );\n\t\t\t}\n\n\t\t\tfunction _superApply( args ) {\n\t\t\t\treturn base.prototype[ prop ].apply( this, args );\n\t\t\t}\n\n\t\t\treturn function() {\n\t\t\t\tvar __super = this._super;\n\t\t\t\tvar __superApply = this._superApply;\n\t\t\t\tvar returnValue;\n\n\t\t\t\tthis._super = _super;\n\t\t\t\tthis._superApply = _superApply;\n\n\t\t\t\treturnValue = value.apply( this, arguments );\n\n\t\t\t\tthis._super = __super;\n\t\t\t\tthis._superApply = __superApply;\n\n\t\t\t\treturn returnValue;\n\t\t\t};\n\t\t} )();\n\t} );\n\tconstructor.prototype = $.widget.extend( basePrototype, {\n\n\t\t// TODO: remove support for widgetEventPrefix\n\t\t// always use the name + a colon as the prefix, e.g., draggable:start\n\t\t// don't prefix for widgets that aren't DOM-based\n\t\twidgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name\n\t}, proxiedPrototype, {\n\t\tconstructor: constructor,\n\t\tnamespace: namespace,\n\t\twidgetName: name,\n\t\twidgetFullName: fullName\n\t} );\n\n\t// If this widget is being redefined then we need to find all widgets that\n\t// are inheriting from it and redefine all of them so that they inherit from\n\t// the new version of this widget. We're essentially trying to replace one\n\t// level in the prototype chain.\n\tif ( existingConstructor ) {\n\t\t$.each( existingConstructor._childConstructors, function( i, child ) {\n\t\t\tvar childPrototype = child.prototype;\n\n\t\t\t// Redefine the child widget using the same prototype that was\n\t\t\t// originally used, but inherit from the new version of the base\n\t\t\t$.widget( childPrototype.namespace + \".\" + childPrototype.widgetName, constructor,\n\t\t\t\tchild._proto );\n\t\t} );\n\n\t\t// Remove the list of existing child constructors from the old constructor\n\t\t// so the old child constructors can be garbage collected\n\t\tdelete existingConstructor._childConstructors;\n\t} else {\n\t\tbase._childConstructors.push( constructor );\n\t}\n\n\t$.widget.bridge( name, constructor );\n\n\treturn constructor;\n};\n\n$.widget.extend = function( target ) {\n\tvar input = widgetSlice.call( arguments, 1 );\n\tvar inputIndex = 0;\n\tvar inputLength = input.length;\n\tvar key;\n\tvar value;\n\n\tfor ( ; inputIndex < inputLength; inputIndex++ ) {\n\t\tfor ( key in input[ inputIndex ] ) {\n\t\t\tvalue = input[ inputIndex ][ key ];\n\t\t\tif ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {\n\n\t\t\t\t// Clone objects\n\t\t\t\tif ( $.isPlainObject( value ) ) {\n\t\t\t\t\ttarget[ key ] = $.isPlainObject( target[ key ] ) ?\n\t\t\t\t\t\t$.widget.extend( {}, target[ key ], value ) :\n\n\t\t\t\t\t\t// Don't extend strings, arrays, etc. with objects\n\t\t\t\t\t\t$.widget.extend( {}, value );\n\n\t\t\t\t// Copy everything else by reference\n\t\t\t\t} else {\n\t\t\t\t\ttarget[ key ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn target;\n};\n\n$.widget.bridge = function( name, object ) {\n\tvar fullName = object.prototype.widgetFullName || name;\n\t$.fn[ name ] = function( options ) {\n\t\tvar isMethodCall = typeof options === \"string\";\n\t\tvar args = widgetSlice.call( arguments, 1 );\n\t\tvar returnValue = this;\n\n\t\tif ( isMethodCall ) {\n\n\t\t\t// If this is an empty collection, we need to have the instance method\n\t\t\t// return undefined instead of the jQuery instance\n\t\t\tif ( !this.length && options === \"instance\" ) {\n\t\t\t\treturnValue = undefined;\n\t\t\t} else {\n\t\t\t\tthis.each( function() {\n\t\t\t\t\tvar methodValue;\n\t\t\t\t\tvar instance = $.data( this, fullName );\n\n\t\t\t\t\tif ( options === \"instance\" ) {\n\t\t\t\t\t\treturnValue = instance;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !instance ) {\n\t\t\t\t\t\treturn $.error( \"cannot call methods on \" + name +\n\t\t\t\t\t\t\t\" prior to initialization; \" +\n\t\t\t\t\t\t\t\"attempted to call method '\" + options + \"'\" );\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === \"_\" ) {\n\t\t\t\t\t\treturn $.error( \"no such method '\" + options + \"' for \" + name +\n\t\t\t\t\t\t\t\" widget instance\" );\n\t\t\t\t\t}\n\n\t\t\t\t\tmethodValue = instance[ options ].apply( instance, args );\n\n\t\t\t\t\tif ( methodValue !== instance && methodValue !== undefined ) {\n\t\t\t\t\t\treturnValue = methodValue && methodValue.jquery ?\n\t\t\t\t\t\t\treturnValue.pushStack( methodValue.get() ) :\n\t\t\t\t\t\t\tmethodValue;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t} else {\n\n\t\t\t// Allow multiple hashes to be passed on init\n\t\t\tif ( args.length ) {\n\t\t\t\toptions = $.widget.extend.apply( null, [ options ].concat( args ) );\n\t\t\t}\n\n\t\t\tthis.each( function() {\n\t\t\t\tvar instance = $.data( this, fullName );\n\t\t\t\tif ( instance ) {\n\t\t\t\t\tinstance.option( options || {} );\n\t\t\t\t\tif ( instance._init ) {\n\t\t\t\t\t\tinstance._init();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t$.data( this, fullName, new object( options, this ) );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\treturn returnValue;\n\t};\n};\n\n$.Widget = function( /* options, element */ ) {};\n$.Widget._childConstructors = [];\n\n$.Widget.prototype = {\n\twidgetName: \"widget\",\n\twidgetEventPrefix: \"\",\n\tdefaultElement: \"<div>\",\n\n\toptions: {\n\t\tclasses: {},\n\t\tdisabled: false,\n\n\t\t// Callbacks\n\t\tcreate: null\n\t},\n\n\t_createWidget: function( options, element ) {\n\t\telement = $( element || this.defaultElement || this )[ 0 ];\n\t\tthis.element = $( element );\n\t\tthis.uuid = widgetUuid++;\n\t\tthis.eventNamespace = \".\" + this.widgetName + this.uuid;\n\n\t\tthis.bindings = $();\n\t\tthis.hoverable = $();\n\t\tthis.focusable = $();\n\t\tthis.classesElementLookup = {};\n\n\t\tif ( element !== this ) {\n\t\t\t$.data( element, this.widgetFullName, this );\n\t\t\tthis._on( true, this.element, {\n\t\t\t\tremove: function( event ) {\n\t\t\t\t\tif ( event.target === element ) {\n\t\t\t\t\t\tthis.destroy();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t\tthis.document = $( element.style ?\n\n\t\t\t\t// Element within the document\n\t\t\t\telement.ownerDocument :\n\n\t\t\t\t// Element is window or document\n\t\t\t\telement.document || element );\n\t\t\tthis.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );\n\t\t}\n\n\t\tthis.options = $.widget.extend( {},\n\t\t\tthis.options,\n\t\t\tthis._getCreateOptions(),\n\t\t\toptions );\n\n\t\tthis._create();\n\n\t\tif ( this.options.disabled ) {\n\t\t\tthis._setOptionDisabled( this.options.disabled );\n\t\t}\n\n\t\tthis._trigger( \"create\", null, this._getCreateEventData() );\n\t\tthis._init();\n\t},\n\n\t_getCreateOptions: function() {\n\t\treturn {};\n\t},\n\n\t_getCreateEventData: $.noop,\n\n\t_create: $.noop,\n\n\t_init: $.noop,\n\n\tdestroy: function() {\n\t\tvar that = this;\n\n\t\tthis._destroy();\n\t\t$.each( this.classesElementLookup, function( key, value ) {\n\t\t\tthat._removeClass( value, key );\n\t\t} );\n\n\t\t// We can probably remove the unbind calls in 2.0\n\t\t// all event bindings should go through this._on()\n\t\tthis.element\n\t\t\t.off( this.eventNamespace )\n\t\t\t.removeData( this.widgetFullName );\n\t\tthis.widget()\n\t\t\t.off( this.eventNamespace )\n\t\t\t.removeAttr( \"aria-disabled\" );\n\n\t\t// Clean up events and states\n\t\tthis.bindings.off( this.eventNamespace );\n\t},\n\n\t_destroy: $.noop,\n\n\twidget: function() {\n\t\treturn this.element;\n\t},\n\n\toption: function( key, value ) {\n\t\tvar options = key;\n\t\tvar parts;\n\t\tvar curOption;\n\t\tvar i;\n\n\t\tif ( arguments.length === 0 ) {\n\n\t\t\t// Don't return a reference to the internal hash\n\t\t\treturn $.widget.extend( {}, this.options );\n\t\t}\n\n\t\tif ( typeof key === \"string\" ) {\n\n\t\t\t// Handle nested keys, e.g., \"foo.bar\" => { foo: { bar: ___ } }\n\t\t\toptions = {};\n\t\t\tparts = key.split( \".\" );\n\t\t\tkey = parts.shift();\n\t\t\tif ( parts.length ) {\n\t\t\t\tcurOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );\n\t\t\t\tfor ( i = 0; i < parts.length - 1; i++ ) {\n\t\t\t\t\tcurOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};\n\t\t\t\t\tcurOption = curOption[ parts[ i ] ];\n\t\t\t\t}\n\t\t\t\tkey = parts.pop();\n\t\t\t\tif ( arguments.length === 1 ) {\n\t\t\t\t\treturn curOption[ key ] === undefined ? null : curOption[ key ];\n\t\t\t\t}\n\t\t\t\tcurOption[ key ] = value;\n\t\t\t} else {\n\t\t\t\tif ( arguments.length === 1 ) {\n\t\t\t\t\treturn this.options[ key ] === undefined ? null : this.options[ key ];\n\t\t\t\t}\n\t\t\t\toptions[ key ] = value;\n\t\t\t}\n\t\t}\n\n\t\tthis._setOptions( options );\n\n\t\treturn this;\n\t},\n\n\t_setOptions: function( options ) {\n\t\tvar key;\n\n\t\tfor ( key in options ) {\n\t\t\tthis._setOption( key, options[ key ] );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"classes\" ) {\n\t\t\tthis._setOptionClasses( value );\n\t\t}\n\n\t\tthis.options[ key ] = value;\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._setOptionDisabled( value );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_setOptionClasses: function( value ) {\n\t\tvar classKey, elements, currentElements;\n\n\t\tfor ( classKey in value ) {\n\t\t\tcurrentElements = this.classesElementLookup[ classKey ];\n\t\t\tif ( value[ classKey ] === this.options.classes[ classKey ] ||\n\t\t\t\t\t!currentElements ||\n\t\t\t\t\t!currentElements.length ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// We are doing this to create a new jQuery object because the _removeClass() call\n\t\t\t// on the next line is going to destroy the reference to the current elements being\n\t\t\t// tracked. We need to save a copy of this collection so that we can add the new classes\n\t\t\t// below.\n\t\t\telements = $( currentElements.get() );\n\t\t\tthis._removeClass( currentElements, classKey );\n\n\t\t\t// We don't use _addClass() here, because that uses this.options.classes\n\t\t\t// for generating the string of classes. We want to use the value passed in from\n\t\t\t// _setOption(), this is the new value of the classes option which was passed to\n\t\t\t// _setOption(). We pass this value directly to _classes().\n\t\t\telements.addClass( this._classes( {\n\t\t\t\telement: elements,\n\t\t\t\tkeys: classKey,\n\t\t\t\tclasses: value,\n\t\t\t\tadd: true\n\t\t\t} ) );\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._toggleClass( this.widget(), this.widgetFullName + \"-disabled\", null, !!value );\n\n\t\t// If the widget is becoming disabled, then nothing is interactive\n\t\tif ( value ) {\n\t\t\tthis._removeClass( this.hoverable, null, \"ui-state-hover\" );\n\t\t\tthis._removeClass( this.focusable, null, \"ui-state-focus\" );\n\t\t}\n\t},\n\n\tenable: function() {\n\t\treturn this._setOptions( { disabled: false } );\n\t},\n\n\tdisable: function() {\n\t\treturn this._setOptions( { disabled: true } );\n\t},\n\n\t_classes: function( options ) {\n\t\tvar full = [];\n\t\tvar that = this;\n\n\t\toptions = $.extend( {\n\t\t\telement: this.element,\n\t\t\tclasses: this.options.classes || {}\n\t\t}, options );\n\n\t\tfunction processClassString( classes, checkOption ) {\n\t\t\tvar current, i;\n\t\t\tfor ( i = 0; i < classes.length; i++ ) {\n\t\t\t\tcurrent = that.classesElementLookup[ classes[ i ] ] || $();\n\t\t\t\tif ( options.add ) {\n\t\t\t\t\tcurrent = $( $.unique( current.get().concat( options.element.get() ) ) );\n\t\t\t\t} else {\n\t\t\t\t\tcurrent = $( current.not( options.element ).get() );\n\t\t\t\t}\n\t\t\t\tthat.classesElementLookup[ classes[ i ] ] = current;\n\t\t\t\tfull.push( classes[ i ] );\n\t\t\t\tif ( checkOption && options.classes[ classes[ i ] ] ) {\n\t\t\t\t\tfull.push( options.classes[ classes[ i ] ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis._on( options.element, {\n\t\t\t\"remove\": \"_untrackClassesElement\"\n\t\t} );\n\n\t\tif ( options.keys ) {\n\t\t\tprocessClassString( options.keys.match( /\\S+/g ) || [], true );\n\t\t}\n\t\tif ( options.extra ) {\n\t\t\tprocessClassString( options.extra.match( /\\S+/g ) || [] );\n\t\t}\n\n\t\treturn full.join( \" \" );\n\t},\n\n\t_untrackClassesElement: function( event ) {\n\t\tvar that = this;\n\t\t$.each( that.classesElementLookup, function( key, value ) {\n\t\t\tif ( $.inArray( event.target, value ) !== -1 ) {\n\t\t\t\tthat.classesElementLookup[ key ] = $( value.not( event.target ).get() );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_removeClass: function( element, keys, extra ) {\n\t\treturn this._toggleClass( element, keys, extra, false );\n\t},\n\n\t_addClass: function( element, keys, extra ) {\n\t\treturn this._toggleClass( element, keys, extra, true );\n\t},\n\n\t_toggleClass: function( element, keys, extra, add ) {\n\t\tadd = ( typeof add === \"boolean\" ) ? add : extra;\n\t\tvar shift = ( typeof element === \"string\" || element === null ),\n\t\t\toptions = {\n\t\t\t\textra: shift ? keys : extra,\n\t\t\t\tkeys: shift ? element : keys,\n\t\t\t\telement: shift ? this.element : element,\n\t\t\t\tadd: add\n\t\t\t};\n\t\toptions.element.toggleClass( this._classes( options ), add );\n\t\treturn this;\n\t},\n\n\t_on: function( suppressDisabledCheck, element, handlers ) {\n\t\tvar delegateElement;\n\t\tvar instance = this;\n\n\t\t// No suppressDisabledCheck flag, shuffle arguments\n\t\tif ( typeof suppressDisabledCheck !== \"boolean\" ) {\n\t\t\thandlers = element;\n\t\t\telement = suppressDisabledCheck;\n\t\t\tsuppressDisabledCheck = false;\n\t\t}\n\n\t\t// No element argument, shuffle and use this.element\n\t\tif ( !handlers ) {\n\t\t\thandlers = element;\n\t\t\telement = this.element;\n\t\t\tdelegateElement = this.widget();\n\t\t} else {\n\t\t\telement = delegateElement = $( element );\n\t\t\tthis.bindings = this.bindings.add( element );\n\t\t}\n\n\t\t$.each( handlers, function( event, handler ) {\n\t\t\tfunction handlerProxy() {\n\n\t\t\t\t// Allow widgets to customize the disabled handling\n\t\t\t\t// - disabled as an array instead of boolean\n\t\t\t\t// - disabled class as method for disabling individual parts\n\t\t\t\tif ( !suppressDisabledCheck &&\n\t\t\t\t\t\t( instance.options.disabled === true ||\n\t\t\t\t\t\t$( this ).hasClass( \"ui-state-disabled\" ) ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t\t.apply( instance, arguments );\n\t\t\t}\n\n\t\t\t// Copy the guid so direct unbinding works\n\t\t\tif ( typeof handler !== \"string\" ) {\n\t\t\t\thandlerProxy.guid = handler.guid =\n\t\t\t\t\thandler.guid || handlerProxy.guid || $.guid++;\n\t\t\t}\n\n\t\t\tvar match = event.match( /^([\\w:-]*)\\s*(.*)$/ );\n\t\t\tvar eventName = match[ 1 ] + instance.eventNamespace;\n\t\t\tvar selector = match[ 2 ];\n\n\t\t\tif ( selector ) {\n\t\t\t\tdelegateElement.on( eventName, selector, handlerProxy );\n\t\t\t} else {\n\t\t\t\telement.on( eventName, handlerProxy );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_off: function( element, eventName ) {\n\t\teventName = ( eventName || \"\" ).split( \" \" ).join( this.eventNamespace + \" \" ) +\n\t\t\tthis.eventNamespace;\n\t\telement.off( eventName ).off( eventName );\n\n\t\t// Clear the stack to avoid memory leaks (#10056)\n\t\tthis.bindings = $( this.bindings.not( element ).get() );\n\t\tthis.focusable = $( this.focusable.not( element ).get() );\n\t\tthis.hoverable = $( this.hoverable.not( element ).get() );\n\t},\n\n\t_delay: function( handler, delay ) {\n\t\tfunction handlerProxy() {\n\t\t\treturn ( typeof handler === \"string\" ? instance[ handler ] : handler )\n\t\t\t\t.apply( instance, arguments );\n\t\t}\n\t\tvar instance = this;\n\t\treturn setTimeout( handlerProxy, delay || 0 );\n\t},\n\n\t_hoverable: function( element ) {\n\t\tthis.hoverable = this.hoverable.add( element );\n\t\tthis._on( element, {\n\t\t\tmouseenter: function( event ) {\n\t\t\t\tthis._addClass( $( event.currentTarget ), null, \"ui-state-hover\" );\n\t\t\t},\n\t\t\tmouseleave: function( event ) {\n\t\t\t\tthis._removeClass( $( event.currentTarget ), null, \"ui-state-hover\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_focusable: function( element ) {\n\t\tthis.focusable = this.focusable.add( element );\n\t\tthis._on( element, {\n\t\t\tfocusin: function( event ) {\n\t\t\t\tthis._addClass( $( event.currentTarget ), null, \"ui-state-focus\" );\n\t\t\t},\n\t\t\tfocusout: function( event ) {\n\t\t\t\tthis._removeClass( $( event.currentTarget ), null, \"ui-state-focus\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_trigger: function( type, event, data ) {\n\t\tvar prop, orig;\n\t\tvar callback = this.options[ type ];\n\n\t\tdata = data || {};\n\t\tevent = $.Event( event );\n\t\tevent.type = ( type === this.widgetEventPrefix ?\n\t\t\ttype :\n\t\t\tthis.widgetEventPrefix + type ).toLowerCase();\n\n\t\t// The original event may come from any element\n\t\t// so we need to reset the target on the new event\n\t\tevent.target = this.element[ 0 ];\n\n\t\t// Copy original event properties over to the new event\n\t\torig = event.originalEvent;\n\t\tif ( orig ) {\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tif ( !( prop in event ) ) {\n\t\t\t\t\tevent[ prop ] = orig[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.element.trigger( event, data );\n\t\treturn !( $.isFunction( callback ) &&\n\t\t\tcallback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||\n\t\t\tevent.isDefaultPrevented() );\n\t}\n};\n\n$.each( { show: \"fadeIn\", hide: \"fadeOut\" }, function( method, defaultEffect ) {\n\t$.Widget.prototype[ \"_\" + method ] = function( element, options, callback ) {\n\t\tif ( typeof options === \"string\" ) {\n\t\t\toptions = { effect: options };\n\t\t}\n\n\t\tvar hasOptions;\n\t\tvar effectName = !options ?\n\t\t\tmethod :\n\t\t\toptions === true || typeof options === \"number\" ?\n\t\t\t\tdefaultEffect :\n\t\t\t\toptions.effect || defaultEffect;\n\n\t\toptions = options || {};\n\t\tif ( typeof options === \"number\" ) {\n\t\t\toptions = { duration: options };\n\t\t}\n\n\t\thasOptions = !$.isEmptyObject( options );\n\t\toptions.complete = callback;\n\n\t\tif ( options.delay ) {\n\t\t\telement.delay( options.delay );\n\t\t}\n\n\t\tif ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {\n\t\t\telement[ method ]( options );\n\t\t} else if ( effectName !== method && element[ effectName ] ) {\n\t\t\telement[ effectName ]( options.duration, options.easing, callback );\n\t\t} else {\n\t\t\telement.queue( function( next ) {\n\t\t\t\t$( this )[ method ]();\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback.call( element[ 0 ] );\n\t\t\t\t}\n\t\t\t\tnext();\n\t\t\t} );\n\t\t}\n\t};\n} );\n\nvar widget = $.widget;\n\n\n/*!\n * jQuery UI Position 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * http://api.jqueryui.com/position/\n */\n\n//>>label: Position\n//>>group: Core\n//>>description: Positions elements relative to other elements.\n//>>docs: http://api.jqueryui.com/position/\n//>>demos: http://jqueryui.com/position/\n\n\n( function() {\nvar cachedScrollbarWidth,\n\tmax = Math.max,\n\tabs = Math.abs,\n\trhorizontal = /left|center|right/,\n\trvertical = /top|center|bottom/,\n\troffset = /[\\+\\-]\\d+(\\.[\\d]+)?%?/,\n\trposition = /^\\w+/,\n\trpercent = /%$/,\n\t_position = $.fn.position;\n\nfunction getOffsets( offsets, width, height ) {\n\treturn [\n\t\tparseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),\n\t\tparseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )\n\t];\n}\n\nfunction parseCss( element, property ) {\n\treturn parseInt( $.css( element, property ), 10 ) || 0;\n}\n\nfunction getDimensions( elem ) {\n\tvar raw = elem[ 0 ];\n\tif ( raw.nodeType === 9 ) {\n\t\treturn {\n\t\t\twidth: elem.width(),\n\t\t\theight: elem.height(),\n\t\t\toffset: { top: 0, left: 0 }\n\t\t};\n\t}\n\tif ( $.isWindow( raw ) ) {\n\t\treturn {\n\t\t\twidth: elem.width(),\n\t\t\theight: elem.height(),\n\t\t\toffset: { top: elem.scrollTop(), left: elem.scrollLeft() }\n\t\t};\n\t}\n\tif ( raw.preventDefault ) {\n\t\treturn {\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\toffset: { top: raw.pageY, left: raw.pageX }\n\t\t};\n\t}\n\treturn {\n\t\twidth: elem.outerWidth(),\n\t\theight: elem.outerHeight(),\n\t\toffset: elem.offset()\n\t};\n}\n\n$.position = {\n\tscrollbarWidth: function() {\n\t\tif ( cachedScrollbarWidth !== undefined ) {\n\t\t\treturn cachedScrollbarWidth;\n\t\t}\n\t\tvar w1, w2,\n\t\t\tdiv = $( \"<div \" +\n\t\t\t\t\"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>\" +\n\t\t\t\t\"<div style='height:100px;width:auto;'></div></div>\" ),\n\t\t\tinnerDiv = div.children()[ 0 ];\n\n\t\t$( \"body\" ).append( div );\n\t\tw1 = innerDiv.offsetWidth;\n\t\tdiv.css( \"overflow\", \"scroll\" );\n\n\t\tw2 = innerDiv.offsetWidth;\n\n\t\tif ( w1 === w2 ) {\n\t\t\tw2 = div[ 0 ].clientWidth;\n\t\t}\n\n\t\tdiv.remove();\n\n\t\treturn ( cachedScrollbarWidth = w1 - w2 );\n\t},\n\tgetScrollInfo: function( within ) {\n\t\tvar overflowX = within.isWindow || within.isDocument ? \"\" :\n\t\t\t\twithin.element.css( \"overflow-x\" ),\n\t\t\toverflowY = within.isWindow || within.isDocument ? \"\" :\n\t\t\t\twithin.element.css( \"overflow-y\" ),\n\t\t\thasOverflowX = overflowX === \"scroll\" ||\n\t\t\t\t( overflowX === \"auto\" && within.width < within.element[ 0 ].scrollWidth ),\n\t\t\thasOverflowY = overflowY === \"scroll\" ||\n\t\t\t\t( overflowY === \"auto\" && within.height < within.element[ 0 ].scrollHeight );\n\t\treturn {\n\t\t\twidth: hasOverflowY ? $.position.scrollbarWidth() : 0,\n\t\t\theight: hasOverflowX ? $.position.scrollbarWidth() : 0\n\t\t};\n\t},\n\tgetWithinInfo: function( element ) {\n\t\tvar withinElement = $( element || window ),\n\t\t\tisWindow = $.isWindow( withinElement[ 0 ] ),\n\t\t\tisDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,\n\t\t\thasOffset = !isWindow && !isDocument;\n\t\treturn {\n\t\t\telement: withinElement,\n\t\t\tisWindow: isWindow,\n\t\t\tisDocument: isDocument,\n\t\t\toffset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },\n\t\t\tscrollLeft: withinElement.scrollLeft(),\n\t\t\tscrollTop: withinElement.scrollTop(),\n\t\t\twidth: withinElement.outerWidth(),\n\t\t\theight: withinElement.outerHeight()\n\t\t};\n\t}\n};\n\n$.fn.position = function( options ) {\n\tif ( !options || !options.of ) {\n\t\treturn _position.apply( this, arguments );\n\t}\n\n\t// Make a copy, we don't want to modify arguments\n\toptions = $.extend( {}, options );\n\n\tvar atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,\n\t\ttarget = $( options.of ),\n\t\twithin = $.position.getWithinInfo( options.within ),\n\t\tscrollInfo = $.position.getScrollInfo( within ),\n\t\tcollision = ( options.collision || \"flip\" ).split( \" \" ),\n\t\toffsets = {};\n\n\tdimensions = getDimensions( target );\n\tif ( target[ 0 ].preventDefault ) {\n\n\t\t// Force left top to allow flipping\n\t\toptions.at = \"left top\";\n\t}\n\ttargetWidth = dimensions.width;\n\ttargetHeight = dimensions.height;\n\ttargetOffset = dimensions.offset;\n\n\t// Clone to reuse original targetOffset later\n\tbasePosition = $.extend( {}, targetOffset );\n\n\t// Force my and at to have valid horizontal and vertical positions\n\t// if a value is missing or invalid, it will be converted to center\n\t$.each( [ \"my\", \"at\" ], function() {\n\t\tvar pos = ( options[ this ] || \"\" ).split( \" \" ),\n\t\t\thorizontalOffset,\n\t\t\tverticalOffset;\n\n\t\tif ( pos.length === 1 ) {\n\t\t\tpos = rhorizontal.test( pos[ 0 ] ) ?\n\t\t\t\tpos.concat( [ \"center\" ] ) :\n\t\t\t\trvertical.test( pos[ 0 ] ) ?\n\t\t\t\t\t[ \"center\" ].concat( pos ) :\n\t\t\t\t\t[ \"center\", \"center\" ];\n\t\t}\n\t\tpos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : \"center\";\n\t\tpos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : \"center\";\n\n\t\t// Calculate offsets\n\t\thorizontalOffset = roffset.exec( pos[ 0 ] );\n\t\tverticalOffset = roffset.exec( pos[ 1 ] );\n\t\toffsets[ this ] = [\n\t\t\thorizontalOffset ? horizontalOffset[ 0 ] : 0,\n\t\t\tverticalOffset ? verticalOffset[ 0 ] : 0\n\t\t];\n\n\t\t// Reduce to just the positions without the offsets\n\t\toptions[ this ] = [\n\t\t\trposition.exec( pos[ 0 ] )[ 0 ],\n\t\t\trposition.exec( pos[ 1 ] )[ 0 ]\n\t\t];\n\t} );\n\n\t// Normalize collision option\n\tif ( collision.length === 1 ) {\n\t\tcollision[ 1 ] = collision[ 0 ];\n\t}\n\n\tif ( options.at[ 0 ] === \"right\" ) {\n\t\tbasePosition.left += targetWidth;\n\t} else if ( options.at[ 0 ] === \"center\" ) {\n\t\tbasePosition.left += targetWidth / 2;\n\t}\n\n\tif ( options.at[ 1 ] === \"bottom\" ) {\n\t\tbasePosition.top += targetHeight;\n\t} else if ( options.at[ 1 ] === \"center\" ) {\n\t\tbasePosition.top += targetHeight / 2;\n\t}\n\n\tatOffset = getOffsets( offsets.at, targetWidth, targetHeight );\n\tbasePosition.left += atOffset[ 0 ];\n\tbasePosition.top += atOffset[ 1 ];\n\n\treturn this.each( function() {\n\t\tvar collisionPosition, using,\n\t\t\telem = $( this ),\n\t\t\telemWidth = elem.outerWidth(),\n\t\t\telemHeight = elem.outerHeight(),\n\t\t\tmarginLeft = parseCss( this, \"marginLeft\" ),\n\t\t\tmarginTop = parseCss( this, \"marginTop\" ),\n\t\t\tcollisionWidth = elemWidth + marginLeft + parseCss( this, \"marginRight\" ) +\n\t\t\t\tscrollInfo.width,\n\t\t\tcollisionHeight = elemHeight + marginTop + parseCss( this, \"marginBottom\" ) +\n\t\t\t\tscrollInfo.height,\n\t\t\tposition = $.extend( {}, basePosition ),\n\t\t\tmyOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );\n\n\t\tif ( options.my[ 0 ] === \"right\" ) {\n\t\t\tposition.left -= elemWidth;\n\t\t} else if ( options.my[ 0 ] === \"center\" ) {\n\t\t\tposition.left -= elemWidth / 2;\n\t\t}\n\n\t\tif ( options.my[ 1 ] === \"bottom\" ) {\n\t\t\tposition.top -= elemHeight;\n\t\t} else if ( options.my[ 1 ] === \"center\" ) {\n\t\t\tposition.top -= elemHeight / 2;\n\t\t}\n\n\t\tposition.left += myOffset[ 0 ];\n\t\tposition.top += myOffset[ 1 ];\n\n\t\tcollisionPosition = {\n\t\t\tmarginLeft: marginLeft,\n\t\t\tmarginTop: marginTop\n\t\t};\n\n\t\t$.each( [ \"left\", \"top\" ], function( i, dir ) {\n\t\t\tif ( $.ui.position[ collision[ i ] ] ) {\n\t\t\t\t$.ui.position[ collision[ i ] ][ dir ]( position, {\n\t\t\t\t\ttargetWidth: targetWidth,\n\t\t\t\t\ttargetHeight: targetHeight,\n\t\t\t\t\telemWidth: elemWidth,\n\t\t\t\t\telemHeight: elemHeight,\n\t\t\t\t\tcollisionPosition: collisionPosition,\n\t\t\t\t\tcollisionWidth: collisionWidth,\n\t\t\t\t\tcollisionHeight: collisionHeight,\n\t\t\t\t\toffset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],\n\t\t\t\t\tmy: options.my,\n\t\t\t\t\tat: options.at,\n\t\t\t\t\twithin: within,\n\t\t\t\t\telem: elem\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\n\t\tif ( options.using ) {\n\n\t\t\t// Adds feedback as second argument to using callback, if present\n\t\t\tusing = function( props ) {\n\t\t\t\tvar left = targetOffset.left - position.left,\n\t\t\t\t\tright = left + targetWidth - elemWidth,\n\t\t\t\t\ttop = targetOffset.top - position.top,\n\t\t\t\t\tbottom = top + targetHeight - elemHeight,\n\t\t\t\t\tfeedback = {\n\t\t\t\t\t\ttarget: {\n\t\t\t\t\t\t\telement: target,\n\t\t\t\t\t\t\tleft: targetOffset.left,\n\t\t\t\t\t\t\ttop: targetOffset.top,\n\t\t\t\t\t\t\twidth: targetWidth,\n\t\t\t\t\t\t\theight: targetHeight\n\t\t\t\t\t\t},\n\t\t\t\t\t\telement: {\n\t\t\t\t\t\t\telement: elem,\n\t\t\t\t\t\t\tleft: position.left,\n\t\t\t\t\t\t\ttop: position.top,\n\t\t\t\t\t\t\twidth: elemWidth,\n\t\t\t\t\t\t\theight: elemHeight\n\t\t\t\t\t\t},\n\t\t\t\t\t\thorizontal: right < 0 ? \"left\" : left > 0 ? \"right\" : \"center\",\n\t\t\t\t\t\tvertical: bottom < 0 ? \"top\" : top > 0 ? \"bottom\" : \"middle\"\n\t\t\t\t\t};\n\t\t\t\tif ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {\n\t\t\t\t\tfeedback.horizontal = \"center\";\n\t\t\t\t}\n\t\t\t\tif ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {\n\t\t\t\t\tfeedback.vertical = \"middle\";\n\t\t\t\t}\n\t\t\t\tif ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {\n\t\t\t\t\tfeedback.important = \"horizontal\";\n\t\t\t\t} else {\n\t\t\t\t\tfeedback.important = \"vertical\";\n\t\t\t\t}\n\t\t\t\toptions.using.call( this, props, feedback );\n\t\t\t};\n\t\t}\n\n\t\telem.offset( $.extend( position, { using: using } ) );\n\t} );\n};\n\n$.ui.position = {\n\tfit: {\n\t\tleft: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.isWindow ? within.scrollLeft : within.offset.left,\n\t\t\t\touterWidth = within.width,\n\t\t\t\tcollisionPosLeft = position.left - data.collisionPosition.marginLeft,\n\t\t\t\toverLeft = withinOffset - collisionPosLeft,\n\t\t\t\toverRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,\n\t\t\t\tnewOverRight;\n\n\t\t\t// Element is wider than within\n\t\t\tif ( data.collisionWidth > outerWidth ) {\n\n\t\t\t\t// Element is initially over the left side of within\n\t\t\t\tif ( overLeft > 0 && overRight <= 0 ) {\n\t\t\t\t\tnewOverRight = position.left + overLeft + data.collisionWidth - outerWidth -\n\t\t\t\t\t\twithinOffset;\n\t\t\t\t\tposition.left += overLeft - newOverRight;\n\n\t\t\t\t// Element is initially over right side of within\n\t\t\t\t} else if ( overRight > 0 && overLeft <= 0 ) {\n\t\t\t\t\tposition.left = withinOffset;\n\n\t\t\t\t// Element is initially over both left and right sides of within\n\t\t\t\t} else {\n\t\t\t\t\tif ( overLeft > overRight ) {\n\t\t\t\t\t\tposition.left = withinOffset + outerWidth - data.collisionWidth;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tposition.left = withinOffset;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Too far left -> align with left edge\n\t\t\t} else if ( overLeft > 0 ) {\n\t\t\t\tposition.left += overLeft;\n\n\t\t\t// Too far right -> align with right edge\n\t\t\t} else if ( overRight > 0 ) {\n\t\t\t\tposition.left -= overRight;\n\n\t\t\t// Adjust based on position and margin\n\t\t\t} else {\n\t\t\t\tposition.left = max( position.left - collisionPosLeft, position.left );\n\t\t\t}\n\t\t},\n\t\ttop: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.isWindow ? within.scrollTop : within.offset.top,\n\t\t\t\touterHeight = data.within.height,\n\t\t\t\tcollisionPosTop = position.top - data.collisionPosition.marginTop,\n\t\t\t\toverTop = withinOffset - collisionPosTop,\n\t\t\t\toverBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,\n\t\t\t\tnewOverBottom;\n\n\t\t\t// Element is taller than within\n\t\t\tif ( data.collisionHeight > outerHeight ) {\n\n\t\t\t\t// Element is initially over the top of within\n\t\t\t\tif ( overTop > 0 && overBottom <= 0 ) {\n\t\t\t\t\tnewOverBottom = position.top + overTop + data.collisionHeight - outerHeight -\n\t\t\t\t\t\twithinOffset;\n\t\t\t\t\tposition.top += overTop - newOverBottom;\n\n\t\t\t\t// Element is initially over bottom of within\n\t\t\t\t} else if ( overBottom > 0 && overTop <= 0 ) {\n\t\t\t\t\tposition.top = withinOffset;\n\n\t\t\t\t// Element is initially over both top and bottom of within\n\t\t\t\t} else {\n\t\t\t\t\tif ( overTop > overBottom ) {\n\t\t\t\t\t\tposition.top = withinOffset + outerHeight - data.collisionHeight;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tposition.top = withinOffset;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Too far up -> align with top\n\t\t\t} else if ( overTop > 0 ) {\n\t\t\t\tposition.top += overTop;\n\n\t\t\t// Too far down -> align with bottom edge\n\t\t\t} else if ( overBottom > 0 ) {\n\t\t\t\tposition.top -= overBottom;\n\n\t\t\t// Adjust based on position and margin\n\t\t\t} else {\n\t\t\t\tposition.top = max( position.top - collisionPosTop, position.top );\n\t\t\t}\n\t\t}\n\t},\n\tflip: {\n\t\tleft: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.offset.left + within.scrollLeft,\n\t\t\t\touterWidth = within.width,\n\t\t\t\toffsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,\n\t\t\t\tcollisionPosLeft = position.left - data.collisionPosition.marginLeft,\n\t\t\t\toverLeft = collisionPosLeft - offsetLeft,\n\t\t\t\toverRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,\n\t\t\t\tmyOffset = data.my[ 0 ] === \"left\" ?\n\t\t\t\t\t-data.elemWidth :\n\t\t\t\t\tdata.my[ 0 ] === \"right\" ?\n\t\t\t\t\t\tdata.elemWidth :\n\t\t\t\t\t\t0,\n\t\t\t\tatOffset = data.at[ 0 ] === \"left\" ?\n\t\t\t\t\tdata.targetWidth :\n\t\t\t\t\tdata.at[ 0 ] === \"right\" ?\n\t\t\t\t\t\t-data.targetWidth :\n\t\t\t\t\t\t0,\n\t\t\t\toffset = -2 * data.offset[ 0 ],\n\t\t\t\tnewOverRight,\n\t\t\t\tnewOverLeft;\n\n\t\t\tif ( overLeft < 0 ) {\n\t\t\t\tnewOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -\n\t\t\t\t\touterWidth - withinOffset;\n\t\t\t\tif ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {\n\t\t\t\t\tposition.left += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t} else if ( overRight > 0 ) {\n\t\t\t\tnewOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +\n\t\t\t\t\tatOffset + offset - offsetLeft;\n\t\t\t\tif ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {\n\t\t\t\t\tposition.left += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\ttop: function( position, data ) {\n\t\t\tvar within = data.within,\n\t\t\t\twithinOffset = within.offset.top + within.scrollTop,\n\t\t\t\touterHeight = within.height,\n\t\t\t\toffsetTop = within.isWindow ? within.scrollTop : within.offset.top,\n\t\t\t\tcollisionPosTop = position.top - data.collisionPosition.marginTop,\n\t\t\t\toverTop = collisionPosTop - offsetTop,\n\t\t\t\toverBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,\n\t\t\t\ttop = data.my[ 1 ] === \"top\",\n\t\t\t\tmyOffset = top ?\n\t\t\t\t\t-data.elemHeight :\n\t\t\t\t\tdata.my[ 1 ] === \"bottom\" ?\n\t\t\t\t\t\tdata.elemHeight :\n\t\t\t\t\t\t0,\n\t\t\t\tatOffset = data.at[ 1 ] === \"top\" ?\n\t\t\t\t\tdata.targetHeight :\n\t\t\t\t\tdata.at[ 1 ] === \"bottom\" ?\n\t\t\t\t\t\t-data.targetHeight :\n\t\t\t\t\t\t0,\n\t\t\t\toffset = -2 * data.offset[ 1 ],\n\t\t\t\tnewOverTop,\n\t\t\t\tnewOverBottom;\n\t\t\tif ( overTop < 0 ) {\n\t\t\t\tnewOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -\n\t\t\t\t\touterHeight - withinOffset;\n\t\t\t\tif ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {\n\t\t\t\t\tposition.top += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t} else if ( overBottom > 0 ) {\n\t\t\t\tnewOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +\n\t\t\t\t\toffset - offsetTop;\n\t\t\t\tif ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {\n\t\t\t\t\tposition.top += myOffset + atOffset + offset;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\tflipfit: {\n\t\tleft: function() {\n\t\t\t$.ui.position.flip.left.apply( this, arguments );\n\t\t\t$.ui.position.fit.left.apply( this, arguments );\n\t\t},\n\t\ttop: function() {\n\t\t\t$.ui.position.flip.top.apply( this, arguments );\n\t\t\t$.ui.position.fit.top.apply( this, arguments );\n\t\t}\n\t}\n};\n\n} )();\n\nvar position = $.ui.position;\n\n\n/*!\n * jQuery UI :data 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: :data Selector\n//>>group: Core\n//>>description: Selects elements which have data stored under the specified key.\n//>>docs: http://api.jqueryui.com/data-selector/\n\n\nvar data = $.extend( $.expr[ \":\" ], {\n\tdata: $.expr.createPseudo ?\n\t\t$.expr.createPseudo( function( dataName ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn !!$.data( elem, dataName );\n\t\t\t};\n\t\t} ) :\n\n\t\t// Support: jQuery <1.8\n\t\tfunction( elem, i, match ) {\n\t\t\treturn !!$.data( elem, match[ 3 ] );\n\t\t}\n} );\n\n/*!\n * jQuery UI Disable Selection 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: disableSelection\n//>>group: Core\n//>>description: Disable selection of text content within the set of matched elements.\n//>>docs: http://api.jqueryui.com/disableSelection/\n\n// This file is deprecated\n\n\nvar disableSelection = $.fn.extend( {\n\tdisableSelection: ( function() {\n\t\tvar eventType = \"onselectstart\" in document.createElement( \"div\" ) ?\n\t\t\t\"selectstart\" :\n\t\t\t\"mousedown\";\n\n\t\treturn function() {\n\t\t\treturn this.on( eventType + \".ui-disableSelection\", function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t} );\n\t\t};\n\t} )(),\n\n\tenableSelection: function() {\n\t\treturn this.off( \".ui-disableSelection\" );\n\t}\n} );\n\n\n/*!\n * jQuery UI Focusable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: :focusable Selector\n//>>group: Core\n//>>description: Selects elements which can be focused.\n//>>docs: http://api.jqueryui.com/focusable-selector/\n\n\n\n// Selectors\n$.ui.focusable = function( element, hasTabindex ) {\n\tvar map, mapName, img, focusableIfVisible, fieldset,\n\t\tnodeName = element.nodeName.toLowerCase();\n\n\tif ( \"area\" === nodeName ) {\n\t\tmap = element.parentNode;\n\t\tmapName = map.name;\n\t\tif ( !element.href || !mapName || map.nodeName.toLowerCase() !== \"map\" ) {\n\t\t\treturn false;\n\t\t}\n\t\timg = $( \"img[usemap='#\" + mapName + \"']\" );\n\t\treturn img.length > 0 && img.is( \":visible\" );\n\t}\n\n\tif ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) {\n\t\tfocusableIfVisible = !element.disabled;\n\n\t\tif ( focusableIfVisible ) {\n\n\t\t\t// Form controls within a disabled fieldset are disabled.\n\t\t\t// However, controls within the fieldset's legend do not get disabled.\n\t\t\t// Since controls generally aren't placed inside legends, we skip\n\t\t\t// this portion of the check.\n\t\t\tfieldset = $( element ).closest( \"fieldset\" )[ 0 ];\n\t\t\tif ( fieldset ) {\n\t\t\t\tfocusableIfVisible = !fieldset.disabled;\n\t\t\t}\n\t\t}\n\t} else if ( \"a\" === nodeName ) {\n\t\tfocusableIfVisible = element.href || hasTabindex;\n\t} else {\n\t\tfocusableIfVisible = hasTabindex;\n\t}\n\n\treturn focusableIfVisible && $( element ).is( \":visible\" ) && visible( $( element ) );\n};\n\n// Support: IE 8 only\n// IE 8 doesn't resolve inherit to visible/hidden for computed values\nfunction visible( element ) {\n\tvar visibility = element.css( \"visibility\" );\n\twhile ( visibility === \"inherit\" ) {\n\t\telement = element.parent();\n\t\tvisibility = element.css( \"visibility\" );\n\t}\n\treturn visibility !== \"hidden\";\n}\n\n$.extend( $.expr[ \":\" ], {\n\tfocusable: function( element ) {\n\t\treturn $.ui.focusable( element, $.attr( element, \"tabindex\" ) != null );\n\t}\n} );\n\nvar focusable = $.ui.focusable;\n\n\n\n\n// Support: IE8 Only\n// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop\n// with a string, so we need to find the proper form.\nvar form = $.fn.form = function() {\n\treturn typeof this[ 0 ].form === \"string\" ? this.closest( \"form\" ) : $( this[ 0 ].form );\n};\n\n\n/*!\n * jQuery UI Form Reset Mixin 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Form Reset Mixin\n//>>group: Core\n//>>description: Refresh input widgets when their form is reset\n//>>docs: http://api.jqueryui.com/form-reset-mixin/\n\n\n\nvar formResetMixin = $.ui.formResetMixin = {\n\t_formResetHandler: function() {\n\t\tvar form = $( this );\n\n\t\t// Wait for the form reset to actually happen before refreshing\n\t\tsetTimeout( function() {\n\t\t\tvar instances = form.data( \"ui-form-reset-instances\" );\n\t\t\t$.each( instances, function() {\n\t\t\t\tthis.refresh();\n\t\t\t} );\n\t\t} );\n\t},\n\n\t_bindFormResetHandler: function() {\n\t\tthis.form = this.element.form();\n\t\tif ( !this.form.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar instances = this.form.data( \"ui-form-reset-instances\" ) || [];\n\t\tif ( !instances.length ) {\n\n\t\t\t// We don't use _on() here because we use a single event handler per form\n\t\t\tthis.form.on( \"reset.ui-form-reset\", this._formResetHandler );\n\t\t}\n\t\tinstances.push( this );\n\t\tthis.form.data( \"ui-form-reset-instances\", instances );\n\t},\n\n\t_unbindFormResetHandler: function() {\n\t\tif ( !this.form.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar instances = this.form.data( \"ui-form-reset-instances\" );\n\t\tinstances.splice( $.inArray( this, instances ), 1 );\n\t\tif ( instances.length ) {\n\t\t\tthis.form.data( \"ui-form-reset-instances\", instances );\n\t\t} else {\n\t\t\tthis.form\n\t\t\t\t.removeData( \"ui-form-reset-instances\" )\n\t\t\t\t.off( \"reset.ui-form-reset\" );\n\t\t}\n\t}\n};\n\n\n/*!\n * jQuery UI Support for jQuery core 1.7.x 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n */\n\n//>>label: jQuery 1.7 Support\n//>>group: Core\n//>>description: Support version 1.7.x of jQuery core\n\n\n\n// Support: jQuery 1.7 only\n// Not a great way to check versions, but since we only support 1.7+ and only\n// need to detect <1.8, this is a simple check that should suffice. Checking\n// for \"1.7.\" would be a bit safer, but the version string is 1.7, not 1.7.0\n// and we'll never reach 1.70.0 (if we do, we certainly won't be supporting\n// 1.7 anymore). See #11197 for why we're not using feature detection.\nif ( $.fn.jquery.substring( 0, 3 ) === \"1.7\" ) {\n\n\t// Setters for .innerWidth(), .innerHeight(), .outerWidth(), .outerHeight()\n\t// Unlike jQuery Core 1.8+, these only support numeric values to set the\n\t// dimensions in pixels\n\t$.each( [ \"Width\", \"Height\" ], function( i, name ) {\n\t\tvar side = name === \"Width\" ? [ \"Left\", \"Right\" ] : [ \"Top\", \"Bottom\" ],\n\t\t\ttype = name.toLowerCase(),\n\t\t\torig = {\n\t\t\t\tinnerWidth: $.fn.innerWidth,\n\t\t\t\tinnerHeight: $.fn.innerHeight,\n\t\t\t\touterWidth: $.fn.outerWidth,\n\t\t\t\touterHeight: $.fn.outerHeight\n\t\t\t};\n\n\t\tfunction reduce( elem, size, border, margin ) {\n\t\t\t$.each( side, function() {\n\t\t\t\tsize -= parseFloat( $.css( elem, \"padding\" + this ) ) || 0;\n\t\t\t\tif ( border ) {\n\t\t\t\t\tsize -= parseFloat( $.css( elem, \"border\" + this + \"Width\" ) ) || 0;\n\t\t\t\t}\n\t\t\t\tif ( margin ) {\n\t\t\t\t\tsize -= parseFloat( $.css( elem, \"margin\" + this ) ) || 0;\n\t\t\t\t}\n\t\t\t} );\n\t\t\treturn size;\n\t\t}\n\n\t\t$.fn[ \"inner\" + name ] = function( size ) {\n\t\t\tif ( size === undefined ) {\n\t\t\t\treturn orig[ \"inner\" + name ].call( this );\n\t\t\t}\n\n\t\t\treturn this.each( function() {\n\t\t\t\t$( this ).css( type, reduce( this, size ) + \"px\" );\n\t\t\t} );\n\t\t};\n\n\t\t$.fn[ \"outer\" + name ] = function( size, margin ) {\n\t\t\tif ( typeof size !== \"number\" ) {\n\t\t\t\treturn orig[ \"outer\" + name ].call( this, size );\n\t\t\t}\n\n\t\t\treturn this.each( function() {\n\t\t\t\t$( this ).css( type, reduce( this, size, true, margin ) + \"px\" );\n\t\t\t} );\n\t\t};\n\t} );\n\n\t$.fn.addBack = function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter( selector )\n\t\t);\n\t};\n}\n\n;\n/*!\n * jQuery UI Keycode 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Keycode\n//>>group: Core\n//>>description: Provide keycodes as keynames\n//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/\n\n\nvar keycode = $.ui.keyCode = {\n\tBACKSPACE: 8,\n\tCOMMA: 188,\n\tDELETE: 46,\n\tDOWN: 40,\n\tEND: 35,\n\tENTER: 13,\n\tESCAPE: 27,\n\tHOME: 36,\n\tLEFT: 37,\n\tPAGE_DOWN: 34,\n\tPAGE_UP: 33,\n\tPERIOD: 190,\n\tRIGHT: 39,\n\tSPACE: 32,\n\tTAB: 9,\n\tUP: 38\n};\n\n\n\n\n// Internal use only\nvar escapeSelector = $.ui.escapeSelector = ( function() {\n\tvar selectorEscape = /([!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~])/g;\n\treturn function( selector ) {\n\t\treturn selector.replace( selectorEscape, \"\\\\$1\" );\n\t};\n} )();\n\n\n/*!\n * jQuery UI Labels 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: labels\n//>>group: Core\n//>>description: Find all the labels associated with a given input\n//>>docs: http://api.jqueryui.com/labels/\n\n\n\nvar labels = $.fn.labels = function() {\n\tvar ancestor, selector, id, labels, ancestors;\n\n\t// Check control.labels first\n\tif ( this[ 0 ].labels && this[ 0 ].labels.length ) {\n\t\treturn this.pushStack( this[ 0 ].labels );\n\t}\n\n\t// Support: IE <= 11, FF <= 37, Android <= 2.3 only\n\t// Above browsers do not support control.labels. Everything below is to support them\n\t// as well as document fragments. control.labels does not work on document fragments\n\tlabels = this.eq( 0 ).parents( \"label\" );\n\n\t// Look for the label based on the id\n\tid = this.attr( \"id\" );\n\tif ( id ) {\n\n\t\t// We don't search against the document in case the element\n\t\t// is disconnected from the DOM\n\t\tancestor = this.eq( 0 ).parents().last();\n\n\t\t// Get a full set of top level ancestors\n\t\tancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() );\n\n\t\t// Create a selector for the label based on the id\n\t\tselector = \"label[for='\" + $.ui.escapeSelector( id ) + \"']\";\n\n\t\tlabels = labels.add( ancestors.find( selector ).addBack( selector ) );\n\n\t}\n\n\t// Return whatever we have found for labels\n\treturn this.pushStack( labels );\n};\n\n\n/*!\n * jQuery UI Scroll Parent 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: scrollParent\n//>>group: Core\n//>>description: Get the closest ancestor element that is scrollable.\n//>>docs: http://api.jqueryui.com/scrollParent/\n\n\n\nvar scrollParent = $.fn.scrollParent = function( includeHidden ) {\n\tvar position = this.css( \"position\" ),\n\t\texcludeStaticParent = position === \"absolute\",\n\t\toverflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,\n\t\tscrollParent = this.parents().filter( function() {\n\t\t\tvar parent = $( this );\n\t\t\tif ( excludeStaticParent && parent.css( \"position\" ) === \"static\" ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn overflowRegex.test( parent.css( \"overflow\" ) + parent.css( \"overflow-y\" ) +\n\t\t\t\tparent.css( \"overflow-x\" ) );\n\t\t} ).eq( 0 );\n\n\treturn position === \"fixed\" || !scrollParent.length ?\n\t\t$( this[ 0 ].ownerDocument || document ) :\n\t\tscrollParent;\n};\n\n\n/*!\n * jQuery UI Tabbable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: :tabbable Selector\n//>>group: Core\n//>>description: Selects elements which can be tabbed to.\n//>>docs: http://api.jqueryui.com/tabbable-selector/\n\n\n\nvar tabbable = $.extend( $.expr[ \":\" ], {\n\ttabbable: function( element ) {\n\t\tvar tabIndex = $.attr( element, \"tabindex\" ),\n\t\t\thasTabindex = tabIndex != null;\n\t\treturn ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex );\n\t}\n} );\n\n\n/*!\n * jQuery UI Unique ID 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: uniqueId\n//>>group: Core\n//>>description: Functions to generate and remove uniqueId's\n//>>docs: http://api.jqueryui.com/uniqueId/\n\n\n\nvar uniqueId = $.fn.extend( {\n\tuniqueId: ( function() {\n\t\tvar uuid = 0;\n\n\t\treturn function() {\n\t\t\treturn this.each( function() {\n\t\t\t\tif ( !this.id ) {\n\t\t\t\t\tthis.id = \"ui-id-\" + ( ++uuid );\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\t} )(),\n\n\tremoveUniqueId: function() {\n\t\treturn this.each( function() {\n\t\t\tif ( /^ui-id-\\d+$/.test( this.id ) ) {\n\t\t\t\t$( this ).removeAttr( \"id\" );\n\t\t\t}\n\t\t} );\n\t}\n} );\n\n\n\n\n// This file is deprecated\nvar ie = $.ui.ie = !!/msie [\\w.]+/.exec( navigator.userAgent.toLowerCase() );\n\n/*!\n * jQuery UI Mouse 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Mouse\n//>>group: Widgets\n//>>description: Abstracts mouse-based interactions to assist in creating certain widgets.\n//>>docs: http://api.jqueryui.com/mouse/\n\n\n\nvar mouseHandled = false;\n$( document ).on( \"mouseup\", function() {\n\tmouseHandled = false;\n} );\n\nvar widgetsMouse = $.widget( \"ui.mouse\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tcancel: \"input, textarea, button, select, option\",\n\t\tdistance: 1,\n\t\tdelay: 0\n\t},\n\t_mouseInit: function() {\n\t\tvar that = this;\n\n\t\tthis.element\n\t\t\t.on( \"mousedown.\" + this.widgetName, function( event ) {\n\t\t\t\treturn that._mouseDown( event );\n\t\t\t} )\n\t\t\t.on( \"click.\" + this.widgetName, function( event ) {\n\t\t\t\tif ( true === $.data( event.target, that.widgetName + \".preventClickEvent\" ) ) {\n\t\t\t\t\t$.removeData( event.target, that.widgetName + \".preventClickEvent\" );\n\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} );\n\n\t\tthis.started = false;\n\t},\n\n\t// TODO: make sure destroying one instance of mouse doesn't mess with\n\t// other instances of mouse\n\t_mouseDestroy: function() {\n\t\tthis.element.off( \".\" + this.widgetName );\n\t\tif ( this._mouseMoveDelegate ) {\n\t\t\tthis.document\n\t\t\t\t.off( \"mousemove.\" + this.widgetName, this._mouseMoveDelegate )\n\t\t\t\t.off( \"mouseup.\" + this.widgetName, this._mouseUpDelegate );\n\t\t}\n\t},\n\n\t_mouseDown: function( event ) {\n\n\t\t// don't let more than one widget handle mouseStart\n\t\tif ( mouseHandled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._mouseMoved = false;\n\n\t\t// We may have missed mouseup (out of window)\n\t\t( this._mouseStarted && this._mouseUp( event ) );\n\n\t\tthis._mouseDownEvent = event;\n\n\t\tvar that = this,\n\t\t\tbtnIsLeft = ( event.which === 1 ),\n\n\t\t\t// event.target.nodeName works around a bug in IE 8 with\n\t\t\t// disabled inputs (#7620)\n\t\t\telIsCancel = ( typeof this.options.cancel === \"string\" && event.target.nodeName ?\n\t\t\t\t$( event.target ).closest( this.options.cancel ).length : false );\n\t\tif ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) {\n\t\t\treturn true;\n\t\t}\n\n\t\tthis.mouseDelayMet = !this.options.delay;\n\t\tif ( !this.mouseDelayMet ) {\n\t\t\tthis._mouseDelayTimer = setTimeout( function() {\n\t\t\t\tthat.mouseDelayMet = true;\n\t\t\t}, this.options.delay );\n\t\t}\n\n\t\tif ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {\n\t\t\tthis._mouseStarted = ( this._mouseStart( event ) !== false );\n\t\t\tif ( !this._mouseStarted ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// Click event may never have fired (Gecko & Opera)\n\t\tif ( true === $.data( event.target, this.widgetName + \".preventClickEvent\" ) ) {\n\t\t\t$.removeData( event.target, this.widgetName + \".preventClickEvent\" );\n\t\t}\n\n\t\t// These delegates are required to keep context\n\t\tthis._mouseMoveDelegate = function( event ) {\n\t\t\treturn that._mouseMove( event );\n\t\t};\n\t\tthis._mouseUpDelegate = function( event ) {\n\t\t\treturn that._mouseUp( event );\n\t\t};\n\n\t\tthis.document\n\t\t\t.on( \"mousemove.\" + this.widgetName, this._mouseMoveDelegate )\n\t\t\t.on( \"mouseup.\" + this.widgetName, this._mouseUpDelegate );\n\n\t\tevent.preventDefault();\n\n\t\tmouseHandled = true;\n\t\treturn true;\n\t},\n\n\t_mouseMove: function( event ) {\n\n\t\t// Only check for mouseups outside the document if you've moved inside the document\n\t\t// at least once. This prevents the firing of mouseup in the case of IE<9, which will\n\t\t// fire a mousemove event if content is placed under the cursor. See #7778\n\t\t// Support: IE <9\n\t\tif ( this._mouseMoved ) {\n\n\t\t\t// IE mouseup check - mouseup happened when mouse was out of window\n\t\t\tif ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) &&\n\t\t\t\t\t!event.button ) {\n\t\t\t\treturn this._mouseUp( event );\n\n\t\t\t// Iframe mouseup check - mouseup occurred in another document\n\t\t\t} else if ( !event.which ) {\n\n\t\t\t\t// Support: Safari <=8 - 9\n\t\t\t\t// Safari sets which to 0 if you press any of the following keys\n\t\t\t\t// during a drag (#14461)\n\t\t\t\tif ( event.originalEvent.altKey || event.originalEvent.ctrlKey ||\n\t\t\t\t\t\tevent.originalEvent.metaKey || event.originalEvent.shiftKey ) {\n\t\t\t\t\tthis.ignoreMissingWhich = true;\n\t\t\t\t} else if ( !this.ignoreMissingWhich ) {\n\t\t\t\t\treturn this._mouseUp( event );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( event.which || event.button ) {\n\t\t\tthis._mouseMoved = true;\n\t\t}\n\n\t\tif ( this._mouseStarted ) {\n\t\t\tthis._mouseDrag( event );\n\t\t\treturn event.preventDefault();\n\t\t}\n\n\t\tif ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {\n\t\t\tthis._mouseStarted =\n\t\t\t\t( this._mouseStart( this._mouseDownEvent, event ) !== false );\n\t\t\t( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) );\n\t\t}\n\n\t\treturn !this._mouseStarted;\n\t},\n\n\t_mouseUp: function( event ) {\n\t\tthis.document\n\t\t\t.off( \"mousemove.\" + this.widgetName, this._mouseMoveDelegate )\n\t\t\t.off( \"mouseup.\" + this.widgetName, this._mouseUpDelegate );\n\n\t\tif ( this._mouseStarted ) {\n\t\t\tthis._mouseStarted = false;\n\n\t\t\tif ( event.target === this._mouseDownEvent.target ) {\n\t\t\t\t$.data( event.target, this.widgetName + \".preventClickEvent\", true );\n\t\t\t}\n\n\t\t\tthis._mouseStop( event );\n\t\t}\n\n\t\tif ( this._mouseDelayTimer ) {\n\t\t\tclearTimeout( this._mouseDelayTimer );\n\t\t\tdelete this._mouseDelayTimer;\n\t\t}\n\n\t\tthis.ignoreMissingWhich = false;\n\t\tmouseHandled = false;\n\t\tevent.preventDefault();\n\t},\n\n\t_mouseDistanceMet: function( event ) {\n\t\treturn ( Math.max(\n\t\t\t\tMath.abs( this._mouseDownEvent.pageX - event.pageX ),\n\t\t\t\tMath.abs( this._mouseDownEvent.pageY - event.pageY )\n\t\t\t) >= this.options.distance\n\t\t);\n\t},\n\n\t_mouseDelayMet: function( /* event */ ) {\n\t\treturn this.mouseDelayMet;\n\t},\n\n\t// These are placeholder methods, to be overriden by extending plugin\n\t_mouseStart: function( /* event */ ) {},\n\t_mouseDrag: function( /* event */ ) {},\n\t_mouseStop: function( /* event */ ) {},\n\t_mouseCapture: function( /* event */ ) { return true; }\n} );\n\n\n\n\n// $.ui.plugin is deprecated. Use $.widget() extensions instead.\nvar plugin = $.ui.plugin = {\n\tadd: function( module, option, set ) {\n\t\tvar i,\n\t\t\tproto = $.ui[ module ].prototype;\n\t\tfor ( i in set ) {\n\t\t\tproto.plugins[ i ] = proto.plugins[ i ] || [];\n\t\t\tproto.plugins[ i ].push( [ option, set[ i ] ] );\n\t\t}\n\t},\n\tcall: function( instance, name, args, allowDisconnected ) {\n\t\tvar i,\n\t\t\tset = instance.plugins[ name ];\n\n\t\tif ( !set ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !allowDisconnected && ( !instance.element[ 0 ].parentNode ||\n\t\t\t\tinstance.element[ 0 ].parentNode.nodeType === 11 ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor ( i = 0; i < set.length; i++ ) {\n\t\t\tif ( instance.options[ set[ i ][ 0 ] ] ) {\n\t\t\t\tset[ i ][ 1 ].apply( instance.element, args );\n\t\t\t}\n\t\t}\n\t}\n};\n\n\n\nvar safeActiveElement = $.ui.safeActiveElement = function( document ) {\n\tvar activeElement;\n\n\t// Support: IE 9 only\n\t// IE9 throws an \"Unspecified error\" accessing document.activeElement from an <iframe>\n\ttry {\n\t\tactiveElement = document.activeElement;\n\t} catch ( error ) {\n\t\tactiveElement = document.body;\n\t}\n\n\t// Support: IE 9 - 11 only\n\t// IE may return null instead of an element\n\t// Interestingly, this only seems to occur when NOT in an iframe\n\tif ( !activeElement ) {\n\t\tactiveElement = document.body;\n\t}\n\n\t// Support: IE 11 only\n\t// IE11 returns a seemingly empty object in some cases when accessing\n\t// document.activeElement from an <iframe>\n\tif ( !activeElement.nodeName ) {\n\t\tactiveElement = document.body;\n\t}\n\n\treturn activeElement;\n};\n\n\n\nvar safeBlur = $.ui.safeBlur = function( element ) {\n\n\t// Support: IE9 - 10 only\n\t// If the <body> is blurred, IE will switch windows, see #9420\n\tif ( element && element.nodeName.toLowerCase() !== \"body\" ) {\n\t\t$( element ).trigger( \"blur\" );\n\t}\n};\n\n\n/*!\n * jQuery UI Draggable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Draggable\n//>>group: Interactions\n//>>description: Enables dragging functionality for any element.\n//>>docs: http://api.jqueryui.com/draggable/\n//>>demos: http://jqueryui.com/draggable/\n//>>css.structure: ../../themes/base/draggable.css\n\n\n\n$.widget( \"ui.draggable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"drag\",\n\toptions: {\n\t\taddClasses: true,\n\t\tappendTo: \"parent\",\n\t\taxis: false,\n\t\tconnectToSortable: false,\n\t\tcontainment: false,\n\t\tcursor: \"auto\",\n\t\tcursorAt: false,\n\t\tgrid: false,\n\t\thandle: false,\n\t\thelper: \"original\",\n\t\tiframeFix: false,\n\t\topacity: false,\n\t\trefreshPositions: false,\n\t\trevert: false,\n\t\trevertDuration: 500,\n\t\tscope: \"default\",\n\t\tscroll: true,\n\t\tscrollSensitivity: 20,\n\t\tscrollSpeed: 20,\n\t\tsnap: false,\n\t\tsnapMode: \"both\",\n\t\tsnapTolerance: 20,\n\t\tstack: false,\n\t\tzIndex: false,\n\n\t\t// Callbacks\n\t\tdrag: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\t_create: function() {\n\n\t\tif ( this.options.helper === \"original\" ) {\n\t\t\tthis._setPositionRelative();\n\t\t}\n\t\tif ( this.options.addClasses ) {\n\t\t\tthis._addClass( \"ui-draggable\" );\n\t\t}\n\t\tthis._setHandleClassName();\n\n\t\tthis._mouseInit();\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\t\tif ( key === \"handle\" ) {\n\t\t\tthis._removeHandleClassName();\n\t\t\tthis._setHandleClassName();\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tif ( ( this.helper || this.element ).is( \".ui-draggable-dragging\" ) ) {\n\t\t\tthis.destroyOnClear = true;\n\t\t\treturn;\n\t\t}\n\t\tthis._removeHandleClassName();\n\t\tthis._mouseDestroy();\n\t},\n\n\t_mouseCapture: function( event ) {\n\t\tvar o = this.options;\n\n\t\t// Among others, prevent a drag on a resizable-handle\n\t\tif ( this.helper || o.disabled ||\n\t\t\t\t$( event.target ).closest( \".ui-resizable-handle\" ).length > 0 ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t//Quit if we're not on a valid handle\n\t\tthis.handle = this._getHandle( event );\n\t\tif ( !this.handle ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._blurActiveElement( event );\n\n\t\tthis._blockFrames( o.iframeFix === true ? \"iframe\" : o.iframeFix );\n\n\t\treturn true;\n\n\t},\n\n\t_blockFrames: function( selector ) {\n\t\tthis.iframeBlocks = this.document.find( selector ).map( function() {\n\t\t\tvar iframe = $( this );\n\n\t\t\treturn $( \"<div>\" )\n\t\t\t\t.css( \"position\", \"absolute\" )\n\t\t\t\t.appendTo( iframe.parent() )\n\t\t\t\t.outerWidth( iframe.outerWidth() )\n\t\t\t\t.outerHeight( iframe.outerHeight() )\n\t\t\t\t.offset( iframe.offset() )[ 0 ];\n\t\t} );\n\t},\n\n\t_unblockFrames: function() {\n\t\tif ( this.iframeBlocks ) {\n\t\t\tthis.iframeBlocks.remove();\n\t\t\tdelete this.iframeBlocks;\n\t\t}\n\t},\n\n\t_blurActiveElement: function( event ) {\n\t\tvar activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),\n\t\t\ttarget = $( event.target );\n\n\t\t// Don't blur if the event occurred on an element that is within\n\t\t// the currently focused element\n\t\t// See #10527, #12472\n\t\tif ( target.closest( activeElement ).length ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Blur any element that currently has focus, see #4261\n\t\t$.ui.safeBlur( activeElement );\n\t},\n\n\t_mouseStart: function( event ) {\n\n\t\tvar o = this.options;\n\n\t\t//Create and append the visible helper\n\t\tthis.helper = this._createHelper( event );\n\n\t\tthis._addClass( this.helper, \"ui-draggable-dragging\" );\n\n\t\t//Cache the helper size\n\t\tthis._cacheHelperProportions();\n\n\t\t//If ddmanager is used for droppables, set the global draggable\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.current = this;\n\t\t}\n\n\t\t/*\n\t\t * - Position generation -\n\t\t * This block generates everything position related - it's the core of draggables.\n\t\t */\n\n\t\t//Cache the margins of the original element\n\t\tthis._cacheMargins();\n\n\t\t//Store the helper's css position\n\t\tthis.cssPosition = this.helper.css( \"position\" );\n\t\tthis.scrollParent = this.helper.scrollParent( true );\n\t\tthis.offsetParent = this.helper.offsetParent();\n\t\tthis.hasFixedAncestor = this.helper.parents().filter( function() {\n\t\t\t\treturn $( this ).css( \"position\" ) === \"fixed\";\n\t\t\t} ).length > 0;\n\n\t\t//The element's absolute position on the page minus margins\n\t\tthis.positionAbs = this.element.offset();\n\t\tthis._refreshOffsets( event );\n\n\t\t//Generate the original position\n\t\tthis.originalPosition = this.position = this._generatePosition( event, false );\n\t\tthis.originalPageX = event.pageX;\n\t\tthis.originalPageY = event.pageY;\n\n\t\t//Adjust the mouse offset relative to the helper if \"cursorAt\" is supplied\n\t\t( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );\n\n\t\t//Set a containment if given in the options\n\t\tthis._setContainment();\n\n\t\t//Trigger event + callbacks\n\t\tif ( this._trigger( \"start\", event ) === false ) {\n\t\t\tthis._clear();\n\t\t\treturn false;\n\t\t}\n\n\t\t//Recache the helper size\n\t\tthis._cacheHelperProportions();\n\n\t\t//Prepare the droppable offsets\n\t\tif ( $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( this, event );\n\t\t}\n\n\t\t// Execute the drag once - this causes the helper not to be visible before getting its\n\t\t// correct position\n\t\tthis._mouseDrag( event, true );\n\n\t\t// If the ddmanager is used for droppables, inform the manager that dragging has started\n\t\t// (see #5003)\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.dragStart( this, event );\n\t\t}\n\n\t\treturn true;\n\t},\n\n\t_refreshOffsets: function( event ) {\n\t\tthis.offset = {\n\t\t\ttop: this.positionAbs.top - this.margins.top,\n\t\t\tleft: this.positionAbs.left - this.margins.left,\n\t\t\tscroll: false,\n\t\t\tparent: this._getParentOffset(),\n\t\t\trelative: this._getRelativeOffset()\n\t\t};\n\n\t\tthis.offset.click = {\n\t\t\tleft: event.pageX - this.offset.left,\n\t\t\ttop: event.pageY - this.offset.top\n\t\t};\n\t},\n\n\t_mouseDrag: function( event, noPropagation ) {\n\n\t\t// reset any necessary cached properties (see #5009)\n\t\tif ( this.hasFixedAncestor ) {\n\t\t\tthis.offset.parent = this._getParentOffset();\n\t\t}\n\n\t\t//Compute the helpers position\n\t\tthis.position = this._generatePosition( event, true );\n\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\n\t\t//Call plugins and callbacks and use the resulting position if something is returned\n\t\tif ( !noPropagation ) {\n\t\t\tvar ui = this._uiHash();\n\t\t\tif ( this._trigger( \"drag\", event, ui ) === false ) {\n\t\t\t\tthis._mouseUp( new $.Event( \"mouseup\", event ) );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.position = ui.position;\n\t\t}\n\n\t\tthis.helper[ 0 ].style.left = this.position.left + \"px\";\n\t\tthis.helper[ 0 ].style.top = this.position.top + \"px\";\n\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.drag( this, event );\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\n\t\t//If we are using droppables, inform the manager about the drop\n\t\tvar that = this,\n\t\t\tdropped = false;\n\t\tif ( $.ui.ddmanager && !this.options.dropBehaviour ) {\n\t\t\tdropped = $.ui.ddmanager.drop( this, event );\n\t\t}\n\n\t\t//if a drop comes from outside (a sortable)\n\t\tif ( this.dropped ) {\n\t\t\tdropped = this.dropped;\n\t\t\tthis.dropped = false;\n\t\t}\n\n\t\tif ( ( this.options.revert === \"invalid\" && !dropped ) ||\n\t\t\t\t( this.options.revert === \"valid\" && dropped ) ||\n\t\t\t\tthis.options.revert === true || ( $.isFunction( this.options.revert ) &&\n\t\t\t\tthis.options.revert.call( this.element, dropped ) )\n\t\t) {\n\t\t\t$( this.helper ).animate(\n\t\t\t\tthis.originalPosition,\n\t\t\t\tparseInt( this.options.revertDuration, 10 ),\n\t\t\t\tfunction() {\n\t\t\t\t\tif ( that._trigger( \"stop\", event ) !== false ) {\n\t\t\t\t\t\tthat._clear();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t} else {\n\t\t\tif ( this._trigger( \"stop\", event ) !== false ) {\n\t\t\t\tthis._clear();\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_mouseUp: function( event ) {\n\t\tthis._unblockFrames();\n\n\t\t// If the ddmanager is used for droppables, inform the manager that dragging has stopped\n\t\t// (see #5003)\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.dragStop( this, event );\n\t\t}\n\n\t\t// Only need to focus if the event occurred on the draggable itself, see #10527\n\t\tif ( this.handleElement.is( event.target ) ) {\n\n\t\t\t// The interaction is over; whether or not the click resulted in a drag,\n\t\t\t// focus the element\n\t\t\tthis.element.trigger( \"focus\" );\n\t\t}\n\n\t\treturn $.ui.mouse.prototype._mouseUp.call( this, event );\n\t},\n\n\tcancel: function() {\n\n\t\tif ( this.helper.is( \".ui-draggable-dragging\" ) ) {\n\t\t\tthis._mouseUp( new $.Event( \"mouseup\", { target: this.element[ 0 ] } ) );\n\t\t} else {\n\t\t\tthis._clear();\n\t\t}\n\n\t\treturn this;\n\n\t},\n\n\t_getHandle: function( event ) {\n\t\treturn this.options.handle ?\n\t\t\t!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :\n\t\t\ttrue;\n\t},\n\n\t_setHandleClassName: function() {\n\t\tthis.handleElement = this.options.handle ?\n\t\t\tthis.element.find( this.options.handle ) : this.element;\n\t\tthis._addClass( this.handleElement, \"ui-draggable-handle\" );\n\t},\n\n\t_removeHandleClassName: function() {\n\t\tthis._removeClass( this.handleElement, \"ui-draggable-handle\" );\n\t},\n\n\t_createHelper: function( event ) {\n\n\t\tvar o = this.options,\n\t\t\thelperIsFunction = $.isFunction( o.helper ),\n\t\t\thelper = helperIsFunction ?\n\t\t\t\t$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :\n\t\t\t\t( o.helper === \"clone\" ?\n\t\t\t\t\tthis.element.clone().removeAttr( \"id\" ) :\n\t\t\t\t\tthis.element );\n\n\t\tif ( !helper.parents( \"body\" ).length ) {\n\t\t\thelper.appendTo( ( o.appendTo === \"parent\" ?\n\t\t\t\tthis.element[ 0 ].parentNode :\n\t\t\t\to.appendTo ) );\n\t\t}\n\n\t\t// Http://bugs.jqueryui.com/ticket/9446\n\t\t// a helper function can return the original element\n\t\t// which wouldn't have been set to relative in _create\n\t\tif ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {\n\t\t\tthis._setPositionRelative();\n\t\t}\n\n\t\tif ( helper[ 0 ] !== this.element[ 0 ] &&\n\t\t\t\t!( /(fixed|absolute)/ ).test( helper.css( \"position\" ) ) ) {\n\t\t\thelper.css( \"position\", \"absolute\" );\n\t\t}\n\n\t\treturn helper;\n\n\t},\n\n\t_setPositionRelative: function() {\n\t\tif ( !( /^(?:r|a|f)/ ).test( this.element.css( \"position\" ) ) ) {\n\t\t\tthis.element[ 0 ].style.position = \"relative\";\n\t\t}\n\t},\n\n\t_adjustOffsetFromHelper: function( obj ) {\n\t\tif ( typeof obj === \"string\" ) {\n\t\t\tobj = obj.split( \" \" );\n\t\t}\n\t\tif ( $.isArray( obj ) ) {\n\t\t\tobj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };\n\t\t}\n\t\tif ( \"left\" in obj ) {\n\t\t\tthis.offset.click.left = obj.left + this.margins.left;\n\t\t}\n\t\tif ( \"right\" in obj ) {\n\t\t\tthis.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;\n\t\t}\n\t\tif ( \"top\" in obj ) {\n\t\t\tthis.offset.click.top = obj.top + this.margins.top;\n\t\t}\n\t\tif ( \"bottom\" in obj ) {\n\t\t\tthis.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;\n\t\t}\n\t},\n\n\t_isRootNode: function( element ) {\n\t\treturn ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];\n\t},\n\n\t_getParentOffset: function() {\n\n\t\t//Get the offsetParent and cache its position\n\t\tvar po = this.offsetParent.offset(),\n\t\t\tdocument = this.document[ 0 ];\n\n\t\t// This is a special case where we need to modify a offset calculated on start, since the\n\t\t// following happened:\n\t\t// 1. The position of the helper is absolute, so it's position is calculated based on the\n\t\t// next positioned parent\n\t\t// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't\n\t\t// the document, which means that the scroll is included in the initial calculation of the\n\t\t// offset of the parent, and never recalculated upon drag\n\t\tif ( this.cssPosition === \"absolute\" && this.scrollParent[ 0 ] !== document &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {\n\t\t\tpo.left += this.scrollParent.scrollLeft();\n\t\t\tpo.top += this.scrollParent.scrollTop();\n\t\t}\n\n\t\tif ( this._isRootNode( this.offsetParent[ 0 ] ) ) {\n\t\t\tpo = { top: 0, left: 0 };\n\t\t}\n\n\t\treturn {\n\t\t\ttop: po.top + ( parseInt( this.offsetParent.css( \"borderTopWidth\" ), 10 ) || 0 ),\n\t\t\tleft: po.left + ( parseInt( this.offsetParent.css( \"borderLeftWidth\" ), 10 ) || 0 )\n\t\t};\n\n\t},\n\n\t_getRelativeOffset: function() {\n\t\tif ( this.cssPosition !== \"relative\" ) {\n\t\t\treturn { top: 0, left: 0 };\n\t\t}\n\n\t\tvar p = this.element.position(),\n\t\t\tscrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );\n\n\t\treturn {\n\t\t\ttop: p.top - ( parseInt( this.helper.css( \"top\" ), 10 ) || 0 ) +\n\t\t\t\t( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),\n\t\t\tleft: p.left - ( parseInt( this.helper.css( \"left\" ), 10 ) || 0 ) +\n\t\t\t\t( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )\n\t\t};\n\n\t},\n\n\t_cacheMargins: function() {\n\t\tthis.margins = {\n\t\t\tleft: ( parseInt( this.element.css( \"marginLeft\" ), 10 ) || 0 ),\n\t\t\ttop: ( parseInt( this.element.css( \"marginTop\" ), 10 ) || 0 ),\n\t\t\tright: ( parseInt( this.element.css( \"marginRight\" ), 10 ) || 0 ),\n\t\t\tbottom: ( parseInt( this.element.css( \"marginBottom\" ), 10 ) || 0 )\n\t\t};\n\t},\n\n\t_cacheHelperProportions: function() {\n\t\tthis.helperProportions = {\n\t\t\twidth: this.helper.outerWidth(),\n\t\t\theight: this.helper.outerHeight()\n\t\t};\n\t},\n\n\t_setContainment: function() {\n\n\t\tvar isUserScrollable, c, ce,\n\t\t\to = this.options,\n\t\t\tdocument = this.document[ 0 ];\n\n\t\tthis.relativeContainer = null;\n\n\t\tif ( !o.containment ) {\n\t\t\tthis.containment = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment === \"window\" ) {\n\t\t\tthis.containment = [\n\t\t\t\t$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,\n\t\t\t\t$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,\n\t\t\t\t$( window ).scrollLeft() + $( window ).width() -\n\t\t\t\t\tthis.helperProportions.width - this.margins.left,\n\t\t\t\t$( window ).scrollTop() +\n\t\t\t\t\t( $( window ).height() || document.body.parentNode.scrollHeight ) -\n\t\t\t\t\tthis.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment === \"document\" ) {\n\t\t\tthis.containment = [\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t$( document ).width() - this.helperProportions.width - this.margins.left,\n\t\t\t\t( $( document ).height() || document.body.parentNode.scrollHeight ) -\n\t\t\t\t\tthis.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment.constructor === Array ) {\n\t\t\tthis.containment = o.containment;\n\t\t\treturn;\n\t\t}\n\n\t\tif ( o.containment === \"parent\" ) {\n\t\t\to.containment = this.helper[ 0 ].parentNode;\n\t\t}\n\n\t\tc = $( o.containment );\n\t\tce = c[ 0 ];\n\n\t\tif ( !ce ) {\n\t\t\treturn;\n\t\t}\n\n\t\tisUserScrollable = /(scroll|auto)/.test( c.css( \"overflow\" ) );\n\n\t\tthis.containment = [\n\t\t\t( parseInt( c.css( \"borderLeftWidth\" ), 10 ) || 0 ) +\n\t\t\t\t( parseInt( c.css( \"paddingLeft\" ), 10 ) || 0 ),\n\t\t\t( parseInt( c.css( \"borderTopWidth\" ), 10 ) || 0 ) +\n\t\t\t\t( parseInt( c.css( \"paddingTop\" ), 10 ) || 0 ),\n\t\t\t( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -\n\t\t\t\t( parseInt( c.css( \"borderRightWidth\" ), 10 ) || 0 ) -\n\t\t\t\t( parseInt( c.css( \"paddingRight\" ), 10 ) || 0 ) -\n\t\t\t\tthis.helperProportions.width -\n\t\t\t\tthis.margins.left -\n\t\t\t\tthis.margins.right,\n\t\t\t( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -\n\t\t\t\t( parseInt( c.css( \"borderBottomWidth\" ), 10 ) || 0 ) -\n\t\t\t\t( parseInt( c.css( \"paddingBottom\" ), 10 ) || 0 ) -\n\t\t\t\tthis.helperProportions.height -\n\t\t\t\tthis.margins.top -\n\t\t\t\tthis.margins.bottom\n\t\t];\n\t\tthis.relativeContainer = c;\n\t},\n\n\t_convertPositionTo: function( d, pos ) {\n\n\t\tif ( !pos ) {\n\t\t\tpos = this.position;\n\t\t}\n\n\t\tvar mod = d === \"absolute\" ? 1 : -1,\n\t\t\tscrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.top\t+\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top * mod -\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.top :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.left +\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left * mod\t-\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.left :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_generatePosition: function( event, constrainPosition ) {\n\n\t\tvar containment, co, top, left,\n\t\t\to = this.options,\n\t\t\tscrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),\n\t\t\tpageX = event.pageX,\n\t\t\tpageY = event.pageY;\n\n\t\t// Cache the scroll\n\t\tif ( !scrollIsRootNode || !this.offset.scroll ) {\n\t\t\tthis.offset.scroll = {\n\t\t\t\ttop: this.scrollParent.scrollTop(),\n\t\t\t\tleft: this.scrollParent.scrollLeft()\n\t\t\t};\n\t\t}\n\n\t\t/*\n\t\t * - Position constraining -\n\t\t * Constrain the position to a mix of grid, containment.\n\t\t */\n\n\t\t// If we are not dragging yet, we won't check for options\n\t\tif ( constrainPosition ) {\n\t\t\tif ( this.containment ) {\n\t\t\t\tif ( this.relativeContainer ) {\n\t\t\t\t\tco = this.relativeContainer.offset();\n\t\t\t\t\tcontainment = [\n\t\t\t\t\t\tthis.containment[ 0 ] + co.left,\n\t\t\t\t\t\tthis.containment[ 1 ] + co.top,\n\t\t\t\t\t\tthis.containment[ 2 ] + co.left,\n\t\t\t\t\t\tthis.containment[ 3 ] + co.top\n\t\t\t\t\t];\n\t\t\t\t} else {\n\t\t\t\t\tcontainment = this.containment;\n\t\t\t\t}\n\n\t\t\t\tif ( event.pageX - this.offset.click.left < containment[ 0 ] ) {\n\t\t\t\t\tpageX = containment[ 0 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top < containment[ 1 ] ) {\n\t\t\t\t\tpageY = containment[ 1 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t\tif ( event.pageX - this.offset.click.left > containment[ 2 ] ) {\n\t\t\t\t\tpageX = containment[ 2 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top > containment[ 3 ] ) {\n\t\t\t\t\tpageY = containment[ 3 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( o.grid ) {\n\n\t\t\t\t//Check for grid elements set to 0 to prevent divide by 0 error causing invalid\n\t\t\t\t// argument errors in IE (see ticket #6950)\n\t\t\t\ttop = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -\n\t\t\t\t\tthis.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;\n\t\t\t\tpageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||\n\t\t\t\t\ttop - this.offset.click.top > containment[ 3 ] ) ?\n\t\t\t\t\t\ttop :\n\t\t\t\t\t\t( ( top - this.offset.click.top >= containment[ 1 ] ) ?\n\t\t\t\t\t\t\ttop - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;\n\n\t\t\t\tleft = o.grid[ 0 ] ? this.originalPageX +\n\t\t\t\t\tMath.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :\n\t\t\t\t\tthis.originalPageX;\n\t\t\t\tpageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||\n\t\t\t\t\tleft - this.offset.click.left > containment[ 2 ] ) ?\n\t\t\t\t\t\tleft :\n\t\t\t\t\t\t( ( left - this.offset.click.left >= containment[ 0 ] ) ?\n\t\t\t\t\t\t\tleft - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;\n\t\t\t}\n\n\t\t\tif ( o.axis === \"y\" ) {\n\t\t\t\tpageX = this.originalPageX;\n\t\t\t}\n\n\t\t\tif ( o.axis === \"x\" ) {\n\t\t\t\tpageY = this.originalPageY;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageY -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.top -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top +\n\t\t\t\t( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.top :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.top ) )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageX -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.left -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left +\n\t\t\t\t( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.offset.scroll.left :\n\t\t\t\t\t( scrollIsRootNode ? 0 : this.offset.scroll.left ) )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_clear: function() {\n\t\tthis._removeClass( this.helper, \"ui-draggable-dragging\" );\n\t\tif ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {\n\t\t\tthis.helper.remove();\n\t\t}\n\t\tthis.helper = null;\n\t\tthis.cancelHelperRemoval = false;\n\t\tif ( this.destroyOnClear ) {\n\t\t\tthis.destroy();\n\t\t}\n\t},\n\n\t// From now on bulk stuff - mainly helpers\n\n\t_trigger: function( type, event, ui ) {\n\t\tui = ui || this._uiHash();\n\t\t$.ui.plugin.call( this, type, [ event, ui, this ], true );\n\n\t\t// Absolute position and offset (see #6884 ) have to be recalculated after plugins\n\t\tif ( /^(drag|start|stop)/.test( type ) ) {\n\t\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\t\t\tui.offset = this.positionAbs;\n\t\t}\n\t\treturn $.Widget.prototype._trigger.call( this, type, event, ui );\n\t},\n\n\tplugins: {},\n\n\t_uiHash: function() {\n\t\treturn {\n\t\t\thelper: this.helper,\n\t\t\tposition: this.position,\n\t\t\toriginalPosition: this.originalPosition,\n\t\t\toffset: this.positionAbs\n\t\t};\n\t}\n\n} );\n\n$.ui.plugin.add( \"draggable\", \"connectToSortable\", {\n\tstart: function( event, ui, draggable ) {\n\t\tvar uiSortable = $.extend( {}, ui, {\n\t\t\titem: draggable.element\n\t\t} );\n\n\t\tdraggable.sortables = [];\n\t\t$( draggable.options.connectToSortable ).each( function() {\n\t\t\tvar sortable = $( this ).sortable( \"instance\" );\n\n\t\t\tif ( sortable && !sortable.options.disabled ) {\n\t\t\t\tdraggable.sortables.push( sortable );\n\n\t\t\t\t// RefreshPositions is called at drag start to refresh the containerCache\n\t\t\t\t// which is used in drag. This ensures it's initialized and synchronized\n\t\t\t\t// with any changes that might have happened on the page since initialization.\n\t\t\t\tsortable.refreshPositions();\n\t\t\t\tsortable._trigger( \"activate\", event, uiSortable );\n\t\t\t}\n\t\t} );\n\t},\n\tstop: function( event, ui, draggable ) {\n\t\tvar uiSortable = $.extend( {}, ui, {\n\t\t\titem: draggable.element\n\t\t} );\n\n\t\tdraggable.cancelHelperRemoval = false;\n\n\t\t$.each( draggable.sortables, function() {\n\t\t\tvar sortable = this;\n\n\t\t\tif ( sortable.isOver ) {\n\t\t\t\tsortable.isOver = 0;\n\n\t\t\t\t// Allow this sortable to handle removing the helper\n\t\t\t\tdraggable.cancelHelperRemoval = true;\n\t\t\t\tsortable.cancelHelperRemoval = false;\n\n\t\t\t\t// Use _storedCSS To restore properties in the sortable,\n\t\t\t\t// as this also handles revert (#9675) since the draggable\n\t\t\t\t// may have modified them in unexpected ways (#8809)\n\t\t\t\tsortable._storedCSS = {\n\t\t\t\t\tposition: sortable.placeholder.css( \"position\" ),\n\t\t\t\t\ttop: sortable.placeholder.css( \"top\" ),\n\t\t\t\t\tleft: sortable.placeholder.css( \"left\" )\n\t\t\t\t};\n\n\t\t\t\tsortable._mouseStop( event );\n\n\t\t\t\t// Once drag has ended, the sortable should return to using\n\t\t\t\t// its original helper, not the shared helper from draggable\n\t\t\t\tsortable.options.helper = sortable.options._helper;\n\t\t\t} else {\n\n\t\t\t\t// Prevent this Sortable from removing the helper.\n\t\t\t\t// However, don't set the draggable to remove the helper\n\t\t\t\t// either as another connected Sortable may yet handle the removal.\n\t\t\t\tsortable.cancelHelperRemoval = true;\n\n\t\t\t\tsortable._trigger( \"deactivate\", event, uiSortable );\n\t\t\t}\n\t\t} );\n\t},\n\tdrag: function( event, ui, draggable ) {\n\t\t$.each( draggable.sortables, function() {\n\t\t\tvar innermostIntersecting = false,\n\t\t\t\tsortable = this;\n\n\t\t\t// Copy over variables that sortable's _intersectsWith uses\n\t\t\tsortable.positionAbs = draggable.positionAbs;\n\t\t\tsortable.helperProportions = draggable.helperProportions;\n\t\t\tsortable.offset.click = draggable.offset.click;\n\n\t\t\tif ( sortable._intersectsWith( sortable.containerCache ) ) {\n\t\t\t\tinnermostIntersecting = true;\n\n\t\t\t\t$.each( draggable.sortables, function() {\n\n\t\t\t\t\t// Copy over variables that sortable's _intersectsWith uses\n\t\t\t\t\tthis.positionAbs = draggable.positionAbs;\n\t\t\t\t\tthis.helperProportions = draggable.helperProportions;\n\t\t\t\t\tthis.offset.click = draggable.offset.click;\n\n\t\t\t\t\tif ( this !== sortable &&\n\t\t\t\t\t\t\tthis._intersectsWith( this.containerCache ) &&\n\t\t\t\t\t\t\t$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {\n\t\t\t\t\t\tinnermostIntersecting = false;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn innermostIntersecting;\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tif ( innermostIntersecting ) {\n\n\t\t\t\t// If it intersects, we use a little isOver variable and set it once,\n\t\t\t\t// so that the move-in stuff gets fired only once.\n\t\t\t\tif ( !sortable.isOver ) {\n\t\t\t\t\tsortable.isOver = 1;\n\n\t\t\t\t\t// Store draggable's parent in case we need to reappend to it later.\n\t\t\t\t\tdraggable._parent = ui.helper.parent();\n\n\t\t\t\t\tsortable.currentItem = ui.helper\n\t\t\t\t\t\t.appendTo( sortable.element )\n\t\t\t\t\t\t.data( \"ui-sortable-item\", true );\n\n\t\t\t\t\t// Store helper option to later restore it\n\t\t\t\t\tsortable.options._helper = sortable.options.helper;\n\n\t\t\t\t\tsortable.options.helper = function() {\n\t\t\t\t\t\treturn ui.helper[ 0 ];\n\t\t\t\t\t};\n\n\t\t\t\t\t// Fire the start events of the sortable with our passed browser event,\n\t\t\t\t\t// and our own helper (so it doesn't create a new one)\n\t\t\t\t\tevent.target = sortable.currentItem[ 0 ];\n\t\t\t\t\tsortable._mouseCapture( event, true );\n\t\t\t\t\tsortable._mouseStart( event, true, true );\n\n\t\t\t\t\t// Because the browser event is way off the new appended portlet,\n\t\t\t\t\t// modify necessary variables to reflect the changes\n\t\t\t\t\tsortable.offset.click.top = draggable.offset.click.top;\n\t\t\t\t\tsortable.offset.click.left = draggable.offset.click.left;\n\t\t\t\t\tsortable.offset.parent.left -= draggable.offset.parent.left -\n\t\t\t\t\t\tsortable.offset.parent.left;\n\t\t\t\t\tsortable.offset.parent.top -= draggable.offset.parent.top -\n\t\t\t\t\t\tsortable.offset.parent.top;\n\n\t\t\t\t\tdraggable._trigger( \"toSortable\", event );\n\n\t\t\t\t\t// Inform draggable that the helper is in a valid drop zone,\n\t\t\t\t\t// used solely in the revert option to handle \"valid/invalid\".\n\t\t\t\t\tdraggable.dropped = sortable.element;\n\n\t\t\t\t\t// Need to refreshPositions of all sortables in the case that\n\t\t\t\t\t// adding to one sortable changes the location of the other sortables (#9675)\n\t\t\t\t\t$.each( draggable.sortables, function() {\n\t\t\t\t\t\tthis.refreshPositions();\n\t\t\t\t\t} );\n\n\t\t\t\t\t// Hack so receive/update callbacks work (mostly)\n\t\t\t\t\tdraggable.currentItem = draggable.element;\n\t\t\t\t\tsortable.fromOutside = draggable;\n\t\t\t\t}\n\n\t\t\t\tif ( sortable.currentItem ) {\n\t\t\t\t\tsortable._mouseDrag( event );\n\n\t\t\t\t\t// Copy the sortable's position because the draggable's can potentially reflect\n\t\t\t\t\t// a relative position, while sortable is always absolute, which the dragged\n\t\t\t\t\t// element has now become. (#8809)\n\t\t\t\t\tui.position = sortable.position;\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// If it doesn't intersect with the sortable, and it intersected before,\n\t\t\t\t// we fake the drag stop of the sortable, but make sure it doesn't remove\n\t\t\t\t// the helper by using cancelHelperRemoval.\n\t\t\t\tif ( sortable.isOver ) {\n\n\t\t\t\t\tsortable.isOver = 0;\n\t\t\t\t\tsortable.cancelHelperRemoval = true;\n\n\t\t\t\t\t// Calling sortable's mouseStop would trigger a revert,\n\t\t\t\t\t// so revert must be temporarily false until after mouseStop is called.\n\t\t\t\t\tsortable.options._revert = sortable.options.revert;\n\t\t\t\t\tsortable.options.revert = false;\n\n\t\t\t\t\tsortable._trigger( \"out\", event, sortable._uiHash( sortable ) );\n\t\t\t\t\tsortable._mouseStop( event, true );\n\n\t\t\t\t\t// Restore sortable behaviors that were modfied\n\t\t\t\t\t// when the draggable entered the sortable area (#9481)\n\t\t\t\t\tsortable.options.revert = sortable.options._revert;\n\t\t\t\t\tsortable.options.helper = sortable.options._helper;\n\n\t\t\t\t\tif ( sortable.placeholder ) {\n\t\t\t\t\t\tsortable.placeholder.remove();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Restore and recalculate the draggable's offset considering the sortable\n\t\t\t\t\t// may have modified them in unexpected ways. (#8809, #10669)\n\t\t\t\t\tui.helper.appendTo( draggable._parent );\n\t\t\t\t\tdraggable._refreshOffsets( event );\n\t\t\t\t\tui.position = draggable._generatePosition( event, true );\n\n\t\t\t\t\tdraggable._trigger( \"fromSortable\", event );\n\n\t\t\t\t\t// Inform draggable that the helper is no longer in a valid drop zone\n\t\t\t\t\tdraggable.dropped = false;\n\n\t\t\t\t\t// Need to refreshPositions of all sortables just in case removing\n\t\t\t\t\t// from one sortable changes the location of other sortables (#9675)\n\t\t\t\t\t$.each( draggable.sortables, function() {\n\t\t\t\t\t\tthis.refreshPositions();\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"cursor\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar t = $( \"body\" ),\n\t\t\to = instance.options;\n\n\t\tif ( t.css( \"cursor\" ) ) {\n\t\t\to._cursor = t.css( \"cursor\" );\n\t\t}\n\t\tt.css( \"cursor\", o.cursor );\n\t},\n\tstop: function( event, ui, instance ) {\n\t\tvar o = instance.options;\n\t\tif ( o._cursor ) {\n\t\t\t$( \"body\" ).css( \"cursor\", o._cursor );\n\t\t}\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"opacity\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar t = $( ui.helper ),\n\t\t\to = instance.options;\n\t\tif ( t.css( \"opacity\" ) ) {\n\t\t\to._opacity = t.css( \"opacity\" );\n\t\t}\n\t\tt.css( \"opacity\", o.opacity );\n\t},\n\tstop: function( event, ui, instance ) {\n\t\tvar o = instance.options;\n\t\tif ( o._opacity ) {\n\t\t\t$( ui.helper ).css( \"opacity\", o._opacity );\n\t\t}\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"scroll\", {\n\tstart: function( event, ui, i ) {\n\t\tif ( !i.scrollParentNotHidden ) {\n\t\t\ti.scrollParentNotHidden = i.helper.scrollParent( false );\n\t\t}\n\n\t\tif ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&\n\t\t\t\ti.scrollParentNotHidden[ 0 ].tagName !== \"HTML\" ) {\n\t\t\ti.overflowOffset = i.scrollParentNotHidden.offset();\n\t\t}\n\t},\n\tdrag: function( event, ui, i  ) {\n\n\t\tvar o = i.options,\n\t\t\tscrolled = false,\n\t\t\tscrollParent = i.scrollParentNotHidden[ 0 ],\n\t\t\tdocument = i.document[ 0 ];\n\n\t\tif ( scrollParent !== document && scrollParent.tagName !== \"HTML\" ) {\n\t\t\tif ( !o.axis || o.axis !== \"x\" ) {\n\t\t\t\tif ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !o.axis || o.axis !== \"y\" ) {\n\t\t\t\tif ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {\n\t\t\t\t\tscrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\n\t\t\tif ( !o.axis || o.axis !== \"x\" ) {\n\t\t\t\tif ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );\n\t\t\t\t} else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !o.axis || o.axis !== \"y\" ) {\n\t\t\t\tif ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollLeft(\n\t\t\t\t\t\t$( document ).scrollLeft() - o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t} else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = $( document ).scrollLeft(\n\t\t\t\t\t\t$( document ).scrollLeft() + o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tif ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( i, event );\n\t\t}\n\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"snap\", {\n\tstart: function( event, ui, i ) {\n\n\t\tvar o = i.options;\n\n\t\ti.snapElements = [];\n\n\t\t$( o.snap.constructor !== String ? ( o.snap.items || \":data(ui-draggable)\" ) : o.snap )\n\t\t\t.each( function() {\n\t\t\t\tvar $t = $( this ),\n\t\t\t\t\t$o = $t.offset();\n\t\t\t\tif ( this !== i.element[ 0 ] ) {\n\t\t\t\t\ti.snapElements.push( {\n\t\t\t\t\t\titem: this,\n\t\t\t\t\t\twidth: $t.outerWidth(), height: $t.outerHeight(),\n\t\t\t\t\t\ttop: $o.top, left: $o.left\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} );\n\n\t},\n\tdrag: function( event, ui, inst ) {\n\n\t\tvar ts, bs, ls, rs, l, r, t, b, i, first,\n\t\t\to = inst.options,\n\t\t\td = o.snapTolerance,\n\t\t\tx1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,\n\t\t\ty1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;\n\n\t\tfor ( i = inst.snapElements.length - 1; i >= 0; i-- ) {\n\n\t\t\tl = inst.snapElements[ i ].left - inst.margins.left;\n\t\t\tr = l + inst.snapElements[ i ].width;\n\t\t\tt = inst.snapElements[ i ].top - inst.margins.top;\n\t\t\tb = t + inst.snapElements[ i ].height;\n\n\t\t\tif ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||\n\t\t\t\t\t!$.contains( inst.snapElements[ i ].item.ownerDocument,\n\t\t\t\t\tinst.snapElements[ i ].item ) ) {\n\t\t\t\tif ( inst.snapElements[ i ].snapping ) {\n\t\t\t\t\t( inst.options.snap.release &&\n\t\t\t\t\t\tinst.options.snap.release.call(\n\t\t\t\t\t\t\tinst.element,\n\t\t\t\t\t\t\tevent,\n\t\t\t\t\t\t\t$.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )\n\t\t\t\t\t\t) );\n\t\t\t\t}\n\t\t\t\tinst.snapElements[ i ].snapping = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( o.snapMode !== \"inner\" ) {\n\t\t\t\tts = Math.abs( t - y2 ) <= d;\n\t\t\t\tbs = Math.abs( b - y1 ) <= d;\n\t\t\t\tls = Math.abs( l - x2 ) <= d;\n\t\t\t\trs = Math.abs( r - x1 ) <= d;\n\t\t\t\tif ( ts ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: t - inst.helperProportions.height,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( bs ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: b,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( ls ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: l - inst.helperProportions.width\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t\tif ( rs ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: r\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfirst = ( ts || bs || ls || rs );\n\n\t\t\tif ( o.snapMode !== \"outer\" ) {\n\t\t\t\tts = Math.abs( t - y1 ) <= d;\n\t\t\t\tbs = Math.abs( b - y2 ) <= d;\n\t\t\t\tls = Math.abs( l - x1 ) <= d;\n\t\t\t\trs = Math.abs( r - x2 ) <= d;\n\t\t\t\tif ( ts ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: t,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( bs ) {\n\t\t\t\t\tui.position.top = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: b - inst.helperProportions.height,\n\t\t\t\t\t\tleft: 0\n\t\t\t\t\t} ).top;\n\t\t\t\t}\n\t\t\t\tif ( ls ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: l\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t\tif ( rs ) {\n\t\t\t\t\tui.position.left = inst._convertPositionTo( \"relative\", {\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: r - inst.helperProportions.width\n\t\t\t\t\t} ).left;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {\n\t\t\t\t( inst.options.snap.snap &&\n\t\t\t\t\tinst.options.snap.snap.call(\n\t\t\t\t\t\tinst.element,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\t$.extend( inst._uiHash(), {\n\t\t\t\t\t\t\tsnapItem: inst.snapElements[ i ].item\n\t\t\t\t\t\t} ) ) );\n\t\t\t}\n\t\t\tinst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );\n\n\t\t}\n\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"stack\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar min,\n\t\t\to = instance.options,\n\t\t\tgroup = $.makeArray( $( o.stack ) ).sort( function( a, b ) {\n\t\t\t\treturn ( parseInt( $( a ).css( \"zIndex\" ), 10 ) || 0 ) -\n\t\t\t\t\t( parseInt( $( b ).css( \"zIndex\" ), 10 ) || 0 );\n\t\t\t} );\n\n\t\tif ( !group.length ) { return; }\n\n\t\tmin = parseInt( $( group[ 0 ] ).css( \"zIndex\" ), 10 ) || 0;\n\t\t$( group ).each( function( i ) {\n\t\t\t$( this ).css( \"zIndex\", min + i );\n\t\t} );\n\t\tthis.css( \"zIndex\", ( min + group.length ) );\n\t}\n} );\n\n$.ui.plugin.add( \"draggable\", \"zIndex\", {\n\tstart: function( event, ui, instance ) {\n\t\tvar t = $( ui.helper ),\n\t\t\to = instance.options;\n\n\t\tif ( t.css( \"zIndex\" ) ) {\n\t\t\to._zIndex = t.css( \"zIndex\" );\n\t\t}\n\t\tt.css( \"zIndex\", o.zIndex );\n\t},\n\tstop: function( event, ui, instance ) {\n\t\tvar o = instance.options;\n\n\t\tif ( o._zIndex ) {\n\t\t\t$( ui.helper ).css( \"zIndex\", o._zIndex );\n\t\t}\n\t}\n} );\n\nvar widgetsDraggable = $.ui.draggable;\n\n\n/*!\n * jQuery UI Droppable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Droppable\n//>>group: Interactions\n//>>description: Enables drop targets for draggable elements.\n//>>docs: http://api.jqueryui.com/droppable/\n//>>demos: http://jqueryui.com/droppable/\n\n\n\n$.widget( \"ui.droppable\", {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"drop\",\n\toptions: {\n\t\taccept: \"*\",\n\t\taddClasses: true,\n\t\tgreedy: false,\n\t\tscope: \"default\",\n\t\ttolerance: \"intersect\",\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tdeactivate: null,\n\t\tdrop: null,\n\t\tout: null,\n\t\tover: null\n\t},\n\t_create: function() {\n\n\t\tvar proportions,\n\t\t\to = this.options,\n\t\t\taccept = o.accept;\n\n\t\tthis.isover = false;\n\t\tthis.isout = true;\n\n\t\tthis.accept = $.isFunction( accept ) ? accept : function( d ) {\n\t\t\treturn d.is( accept );\n\t\t};\n\n\t\tthis.proportions = function( /* valueToWrite */ ) {\n\t\t\tif ( arguments.length ) {\n\n\t\t\t\t// Store the droppable's proportions\n\t\t\t\tproportions = arguments[ 0 ];\n\t\t\t} else {\n\n\t\t\t\t// Retrieve or derive the droppable's proportions\n\t\t\t\treturn proportions ?\n\t\t\t\t\tproportions :\n\t\t\t\t\tproportions = {\n\t\t\t\t\t\twidth: this.element[ 0 ].offsetWidth,\n\t\t\t\t\t\theight: this.element[ 0 ].offsetHeight\n\t\t\t\t\t};\n\t\t\t}\n\t\t};\n\n\t\tthis._addToManager( o.scope );\n\n\t\to.addClasses && this._addClass( \"ui-droppable\" );\n\n\t},\n\n\t_addToManager: function( scope ) {\n\n\t\t// Add the reference and positions to the manager\n\t\t$.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];\n\t\t$.ui.ddmanager.droppables[ scope ].push( this );\n\t},\n\n\t_splice: function( drop ) {\n\t\tvar i = 0;\n\t\tfor ( ; i < drop.length; i++ ) {\n\t\t\tif ( drop[ i ] === this ) {\n\t\t\t\tdrop.splice( i, 1 );\n\t\t\t}\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tvar drop = $.ui.ddmanager.droppables[ this.options.scope ];\n\n\t\tthis._splice( drop );\n\t},\n\n\t_setOption: function( key, value ) {\n\n\t\tif ( key === \"accept\" ) {\n\t\t\tthis.accept = $.isFunction( value ) ? value : function( d ) {\n\t\t\t\treturn d.is( value );\n\t\t\t};\n\t\t} else if ( key === \"scope\" ) {\n\t\t\tvar drop = $.ui.ddmanager.droppables[ this.options.scope ];\n\n\t\t\tthis._splice( drop );\n\t\t\tthis._addToManager( value );\n\t\t}\n\n\t\tthis._super( key, value );\n\t},\n\n\t_activate: function( event ) {\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\tthis._addActiveClass();\n\t\tif ( draggable ) {\n\t\t\tthis._trigger( \"activate\", event, this.ui( draggable ) );\n\t\t}\n\t},\n\n\t_deactivate: function( event ) {\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\tthis._removeActiveClass();\n\t\tif ( draggable ) {\n\t\t\tthis._trigger( \"deactivate\", event, this.ui( draggable ) );\n\t\t}\n\t},\n\n\t_over: function( event ) {\n\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\t// Bail if draggable and droppable are same element\n\t\tif ( !draggable || ( draggable.currentItem ||\n\t\t\t\tdraggable.element )[ 0 ] === this.element[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||\n\t\t\t\tdraggable.element ) ) ) {\n\t\t\tthis._addHoverClass();\n\t\t\tthis._trigger( \"over\", event, this.ui( draggable ) );\n\t\t}\n\n\t},\n\n\t_out: function( event ) {\n\n\t\tvar draggable = $.ui.ddmanager.current;\n\n\t\t// Bail if draggable and droppable are same element\n\t\tif ( !draggable || ( draggable.currentItem ||\n\t\t\t\tdraggable.element )[ 0 ] === this.element[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||\n\t\t\t\tdraggable.element ) ) ) {\n\t\t\tthis._removeHoverClass();\n\t\t\tthis._trigger( \"out\", event, this.ui( draggable ) );\n\t\t}\n\n\t},\n\n\t_drop: function( event, custom ) {\n\n\t\tvar draggable = custom || $.ui.ddmanager.current,\n\t\t\tchildrenIntersection = false;\n\n\t\t// Bail if draggable and droppable are same element\n\t\tif ( !draggable || ( draggable.currentItem ||\n\t\t\t\tdraggable.element )[ 0 ] === this.element[ 0 ] ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.element\n\t\t\t.find( \":data(ui-droppable)\" )\n\t\t\t.not( \".ui-draggable-dragging\" )\n\t\t\t.each( function() {\n\t\t\t\tvar inst = $( this ).droppable( \"instance\" );\n\t\t\t\tif (\n\t\t\t\t\tinst.options.greedy &&\n\t\t\t\t\t!inst.options.disabled &&\n\t\t\t\t\tinst.options.scope === draggable.options.scope &&\n\t\t\t\t\tinst.accept.call(\n\t\t\t\t\t\tinst.element[ 0 ], ( draggable.currentItem || draggable.element )\n\t\t\t\t\t) &&\n\t\t\t\t\tintersect(\n\t\t\t\t\t\tdraggable,\n\t\t\t\t\t\t$.extend( inst, { offset: inst.element.offset() } ),\n\t\t\t\t\t\tinst.options.tolerance, event\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tchildrenIntersection = true;\n\t\t\t\t\treturn false; }\n\t\t\t} );\n\t\tif ( childrenIntersection ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( this.accept.call( this.element[ 0 ],\n\t\t\t\t( draggable.currentItem || draggable.element ) ) ) {\n\t\t\tthis._removeActiveClass();\n\t\t\tthis._removeHoverClass();\n\n\t\t\tthis._trigger( \"drop\", event, this.ui( draggable ) );\n\t\t\treturn this.element;\n\t\t}\n\n\t\treturn false;\n\n\t},\n\n\tui: function( c ) {\n\t\treturn {\n\t\t\tdraggable: ( c.currentItem || c.element ),\n\t\t\thelper: c.helper,\n\t\t\tposition: c.position,\n\t\t\toffset: c.positionAbs\n\t\t};\n\t},\n\n\t// Extension points just to make backcompat sane and avoid duplicating logic\n\t// TODO: Remove in 1.13 along with call to it below\n\t_addHoverClass: function() {\n\t\tthis._addClass( \"ui-droppable-hover\" );\n\t},\n\n\t_removeHoverClass: function() {\n\t\tthis._removeClass( \"ui-droppable-hover\" );\n\t},\n\n\t_addActiveClass: function() {\n\t\tthis._addClass( \"ui-droppable-active\" );\n\t},\n\n\t_removeActiveClass: function() {\n\t\tthis._removeClass( \"ui-droppable-active\" );\n\t}\n} );\n\nvar intersect = $.ui.intersect = ( function() {\n\tfunction isOverAxis( x, reference, size ) {\n\t\treturn ( x >= reference ) && ( x < ( reference + size ) );\n\t}\n\n\treturn function( draggable, droppable, toleranceMode, event ) {\n\n\t\tif ( !droppable.offset ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar x1 = ( draggable.positionAbs ||\n\t\t\t\tdraggable.position.absolute ).left + draggable.margins.left,\n\t\t\ty1 = ( draggable.positionAbs ||\n\t\t\t\tdraggable.position.absolute ).top + draggable.margins.top,\n\t\t\tx2 = x1 + draggable.helperProportions.width,\n\t\t\ty2 = y1 + draggable.helperProportions.height,\n\t\t\tl = droppable.offset.left,\n\t\t\tt = droppable.offset.top,\n\t\t\tr = l + droppable.proportions().width,\n\t\t\tb = t + droppable.proportions().height;\n\n\t\tswitch ( toleranceMode ) {\n\t\tcase \"fit\":\n\t\t\treturn ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );\n\t\tcase \"intersect\":\n\t\t\treturn ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half\n\t\t\t\tx2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half\n\t\t\t\tt < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half\n\t\t\t\ty2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half\n\t\tcase \"pointer\":\n\t\t\treturn isOverAxis( event.pageY, t, droppable.proportions().height ) &&\n\t\t\t\tisOverAxis( event.pageX, l, droppable.proportions().width );\n\t\tcase \"touch\":\n\t\t\treturn (\n\t\t\t\t( y1 >= t && y1 <= b ) || // Top edge touching\n\t\t\t\t( y2 >= t && y2 <= b ) || // Bottom edge touching\n\t\t\t\t( y1 < t && y2 > b ) // Surrounded vertically\n\t\t\t) && (\n\t\t\t\t( x1 >= l && x1 <= r ) || // Left edge touching\n\t\t\t\t( x2 >= l && x2 <= r ) || // Right edge touching\n\t\t\t\t( x1 < l && x2 > r ) // Surrounded horizontally\n\t\t\t);\n\t\tdefault:\n\t\t\treturn false;\n\t\t}\n\t};\n} )();\n\n/*\n\tThis manager tracks offsets of draggables and droppables\n*/\n$.ui.ddmanager = {\n\tcurrent: null,\n\tdroppables: { \"default\": [] },\n\tprepareOffsets: function( t, event ) {\n\n\t\tvar i, j,\n\t\t\tm = $.ui.ddmanager.droppables[ t.options.scope ] || [],\n\t\t\ttype = event ? event.type : null, // workaround for #2317\n\t\t\tlist = ( t.currentItem || t.element ).find( \":data(ui-droppable)\" ).addBack();\n\n\t\tdroppablesLoop: for ( i = 0; i < m.length; i++ ) {\n\n\t\t\t// No disabled and non-accepted\n\t\t\tif ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ],\n\t\t\t\t\t( t.currentItem || t.element ) ) ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Filter out elements in the current dragged item\n\t\t\tfor ( j = 0; j < list.length; j++ ) {\n\t\t\t\tif ( list[ j ] === m[ i ].element[ 0 ] ) {\n\t\t\t\t\tm[ i ].proportions().height = 0;\n\t\t\t\t\tcontinue droppablesLoop;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm[ i ].visible = m[ i ].element.css( \"display\" ) !== \"none\";\n\t\t\tif ( !m[ i ].visible ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Activate the droppable if used directly from draggables\n\t\t\tif ( type === \"mousedown\" ) {\n\t\t\t\tm[ i ]._activate.call( m[ i ], event );\n\t\t\t}\n\n\t\t\tm[ i ].offset = m[ i ].element.offset();\n\t\t\tm[ i ].proportions( {\n\t\t\t\twidth: m[ i ].element[ 0 ].offsetWidth,\n\t\t\t\theight: m[ i ].element[ 0 ].offsetHeight\n\t\t\t} );\n\n\t\t}\n\n\t},\n\tdrop: function( draggable, event ) {\n\n\t\tvar dropped = false;\n\n\t\t// Create a copy of the droppables in case the list changes during the drop (#9116)\n\t\t$.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {\n\n\t\t\tif ( !this.options ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( !this.options.disabled && this.visible &&\n\t\t\t\t\tintersect( draggable, this, this.options.tolerance, event ) ) {\n\t\t\t\tdropped = this._drop.call( this, event ) || dropped;\n\t\t\t}\n\n\t\t\tif ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ],\n\t\t\t\t\t( draggable.currentItem || draggable.element ) ) ) {\n\t\t\t\tthis.isout = true;\n\t\t\t\tthis.isover = false;\n\t\t\t\tthis._deactivate.call( this, event );\n\t\t\t}\n\n\t\t} );\n\t\treturn dropped;\n\n\t},\n\tdragStart: function( draggable, event ) {\n\n\t\t// Listen for scrolling so that if the dragging causes scrolling the position of the\n\t\t// droppables can be recalculated (see #5003)\n\t\tdraggable.element.parentsUntil( \"body\" ).on( \"scroll.droppable\", function() {\n\t\t\tif ( !draggable.options.refreshPositions ) {\n\t\t\t\t$.ui.ddmanager.prepareOffsets( draggable, event );\n\t\t\t}\n\t\t} );\n\t},\n\tdrag: function( draggable, event ) {\n\n\t\t// If you have a highly dynamic page, you might try this option. It renders positions\n\t\t// every time you move the mouse.\n\t\tif ( draggable.options.refreshPositions ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( draggable, event );\n\t\t}\n\n\t\t// Run through all droppables and check their positions based on specific tolerance options\n\t\t$.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {\n\n\t\t\tif ( this.options.disabled || this.greedyChild || !this.visible ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar parentInstance, scope, parent,\n\t\t\t\tintersects = intersect( draggable, this, this.options.tolerance, event ),\n\t\t\t\tc = !intersects && this.isover ?\n\t\t\t\t\t\"isout\" :\n\t\t\t\t\t( intersects && !this.isover ? \"isover\" : null );\n\t\t\tif ( !c ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( this.options.greedy ) {\n\n\t\t\t\t// find droppable parents with same scope\n\t\t\t\tscope = this.options.scope;\n\t\t\t\tparent = this.element.parents( \":data(ui-droppable)\" ).filter( function() {\n\t\t\t\t\treturn $( this ).droppable( \"instance\" ).options.scope === scope;\n\t\t\t\t} );\n\n\t\t\t\tif ( parent.length ) {\n\t\t\t\t\tparentInstance = $( parent[ 0 ] ).droppable( \"instance\" );\n\t\t\t\t\tparentInstance.greedyChild = ( c === \"isover\" );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We just moved into a greedy child\n\t\t\tif ( parentInstance && c === \"isover\" ) {\n\t\t\t\tparentInstance.isover = false;\n\t\t\t\tparentInstance.isout = true;\n\t\t\t\tparentInstance._out.call( parentInstance, event );\n\t\t\t}\n\n\t\t\tthis[ c ] = true;\n\t\t\tthis[ c === \"isout\" ? \"isover\" : \"isout\" ] = false;\n\t\t\tthis[ c === \"isover\" ? \"_over\" : \"_out\" ].call( this, event );\n\n\t\t\t// We just moved out of a greedy child\n\t\t\tif ( parentInstance && c === \"isout\" ) {\n\t\t\t\tparentInstance.isout = false;\n\t\t\t\tparentInstance.isover = true;\n\t\t\t\tparentInstance._over.call( parentInstance, event );\n\t\t\t}\n\t\t} );\n\n\t},\n\tdragStop: function( draggable, event ) {\n\t\tdraggable.element.parentsUntil( \"body\" ).off( \"scroll.droppable\" );\n\n\t\t// Call prepareOffsets one final time since IE does not fire return scroll events when\n\t\t// overflow was caused by drag (see #5003)\n\t\tif ( !draggable.options.refreshPositions ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( draggable, event );\n\t\t}\n\t}\n};\n\n// DEPRECATED\n// TODO: switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for activeClass and hoverClass options\n\t$.widget( \"ui.droppable\", $.ui.droppable, {\n\t\toptions: {\n\t\t\thoverClass: false,\n\t\t\tactiveClass: false\n\t\t},\n\t\t_addActiveClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.activeClass ) {\n\t\t\t\tthis.element.addClass( this.options.activeClass );\n\t\t\t}\n\t\t},\n\t\t_removeActiveClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.activeClass ) {\n\t\t\t\tthis.element.removeClass( this.options.activeClass );\n\t\t\t}\n\t\t},\n\t\t_addHoverClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.hoverClass ) {\n\t\t\t\tthis.element.addClass( this.options.hoverClass );\n\t\t\t}\n\t\t},\n\t\t_removeHoverClass: function() {\n\t\t\tthis._super();\n\t\t\tif ( this.options.hoverClass ) {\n\t\t\t\tthis.element.removeClass( this.options.hoverClass );\n\t\t\t}\n\t\t}\n\t} );\n}\n\nvar widgetsDroppable = $.ui.droppable;\n\n\n/*!\n * jQuery UI Resizable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Resizable\n//>>group: Interactions\n//>>description: Enables resize functionality for any element.\n//>>docs: http://api.jqueryui.com/resizable/\n//>>demos: http://jqueryui.com/resizable/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/resizable.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.resizable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"resize\",\n\toptions: {\n\t\talsoResize: false,\n\t\tanimate: false,\n\t\tanimateDuration: \"slow\",\n\t\tanimateEasing: \"swing\",\n\t\taspectRatio: false,\n\t\tautoHide: false,\n\t\tclasses: {\n\t\t\t\"ui-resizable-se\": \"ui-icon ui-icon-gripsmall-diagonal-se\"\n\t\t},\n\t\tcontainment: false,\n\t\tghost: false,\n\t\tgrid: false,\n\t\thandles: \"e,s,se\",\n\t\thelper: false,\n\t\tmaxHeight: null,\n\t\tmaxWidth: null,\n\t\tminHeight: 10,\n\t\tminWidth: 10,\n\n\t\t// See #7960\n\t\tzIndex: 90,\n\n\t\t// Callbacks\n\t\tresize: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\n\t_num: function( value ) {\n\t\treturn parseFloat( value ) || 0;\n\t},\n\n\t_isNumber: function( value ) {\n\t\treturn !isNaN( parseFloat( value ) );\n\t},\n\n\t_hasScroll: function( el, a ) {\n\n\t\tif ( $( el ).css( \"overflow\" ) === \"hidden\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar scroll = ( a && a === \"left\" ) ? \"scrollLeft\" : \"scrollTop\",\n\t\t\thas = false;\n\n\t\tif ( el[ scroll ] > 0 ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// TODO: determine which cases actually cause this to happen\n\t\t// if the element doesn't have the scroll set, see if it's possible to\n\t\t// set the scroll\n\t\tel[ scroll ] = 1;\n\t\thas = ( el[ scroll ] > 0 );\n\t\tel[ scroll ] = 0;\n\t\treturn has;\n\t},\n\n\t_create: function() {\n\n\t\tvar margins,\n\t\t\to = this.options,\n\t\t\tthat = this;\n\t\tthis._addClass( \"ui-resizable\" );\n\n\t\t$.extend( this, {\n\t\t\t_aspectRatio: !!( o.aspectRatio ),\n\t\t\taspectRatio: o.aspectRatio,\n\t\t\toriginalElement: this.element,\n\t\t\t_proportionallyResizeElements: [],\n\t\t\t_helper: o.helper || o.ghost || o.animate ? o.helper || \"ui-resizable-helper\" : null\n\t\t} );\n\n\t\t// Wrap the element if it cannot hold child nodes\n\t\tif ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) {\n\n\t\t\tthis.element.wrap(\n\t\t\t\t$( \"<div class='ui-wrapper' style='overflow: hidden;'></div>\" ).css( {\n\t\t\t\t\tposition: this.element.css( \"position\" ),\n\t\t\t\t\twidth: this.element.outerWidth(),\n\t\t\t\t\theight: this.element.outerHeight(),\n\t\t\t\t\ttop: this.element.css( \"top\" ),\n\t\t\t\t\tleft: this.element.css( \"left\" )\n\t\t\t\t} )\n\t\t\t);\n\n\t\t\tthis.element = this.element.parent().data(\n\t\t\t\t\"ui-resizable\", this.element.resizable( \"instance\" )\n\t\t\t);\n\n\t\t\tthis.elementIsWrapper = true;\n\n\t\t\tmargins = {\n\t\t\t\tmarginTop: this.originalElement.css( \"marginTop\" ),\n\t\t\t\tmarginRight: this.originalElement.css( \"marginRight\" ),\n\t\t\t\tmarginBottom: this.originalElement.css( \"marginBottom\" ),\n\t\t\t\tmarginLeft: this.originalElement.css( \"marginLeft\" )\n\t\t\t};\n\n\t\t\tthis.element.css( margins );\n\t\t\tthis.originalElement.css( \"margin\", 0 );\n\n\t\t\t// support: Safari\n\t\t\t// Prevent Safari textarea resize\n\t\t\tthis.originalResizeStyle = this.originalElement.css( \"resize\" );\n\t\t\tthis.originalElement.css( \"resize\", \"none\" );\n\n\t\t\tthis._proportionallyResizeElements.push( this.originalElement.css( {\n\t\t\t\tposition: \"static\",\n\t\t\t\tzoom: 1,\n\t\t\t\tdisplay: \"block\"\n\t\t\t} ) );\n\n\t\t\t// Support: IE9\n\t\t\t// avoid IE jump (hard set the margin)\n\t\t\tthis.originalElement.css( margins );\n\n\t\t\tthis._proportionallyResize();\n\t\t}\n\n\t\tthis._setupHandles();\n\n\t\tif ( o.autoHide ) {\n\t\t\t$( this.element )\n\t\t\t\t.on( \"mouseenter\", function() {\n\t\t\t\t\tif ( o.disabled ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthat._removeClass( \"ui-resizable-autohide\" );\n\t\t\t\t\tthat._handles.show();\n\t\t\t\t} )\n\t\t\t\t.on( \"mouseleave\", function() {\n\t\t\t\t\tif ( o.disabled ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif ( !that.resizing ) {\n\t\t\t\t\t\tthat._addClass( \"ui-resizable-autohide\" );\n\t\t\t\t\t\tthat._handles.hide();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t}\n\n\t\tthis._mouseInit();\n\t},\n\n\t_destroy: function() {\n\n\t\tthis._mouseDestroy();\n\n\t\tvar wrapper,\n\t\t\t_destroy = function( exp ) {\n\t\t\t\t$( exp )\n\t\t\t\t\t.removeData( \"resizable\" )\n\t\t\t\t\t.removeData( \"ui-resizable\" )\n\t\t\t\t\t.off( \".resizable\" )\n\t\t\t\t\t.find( \".ui-resizable-handle\" )\n\t\t\t\t\t\t.remove();\n\t\t\t};\n\n\t\t// TODO: Unwrap at same DOM position\n\t\tif ( this.elementIsWrapper ) {\n\t\t\t_destroy( this.element );\n\t\t\twrapper = this.element;\n\t\t\tthis.originalElement.css( {\n\t\t\t\tposition: wrapper.css( \"position\" ),\n\t\t\t\twidth: wrapper.outerWidth(),\n\t\t\t\theight: wrapper.outerHeight(),\n\t\t\t\ttop: wrapper.css( \"top\" ),\n\t\t\t\tleft: wrapper.css( \"left\" )\n\t\t\t} ).insertAfter( wrapper );\n\t\t\twrapper.remove();\n\t\t}\n\n\t\tthis.originalElement.css( \"resize\", this.originalResizeStyle );\n\t\t_destroy( this.originalElement );\n\n\t\treturn this;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\n\t\tswitch ( key ) {\n\t\tcase \"handles\":\n\t\t\tthis._removeHandles();\n\t\t\tthis._setupHandles();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t},\n\n\t_setupHandles: function() {\n\t\tvar o = this.options, handle, i, n, hname, axis, that = this;\n\t\tthis.handles = o.handles ||\n\t\t\t( !$( \".ui-resizable-handle\", this.element ).length ?\n\t\t\t\t\"e,s,se\" : {\n\t\t\t\t\tn: \".ui-resizable-n\",\n\t\t\t\t\te: \".ui-resizable-e\",\n\t\t\t\t\ts: \".ui-resizable-s\",\n\t\t\t\t\tw: \".ui-resizable-w\",\n\t\t\t\t\tse: \".ui-resizable-se\",\n\t\t\t\t\tsw: \".ui-resizable-sw\",\n\t\t\t\t\tne: \".ui-resizable-ne\",\n\t\t\t\t\tnw: \".ui-resizable-nw\"\n\t\t\t\t} );\n\n\t\tthis._handles = $();\n\t\tif ( this.handles.constructor === String ) {\n\n\t\t\tif ( this.handles === \"all\" ) {\n\t\t\t\tthis.handles = \"n,e,s,w,se,sw,ne,nw\";\n\t\t\t}\n\n\t\t\tn = this.handles.split( \",\" );\n\t\t\tthis.handles = {};\n\n\t\t\tfor ( i = 0; i < n.length; i++ ) {\n\n\t\t\t\thandle = $.trim( n[ i ] );\n\t\t\t\thname = \"ui-resizable-\" + handle;\n\t\t\t\taxis = $( \"<div>\" );\n\t\t\t\tthis._addClass( axis, \"ui-resizable-handle \" + hname );\n\n\t\t\t\taxis.css( { zIndex: o.zIndex } );\n\n\t\t\t\tthis.handles[ handle ] = \".ui-resizable-\" + handle;\n\t\t\t\tthis.element.append( axis );\n\t\t\t}\n\n\t\t}\n\n\t\tthis._renderAxis = function( target ) {\n\n\t\t\tvar i, axis, padPos, padWrapper;\n\n\t\t\ttarget = target || this.element;\n\n\t\t\tfor ( i in this.handles ) {\n\n\t\t\t\tif ( this.handles[ i ].constructor === String ) {\n\t\t\t\t\tthis.handles[ i ] = this.element.children( this.handles[ i ] ).first().show();\n\t\t\t\t} else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) {\n\t\t\t\t\tthis.handles[ i ] = $( this.handles[ i ] );\n\t\t\t\t\tthis._on( this.handles[ i ], { \"mousedown\": that._mouseDown } );\n\t\t\t\t}\n\n\t\t\t\tif ( this.elementIsWrapper &&\n\t\t\t\t\t\tthis.originalElement[ 0 ]\n\t\t\t\t\t\t\t.nodeName\n\t\t\t\t\t\t\t.match( /^(textarea|input|select|button)$/i ) ) {\n\t\t\t\t\taxis = $( this.handles[ i ], this.element );\n\n\t\t\t\t\tpadWrapper = /sw|ne|nw|se|n|s/.test( i ) ?\n\t\t\t\t\t\taxis.outerHeight() :\n\t\t\t\t\t\taxis.outerWidth();\n\n\t\t\t\t\tpadPos = [ \"padding\",\n\t\t\t\t\t\t/ne|nw|n/.test( i ) ? \"Top\" :\n\t\t\t\t\t\t/se|sw|s/.test( i ) ? \"Bottom\" :\n\t\t\t\t\t\t/^e$/.test( i ) ? \"Right\" : \"Left\" ].join( \"\" );\n\n\t\t\t\t\ttarget.css( padPos, padWrapper );\n\n\t\t\t\t\tthis._proportionallyResize();\n\t\t\t\t}\n\n\t\t\t\tthis._handles = this._handles.add( this.handles[ i ] );\n\t\t\t}\n\t\t};\n\n\t\t// TODO: make renderAxis a prototype function\n\t\tthis._renderAxis( this.element );\n\n\t\tthis._handles = this._handles.add( this.element.find( \".ui-resizable-handle\" ) );\n\t\tthis._handles.disableSelection();\n\n\t\tthis._handles.on( \"mouseover\", function() {\n\t\t\tif ( !that.resizing ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\taxis = this.className.match( /ui-resizable-(se|sw|ne|nw|n|e|s|w)/i );\n\t\t\t\t}\n\t\t\t\tthat.axis = axis && axis[ 1 ] ? axis[ 1 ] : \"se\";\n\t\t\t}\n\t\t} );\n\n\t\tif ( o.autoHide ) {\n\t\t\tthis._handles.hide();\n\t\t\tthis._addClass( \"ui-resizable-autohide\" );\n\t\t}\n\t},\n\n\t_removeHandles: function() {\n\t\tthis._handles.remove();\n\t},\n\n\t_mouseCapture: function( event ) {\n\t\tvar i, handle,\n\t\t\tcapture = false;\n\n\t\tfor ( i in this.handles ) {\n\t\t\thandle = $( this.handles[ i ] )[ 0 ];\n\t\t\tif ( handle === event.target || $.contains( handle, event.target ) ) {\n\t\t\t\tcapture = true;\n\t\t\t}\n\t\t}\n\n\t\treturn !this.options.disabled && capture;\n\t},\n\n\t_mouseStart: function( event ) {\n\n\t\tvar curleft, curtop, cursor,\n\t\t\to = this.options,\n\t\t\tel = this.element;\n\n\t\tthis.resizing = true;\n\n\t\tthis._renderProxy();\n\n\t\tcurleft = this._num( this.helper.css( \"left\" ) );\n\t\tcurtop = this._num( this.helper.css( \"top\" ) );\n\n\t\tif ( o.containment ) {\n\t\t\tcurleft += $( o.containment ).scrollLeft() || 0;\n\t\t\tcurtop += $( o.containment ).scrollTop() || 0;\n\t\t}\n\n\t\tthis.offset = this.helper.offset();\n\t\tthis.position = { left: curleft, top: curtop };\n\n\t\tthis.size = this._helper ? {\n\t\t\t\twidth: this.helper.width(),\n\t\t\t\theight: this.helper.height()\n\t\t\t} : {\n\t\t\t\twidth: el.width(),\n\t\t\t\theight: el.height()\n\t\t\t};\n\n\t\tthis.originalSize = this._helper ? {\n\t\t\t\twidth: el.outerWidth(),\n\t\t\t\theight: el.outerHeight()\n\t\t\t} : {\n\t\t\t\twidth: el.width(),\n\t\t\t\theight: el.height()\n\t\t\t};\n\n\t\tthis.sizeDiff = {\n\t\t\twidth: el.outerWidth() - el.width(),\n\t\t\theight: el.outerHeight() - el.height()\n\t\t};\n\n\t\tthis.originalPosition = { left: curleft, top: curtop };\n\t\tthis.originalMousePosition = { left: event.pageX, top: event.pageY };\n\n\t\tthis.aspectRatio = ( typeof o.aspectRatio === \"number\" ) ?\n\t\t\to.aspectRatio :\n\t\t\t( ( this.originalSize.width / this.originalSize.height ) || 1 );\n\n\t\tcursor = $( \".ui-resizable-\" + this.axis ).css( \"cursor\" );\n\t\t$( \"body\" ).css( \"cursor\", cursor === \"auto\" ? this.axis + \"-resize\" : cursor );\n\n\t\tthis._addClass( \"ui-resizable-resizing\" );\n\t\tthis._propagate( \"start\", event );\n\t\treturn true;\n\t},\n\n\t_mouseDrag: function( event ) {\n\n\t\tvar data, props,\n\t\t\tsmp = this.originalMousePosition,\n\t\t\ta = this.axis,\n\t\t\tdx = ( event.pageX - smp.left ) || 0,\n\t\t\tdy = ( event.pageY - smp.top ) || 0,\n\t\t\ttrigger = this._change[ a ];\n\n\t\tthis._updatePrevProperties();\n\n\t\tif ( !trigger ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tdata = trigger.apply( this, [ event, dx, dy ] );\n\n\t\tthis._updateVirtualBoundaries( event.shiftKey );\n\t\tif ( this._aspectRatio || event.shiftKey ) {\n\t\t\tdata = this._updateRatio( data, event );\n\t\t}\n\n\t\tdata = this._respectSize( data, event );\n\n\t\tthis._updateCache( data );\n\n\t\tthis._propagate( \"resize\", event );\n\n\t\tprops = this._applyChanges();\n\n\t\tif ( !this._helper && this._proportionallyResizeElements.length ) {\n\t\t\tthis._proportionallyResize();\n\t\t}\n\n\t\tif ( !$.isEmptyObject( props ) ) {\n\t\t\tthis._updatePrevProperties();\n\t\t\tthis._trigger( \"resize\", event, this.ui() );\n\t\t\tthis._applyChanges();\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\n\t\tthis.resizing = false;\n\t\tvar pr, ista, soffseth, soffsetw, s, left, top,\n\t\t\to = this.options, that = this;\n\n\t\tif ( this._helper ) {\n\n\t\t\tpr = this._proportionallyResizeElements;\n\t\t\tista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName );\n\t\t\tsoffseth = ista && this._hasScroll( pr[ 0 ], \"left\" ) ? 0 : that.sizeDiff.height;\n\t\t\tsoffsetw = ista ? 0 : that.sizeDiff.width;\n\n\t\t\ts = {\n\t\t\t\twidth: ( that.helper.width()  - soffsetw ),\n\t\t\t\theight: ( that.helper.height() - soffseth )\n\t\t\t};\n\t\t\tleft = ( parseFloat( that.element.css( \"left\" ) ) +\n\t\t\t\t( that.position.left - that.originalPosition.left ) ) || null;\n\t\t\ttop = ( parseFloat( that.element.css( \"top\" ) ) +\n\t\t\t\t( that.position.top - that.originalPosition.top ) ) || null;\n\n\t\t\tif ( !o.animate ) {\n\t\t\t\tthis.element.css( $.extend( s, { top: top, left: left } ) );\n\t\t\t}\n\n\t\t\tthat.helper.height( that.size.height );\n\t\t\tthat.helper.width( that.size.width );\n\n\t\t\tif ( this._helper && !o.animate ) {\n\t\t\t\tthis._proportionallyResize();\n\t\t\t}\n\t\t}\n\n\t\t$( \"body\" ).css( \"cursor\", \"auto\" );\n\n\t\tthis._removeClass( \"ui-resizable-resizing\" );\n\n\t\tthis._propagate( \"stop\", event );\n\n\t\tif ( this._helper ) {\n\t\t\tthis.helper.remove();\n\t\t}\n\n\t\treturn false;\n\n\t},\n\n\t_updatePrevProperties: function() {\n\t\tthis.prevPosition = {\n\t\t\ttop: this.position.top,\n\t\t\tleft: this.position.left\n\t\t};\n\t\tthis.prevSize = {\n\t\t\twidth: this.size.width,\n\t\t\theight: this.size.height\n\t\t};\n\t},\n\n\t_applyChanges: function() {\n\t\tvar props = {};\n\n\t\tif ( this.position.top !== this.prevPosition.top ) {\n\t\t\tprops.top = this.position.top + \"px\";\n\t\t}\n\t\tif ( this.position.left !== this.prevPosition.left ) {\n\t\t\tprops.left = this.position.left + \"px\";\n\t\t}\n\t\tif ( this.size.width !== this.prevSize.width ) {\n\t\t\tprops.width = this.size.width + \"px\";\n\t\t}\n\t\tif ( this.size.height !== this.prevSize.height ) {\n\t\t\tprops.height = this.size.height + \"px\";\n\t\t}\n\n\t\tthis.helper.css( props );\n\n\t\treturn props;\n\t},\n\n\t_updateVirtualBoundaries: function( forceAspectRatio ) {\n\t\tvar pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,\n\t\t\to = this.options;\n\n\t\tb = {\n\t\t\tminWidth: this._isNumber( o.minWidth ) ? o.minWidth : 0,\n\t\t\tmaxWidth: this._isNumber( o.maxWidth ) ? o.maxWidth : Infinity,\n\t\t\tminHeight: this._isNumber( o.minHeight ) ? o.minHeight : 0,\n\t\t\tmaxHeight: this._isNumber( o.maxHeight ) ? o.maxHeight : Infinity\n\t\t};\n\n\t\tif ( this._aspectRatio || forceAspectRatio ) {\n\t\t\tpMinWidth = b.minHeight * this.aspectRatio;\n\t\t\tpMinHeight = b.minWidth / this.aspectRatio;\n\t\t\tpMaxWidth = b.maxHeight * this.aspectRatio;\n\t\t\tpMaxHeight = b.maxWidth / this.aspectRatio;\n\n\t\t\tif ( pMinWidth > b.minWidth ) {\n\t\t\t\tb.minWidth = pMinWidth;\n\t\t\t}\n\t\t\tif ( pMinHeight > b.minHeight ) {\n\t\t\t\tb.minHeight = pMinHeight;\n\t\t\t}\n\t\t\tif ( pMaxWidth < b.maxWidth ) {\n\t\t\t\tb.maxWidth = pMaxWidth;\n\t\t\t}\n\t\t\tif ( pMaxHeight < b.maxHeight ) {\n\t\t\t\tb.maxHeight = pMaxHeight;\n\t\t\t}\n\t\t}\n\t\tthis._vBoundaries = b;\n\t},\n\n\t_updateCache: function( data ) {\n\t\tthis.offset = this.helper.offset();\n\t\tif ( this._isNumber( data.left ) ) {\n\t\t\tthis.position.left = data.left;\n\t\t}\n\t\tif ( this._isNumber( data.top ) ) {\n\t\t\tthis.position.top = data.top;\n\t\t}\n\t\tif ( this._isNumber( data.height ) ) {\n\t\t\tthis.size.height = data.height;\n\t\t}\n\t\tif ( this._isNumber( data.width ) ) {\n\t\t\tthis.size.width = data.width;\n\t\t}\n\t},\n\n\t_updateRatio: function( data ) {\n\n\t\tvar cpos = this.position,\n\t\t\tcsize = this.size,\n\t\t\ta = this.axis;\n\n\t\tif ( this._isNumber( data.height ) ) {\n\t\t\tdata.width = ( data.height * this.aspectRatio );\n\t\t} else if ( this._isNumber( data.width ) ) {\n\t\t\tdata.height = ( data.width / this.aspectRatio );\n\t\t}\n\n\t\tif ( a === \"sw\" ) {\n\t\t\tdata.left = cpos.left + ( csize.width - data.width );\n\t\t\tdata.top = null;\n\t\t}\n\t\tif ( a === \"nw\" ) {\n\t\t\tdata.top = cpos.top + ( csize.height - data.height );\n\t\t\tdata.left = cpos.left + ( csize.width - data.width );\n\t\t}\n\n\t\treturn data;\n\t},\n\n\t_respectSize: function( data ) {\n\n\t\tvar o = this._vBoundaries,\n\t\t\ta = this.axis,\n\t\t\tismaxw = this._isNumber( data.width ) && o.maxWidth && ( o.maxWidth < data.width ),\n\t\t\tismaxh = this._isNumber( data.height ) && o.maxHeight && ( o.maxHeight < data.height ),\n\t\t\tisminw = this._isNumber( data.width ) && o.minWidth && ( o.minWidth > data.width ),\n\t\t\tisminh = this._isNumber( data.height ) && o.minHeight && ( o.minHeight > data.height ),\n\t\t\tdw = this.originalPosition.left + this.originalSize.width,\n\t\t\tdh = this.originalPosition.top + this.originalSize.height,\n\t\t\tcw = /sw|nw|w/.test( a ), ch = /nw|ne|n/.test( a );\n\t\tif ( isminw ) {\n\t\t\tdata.width = o.minWidth;\n\t\t}\n\t\tif ( isminh ) {\n\t\t\tdata.height = o.minHeight;\n\t\t}\n\t\tif ( ismaxw ) {\n\t\t\tdata.width = o.maxWidth;\n\t\t}\n\t\tif ( ismaxh ) {\n\t\t\tdata.height = o.maxHeight;\n\t\t}\n\n\t\tif ( isminw && cw ) {\n\t\t\tdata.left = dw - o.minWidth;\n\t\t}\n\t\tif ( ismaxw && cw ) {\n\t\t\tdata.left = dw - o.maxWidth;\n\t\t}\n\t\tif ( isminh && ch ) {\n\t\t\tdata.top = dh - o.minHeight;\n\t\t}\n\t\tif ( ismaxh && ch ) {\n\t\t\tdata.top = dh - o.maxHeight;\n\t\t}\n\n\t\t// Fixing jump error on top/left - bug #2330\n\t\tif ( !data.width && !data.height && !data.left && data.top ) {\n\t\t\tdata.top = null;\n\t\t} else if ( !data.width && !data.height && !data.top && data.left ) {\n\t\t\tdata.left = null;\n\t\t}\n\n\t\treturn data;\n\t},\n\n\t_getPaddingPlusBorderDimensions: function( element ) {\n\t\tvar i = 0,\n\t\t\twidths = [],\n\t\t\tborders = [\n\t\t\t\telement.css( \"borderTopWidth\" ),\n\t\t\t\telement.css( \"borderRightWidth\" ),\n\t\t\t\telement.css( \"borderBottomWidth\" ),\n\t\t\t\telement.css( \"borderLeftWidth\" )\n\t\t\t],\n\t\t\tpaddings = [\n\t\t\t\telement.css( \"paddingTop\" ),\n\t\t\t\telement.css( \"paddingRight\" ),\n\t\t\t\telement.css( \"paddingBottom\" ),\n\t\t\t\telement.css( \"paddingLeft\" )\n\t\t\t];\n\n\t\tfor ( ; i < 4; i++ ) {\n\t\t\twidths[ i ] = ( parseFloat( borders[ i ] ) || 0 );\n\t\t\twidths[ i ] += ( parseFloat( paddings[ i ] ) || 0 );\n\t\t}\n\n\t\treturn {\n\t\t\theight: widths[ 0 ] + widths[ 2 ],\n\t\t\twidth: widths[ 1 ] + widths[ 3 ]\n\t\t};\n\t},\n\n\t_proportionallyResize: function() {\n\n\t\tif ( !this._proportionallyResizeElements.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar prel,\n\t\t\ti = 0,\n\t\t\telement = this.helper || this.element;\n\n\t\tfor ( ; i < this._proportionallyResizeElements.length; i++ ) {\n\n\t\t\tprel = this._proportionallyResizeElements[ i ];\n\n\t\t\t// TODO: Seems like a bug to cache this.outerDimensions\n\t\t\t// considering that we are in a loop.\n\t\t\tif ( !this.outerDimensions ) {\n\t\t\t\tthis.outerDimensions = this._getPaddingPlusBorderDimensions( prel );\n\t\t\t}\n\n\t\t\tprel.css( {\n\t\t\t\theight: ( element.height() - this.outerDimensions.height ) || 0,\n\t\t\t\twidth: ( element.width() - this.outerDimensions.width ) || 0\n\t\t\t} );\n\n\t\t}\n\n\t},\n\n\t_renderProxy: function() {\n\n\t\tvar el = this.element, o = this.options;\n\t\tthis.elementOffset = el.offset();\n\n\t\tif ( this._helper ) {\n\n\t\t\tthis.helper = this.helper || $( \"<div style='overflow:hidden;'></div>\" );\n\n\t\t\tthis._addClass( this.helper, this._helper );\n\t\t\tthis.helper.css( {\n\t\t\t\twidth: this.element.outerWidth(),\n\t\t\t\theight: this.element.outerHeight(),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tleft: this.elementOffset.left + \"px\",\n\t\t\t\ttop: this.elementOffset.top + \"px\",\n\t\t\t\tzIndex: ++o.zIndex //TODO: Don't modify option\n\t\t\t} );\n\n\t\t\tthis.helper\n\t\t\t\t.appendTo( \"body\" )\n\t\t\t\t.disableSelection();\n\n\t\t} else {\n\t\t\tthis.helper = this.element;\n\t\t}\n\n\t},\n\n\t_change: {\n\t\te: function( event, dx ) {\n\t\t\treturn { width: this.originalSize.width + dx };\n\t\t},\n\t\tw: function( event, dx ) {\n\t\t\tvar cs = this.originalSize, sp = this.originalPosition;\n\t\t\treturn { left: sp.left + dx, width: cs.width - dx };\n\t\t},\n\t\tn: function( event, dx, dy ) {\n\t\t\tvar cs = this.originalSize, sp = this.originalPosition;\n\t\t\treturn { top: sp.top + dy, height: cs.height - dy };\n\t\t},\n\t\ts: function( event, dx, dy ) {\n\t\t\treturn { height: this.originalSize.height + dy };\n\t\t},\n\t\tse: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.s.apply( this, arguments ),\n\t\t\t\tthis._change.e.apply( this, [ event, dx, dy ] ) );\n\t\t},\n\t\tsw: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.s.apply( this, arguments ),\n\t\t\t\tthis._change.w.apply( this, [ event, dx, dy ] ) );\n\t\t},\n\t\tne: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.n.apply( this, arguments ),\n\t\t\t\tthis._change.e.apply( this, [ event, dx, dy ] ) );\n\t\t},\n\t\tnw: function( event, dx, dy ) {\n\t\t\treturn $.extend( this._change.n.apply( this, arguments ),\n\t\t\t\tthis._change.w.apply( this, [ event, dx, dy ] ) );\n\t\t}\n\t},\n\n\t_propagate: function( n, event ) {\n\t\t$.ui.plugin.call( this, n, [ event, this.ui() ] );\n\t\t( n !== \"resize\" && this._trigger( n, event, this.ui() ) );\n\t},\n\n\tplugins: {},\n\n\tui: function() {\n\t\treturn {\n\t\t\toriginalElement: this.originalElement,\n\t\t\telement: this.element,\n\t\t\thelper: this.helper,\n\t\t\tposition: this.position,\n\t\t\tsize: this.size,\n\t\t\toriginalSize: this.originalSize,\n\t\t\toriginalPosition: this.originalPosition\n\t\t};\n\t}\n\n} );\n\n/*\n * Resizable Extensions\n */\n\n$.ui.plugin.add( \"resizable\", \"animate\", {\n\n\tstop: function( event ) {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tpr = that._proportionallyResizeElements,\n\t\t\tista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ),\n\t\t\tsoffseth = ista && that._hasScroll( pr[ 0 ], \"left\" ) ? 0 : that.sizeDiff.height,\n\t\t\tsoffsetw = ista ? 0 : that.sizeDiff.width,\n\t\t\tstyle = {\n\t\t\t\twidth: ( that.size.width - soffsetw ),\n\t\t\t\theight: ( that.size.height - soffseth )\n\t\t\t},\n\t\t\tleft = ( parseFloat( that.element.css( \"left\" ) ) +\n\t\t\t\t( that.position.left - that.originalPosition.left ) ) || null,\n\t\t\ttop = ( parseFloat( that.element.css( \"top\" ) ) +\n\t\t\t\t( that.position.top - that.originalPosition.top ) ) || null;\n\n\t\tthat.element.animate(\n\t\t\t$.extend( style, top && left ? { top: top, left: left } : {} ), {\n\t\t\t\tduration: o.animateDuration,\n\t\t\t\teasing: o.animateEasing,\n\t\t\t\tstep: function() {\n\n\t\t\t\t\tvar data = {\n\t\t\t\t\t\twidth: parseFloat( that.element.css( \"width\" ) ),\n\t\t\t\t\t\theight: parseFloat( that.element.css( \"height\" ) ),\n\t\t\t\t\t\ttop: parseFloat( that.element.css( \"top\" ) ),\n\t\t\t\t\t\tleft: parseFloat( that.element.css( \"left\" ) )\n\t\t\t\t\t};\n\n\t\t\t\t\tif ( pr && pr.length ) {\n\t\t\t\t\t\t$( pr[ 0 ] ).css( { width: data.width, height: data.height } );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Propagating resize, and updating values for each animation step\n\t\t\t\t\tthat._updateCache( data );\n\t\t\t\t\tthat._propagate( \"resize\", event );\n\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n} );\n\n$.ui.plugin.add( \"resizable\", \"containment\", {\n\n\tstart: function() {\n\t\tvar element, p, co, ch, cw, width, height,\n\t\t\tthat = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tel = that.element,\n\t\t\toc = o.containment,\n\t\t\tce = ( oc instanceof $ ) ?\n\t\t\t\toc.get( 0 ) :\n\t\t\t\t( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc;\n\n\t\tif ( !ce ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthat.containerElement = $( ce );\n\n\t\tif ( /document/.test( oc ) || oc === document ) {\n\t\t\tthat.containerOffset = {\n\t\t\t\tleft: 0,\n\t\t\t\ttop: 0\n\t\t\t};\n\t\t\tthat.containerPosition = {\n\t\t\t\tleft: 0,\n\t\t\t\ttop: 0\n\t\t\t};\n\n\t\t\tthat.parentData = {\n\t\t\t\telement: $( document ),\n\t\t\t\tleft: 0,\n\t\t\t\ttop: 0,\n\t\t\t\twidth: $( document ).width(),\n\t\t\t\theight: $( document ).height() || document.body.parentNode.scrollHeight\n\t\t\t};\n\t\t} else {\n\t\t\telement = $( ce );\n\t\t\tp = [];\n\t\t\t$( [ \"Top\", \"Right\", \"Left\", \"Bottom\" ] ).each( function( i, name ) {\n\t\t\t\tp[ i ] = that._num( element.css( \"padding\" + name ) );\n\t\t\t} );\n\n\t\t\tthat.containerOffset = element.offset();\n\t\t\tthat.containerPosition = element.position();\n\t\t\tthat.containerSize = {\n\t\t\t\theight: ( element.innerHeight() - p[ 3 ] ),\n\t\t\t\twidth: ( element.innerWidth() - p[ 1 ] )\n\t\t\t};\n\n\t\t\tco = that.containerOffset;\n\t\t\tch = that.containerSize.height;\n\t\t\tcw = that.containerSize.width;\n\t\t\twidth = ( that._hasScroll ( ce, \"left\" ) ? ce.scrollWidth : cw );\n\t\t\theight = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ;\n\n\t\t\tthat.parentData = {\n\t\t\t\telement: ce,\n\t\t\t\tleft: co.left,\n\t\t\t\ttop: co.top,\n\t\t\t\twidth: width,\n\t\t\t\theight: height\n\t\t\t};\n\t\t}\n\t},\n\n\tresize: function( event ) {\n\t\tvar woset, hoset, isParent, isOffsetRelative,\n\t\t\tthat = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tco = that.containerOffset,\n\t\t\tcp = that.position,\n\t\t\tpRatio = that._aspectRatio || event.shiftKey,\n\t\t\tcop = {\n\t\t\t\ttop: 0,\n\t\t\t\tleft: 0\n\t\t\t},\n\t\t\tce = that.containerElement,\n\t\t\tcontinueResize = true;\n\n\t\tif ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( \"position\" ) ) ) {\n\t\t\tcop = co;\n\t\t}\n\n\t\tif ( cp.left < ( that._helper ? co.left : 0 ) ) {\n\t\t\tthat.size.width = that.size.width +\n\t\t\t\t( that._helper ?\n\t\t\t\t\t( that.position.left - co.left ) :\n\t\t\t\t\t( that.position.left - cop.left ) );\n\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.height = that.size.width / that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t\tthat.position.left = o.helper ? co.left : 0;\n\t\t}\n\n\t\tif ( cp.top < ( that._helper ? co.top : 0 ) ) {\n\t\t\tthat.size.height = that.size.height +\n\t\t\t\t( that._helper ?\n\t\t\t\t\t( that.position.top - co.top ) :\n\t\t\t\t\tthat.position.top );\n\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.width = that.size.height * that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t\tthat.position.top = that._helper ? co.top : 0;\n\t\t}\n\n\t\tisParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );\n\t\tisOffsetRelative = /relative|absolute/.test( that.containerElement.css( \"position\" ) );\n\n\t\tif ( isParent && isOffsetRelative ) {\n\t\t\tthat.offset.left = that.parentData.left + that.position.left;\n\t\t\tthat.offset.top = that.parentData.top + that.position.top;\n\t\t} else {\n\t\t\tthat.offset.left = that.element.offset().left;\n\t\t\tthat.offset.top = that.element.offset().top;\n\t\t}\n\n\t\twoset = Math.abs( that.sizeDiff.width +\n\t\t\t( that._helper ?\n\t\t\t\tthat.offset.left - cop.left :\n\t\t\t\t( that.offset.left - co.left ) ) );\n\n\t\thoset = Math.abs( that.sizeDiff.height +\n\t\t\t( that._helper ?\n\t\t\t\tthat.offset.top - cop.top :\n\t\t\t\t( that.offset.top - co.top ) ) );\n\n\t\tif ( woset + that.size.width >= that.parentData.width ) {\n\t\t\tthat.size.width = that.parentData.width - woset;\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.height = that.size.width / that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t}\n\n\t\tif ( hoset + that.size.height >= that.parentData.height ) {\n\t\t\tthat.size.height = that.parentData.height - hoset;\n\t\t\tif ( pRatio ) {\n\t\t\t\tthat.size.width = that.size.height * that.aspectRatio;\n\t\t\t\tcontinueResize = false;\n\t\t\t}\n\t\t}\n\n\t\tif ( !continueResize ) {\n\t\t\tthat.position.left = that.prevPosition.left;\n\t\t\tthat.position.top = that.prevPosition.top;\n\t\t\tthat.size.width = that.prevSize.width;\n\t\t\tthat.size.height = that.prevSize.height;\n\t\t}\n\t},\n\n\tstop: function() {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tco = that.containerOffset,\n\t\t\tcop = that.containerPosition,\n\t\t\tce = that.containerElement,\n\t\t\thelper = $( that.helper ),\n\t\t\tho = helper.offset(),\n\t\t\tw = helper.outerWidth() - that.sizeDiff.width,\n\t\t\th = helper.outerHeight() - that.sizeDiff.height;\n\n\t\tif ( that._helper && !o.animate && ( /relative/ ).test( ce.css( \"position\" ) ) ) {\n\t\t\t$( this ).css( {\n\t\t\t\tleft: ho.left - cop.left - co.left,\n\t\t\t\twidth: w,\n\t\t\t\theight: h\n\t\t\t} );\n\t\t}\n\n\t\tif ( that._helper && !o.animate && ( /static/ ).test( ce.css( \"position\" ) ) ) {\n\t\t\t$( this ).css( {\n\t\t\t\tleft: ho.left - cop.left - co.left,\n\t\t\t\twidth: w,\n\t\t\t\theight: h\n\t\t\t} );\n\t\t}\n\t}\n} );\n\n$.ui.plugin.add( \"resizable\", \"alsoResize\", {\n\n\tstart: function() {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options;\n\n\t\t$( o.alsoResize ).each( function() {\n\t\t\tvar el = $( this );\n\t\t\tel.data( \"ui-resizable-alsoresize\", {\n\t\t\t\twidth: parseFloat( el.width() ), height: parseFloat( el.height() ),\n\t\t\t\tleft: parseFloat( el.css( \"left\" ) ), top: parseFloat( el.css( \"top\" ) )\n\t\t\t} );\n\t\t} );\n\t},\n\n\tresize: function( event, ui ) {\n\t\tvar that = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tos = that.originalSize,\n\t\t\top = that.originalPosition,\n\t\t\tdelta = {\n\t\t\t\theight: ( that.size.height - os.height ) || 0,\n\t\t\t\twidth: ( that.size.width - os.width ) || 0,\n\t\t\t\ttop: ( that.position.top - op.top ) || 0,\n\t\t\t\tleft: ( that.position.left - op.left ) || 0\n\t\t\t};\n\n\t\t\t$( o.alsoResize ).each( function() {\n\t\t\t\tvar el = $( this ), start = $( this ).data( \"ui-resizable-alsoresize\" ), style = {},\n\t\t\t\t\tcss = el.parents( ui.originalElement[ 0 ] ).length ?\n\t\t\t\t\t\t\t[ \"width\", \"height\" ] :\n\t\t\t\t\t\t\t[ \"width\", \"height\", \"top\", \"left\" ];\n\n\t\t\t\t$.each( css, function( i, prop ) {\n\t\t\t\t\tvar sum = ( start[ prop ] || 0 ) + ( delta[ prop ] || 0 );\n\t\t\t\t\tif ( sum && sum >= 0 ) {\n\t\t\t\t\t\tstyle[ prop ] = sum || null;\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\t\tel.css( style );\n\t\t\t} );\n\t},\n\n\tstop: function() {\n\t\t$( this ).removeData( \"ui-resizable-alsoresize\" );\n\t}\n} );\n\n$.ui.plugin.add( \"resizable\", \"ghost\", {\n\n\tstart: function() {\n\n\t\tvar that = $( this ).resizable( \"instance\" ), cs = that.size;\n\n\t\tthat.ghost = that.originalElement.clone();\n\t\tthat.ghost.css( {\n\t\t\topacity: 0.25,\n\t\t\tdisplay: \"block\",\n\t\t\tposition: \"relative\",\n\t\t\theight: cs.height,\n\t\t\twidth: cs.width,\n\t\t\tmargin: 0,\n\t\t\tleft: 0,\n\t\t\ttop: 0\n\t\t} );\n\n\t\tthat._addClass( that.ghost, \"ui-resizable-ghost\" );\n\n\t\t// DEPRECATED\n\t\t// TODO: remove after 1.12\n\t\tif ( $.uiBackCompat !== false && typeof that.options.ghost === \"string\" ) {\n\n\t\t\t// Ghost option\n\t\t\tthat.ghost.addClass( this.options.ghost );\n\t\t}\n\n\t\tthat.ghost.appendTo( that.helper );\n\n\t},\n\n\tresize: function() {\n\t\tvar that = $( this ).resizable( \"instance\" );\n\t\tif ( that.ghost ) {\n\t\t\tthat.ghost.css( {\n\t\t\t\tposition: \"relative\",\n\t\t\t\theight: that.size.height,\n\t\t\t\twidth: that.size.width\n\t\t\t} );\n\t\t}\n\t},\n\n\tstop: function() {\n\t\tvar that = $( this ).resizable( \"instance\" );\n\t\tif ( that.ghost && that.helper ) {\n\t\t\tthat.helper.get( 0 ).removeChild( that.ghost.get( 0 ) );\n\t\t}\n\t}\n\n} );\n\n$.ui.plugin.add( \"resizable\", \"grid\", {\n\n\tresize: function() {\n\t\tvar outerDimensions,\n\t\t\tthat = $( this ).resizable( \"instance\" ),\n\t\t\to = that.options,\n\t\t\tcs = that.size,\n\t\t\tos = that.originalSize,\n\t\t\top = that.originalPosition,\n\t\t\ta = that.axis,\n\t\t\tgrid = typeof o.grid === \"number\" ? [ o.grid, o.grid ] : o.grid,\n\t\t\tgridX = ( grid[ 0 ] || 1 ),\n\t\t\tgridY = ( grid[ 1 ] || 1 ),\n\t\t\tox = Math.round( ( cs.width - os.width ) / gridX ) * gridX,\n\t\t\toy = Math.round( ( cs.height - os.height ) / gridY ) * gridY,\n\t\t\tnewWidth = os.width + ox,\n\t\t\tnewHeight = os.height + oy,\n\t\t\tisMaxWidth = o.maxWidth && ( o.maxWidth < newWidth ),\n\t\t\tisMaxHeight = o.maxHeight && ( o.maxHeight < newHeight ),\n\t\t\tisMinWidth = o.minWidth && ( o.minWidth > newWidth ),\n\t\t\tisMinHeight = o.minHeight && ( o.minHeight > newHeight );\n\n\t\to.grid = grid;\n\n\t\tif ( isMinWidth ) {\n\t\t\tnewWidth += gridX;\n\t\t}\n\t\tif ( isMinHeight ) {\n\t\t\tnewHeight += gridY;\n\t\t}\n\t\tif ( isMaxWidth ) {\n\t\t\tnewWidth -= gridX;\n\t\t}\n\t\tif ( isMaxHeight ) {\n\t\t\tnewHeight -= gridY;\n\t\t}\n\n\t\tif ( /^(se|s|e)$/.test( a ) ) {\n\t\t\tthat.size.width = newWidth;\n\t\t\tthat.size.height = newHeight;\n\t\t} else if ( /^(ne)$/.test( a ) ) {\n\t\t\tthat.size.width = newWidth;\n\t\t\tthat.size.height = newHeight;\n\t\t\tthat.position.top = op.top - oy;\n\t\t} else if ( /^(sw)$/.test( a ) ) {\n\t\t\tthat.size.width = newWidth;\n\t\t\tthat.size.height = newHeight;\n\t\t\tthat.position.left = op.left - ox;\n\t\t} else {\n\t\t\tif ( newHeight - gridY <= 0 || newWidth - gridX <= 0 ) {\n\t\t\t\touterDimensions = that._getPaddingPlusBorderDimensions( this );\n\t\t\t}\n\n\t\t\tif ( newHeight - gridY > 0 ) {\n\t\t\t\tthat.size.height = newHeight;\n\t\t\t\tthat.position.top = op.top - oy;\n\t\t\t} else {\n\t\t\t\tnewHeight = gridY - outerDimensions.height;\n\t\t\t\tthat.size.height = newHeight;\n\t\t\t\tthat.position.top = op.top + os.height - newHeight;\n\t\t\t}\n\t\t\tif ( newWidth - gridX > 0 ) {\n\t\t\t\tthat.size.width = newWidth;\n\t\t\t\tthat.position.left = op.left - ox;\n\t\t\t} else {\n\t\t\t\tnewWidth = gridX - outerDimensions.width;\n\t\t\t\tthat.size.width = newWidth;\n\t\t\t\tthat.position.left = op.left + os.width - newWidth;\n\t\t\t}\n\t\t}\n\t}\n\n} );\n\nvar widgetsResizable = $.ui.resizable;\n\n\n/*!\n * jQuery UI Selectable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Selectable\n//>>group: Interactions\n//>>description: Allows groups of elements to be selected with the mouse.\n//>>docs: http://api.jqueryui.com/selectable/\n//>>demos: http://jqueryui.com/selectable/\n//>>css.structure: ../../themes/base/selectable.css\n\n\n\nvar widgetsSelectable = $.widget( \"ui.selectable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tappendTo: \"body\",\n\t\tautoRefresh: true,\n\t\tdistance: 0,\n\t\tfilter: \"*\",\n\t\ttolerance: \"touch\",\n\n\t\t// Callbacks\n\t\tselected: null,\n\t\tselecting: null,\n\t\tstart: null,\n\t\tstop: null,\n\t\tunselected: null,\n\t\tunselecting: null\n\t},\n\t_create: function() {\n\t\tvar that = this;\n\n\t\tthis._addClass( \"ui-selectable\" );\n\n\t\tthis.dragged = false;\n\n\t\t// Cache selectee children based on filter\n\t\tthis.refresh = function() {\n\t\t\tthat.elementPos = $( that.element[ 0 ] ).offset();\n\t\t\tthat.selectees = $( that.options.filter, that.element[ 0 ] );\n\t\t\tthat._addClass( that.selectees, \"ui-selectee\" );\n\t\t\tthat.selectees.each( function() {\n\t\t\t\tvar $this = $( this ),\n\t\t\t\t\tselecteeOffset = $this.offset(),\n\t\t\t\t\tpos = {\n\t\t\t\t\t\tleft: selecteeOffset.left - that.elementPos.left,\n\t\t\t\t\t\ttop: selecteeOffset.top - that.elementPos.top\n\t\t\t\t\t};\n\t\t\t\t$.data( this, \"selectable-item\", {\n\t\t\t\t\telement: this,\n\t\t\t\t\t$element: $this,\n\t\t\t\t\tleft: pos.left,\n\t\t\t\t\ttop: pos.top,\n\t\t\t\t\tright: pos.left + $this.outerWidth(),\n\t\t\t\t\tbottom: pos.top + $this.outerHeight(),\n\t\t\t\t\tstartselected: false,\n\t\t\t\t\tselected: $this.hasClass( \"ui-selected\" ),\n\t\t\t\t\tselecting: $this.hasClass( \"ui-selecting\" ),\n\t\t\t\t\tunselecting: $this.hasClass( \"ui-unselecting\" )\n\t\t\t\t} );\n\t\t\t} );\n\t\t};\n\t\tthis.refresh();\n\n\t\tthis._mouseInit();\n\n\t\tthis.helper = $( \"<div>\" );\n\t\tthis._addClass( this.helper, \"ui-selectable-helper\" );\n\t},\n\n\t_destroy: function() {\n\t\tthis.selectees.removeData( \"selectable-item\" );\n\t\tthis._mouseDestroy();\n\t},\n\n\t_mouseStart: function( event ) {\n\t\tvar that = this,\n\t\t\toptions = this.options;\n\n\t\tthis.opos = [ event.pageX, event.pageY ];\n\t\tthis.elementPos = $( this.element[ 0 ] ).offset();\n\n\t\tif ( this.options.disabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.selectees = $( options.filter, this.element[ 0 ] );\n\n\t\tthis._trigger( \"start\", event );\n\n\t\t$( options.appendTo ).append( this.helper );\n\n\t\t// position helper (lasso)\n\t\tthis.helper.css( {\n\t\t\t\"left\": event.pageX,\n\t\t\t\"top\": event.pageY,\n\t\t\t\"width\": 0,\n\t\t\t\"height\": 0\n\t\t} );\n\n\t\tif ( options.autoRefresh ) {\n\t\t\tthis.refresh();\n\t\t}\n\n\t\tthis.selectees.filter( \".ui-selected\" ).each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" );\n\t\t\tselectee.startselected = true;\n\t\t\tif ( !event.metaKey && !event.ctrlKey ) {\n\t\t\t\tthat._removeClass( selectee.$element, \"ui-selected\" );\n\t\t\t\tselectee.selected = false;\n\t\t\t\tthat._addClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\tselectee.unselecting = true;\n\n\t\t\t\t// selectable UNSELECTING callback\n\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\n\t\t$( event.target ).parents().addBack().each( function() {\n\t\t\tvar doSelect,\n\t\t\t\tselectee = $.data( this, \"selectable-item\" );\n\t\t\tif ( selectee ) {\n\t\t\t\tdoSelect = ( !event.metaKey && !event.ctrlKey ) ||\n\t\t\t\t\t!selectee.$element.hasClass( \"ui-selected\" );\n\t\t\t\tthat._removeClass( selectee.$element, doSelect ? \"ui-unselecting\" : \"ui-selected\" )\n\t\t\t\t\t._addClass( selectee.$element, doSelect ? \"ui-selecting\" : \"ui-unselecting\" );\n\t\t\t\tselectee.unselecting = !doSelect;\n\t\t\t\tselectee.selecting = doSelect;\n\t\t\t\tselectee.selected = doSelect;\n\n\t\t\t\t// selectable (UN)SELECTING callback\n\t\t\t\tif ( doSelect ) {\n\t\t\t\t\tthat._trigger( \"selecting\", event, {\n\t\t\t\t\t\tselecting: selectee.element\n\t\t\t\t\t} );\n\t\t\t\t} else {\n\t\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} );\n\n\t},\n\n\t_mouseDrag: function( event ) {\n\n\t\tthis.dragged = true;\n\n\t\tif ( this.options.disabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tmp,\n\t\t\tthat = this,\n\t\t\toptions = this.options,\n\t\t\tx1 = this.opos[ 0 ],\n\t\t\ty1 = this.opos[ 1 ],\n\t\t\tx2 = event.pageX,\n\t\t\ty2 = event.pageY;\n\n\t\tif ( x1 > x2 ) { tmp = x2; x2 = x1; x1 = tmp; }\n\t\tif ( y1 > y2 ) { tmp = y2; y2 = y1; y1 = tmp; }\n\t\tthis.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } );\n\n\t\tthis.selectees.each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" ),\n\t\t\t\thit = false,\n\t\t\t\toffset = {};\n\n\t\t\t//prevent helper from being selected if appendTo: selectable\n\t\t\tif ( !selectee || selectee.element === that.element[ 0 ] ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\toffset.left   = selectee.left   + that.elementPos.left;\n\t\t\toffset.right  = selectee.right  + that.elementPos.left;\n\t\t\toffset.top    = selectee.top    + that.elementPos.top;\n\t\t\toffset.bottom = selectee.bottom + that.elementPos.top;\n\n\t\t\tif ( options.tolerance === \"touch\" ) {\n\t\t\t\thit = ( !( offset.left > x2 || offset.right < x1 || offset.top > y2 ||\n                    offset.bottom < y1 ) );\n\t\t\t} else if ( options.tolerance === \"fit\" ) {\n\t\t\t\thit = ( offset.left > x1 && offset.right < x2 && offset.top > y1 &&\n                    offset.bottom < y2 );\n\t\t\t}\n\n\t\t\tif ( hit ) {\n\n\t\t\t\t// SELECT\n\t\t\t\tif ( selectee.selected ) {\n\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selected\" );\n\t\t\t\t\tselectee.selected = false;\n\t\t\t\t}\n\t\t\t\tif ( selectee.unselecting ) {\n\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\t\tselectee.unselecting = false;\n\t\t\t\t}\n\t\t\t\tif ( !selectee.selecting ) {\n\t\t\t\t\tthat._addClass( selectee.$element, \"ui-selecting\" );\n\t\t\t\t\tselectee.selecting = true;\n\n\t\t\t\t\t// selectable SELECTING callback\n\t\t\t\t\tthat._trigger( \"selecting\", event, {\n\t\t\t\t\t\tselecting: selectee.element\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// UNSELECT\n\t\t\t\tif ( selectee.selecting ) {\n\t\t\t\t\tif ( ( event.metaKey || event.ctrlKey ) && selectee.startselected ) {\n\t\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selecting\" );\n\t\t\t\t\t\tselectee.selecting = false;\n\t\t\t\t\t\tthat._addClass( selectee.$element, \"ui-selected\" );\n\t\t\t\t\t\tselectee.selected = true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selecting\" );\n\t\t\t\t\t\tselectee.selecting = false;\n\t\t\t\t\t\tif ( selectee.startselected ) {\n\t\t\t\t\t\t\tthat._addClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\t\t\t\tselectee.unselecting = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// selectable UNSELECTING callback\n\t\t\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ( selectee.selected ) {\n\t\t\t\t\tif ( !event.metaKey && !event.ctrlKey && !selectee.startselected ) {\n\t\t\t\t\t\tthat._removeClass( selectee.$element, \"ui-selected\" );\n\t\t\t\t\t\tselectee.selected = false;\n\n\t\t\t\t\t\tthat._addClass( selectee.$element, \"ui-unselecting\" );\n\t\t\t\t\t\tselectee.unselecting = true;\n\n\t\t\t\t\t\t// selectable UNSELECTING callback\n\t\t\t\t\t\tthat._trigger( \"unselecting\", event, {\n\t\t\t\t\t\t\tunselecting: selectee.element\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\t\tvar that = this;\n\n\t\tthis.dragged = false;\n\n\t\t$( \".ui-unselecting\", this.element[ 0 ] ).each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" );\n\t\t\tthat._removeClass( selectee.$element, \"ui-unselecting\" );\n\t\t\tselectee.unselecting = false;\n\t\t\tselectee.startselected = false;\n\t\t\tthat._trigger( \"unselected\", event, {\n\t\t\t\tunselected: selectee.element\n\t\t\t} );\n\t\t} );\n\t\t$( \".ui-selecting\", this.element[ 0 ] ).each( function() {\n\t\t\tvar selectee = $.data( this, \"selectable-item\" );\n\t\t\tthat._removeClass( selectee.$element, \"ui-selecting\" )\n\t\t\t\t._addClass( selectee.$element, \"ui-selected\" );\n\t\t\tselectee.selecting = false;\n\t\t\tselectee.selected = true;\n\t\t\tselectee.startselected = true;\n\t\t\tthat._trigger( \"selected\", event, {\n\t\t\t\tselected: selectee.element\n\t\t\t} );\n\t\t} );\n\t\tthis._trigger( \"stop\", event );\n\n\t\tthis.helper.remove();\n\n\t\treturn false;\n\t}\n\n} );\n\n\n/*!\n * jQuery UI Sortable 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Sortable\n//>>group: Interactions\n//>>description: Enables items in a list to be sorted using the mouse.\n//>>docs: http://api.jqueryui.com/sortable/\n//>>demos: http://jqueryui.com/sortable/\n//>>css.structure: ../../themes/base/sortable.css\n\n\n\nvar widgetsSortable = $.widget( \"ui.sortable\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"sort\",\n\tready: false,\n\toptions: {\n\t\tappendTo: \"parent\",\n\t\taxis: false,\n\t\tconnectWith: false,\n\t\tcontainment: false,\n\t\tcursor: \"auto\",\n\t\tcursorAt: false,\n\t\tdropOnEmpty: true,\n\t\tforcePlaceholderSize: false,\n\t\tforceHelperSize: false,\n\t\tgrid: false,\n\t\thandle: false,\n\t\thelper: \"original\",\n\t\titems: \"> *\",\n\t\topacity: false,\n\t\tplaceholder: false,\n\t\trevert: false,\n\t\tscroll: true,\n\t\tscrollSensitivity: 20,\n\t\tscrollSpeed: 20,\n\t\tscope: \"default\",\n\t\ttolerance: \"intersect\",\n\t\tzIndex: 1000,\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tbeforeStop: null,\n\t\tchange: null,\n\t\tdeactivate: null,\n\t\tout: null,\n\t\tover: null,\n\t\treceive: null,\n\t\tremove: null,\n\t\tsort: null,\n\t\tstart: null,\n\t\tstop: null,\n\t\tupdate: null\n\t},\n\n\t_isOverAxis: function( x, reference, size ) {\n\t\treturn ( x >= reference ) && ( x < ( reference + size ) );\n\t},\n\n\t_isFloating: function( item ) {\n\t\treturn ( /left|right/ ).test( item.css( \"float\" ) ) ||\n\t\t\t( /inline|table-cell/ ).test( item.css( \"display\" ) );\n\t},\n\n\t_create: function() {\n\t\tthis.containerCache = {};\n\t\tthis._addClass( \"ui-sortable\" );\n\n\t\t//Get the items\n\t\tthis.refresh();\n\n\t\t//Let's determine the parent's offset\n\t\tthis.offset = this.element.offset();\n\n\t\t//Initialize mouse events for interaction\n\t\tthis._mouseInit();\n\n\t\tthis._setHandleClassName();\n\n\t\t//We're ready to go\n\t\tthis.ready = true;\n\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"handle\" ) {\n\t\t\tthis._setHandleClassName();\n\t\t}\n\t},\n\n\t_setHandleClassName: function() {\n\t\tvar that = this;\n\t\tthis._removeClass( this.element.find( \".ui-sortable-handle\" ), \"ui-sortable-handle\" );\n\t\t$.each( this.items, function() {\n\t\t\tthat._addClass(\n\t\t\t\tthis.instance.options.handle ?\n\t\t\t\t\tthis.item.find( this.instance.options.handle ) :\n\t\t\t\t\tthis.item,\n\t\t\t\t\"ui-sortable-handle\"\n\t\t\t);\n\t\t} );\n\t},\n\n\t_destroy: function() {\n\t\tthis._mouseDestroy();\n\n\t\tfor ( var i = this.items.length - 1; i >= 0; i-- ) {\n\t\t\tthis.items[ i ].item.removeData( this.widgetName + \"-item\" );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_mouseCapture: function( event, overrideHandle ) {\n\t\tvar currentItem = null,\n\t\t\tvalidHandle = false,\n\t\t\tthat = this;\n\n\t\tif ( this.reverting ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( this.options.disabled || this.options.type === \"static\" ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t//We have to refresh the items data once first\n\t\tthis._refreshItems( event );\n\n\t\t//Find out if the clicked node (or one of its parents) is a actual item in this.items\n\t\t$( event.target ).parents().each( function() {\n\t\t\tif ( $.data( this, that.widgetName + \"-item\" ) === that ) {\n\t\t\t\tcurrentItem = $( this );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} );\n\t\tif ( $.data( event.target, that.widgetName + \"-item\" ) === that ) {\n\t\t\tcurrentItem = $( event.target );\n\t\t}\n\n\t\tif ( !currentItem ) {\n\t\t\treturn false;\n\t\t}\n\t\tif ( this.options.handle && !overrideHandle ) {\n\t\t\t$( this.options.handle, currentItem ).find( \"*\" ).addBack().each( function() {\n\t\t\t\tif ( this === event.target ) {\n\t\t\t\t\tvalidHandle = true;\n\t\t\t\t}\n\t\t\t} );\n\t\t\tif ( !validHandle ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.currentItem = currentItem;\n\t\tthis._removeCurrentsFromItems();\n\t\treturn true;\n\n\t},\n\n\t_mouseStart: function( event, overrideHandle, noActivation ) {\n\n\t\tvar i, body,\n\t\t\to = this.options;\n\n\t\tthis.currentContainer = this;\n\n\t\t//We only need to call refreshPositions, because the refreshItems call has been moved to\n\t\t// mouseCapture\n\t\tthis.refreshPositions();\n\n\t\t//Create and append the visible helper\n\t\tthis.helper = this._createHelper( event );\n\n\t\t//Cache the helper size\n\t\tthis._cacheHelperProportions();\n\n\t\t/*\n\t\t * - Position generation -\n\t\t * This block generates everything position related - it's the core of draggables.\n\t\t */\n\n\t\t//Cache the margins of the original element\n\t\tthis._cacheMargins();\n\n\t\t//Get the next scrolling parent\n\t\tthis.scrollParent = this.helper.scrollParent();\n\n\t\t//The element's absolute position on the page minus margins\n\t\tthis.offset = this.currentItem.offset();\n\t\tthis.offset = {\n\t\t\ttop: this.offset.top - this.margins.top,\n\t\t\tleft: this.offset.left - this.margins.left\n\t\t};\n\n\t\t$.extend( this.offset, {\n\t\t\tclick: { //Where the click happened, relative to the element\n\t\t\t\tleft: event.pageX - this.offset.left,\n\t\t\t\ttop: event.pageY - this.offset.top\n\t\t\t},\n\t\t\tparent: this._getParentOffset(),\n\n\t\t\t// This is a relative to absolute position minus the actual position calculation -\n\t\t\t// only used for relative positioned helper\n\t\t\trelative: this._getRelativeOffset()\n\t\t} );\n\n\t\t// Only after we got the offset, we can change the helper's position to absolute\n\t\t// TODO: Still need to figure out a way to make relative sorting possible\n\t\tthis.helper.css( \"position\", \"absolute\" );\n\t\tthis.cssPosition = this.helper.css( \"position\" );\n\n\t\t//Generate the original position\n\t\tthis.originalPosition = this._generatePosition( event );\n\t\tthis.originalPageX = event.pageX;\n\t\tthis.originalPageY = event.pageY;\n\n\t\t//Adjust the mouse offset relative to the helper if \"cursorAt\" is supplied\n\t\t( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );\n\n\t\t//Cache the former DOM position\n\t\tthis.domPosition = {\n\t\t\tprev: this.currentItem.prev()[ 0 ],\n\t\t\tparent: this.currentItem.parent()[ 0 ]\n\t\t};\n\n\t\t// If the helper is not the original, hide the original so it's not playing any role during\n\t\t// the drag, won't cause anything bad this way\n\t\tif ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {\n\t\t\tthis.currentItem.hide();\n\t\t}\n\n\t\t//Create the placeholder\n\t\tthis._createPlaceholder();\n\n\t\t//Set a containment if given in the options\n\t\tif ( o.containment ) {\n\t\t\tthis._setContainment();\n\t\t}\n\n\t\tif ( o.cursor && o.cursor !== \"auto\" ) { // cursor option\n\t\t\tbody = this.document.find( \"body\" );\n\n\t\t\t// Support: IE\n\t\t\tthis.storedCursor = body.css( \"cursor\" );\n\t\t\tbody.css( \"cursor\", o.cursor );\n\n\t\t\tthis.storedStylesheet =\n\t\t\t\t$( \"<style>*{ cursor: \" + o.cursor + \" !important; }</style>\" ).appendTo( body );\n\t\t}\n\n\t\tif ( o.opacity ) { // opacity option\n\t\t\tif ( this.helper.css( \"opacity\" ) ) {\n\t\t\t\tthis._storedOpacity = this.helper.css( \"opacity\" );\n\t\t\t}\n\t\t\tthis.helper.css( \"opacity\", o.opacity );\n\t\t}\n\n\t\tif ( o.zIndex ) { // zIndex option\n\t\t\tif ( this.helper.css( \"zIndex\" ) ) {\n\t\t\t\tthis._storedZIndex = this.helper.css( \"zIndex\" );\n\t\t\t}\n\t\t\tthis.helper.css( \"zIndex\", o.zIndex );\n\t\t}\n\n\t\t//Prepare scrolling\n\t\tif ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\tthis.scrollParent[ 0 ].tagName !== \"HTML\" ) {\n\t\t\tthis.overflowOffset = this.scrollParent.offset();\n\t\t}\n\n\t\t//Call callbacks\n\t\tthis._trigger( \"start\", event, this._uiHash() );\n\n\t\t//Recache the helper size\n\t\tif ( !this._preserveHelperProportions ) {\n\t\t\tthis._cacheHelperProportions();\n\t\t}\n\n\t\t//Post \"activate\" events to possible containers\n\t\tif ( !noActivation ) {\n\t\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\t\tthis.containers[ i ]._trigger( \"activate\", event, this._uiHash( this ) );\n\t\t\t}\n\t\t}\n\n\t\t//Prepare possible droppables\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.current = this;\n\t\t}\n\n\t\tif ( $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.prepareOffsets( this, event );\n\t\t}\n\n\t\tthis.dragging = true;\n\n\t\tthis._addClass( this.helper, \"ui-sortable-helper\" );\n\n\t\t// Execute the drag once - this causes the helper not to be visiblebefore getting its\n\t\t// correct position\n\t\tthis._mouseDrag( event );\n\t\treturn true;\n\n\t},\n\n\t_mouseDrag: function( event ) {\n\t\tvar i, item, itemElement, intersection,\n\t\t\to = this.options,\n\t\t\tscrolled = false;\n\n\t\t//Compute the helpers position\n\t\tthis.position = this._generatePosition( event );\n\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\n\t\tif ( !this.lastPositionAbs ) {\n\t\t\tthis.lastPositionAbs = this.positionAbs;\n\t\t}\n\n\t\t//Do scrolling\n\t\tif ( this.options.scroll ) {\n\t\t\tif ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t\tthis.scrollParent[ 0 ].tagName !== \"HTML\" ) {\n\n\t\t\t\tif ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) -\n\t\t\t\t\t\tevent.pageY < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollTop =\n\t\t\t\t\t\tscrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollTop =\n\t\t\t\t\t\tscrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed;\n\t\t\t\t}\n\n\t\t\t\tif ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) -\n\t\t\t\t\t\tevent.pageX < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft = scrolled =\n\t\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft + o.scrollSpeed;\n\t\t\t\t} else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) {\n\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft = scrolled =\n\t\t\t\t\t\tthis.scrollParent[ 0 ].scrollLeft - o.scrollSpeed;\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tif ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed );\n\t\t\t\t} else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed );\n\t\t\t\t}\n\n\t\t\t\tif ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollLeft(\n\t\t\t\t\t\tthis.document.scrollLeft() - o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t} else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) <\n\t\t\t\t\t\to.scrollSensitivity ) {\n\t\t\t\t\tscrolled = this.document.scrollLeft(\n\t\t\t\t\t\tthis.document.scrollLeft() + o.scrollSpeed\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {\n\t\t\t\t$.ui.ddmanager.prepareOffsets( this, event );\n\t\t\t}\n\t\t}\n\n\t\t//Regenerate the absolute position used for position checks\n\t\tthis.positionAbs = this._convertPositionTo( \"absolute\" );\n\n\t\t//Set the helper position\n\t\tif ( !this.options.axis || this.options.axis !== \"y\" ) {\n\t\t\tthis.helper[ 0 ].style.left = this.position.left + \"px\";\n\t\t}\n\t\tif ( !this.options.axis || this.options.axis !== \"x\" ) {\n\t\t\tthis.helper[ 0 ].style.top = this.position.top + \"px\";\n\t\t}\n\n\t\t//Rearrange\n\t\tfor ( i = this.items.length - 1; i >= 0; i-- ) {\n\n\t\t\t//Cache variables and intersection, continue if no intersection\n\t\t\titem = this.items[ i ];\n\t\t\titemElement = item.item[ 0 ];\n\t\t\tintersection = this._intersectsWithPointer( item );\n\t\t\tif ( !intersection ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Only put the placeholder inside the current Container, skip all\n\t\t\t// items from other containers. This works because when moving\n\t\t\t// an item from one container to another the\n\t\t\t// currentContainer is switched before the placeholder is moved.\n\t\t\t//\n\t\t\t// Without this, moving items in \"sub-sortables\" can cause\n\t\t\t// the placeholder to jitter between the outer and inner container.\n\t\t\tif ( item.instance !== this.currentContainer ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Cannot intersect with itself\n\t\t\t// no useless actions that have been done before\n\t\t\t// no action if the item moved is the parent of the item checked\n\t\t\tif ( itemElement !== this.currentItem[ 0 ] &&\n\t\t\t\tthis.placeholder[ intersection === 1 ? \"next\" : \"prev\" ]()[ 0 ] !== itemElement &&\n\t\t\t\t!$.contains( this.placeholder[ 0 ], itemElement ) &&\n\t\t\t\t( this.options.type === \"semi-dynamic\" ?\n\t\t\t\t\t!$.contains( this.element[ 0 ], itemElement ) :\n\t\t\t\t\ttrue\n\t\t\t\t)\n\t\t\t) {\n\n\t\t\t\tthis.direction = intersection === 1 ? \"down\" : \"up\";\n\n\t\t\t\tif ( this.options.tolerance === \"pointer\" || this._intersectsWithSides( item ) ) {\n\t\t\t\t\tthis._rearrange( event, item );\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis._trigger( \"change\", event, this._uiHash() );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t//Post events to containers\n\t\tthis._contactContainers( event );\n\n\t\t//Interconnect with droppables\n\t\tif ( $.ui.ddmanager ) {\n\t\t\t$.ui.ddmanager.drag( this, event );\n\t\t}\n\n\t\t//Call callbacks\n\t\tthis._trigger( \"sort\", event, this._uiHash() );\n\n\t\tthis.lastPositionAbs = this.positionAbs;\n\t\treturn false;\n\n\t},\n\n\t_mouseStop: function( event, noPropagation ) {\n\n\t\tif ( !event ) {\n\t\t\treturn;\n\t\t}\n\n\t\t//If we are using droppables, inform the manager about the drop\n\t\tif ( $.ui.ddmanager && !this.options.dropBehaviour ) {\n\t\t\t$.ui.ddmanager.drop( this, event );\n\t\t}\n\n\t\tif ( this.options.revert ) {\n\t\t\tvar that = this,\n\t\t\t\tcur = this.placeholder.offset(),\n\t\t\t\taxis = this.options.axis,\n\t\t\t\tanimation = {};\n\n\t\t\tif ( !axis || axis === \"x\" ) {\n\t\t\t\tanimation.left = cur.left - this.offset.parent.left - this.margins.left +\n\t\t\t\t\t( this.offsetParent[ 0 ] === this.document[ 0 ].body ?\n\t\t\t\t\t\t0 :\n\t\t\t\t\t\tthis.offsetParent[ 0 ].scrollLeft\n\t\t\t\t\t);\n\t\t\t}\n\t\t\tif ( !axis || axis === \"y\" ) {\n\t\t\t\tanimation.top = cur.top - this.offset.parent.top - this.margins.top +\n\t\t\t\t\t( this.offsetParent[ 0 ] === this.document[ 0 ].body ?\n\t\t\t\t\t\t0 :\n\t\t\t\t\t\tthis.offsetParent[ 0 ].scrollTop\n\t\t\t\t\t);\n\t\t\t}\n\t\t\tthis.reverting = true;\n\t\t\t$( this.helper ).animate(\n\t\t\t\tanimation,\n\t\t\t\tparseInt( this.options.revert, 10 ) || 500,\n\t\t\t\tfunction() {\n\t\t\t\t\tthat._clear( event );\n\t\t\t\t}\n\t\t\t);\n\t\t} else {\n\t\t\tthis._clear( event, noPropagation );\n\t\t}\n\n\t\treturn false;\n\n\t},\n\n\tcancel: function() {\n\n\t\tif ( this.dragging ) {\n\n\t\t\tthis._mouseUp( new $.Event( \"mouseup\", { target: null } ) );\n\n\t\t\tif ( this.options.helper === \"original\" ) {\n\t\t\t\tthis.currentItem.css( this._storedCSS );\n\t\t\t\tthis._removeClass( this.currentItem, \"ui-sortable-helper\" );\n\t\t\t} else {\n\t\t\t\tthis.currentItem.show();\n\t\t\t}\n\n\t\t\t//Post deactivating events to containers\n\t\t\tfor ( var i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\t\tthis.containers[ i ]._trigger( \"deactivate\", null, this._uiHash( this ) );\n\t\t\t\tif ( this.containers[ i ].containerCache.over ) {\n\t\t\t\t\tthis.containers[ i ]._trigger( \"out\", null, this._uiHash( this ) );\n\t\t\t\t\tthis.containers[ i ].containerCache.over = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tif ( this.placeholder ) {\n\n\t\t\t//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,\n\t\t\t// it unbinds ALL events from the original node!\n\t\t\tif ( this.placeholder[ 0 ].parentNode ) {\n\t\t\t\tthis.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );\n\t\t\t}\n\t\t\tif ( this.options.helper !== \"original\" && this.helper &&\n\t\t\t\t\tthis.helper[ 0 ].parentNode ) {\n\t\t\t\tthis.helper.remove();\n\t\t\t}\n\n\t\t\t$.extend( this, {\n\t\t\t\thelper: null,\n\t\t\t\tdragging: false,\n\t\t\t\treverting: false,\n\t\t\t\t_noFinalSort: null\n\t\t\t} );\n\n\t\t\tif ( this.domPosition.prev ) {\n\t\t\t\t$( this.domPosition.prev ).after( this.currentItem );\n\t\t\t} else {\n\t\t\t\t$( this.domPosition.parent ).prepend( this.currentItem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\n\t},\n\n\tserialize: function( o ) {\n\n\t\tvar items = this._getItemsAsjQuery( o && o.connected ),\n\t\t\tstr = [];\n\t\to = o || {};\n\n\t\t$( items ).each( function() {\n\t\t\tvar res = ( $( o.item || this ).attr( o.attribute || \"id\" ) || \"\" )\n\t\t\t\t.match( o.expression || ( /(.+)[\\-=_](.+)/ ) );\n\t\t\tif ( res ) {\n\t\t\t\tstr.push(\n\t\t\t\t\t( o.key || res[ 1 ] + \"[]\" ) +\n\t\t\t\t\t\"=\" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) );\n\t\t\t}\n\t\t} );\n\n\t\tif ( !str.length && o.key ) {\n\t\t\tstr.push( o.key + \"=\" );\n\t\t}\n\n\t\treturn str.join( \"&\" );\n\n\t},\n\n\ttoArray: function( o ) {\n\n\t\tvar items = this._getItemsAsjQuery( o && o.connected ),\n\t\t\tret = [];\n\n\t\to = o || {};\n\n\t\titems.each( function() {\n\t\t\tret.push( $( o.item || this ).attr( o.attribute || \"id\" ) || \"\" );\n\t\t} );\n\t\treturn ret;\n\n\t},\n\n\t/* Be careful with the following core functions */\n\t_intersectsWith: function( item ) {\n\n\t\tvar x1 = this.positionAbs.left,\n\t\t\tx2 = x1 + this.helperProportions.width,\n\t\t\ty1 = this.positionAbs.top,\n\t\t\ty2 = y1 + this.helperProportions.height,\n\t\t\tl = item.left,\n\t\t\tr = l + item.width,\n\t\t\tt = item.top,\n\t\t\tb = t + item.height,\n\t\t\tdyClick = this.offset.click.top,\n\t\t\tdxClick = this.offset.click.left,\n\t\t\tisOverElementHeight = ( this.options.axis === \"x\" ) || ( ( y1 + dyClick ) > t &&\n\t\t\t\t( y1 + dyClick ) < b ),\n\t\t\tisOverElementWidth = ( this.options.axis === \"y\" ) || ( ( x1 + dxClick ) > l &&\n\t\t\t\t( x1 + dxClick ) < r ),\n\t\t\tisOverElement = isOverElementHeight && isOverElementWidth;\n\n\t\tif ( this.options.tolerance === \"pointer\" ||\n\t\t\tthis.options.forcePointerForContainers ||\n\t\t\t( this.options.tolerance !== \"pointer\" &&\n\t\t\t\tthis.helperProportions[ this.floating ? \"width\" : \"height\" ] >\n\t\t\t\titem[ this.floating ? \"width\" : \"height\" ] )\n\t\t) {\n\t\t\treturn isOverElement;\n\t\t} else {\n\n\t\t\treturn ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half\n\t\t\t\tx2 - ( this.helperProportions.width / 2 ) < r && // Left Half\n\t\t\t\tt < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half\n\t\t\t\ty2 - ( this.helperProportions.height / 2 ) < b ); // Top Half\n\n\t\t}\n\t},\n\n\t_intersectsWithPointer: function( item ) {\n\t\tvar verticalDirection, horizontalDirection,\n\t\t\tisOverElementHeight = ( this.options.axis === \"x\" ) ||\n\t\t\t\tthis._isOverAxis(\n\t\t\t\t\tthis.positionAbs.top + this.offset.click.top, item.top, item.height ),\n\t\t\tisOverElementWidth = ( this.options.axis === \"y\" ) ||\n\t\t\t\tthis._isOverAxis(\n\t\t\t\t\tthis.positionAbs.left + this.offset.click.left, item.left, item.width ),\n\t\t\tisOverElement = isOverElementHeight && isOverElementWidth;\n\n\t\tif ( !isOverElement ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tverticalDirection = this._getDragVerticalDirection();\n\t\thorizontalDirection = this._getDragHorizontalDirection();\n\n\t\treturn this.floating ?\n\t\t\t( ( horizontalDirection === \"right\" || verticalDirection === \"down\" ) ? 2 : 1 )\n\t\t\t: ( verticalDirection && ( verticalDirection === \"down\" ? 2 : 1 ) );\n\n\t},\n\n\t_intersectsWithSides: function( item ) {\n\n\t\tvar isOverBottomHalf = this._isOverAxis( this.positionAbs.top +\n\t\t\t\tthis.offset.click.top, item.top + ( item.height / 2 ), item.height ),\n\t\t\tisOverRightHalf = this._isOverAxis( this.positionAbs.left +\n\t\t\t\tthis.offset.click.left, item.left + ( item.width / 2 ), item.width ),\n\t\t\tverticalDirection = this._getDragVerticalDirection(),\n\t\t\thorizontalDirection = this._getDragHorizontalDirection();\n\n\t\tif ( this.floating && horizontalDirection ) {\n\t\t\treturn ( ( horizontalDirection === \"right\" && isOverRightHalf ) ||\n\t\t\t\t( horizontalDirection === \"left\" && !isOverRightHalf ) );\n\t\t} else {\n\t\t\treturn verticalDirection && ( ( verticalDirection === \"down\" && isOverBottomHalf ) ||\n\t\t\t\t( verticalDirection === \"up\" && !isOverBottomHalf ) );\n\t\t}\n\n\t},\n\n\t_getDragVerticalDirection: function() {\n\t\tvar delta = this.positionAbs.top - this.lastPositionAbs.top;\n\t\treturn delta !== 0 && ( delta > 0 ? \"down\" : \"up\" );\n\t},\n\n\t_getDragHorizontalDirection: function() {\n\t\tvar delta = this.positionAbs.left - this.lastPositionAbs.left;\n\t\treturn delta !== 0 && ( delta > 0 ? \"right\" : \"left\" );\n\t},\n\n\trefresh: function( event ) {\n\t\tthis._refreshItems( event );\n\t\tthis._setHandleClassName();\n\t\tthis.refreshPositions();\n\t\treturn this;\n\t},\n\n\t_connectWith: function() {\n\t\tvar options = this.options;\n\t\treturn options.connectWith.constructor === String ?\n\t\t\t[ options.connectWith ] :\n\t\t\toptions.connectWith;\n\t},\n\n\t_getItemsAsjQuery: function( connected ) {\n\n\t\tvar i, j, cur, inst,\n\t\t\titems = [],\n\t\t\tqueries = [],\n\t\t\tconnectWith = this._connectWith();\n\n\t\tif ( connectWith && connected ) {\n\t\t\tfor ( i = connectWith.length - 1; i >= 0; i-- ) {\n\t\t\t\tcur = $( connectWith[ i ], this.document[ 0 ] );\n\t\t\t\tfor ( j = cur.length - 1; j >= 0; j-- ) {\n\t\t\t\t\tinst = $.data( cur[ j ], this.widgetFullName );\n\t\t\t\t\tif ( inst && inst !== this && !inst.options.disabled ) {\n\t\t\t\t\t\tqueries.push( [ $.isFunction( inst.options.items ) ?\n\t\t\t\t\t\t\tinst.options.items.call( inst.element ) :\n\t\t\t\t\t\t\t$( inst.options.items, inst.element )\n\t\t\t\t\t\t\t\t.not( \".ui-sortable-helper\" )\n\t\t\t\t\t\t\t\t.not( \".ui-sortable-placeholder\" ), inst ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tqueries.push( [ $.isFunction( this.options.items ) ?\n\t\t\tthis.options.items\n\t\t\t\t.call( this.element, null, { options: this.options, item: this.currentItem } ) :\n\t\t\t$( this.options.items, this.element )\n\t\t\t\t.not( \".ui-sortable-helper\" )\n\t\t\t\t.not( \".ui-sortable-placeholder\" ), this ] );\n\n\t\tfunction addItems() {\n\t\t\titems.push( this );\n\t\t}\n\t\tfor ( i = queries.length - 1; i >= 0; i-- ) {\n\t\t\tqueries[ i ][ 0 ].each( addItems );\n\t\t}\n\n\t\treturn $( items );\n\n\t},\n\n\t_removeCurrentsFromItems: function() {\n\n\t\tvar list = this.currentItem.find( \":data(\" + this.widgetName + \"-item)\" );\n\n\t\tthis.items = $.grep( this.items, function( item ) {\n\t\t\tfor ( var j = 0; j < list.length; j++ ) {\n\t\t\t\tif ( list[ j ] === item.item[ 0 ] ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} );\n\n\t},\n\n\t_refreshItems: function( event ) {\n\n\t\tthis.items = [];\n\t\tthis.containers = [ this ];\n\n\t\tvar i, j, cur, inst, targetData, _queries, item, queriesLength,\n\t\t\titems = this.items,\n\t\t\tqueries = [ [ $.isFunction( this.options.items ) ?\n\t\t\t\tthis.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) :\n\t\t\t\t$( this.options.items, this.element ), this ] ],\n\t\t\tconnectWith = this._connectWith();\n\n\t\t//Shouldn't be run the first time through due to massive slow-down\n\t\tif ( connectWith && this.ready ) {\n\t\t\tfor ( i = connectWith.length - 1; i >= 0; i-- ) {\n\t\t\t\tcur = $( connectWith[ i ], this.document[ 0 ] );\n\t\t\t\tfor ( j = cur.length - 1; j >= 0; j-- ) {\n\t\t\t\t\tinst = $.data( cur[ j ], this.widgetFullName );\n\t\t\t\t\tif ( inst && inst !== this && !inst.options.disabled ) {\n\t\t\t\t\t\tqueries.push( [ $.isFunction( inst.options.items ) ?\n\t\t\t\t\t\t\tinst.options.items\n\t\t\t\t\t\t\t\t.call( inst.element[ 0 ], event, { item: this.currentItem } ) :\n\t\t\t\t\t\t\t$( inst.options.items, inst.element ), inst ] );\n\t\t\t\t\t\tthis.containers.push( inst );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor ( i = queries.length - 1; i >= 0; i-- ) {\n\t\t\ttargetData = queries[ i ][ 1 ];\n\t\t\t_queries = queries[ i ][ 0 ];\n\n\t\t\tfor ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) {\n\t\t\t\titem = $( _queries[ j ] );\n\n\t\t\t\t// Data for target checking (mouse manager)\n\t\t\t\titem.data( this.widgetName + \"-item\", targetData );\n\n\t\t\t\titems.push( {\n\t\t\t\t\titem: item,\n\t\t\t\t\tinstance: targetData,\n\t\t\t\t\twidth: 0, height: 0,\n\t\t\t\t\tleft: 0, top: 0\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t},\n\n\trefreshPositions: function( fast ) {\n\n\t\t// Determine whether items are being displayed horizontally\n\t\tthis.floating = this.items.length ?\n\t\t\tthis.options.axis === \"x\" || this._isFloating( this.items[ 0 ].item ) :\n\t\t\tfalse;\n\n\t\t//This has to be redone because due to the item being moved out/into the offsetParent,\n\t\t// the offsetParent's position will change\n\t\tif ( this.offsetParent && this.helper ) {\n\t\t\tthis.offset.parent = this._getParentOffset();\n\t\t}\n\n\t\tvar i, item, t, p;\n\n\t\tfor ( i = this.items.length - 1; i >= 0; i-- ) {\n\t\t\titem = this.items[ i ];\n\n\t\t\t//We ignore calculating positions of all connected containers when we're not over them\n\t\t\tif ( item.instance !== this.currentContainer && this.currentContainer &&\n\t\t\t\t\titem.item[ 0 ] !== this.currentItem[ 0 ] ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tt = this.options.toleranceElement ?\n\t\t\t\t$( this.options.toleranceElement, item.item ) :\n\t\t\t\titem.item;\n\n\t\t\tif ( !fast ) {\n\t\t\t\titem.width = t.outerWidth();\n\t\t\t\titem.height = t.outerHeight();\n\t\t\t}\n\n\t\t\tp = t.offset();\n\t\t\titem.left = p.left;\n\t\t\titem.top = p.top;\n\t\t}\n\n\t\tif ( this.options.custom && this.options.custom.refreshContainers ) {\n\t\t\tthis.options.custom.refreshContainers.call( this );\n\t\t} else {\n\t\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\t\tp = this.containers[ i ].element.offset();\n\t\t\t\tthis.containers[ i ].containerCache.left = p.left;\n\t\t\t\tthis.containers[ i ].containerCache.top = p.top;\n\t\t\t\tthis.containers[ i ].containerCache.width =\n\t\t\t\t\tthis.containers[ i ].element.outerWidth();\n\t\t\t\tthis.containers[ i ].containerCache.height =\n\t\t\t\t\tthis.containers[ i ].element.outerHeight();\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t_createPlaceholder: function( that ) {\n\t\tthat = that || this;\n\t\tvar className,\n\t\t\to = that.options;\n\n\t\tif ( !o.placeholder || o.placeholder.constructor === String ) {\n\t\t\tclassName = o.placeholder;\n\t\t\to.placeholder = {\n\t\t\t\telement: function() {\n\n\t\t\t\t\tvar nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(),\n\t\t\t\t\t\telement = $( \"<\" + nodeName + \">\", that.document[ 0 ] );\n\n\t\t\t\t\t\tthat._addClass( element, \"ui-sortable-placeholder\",\n\t\t\t\t\t\t\t\tclassName || that.currentItem[ 0 ].className )\n\t\t\t\t\t\t\t._removeClass( element, \"ui-sortable-helper\" );\n\n\t\t\t\t\tif ( nodeName === \"tbody\" ) {\n\t\t\t\t\t\tthat._createTrPlaceholder(\n\t\t\t\t\t\t\tthat.currentItem.find( \"tr\" ).eq( 0 ),\n\t\t\t\t\t\t\t$( \"<tr>\", that.document[ 0 ] ).appendTo( element )\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if ( nodeName === \"tr\" ) {\n\t\t\t\t\t\tthat._createTrPlaceholder( that.currentItem, element );\n\t\t\t\t\t} else if ( nodeName === \"img\" ) {\n\t\t\t\t\t\telement.attr( \"src\", that.currentItem.attr( \"src\" ) );\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !className ) {\n\t\t\t\t\t\telement.css( \"visibility\", \"hidden\" );\n\t\t\t\t\t}\n\n\t\t\t\t\treturn element;\n\t\t\t\t},\n\t\t\t\tupdate: function( container, p ) {\n\n\t\t\t\t\t// 1. If a className is set as 'placeholder option, we don't force sizes -\n\t\t\t\t\t// the class is responsible for that\n\t\t\t\t\t// 2. The option 'forcePlaceholderSize can be enabled to force it even if a\n\t\t\t\t\t// class name is specified\n\t\t\t\t\tif ( className && !o.forcePlaceholderSize ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t//If the element doesn't have a actual height by itself (without styles coming\n\t\t\t\t\t// from a stylesheet), it receives the inline height from the dragged item\n\t\t\t\t\tif ( !p.height() ) {\n\t\t\t\t\t\tp.height(\n\t\t\t\t\t\t\tthat.currentItem.innerHeight() -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingTop\" ) || 0, 10 ) -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingBottom\" ) || 0, 10 ) );\n\t\t\t\t\t}\n\t\t\t\t\tif ( !p.width() ) {\n\t\t\t\t\t\tp.width(\n\t\t\t\t\t\t\tthat.currentItem.innerWidth() -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingLeft\" ) || 0, 10 ) -\n\t\t\t\t\t\t\tparseInt( that.currentItem.css( \"paddingRight\" ) || 0, 10 ) );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t//Create the placeholder\n\t\tthat.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) );\n\n\t\t//Append it after the actual current item\n\t\tthat.currentItem.after( that.placeholder );\n\n\t\t//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)\n\t\to.placeholder.update( that, that.placeholder );\n\n\t},\n\n\t_createTrPlaceholder: function( sourceTr, targetTr ) {\n\t\tvar that = this;\n\n\t\tsourceTr.children().each( function() {\n\t\t\t$( \"<td>&#160;</td>\", that.document[ 0 ] )\n\t\t\t\t.attr( \"colspan\", $( this ).attr( \"colspan\" ) || 1 )\n\t\t\t\t.appendTo( targetTr );\n\t\t} );\n\t},\n\n\t_contactContainers: function( event ) {\n\t\tvar i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom,\n\t\t\tfloating, axis,\n\t\t\tinnermostContainer = null,\n\t\t\tinnermostIndex = null;\n\n\t\t// Get innermost container that intersects with item\n\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\n\t\t\t// Never consider a container that's located within the item itself\n\t\t\tif ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( this._intersectsWith( this.containers[ i ].containerCache ) ) {\n\n\t\t\t\t// If we've already found a container and it's more \"inner\" than this, then continue\n\t\t\t\tif ( innermostContainer &&\n\t\t\t\t\t\t$.contains(\n\t\t\t\t\t\t\tthis.containers[ i ].element[ 0 ],\n\t\t\t\t\t\t\tinnermostContainer.element[ 0 ] ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tinnermostContainer = this.containers[ i ];\n\t\t\t\tinnermostIndex = i;\n\n\t\t\t} else {\n\n\t\t\t\t// container doesn't intersect. trigger \"out\" event if necessary\n\t\t\t\tif ( this.containers[ i ].containerCache.over ) {\n\t\t\t\t\tthis.containers[ i ]._trigger( \"out\", event, this._uiHash( this ) );\n\t\t\t\t\tthis.containers[ i ].containerCache.over = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\t// If no intersecting containers found, return\n\t\tif ( !innermostContainer ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Move the item into the container if it's not there already\n\t\tif ( this.containers.length === 1 ) {\n\t\t\tif ( !this.containers[ innermostIndex ].containerCache.over ) {\n\t\t\t\tthis.containers[ innermostIndex ]._trigger( \"over\", event, this._uiHash( this ) );\n\t\t\t\tthis.containers[ innermostIndex ].containerCache.over = 1;\n\t\t\t}\n\t\t} else {\n\n\t\t\t// When entering a new container, we will find the item with the least distance and\n\t\t\t// append our item near it\n\t\t\tdist = 10000;\n\t\t\titemWithLeastDistance = null;\n\t\t\tfloating = innermostContainer.floating || this._isFloating( this.currentItem );\n\t\t\tposProperty = floating ? \"left\" : \"top\";\n\t\t\tsizeProperty = floating ? \"width\" : \"height\";\n\t\t\taxis = floating ? \"pageX\" : \"pageY\";\n\n\t\t\tfor ( j = this.items.length - 1; j >= 0; j-- ) {\n\t\t\t\tif ( !$.contains(\n\t\t\t\t\t\tthis.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] )\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tcur = this.items[ j ].item.offset()[ posProperty ];\n\t\t\t\tnearBottom = false;\n\t\t\t\tif ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {\n\t\t\t\t\tnearBottom = true;\n\t\t\t\t}\n\n\t\t\t\tif ( Math.abs( event[ axis ] - cur ) < dist ) {\n\t\t\t\t\tdist = Math.abs( event[ axis ] - cur );\n\t\t\t\t\titemWithLeastDistance = this.items[ j ];\n\t\t\t\t\tthis.direction = nearBottom ? \"up\" : \"down\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//Check if dropOnEmpty is enabled\n\t\t\tif ( !itemWithLeastDistance && !this.options.dropOnEmpty ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( this.currentContainer === this.containers[ innermostIndex ] ) {\n\t\t\t\tif ( !this.currentContainer.containerCache.over ) {\n\t\t\t\t\tthis.containers[ innermostIndex ]._trigger( \"over\", event, this._uiHash() );\n\t\t\t\t\tthis.currentContainer.containerCache.over = 1;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\titemWithLeastDistance ?\n\t\t\t\tthis._rearrange( event, itemWithLeastDistance, null, true ) :\n\t\t\t\tthis._rearrange( event, null, this.containers[ innermostIndex ].element, true );\n\t\t\tthis._trigger( \"change\", event, this._uiHash() );\n\t\t\tthis.containers[ innermostIndex ]._trigger( \"change\", event, this._uiHash( this ) );\n\t\t\tthis.currentContainer = this.containers[ innermostIndex ];\n\n\t\t\t//Update the placeholder\n\t\t\tthis.options.placeholder.update( this.currentContainer, this.placeholder );\n\n\t\t\tthis.containers[ innermostIndex ]._trigger( \"over\", event, this._uiHash( this ) );\n\t\t\tthis.containers[ innermostIndex ].containerCache.over = 1;\n\t\t}\n\n\t},\n\n\t_createHelper: function( event ) {\n\n\t\tvar o = this.options,\n\t\t\thelper = $.isFunction( o.helper ) ?\n\t\t\t\t$( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) :\n\t\t\t\t( o.helper === \"clone\" ? this.currentItem.clone() : this.currentItem );\n\n\t\t//Add the helper to the DOM if that didn't happen already\n\t\tif ( !helper.parents( \"body\" ).length ) {\n\t\t\t$( o.appendTo !== \"parent\" ?\n\t\t\t\to.appendTo :\n\t\t\t\tthis.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] );\n\t\t}\n\n\t\tif ( helper[ 0 ] === this.currentItem[ 0 ] ) {\n\t\t\tthis._storedCSS = {\n\t\t\t\twidth: this.currentItem[ 0 ].style.width,\n\t\t\t\theight: this.currentItem[ 0 ].style.height,\n\t\t\t\tposition: this.currentItem.css( \"position\" ),\n\t\t\t\ttop: this.currentItem.css( \"top\" ),\n\t\t\t\tleft: this.currentItem.css( \"left\" )\n\t\t\t};\n\t\t}\n\n\t\tif ( !helper[ 0 ].style.width || o.forceHelperSize ) {\n\t\t\thelper.width( this.currentItem.width() );\n\t\t}\n\t\tif ( !helper[ 0 ].style.height || o.forceHelperSize ) {\n\t\t\thelper.height( this.currentItem.height() );\n\t\t}\n\n\t\treturn helper;\n\n\t},\n\n\t_adjustOffsetFromHelper: function( obj ) {\n\t\tif ( typeof obj === \"string\" ) {\n\t\t\tobj = obj.split( \" \" );\n\t\t}\n\t\tif ( $.isArray( obj ) ) {\n\t\t\tobj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };\n\t\t}\n\t\tif ( \"left\" in obj ) {\n\t\t\tthis.offset.click.left = obj.left + this.margins.left;\n\t\t}\n\t\tif ( \"right\" in obj ) {\n\t\t\tthis.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;\n\t\t}\n\t\tif ( \"top\" in obj ) {\n\t\t\tthis.offset.click.top = obj.top + this.margins.top;\n\t\t}\n\t\tif ( \"bottom\" in obj ) {\n\t\t\tthis.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;\n\t\t}\n\t},\n\n\t_getParentOffset: function() {\n\n\t\t//Get the offsetParent and cache its position\n\t\tthis.offsetParent = this.helper.offsetParent();\n\t\tvar po = this.offsetParent.offset();\n\n\t\t// This is a special case where we need to modify a offset calculated on start, since the\n\t\t// following happened:\n\t\t// 1. The position of the helper is absolute, so it's position is calculated based on the\n\t\t// next positioned parent\n\t\t// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't\n\t\t// the document, which means that the scroll is included in the initial calculation of the\n\t\t// offset of the parent, and never recalculated upon drag\n\t\tif ( this.cssPosition === \"absolute\" && this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {\n\t\t\tpo.left += this.scrollParent.scrollLeft();\n\t\t\tpo.top += this.scrollParent.scrollTop();\n\t\t}\n\n\t\t// This needs to be actually done for all browsers, since pageX/pageY includes this\n\t\t// information with an ugly IE fix\n\t\tif ( this.offsetParent[ 0 ] === this.document[ 0 ].body ||\n\t\t\t\t( this.offsetParent[ 0 ].tagName &&\n\t\t\t\tthis.offsetParent[ 0 ].tagName.toLowerCase() === \"html\" && $.ui.ie ) ) {\n\t\t\tpo = { top: 0, left: 0 };\n\t\t}\n\n\t\treturn {\n\t\t\ttop: po.top + ( parseInt( this.offsetParent.css( \"borderTopWidth\" ), 10 ) || 0 ),\n\t\t\tleft: po.left + ( parseInt( this.offsetParent.css( \"borderLeftWidth\" ), 10 ) || 0 )\n\t\t};\n\n\t},\n\n\t_getRelativeOffset: function() {\n\n\t\tif ( this.cssPosition === \"relative\" ) {\n\t\t\tvar p = this.currentItem.position();\n\t\t\treturn {\n\t\t\t\ttop: p.top - ( parseInt( this.helper.css( \"top\" ), 10 ) || 0 ) +\n\t\t\t\t\tthis.scrollParent.scrollTop(),\n\t\t\t\tleft: p.left - ( parseInt( this.helper.css( \"left\" ), 10 ) || 0 ) +\n\t\t\t\t\tthis.scrollParent.scrollLeft()\n\t\t\t};\n\t\t} else {\n\t\t\treturn { top: 0, left: 0 };\n\t\t}\n\n\t},\n\n\t_cacheMargins: function() {\n\t\tthis.margins = {\n\t\t\tleft: ( parseInt( this.currentItem.css( \"marginLeft\" ), 10 ) || 0 ),\n\t\t\ttop: ( parseInt( this.currentItem.css( \"marginTop\" ), 10 ) || 0 )\n\t\t};\n\t},\n\n\t_cacheHelperProportions: function() {\n\t\tthis.helperProportions = {\n\t\t\twidth: this.helper.outerWidth(),\n\t\t\theight: this.helper.outerHeight()\n\t\t};\n\t},\n\n\t_setContainment: function() {\n\n\t\tvar ce, co, over,\n\t\t\to = this.options;\n\t\tif ( o.containment === \"parent\" ) {\n\t\t\to.containment = this.helper[ 0 ].parentNode;\n\t\t}\n\t\tif ( o.containment === \"document\" || o.containment === \"window\" ) {\n\t\t\tthis.containment = [\n\t\t\t\t0 - this.offset.relative.left - this.offset.parent.left,\n\t\t\t\t0 - this.offset.relative.top - this.offset.parent.top,\n\t\t\t\to.containment === \"document\" ?\n\t\t\t\t\tthis.document.width() :\n\t\t\t\t\tthis.window.width() - this.helperProportions.width - this.margins.left,\n\t\t\t\t( o.containment === \"document\" ?\n\t\t\t\t\t( this.document.height() || document.body.parentNode.scrollHeight ) :\n\t\t\t\t\tthis.window.height() || this.document[ 0 ].body.parentNode.scrollHeight\n\t\t\t\t) - this.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t}\n\n\t\tif ( !( /^(document|window|parent)$/ ).test( o.containment ) ) {\n\t\t\tce = $( o.containment )[ 0 ];\n\t\t\tco = $( o.containment ).offset();\n\t\t\tover = ( $( ce ).css( \"overflow\" ) !== \"hidden\" );\n\n\t\t\tthis.containment = [\n\t\t\t\tco.left + ( parseInt( $( ce ).css( \"borderLeftWidth\" ), 10 ) || 0 ) +\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingLeft\" ), 10 ) || 0 ) - this.margins.left,\n\t\t\t\tco.top + ( parseInt( $( ce ).css( \"borderTopWidth\" ), 10 ) || 0 ) +\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingTop\" ), 10 ) || 0 ) - this.margins.top,\n\t\t\t\tco.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"borderLeftWidth\" ), 10 ) || 0 ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingRight\" ), 10 ) || 0 ) -\n\t\t\t\t\tthis.helperProportions.width - this.margins.left,\n\t\t\t\tco.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"borderTopWidth\" ), 10 ) || 0 ) -\n\t\t\t\t\t( parseInt( $( ce ).css( \"paddingBottom\" ), 10 ) || 0 ) -\n\t\t\t\t\tthis.helperProportions.height - this.margins.top\n\t\t\t];\n\t\t}\n\n\t},\n\n\t_convertPositionTo: function( d, pos ) {\n\n\t\tif ( !pos ) {\n\t\t\tpos = this.position;\n\t\t}\n\t\tvar mod = d === \"absolute\" ? 1 : -1,\n\t\t\tscroll = this.cssPosition === \"absolute\" &&\n\t\t\t\t!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?\n\t\t\t\t\tthis.offsetParent :\n\t\t\t\t\tthis.scrollParent,\n\t\t\tscrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.top\t+\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top * mod -\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollTop() :\n\t\t\t\t\t( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpos.left +\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left * mod +\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left * mod\t-\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 :\n\t\t\t\t\tscroll.scrollLeft() ) * mod )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_generatePosition: function( event ) {\n\n\t\tvar top, left,\n\t\t\to = this.options,\n\t\t\tpageX = event.pageX,\n\t\t\tpageY = event.pageY,\n\t\t\tscroll = this.cssPosition === \"absolute\" &&\n\t\t\t\t!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\t$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?\n\t\t\t\t\tthis.offsetParent :\n\t\t\t\t\tthis.scrollParent,\n\t\t\t\tscrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );\n\n\t\t// This is another very weird special case that only happens for relative elements:\n\t\t// 1. If the css position is relative\n\t\t// 2. and the scroll parent is the document or similar to the offset parent\n\t\t// we have to refresh the relative offset during the scroll so there are no jumps\n\t\tif ( this.cssPosition === \"relative\" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&\n\t\t\t\tthis.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) {\n\t\t\tthis.offset.relative = this._getRelativeOffset();\n\t\t}\n\n\t\t/*\n\t\t * - Position constraining -\n\t\t * Constrain the position to a mix of grid, containment.\n\t\t */\n\n\t\tif ( this.originalPosition ) { //If we are not dragging yet, we won't check for options\n\n\t\t\tif ( this.containment ) {\n\t\t\t\tif ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) {\n\t\t\t\t\tpageX = this.containment[ 0 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) {\n\t\t\t\t\tpageY = this.containment[ 1 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t\tif ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) {\n\t\t\t\t\tpageX = this.containment[ 2 ] + this.offset.click.left;\n\t\t\t\t}\n\t\t\t\tif ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) {\n\t\t\t\t\tpageY = this.containment[ 3 ] + this.offset.click.top;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( o.grid ) {\n\t\t\t\ttop = this.originalPageY + Math.round( ( pageY - this.originalPageY ) /\n\t\t\t\t\to.grid[ 1 ] ) * o.grid[ 1 ];\n\t\t\t\tpageY = this.containment ?\n\t\t\t\t\t( ( top - this.offset.click.top >= this.containment[ 1 ] &&\n\t\t\t\t\t\ttop - this.offset.click.top <= this.containment[ 3 ] ) ?\n\t\t\t\t\t\t\ttop :\n\t\t\t\t\t\t\t( ( top - this.offset.click.top >= this.containment[ 1 ] ) ?\n\t\t\t\t\t\t\t\ttop - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) :\n\t\t\t\t\t\t\t\ttop;\n\n\t\t\t\tleft = this.originalPageX + Math.round( ( pageX - this.originalPageX ) /\n\t\t\t\t\to.grid[ 0 ] ) * o.grid[ 0 ];\n\t\t\t\tpageX = this.containment ?\n\t\t\t\t\t( ( left - this.offset.click.left >= this.containment[ 0 ] &&\n\t\t\t\t\t\tleft - this.offset.click.left <= this.containment[ 2 ] ) ?\n\t\t\t\t\t\t\tleft :\n\t\t\t\t\t\t\t( ( left - this.offset.click.left >= this.containment[ 0 ] ) ?\n\t\t\t\t\t\t\t\tleft - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) :\n\t\t\t\t\t\t\t\tleft;\n\t\t\t}\n\n\t\t}\n\n\t\treturn {\n\t\t\ttop: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageY -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.top -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.top -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.top +\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollTop() :\n\t\t\t\t\t( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) )\n\t\t\t),\n\t\t\tleft: (\n\n\t\t\t\t// The absolute mouse position\n\t\t\t\tpageX -\n\n\t\t\t\t// Click offset (relative to the element)\n\t\t\t\tthis.offset.click.left -\n\n\t\t\t\t// Only for relative positioned nodes: Relative offset from element to offset parent\n\t\t\t\tthis.offset.relative.left -\n\n\t\t\t\t// The offsetParent's offset without borders (offset + border)\n\t\t\t\tthis.offset.parent.left +\n\t\t\t\t( ( this.cssPosition === \"fixed\" ?\n\t\t\t\t\t-this.scrollParent.scrollLeft() :\n\t\t\t\t\tscrollIsRootNode ? 0 : scroll.scrollLeft() ) )\n\t\t\t)\n\t\t};\n\n\t},\n\n\t_rearrange: function( event, i, a, hardRefresh ) {\n\n\t\ta ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) :\n\t\t\ti.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ],\n\t\t\t\t( this.direction === \"down\" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) );\n\n\t\t//Various things done here to improve the performance:\n\t\t// 1. we create a setTimeout, that calls refreshPositions\n\t\t// 2. on the instance, we have a counter variable, that get's higher after every append\n\t\t// 3. on the local scope, we copy the counter variable, and check in the timeout,\n\t\t// if it's still the same\n\t\t// 4. this lets only the last addition to the timeout stack through\n\t\tthis.counter = this.counter ? ++this.counter : 1;\n\t\tvar counter = this.counter;\n\n\t\tthis._delay( function() {\n\t\t\tif ( counter === this.counter ) {\n\n\t\t\t\t//Precompute after each DOM insertion, NOT on mousemove\n\t\t\t\tthis.refreshPositions( !hardRefresh );\n\t\t\t}\n\t\t} );\n\n\t},\n\n\t_clear: function( event, noPropagation ) {\n\n\t\tthis.reverting = false;\n\n\t\t// We delay all events that have to be triggered to after the point where the placeholder\n\t\t// has been removed and everything else normalized again\n\t\tvar i,\n\t\t\tdelayedTriggers = [];\n\n\t\t// We first have to update the dom position of the actual currentItem\n\t\t// Note: don't do it if the current item is already removed (by a user), or it gets\n\t\t// reappended (see #4088)\n\t\tif ( !this._noFinalSort && this.currentItem.parent().length ) {\n\t\t\tthis.placeholder.before( this.currentItem );\n\t\t}\n\t\tthis._noFinalSort = null;\n\n\t\tif ( this.helper[ 0 ] === this.currentItem[ 0 ] ) {\n\t\t\tfor ( i in this._storedCSS ) {\n\t\t\t\tif ( this._storedCSS[ i ] === \"auto\" || this._storedCSS[ i ] === \"static\" ) {\n\t\t\t\t\tthis._storedCSS[ i ] = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.currentItem.css( this._storedCSS );\n\t\t\tthis._removeClass( this.currentItem, \"ui-sortable-helper\" );\n\t\t} else {\n\t\t\tthis.currentItem.show();\n\t\t}\n\n\t\tif ( this.fromOutside && !noPropagation ) {\n\t\t\tdelayedTriggers.push( function( event ) {\n\t\t\t\tthis._trigger( \"receive\", event, this._uiHash( this.fromOutside ) );\n\t\t\t} );\n\t\t}\n\t\tif ( ( this.fromOutside ||\n\t\t\t\tthis.domPosition.prev !==\n\t\t\t\tthis.currentItem.prev().not( \".ui-sortable-helper\" )[ 0 ] ||\n\t\t\t\tthis.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) {\n\n\t\t\t// Trigger update callback if the DOM position has changed\n\t\t\tdelayedTriggers.push( function( event ) {\n\t\t\t\tthis._trigger( \"update\", event, this._uiHash() );\n\t\t\t} );\n\t\t}\n\n\t\t// Check if the items Container has Changed and trigger appropriate\n\t\t// events.\n\t\tif ( this !== this.currentContainer ) {\n\t\t\tif ( !noPropagation ) {\n\t\t\t\tdelayedTriggers.push( function( event ) {\n\t\t\t\t\tthis._trigger( \"remove\", event, this._uiHash() );\n\t\t\t\t} );\n\t\t\t\tdelayedTriggers.push( ( function( c ) {\n\t\t\t\t\treturn function( event ) {\n\t\t\t\t\t\tc._trigger( \"receive\", event, this._uiHash( this ) );\n\t\t\t\t\t};\n\t\t\t\t} ).call( this, this.currentContainer ) );\n\t\t\t\tdelayedTriggers.push( ( function( c ) {\n\t\t\t\t\treturn function( event ) {\n\t\t\t\t\t\tc._trigger( \"update\", event, this._uiHash( this ) );\n\t\t\t\t\t};\n\t\t\t\t} ).call( this, this.currentContainer ) );\n\t\t\t}\n\t\t}\n\n\t\t//Post events to containers\n\t\tfunction delayEvent( type, instance, container ) {\n\t\t\treturn function( event ) {\n\t\t\t\tcontainer._trigger( type, event, instance._uiHash( instance ) );\n\t\t\t};\n\t\t}\n\t\tfor ( i = this.containers.length - 1; i >= 0; i-- ) {\n\t\t\tif ( !noPropagation ) {\n\t\t\t\tdelayedTriggers.push( delayEvent( \"deactivate\", this, this.containers[ i ] ) );\n\t\t\t}\n\t\t\tif ( this.containers[ i ].containerCache.over ) {\n\t\t\t\tdelayedTriggers.push( delayEvent( \"out\", this, this.containers[ i ] ) );\n\t\t\t\tthis.containers[ i ].containerCache.over = 0;\n\t\t\t}\n\t\t}\n\n\t\t//Do what was originally in plugins\n\t\tif ( this.storedCursor ) {\n\t\t\tthis.document.find( \"body\" ).css( \"cursor\", this.storedCursor );\n\t\t\tthis.storedStylesheet.remove();\n\t\t}\n\t\tif ( this._storedOpacity ) {\n\t\t\tthis.helper.css( \"opacity\", this._storedOpacity );\n\t\t}\n\t\tif ( this._storedZIndex ) {\n\t\t\tthis.helper.css( \"zIndex\", this._storedZIndex === \"auto\" ? \"\" : this._storedZIndex );\n\t\t}\n\n\t\tthis.dragging = false;\n\n\t\tif ( !noPropagation ) {\n\t\t\tthis._trigger( \"beforeStop\", event, this._uiHash() );\n\t\t}\n\n\t\t//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,\n\t\t// it unbinds ALL events from the original node!\n\t\tthis.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );\n\n\t\tif ( !this.cancelHelperRemoval ) {\n\t\t\tif ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {\n\t\t\t\tthis.helper.remove();\n\t\t\t}\n\t\t\tthis.helper = null;\n\t\t}\n\n\t\tif ( !noPropagation ) {\n\t\t\tfor ( i = 0; i < delayedTriggers.length; i++ ) {\n\n\t\t\t\t// Trigger all delayed events\n\t\t\t\tdelayedTriggers[ i ].call( this, event );\n\t\t\t}\n\t\t\tthis._trigger( \"stop\", event, this._uiHash() );\n\t\t}\n\n\t\tthis.fromOutside = false;\n\t\treturn !this.cancelHelperRemoval;\n\n\t},\n\n\t_trigger: function() {\n\t\tif ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) {\n\t\t\tthis.cancel();\n\t\t}\n\t},\n\n\t_uiHash: function( _inst ) {\n\t\tvar inst = _inst || this;\n\t\treturn {\n\t\t\thelper: inst.helper,\n\t\t\tplaceholder: inst.placeholder || $( [] ),\n\t\t\tposition: inst.position,\n\t\t\toriginalPosition: inst.originalPosition,\n\t\t\toffset: inst.positionAbs,\n\t\t\titem: inst.currentItem,\n\t\t\tsender: _inst ? _inst.element : null\n\t\t};\n\t}\n\n} );\n\n\n/*!\n * jQuery UI Accordion 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Accordion\n//>>group: Widgets\n// jscs:disable maximumLineLength\n//>>description: Displays collapsible content panels for presenting information in a limited amount of space.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/accordion/\n//>>demos: http://jqueryui.com/accordion/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/accordion.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsAccordion = $.widget( \"ui.accordion\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tactive: 0,\n\t\tanimate: {},\n\t\tclasses: {\n\t\t\t\"ui-accordion-header\": \"ui-corner-top\",\n\t\t\t\"ui-accordion-header-collapsed\": \"ui-corner-all\",\n\t\t\t\"ui-accordion-content\": \"ui-corner-bottom\"\n\t\t},\n\t\tcollapsible: false,\n\t\tevent: \"click\",\n\t\theader: \"> li > :first-child, > :not(li):even\",\n\t\theightStyle: \"auto\",\n\t\ticons: {\n\t\t\tactiveHeader: \"ui-icon-triangle-1-s\",\n\t\t\theader: \"ui-icon-triangle-1-e\"\n\t\t},\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tbeforeActivate: null\n\t},\n\n\thideProps: {\n\t\tborderTopWidth: \"hide\",\n\t\tborderBottomWidth: \"hide\",\n\t\tpaddingTop: \"hide\",\n\t\tpaddingBottom: \"hide\",\n\t\theight: \"hide\"\n\t},\n\n\tshowProps: {\n\t\tborderTopWidth: \"show\",\n\t\tborderBottomWidth: \"show\",\n\t\tpaddingTop: \"show\",\n\t\tpaddingBottom: \"show\",\n\t\theight: \"show\"\n\t},\n\n\t_create: function() {\n\t\tvar options = this.options;\n\n\t\tthis.prevShow = this.prevHide = $();\n\t\tthis._addClass( \"ui-accordion\", \"ui-widget ui-helper-reset\" );\n\t\tthis.element.attr( \"role\", \"tablist\" );\n\n\t\t// Don't allow collapsible: false and active: false / null\n\t\tif ( !options.collapsible && ( options.active === false || options.active == null ) ) {\n\t\t\toptions.active = 0;\n\t\t}\n\n\t\tthis._processPanels();\n\n\t\t// handle negative values\n\t\tif ( options.active < 0 ) {\n\t\t\toptions.active += this.headers.length;\n\t\t}\n\t\tthis._refresh();\n\t},\n\n\t_getCreateEventData: function() {\n\t\treturn {\n\t\t\theader: this.active,\n\t\t\tpanel: !this.active.length ? $() : this.active.next()\n\t\t};\n\t},\n\n\t_createIcons: function() {\n\t\tvar icon, children,\n\t\t\ticons = this.options.icons;\n\n\t\tif ( icons ) {\n\t\t\ticon = $( \"<span>\" );\n\t\t\tthis._addClass( icon, \"ui-accordion-header-icon\", \"ui-icon \" + icons.header );\n\t\t\ticon.prependTo( this.headers );\n\t\t\tchildren = this.active.children( \".ui-accordion-header-icon\" );\n\t\t\tthis._removeClass( children, icons.header )\n\t\t\t\t._addClass( children, null, icons.activeHeader )\n\t\t\t\t._addClass( this.headers, \"ui-accordion-icons\" );\n\t\t}\n\t},\n\n\t_destroyIcons: function() {\n\t\tthis._removeClass( this.headers, \"ui-accordion-icons\" );\n\t\tthis.headers.children( \".ui-accordion-header-icon\" ).remove();\n\t},\n\n\t_destroy: function() {\n\t\tvar contents;\n\n\t\t// Clean up main element\n\t\tthis.element.removeAttr( \"role\" );\n\n\t\t// Clean up headers\n\t\tthis.headers\n\t\t\t.removeAttr( \"role aria-expanded aria-selected aria-controls tabIndex\" )\n\t\t\t.removeUniqueId();\n\n\t\tthis._destroyIcons();\n\n\t\t// Clean up content panels\n\t\tcontents = this.headers.next()\n\t\t\t.css( \"display\", \"\" )\n\t\t\t.removeAttr( \"role aria-hidden aria-labelledby\" )\n\t\t\t.removeUniqueId();\n\n\t\tif ( this.options.heightStyle !== \"content\" ) {\n\t\t\tcontents.css( \"height\", \"\" );\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"active\" ) {\n\n\t\t\t// _activate() will handle invalid values and update this.options\n\t\t\tthis._activate( value );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( key === \"event\" ) {\n\t\t\tif ( this.options.event ) {\n\t\t\t\tthis._off( this.headers, this.options.event );\n\t\t\t}\n\t\t\tthis._setupEvents( value );\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\t// Setting collapsible: false while collapsed; open first panel\n\t\tif ( key === \"collapsible\" && !value && this.options.active === false ) {\n\t\t\tthis._activate( 0 );\n\t\t}\n\n\t\tif ( key === \"icons\" ) {\n\t\t\tthis._destroyIcons();\n\t\t\tif ( value ) {\n\t\t\t\tthis._createIcons();\n\t\t\t}\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.element.attr( \"aria-disabled\", value );\n\n\t\t// Support: IE8 Only\n\t\t// #5332 / #6059 - opacity doesn't cascade to positioned elements in IE\n\t\t// so we need to add the disabled class to the headers and panels\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t\tthis._toggleClass( this.headers.add( this.headers.next() ), null, \"ui-state-disabled\",\n\t\t\t!!value );\n\t},\n\n\t_keydown: function( event ) {\n\t\tif ( event.altKey || event.ctrlKey ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar keyCode = $.ui.keyCode,\n\t\t\tlength = this.headers.length,\n\t\t\tcurrentIndex = this.headers.index( event.target ),\n\t\t\ttoFocus = false;\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase keyCode.RIGHT:\n\t\tcase keyCode.DOWN:\n\t\t\ttoFocus = this.headers[ ( currentIndex + 1 ) % length ];\n\t\t\tbreak;\n\t\tcase keyCode.LEFT:\n\t\tcase keyCode.UP:\n\t\t\ttoFocus = this.headers[ ( currentIndex - 1 + length ) % length ];\n\t\t\tbreak;\n\t\tcase keyCode.SPACE:\n\t\tcase keyCode.ENTER:\n\t\t\tthis._eventHandler( event );\n\t\t\tbreak;\n\t\tcase keyCode.HOME:\n\t\t\ttoFocus = this.headers[ 0 ];\n\t\t\tbreak;\n\t\tcase keyCode.END:\n\t\t\ttoFocus = this.headers[ length - 1 ];\n\t\t\tbreak;\n\t\t}\n\n\t\tif ( toFocus ) {\n\t\t\t$( event.target ).attr( \"tabIndex\", -1 );\n\t\t\t$( toFocus ).attr( \"tabIndex\", 0 );\n\t\t\t$( toFocus ).trigger( \"focus\" );\n\t\t\tevent.preventDefault();\n\t\t}\n\t},\n\n\t_panelKeyDown: function( event ) {\n\t\tif ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {\n\t\t\t$( event.currentTarget ).prev().trigger( \"focus\" );\n\t\t}\n\t},\n\n\trefresh: function() {\n\t\tvar options = this.options;\n\t\tthis._processPanels();\n\n\t\t// Was collapsed or no panel\n\t\tif ( ( options.active === false && options.collapsible === true ) ||\n\t\t\t\t!this.headers.length ) {\n\t\t\toptions.active = false;\n\t\t\tthis.active = $();\n\n\t\t// active false only when collapsible is true\n\t\t} else if ( options.active === false ) {\n\t\t\tthis._activate( 0 );\n\n\t\t// was active, but active panel is gone\n\t\t} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {\n\n\t\t\t// all remaining panel are disabled\n\t\t\tif ( this.headers.length === this.headers.find( \".ui-state-disabled\" ).length ) {\n\t\t\t\toptions.active = false;\n\t\t\t\tthis.active = $();\n\n\t\t\t// activate previous panel\n\t\t\t} else {\n\t\t\t\tthis._activate( Math.max( 0, options.active - 1 ) );\n\t\t\t}\n\n\t\t// was active, active panel still exists\n\t\t} else {\n\n\t\t\t// make sure active index is correct\n\t\t\toptions.active = this.headers.index( this.active );\n\t\t}\n\n\t\tthis._destroyIcons();\n\n\t\tthis._refresh();\n\t},\n\n\t_processPanels: function() {\n\t\tvar prevHeaders = this.headers,\n\t\t\tprevPanels = this.panels;\n\n\t\tthis.headers = this.element.find( this.options.header );\n\t\tthis._addClass( this.headers, \"ui-accordion-header ui-accordion-header-collapsed\",\n\t\t\t\"ui-state-default\" );\n\n\t\tthis.panels = this.headers.next().filter( \":not(.ui-accordion-content-active)\" ).hide();\n\t\tthis._addClass( this.panels, \"ui-accordion-content\", \"ui-helper-reset ui-widget-content\" );\n\n\t\t// Avoid memory leaks (#10056)\n\t\tif ( prevPanels ) {\n\t\t\tthis._off( prevHeaders.not( this.headers ) );\n\t\t\tthis._off( prevPanels.not( this.panels ) );\n\t\t}\n\t},\n\n\t_refresh: function() {\n\t\tvar maxHeight,\n\t\t\toptions = this.options,\n\t\t\theightStyle = options.heightStyle,\n\t\t\tparent = this.element.parent();\n\n\t\tthis.active = this._findActive( options.active );\n\t\tthis._addClass( this.active, \"ui-accordion-header-active\", \"ui-state-active\" )\n\t\t\t._removeClass( this.active, \"ui-accordion-header-collapsed\" );\n\t\tthis._addClass( this.active.next(), \"ui-accordion-content-active\" );\n\t\tthis.active.next().show();\n\n\t\tthis.headers\n\t\t\t.attr( \"role\", \"tab\" )\n\t\t\t.each( function() {\n\t\t\t\tvar header = $( this ),\n\t\t\t\t\theaderId = header.uniqueId().attr( \"id\" ),\n\t\t\t\t\tpanel = header.next(),\n\t\t\t\t\tpanelId = panel.uniqueId().attr( \"id\" );\n\t\t\t\theader.attr( \"aria-controls\", panelId );\n\t\t\t\tpanel.attr( \"aria-labelledby\", headerId );\n\t\t\t} )\n\t\t\t.next()\n\t\t\t\t.attr( \"role\", \"tabpanel\" );\n\n\t\tthis.headers\n\t\t\t.not( this.active )\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-selected\": \"false\",\n\t\t\t\t\t\"aria-expanded\": \"false\",\n\t\t\t\t\ttabIndex: -1\n\t\t\t\t} )\n\t\t\t\t.next()\n\t\t\t\t\t.attr( {\n\t\t\t\t\t\t\"aria-hidden\": \"true\"\n\t\t\t\t\t} )\n\t\t\t\t\t.hide();\n\n\t\t// Make sure at least one header is in the tab order\n\t\tif ( !this.active.length ) {\n\t\t\tthis.headers.eq( 0 ).attr( \"tabIndex\", 0 );\n\t\t} else {\n\t\t\tthis.active.attr( {\n\t\t\t\t\"aria-selected\": \"true\",\n\t\t\t\t\"aria-expanded\": \"true\",\n\t\t\t\ttabIndex: 0\n\t\t\t} )\n\t\t\t\t.next()\n\t\t\t\t\t.attr( {\n\t\t\t\t\t\t\"aria-hidden\": \"false\"\n\t\t\t\t\t} );\n\t\t}\n\n\t\tthis._createIcons();\n\n\t\tthis._setupEvents( options.event );\n\n\t\tif ( heightStyle === \"fill\" ) {\n\t\t\tmaxHeight = parent.height();\n\t\t\tthis.element.siblings( \":visible\" ).each( function() {\n\t\t\t\tvar elem = $( this ),\n\t\t\t\t\tposition = elem.css( \"position\" );\n\n\t\t\t\tif ( position === \"absolute\" || position === \"fixed\" ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmaxHeight -= elem.outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.headers.each( function() {\n\t\t\t\tmaxHeight -= $( this ).outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.headers.next()\n\t\t\t\t.each( function() {\n\t\t\t\t\t$( this ).height( Math.max( 0, maxHeight -\n\t\t\t\t\t\t$( this ).innerHeight() + $( this ).height() ) );\n\t\t\t\t} )\n\t\t\t\t.css( \"overflow\", \"auto\" );\n\t\t} else if ( heightStyle === \"auto\" ) {\n\t\t\tmaxHeight = 0;\n\t\t\tthis.headers.next()\n\t\t\t\t.each( function() {\n\t\t\t\t\tvar isVisible = $( this ).is( \":visible\" );\n\t\t\t\t\tif ( !isVisible ) {\n\t\t\t\t\t\t$( this ).show();\n\t\t\t\t\t}\n\t\t\t\t\tmaxHeight = Math.max( maxHeight, $( this ).css( \"height\", \"\" ).height() );\n\t\t\t\t\tif ( !isVisible ) {\n\t\t\t\t\t\t$( this ).hide();\n\t\t\t\t\t}\n\t\t\t\t} )\n\t\t\t\t.height( maxHeight );\n\t\t}\n\t},\n\n\t_activate: function( index ) {\n\t\tvar active = this._findActive( index )[ 0 ];\n\n\t\t// Trying to activate the already active panel\n\t\tif ( active === this.active[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Trying to collapse, simulate a click on the currently active header\n\t\tactive = active || this.active[ 0 ];\n\n\t\tthis._eventHandler( {\n\t\t\ttarget: active,\n\t\t\tcurrentTarget: active,\n\t\t\tpreventDefault: $.noop\n\t\t} );\n\t},\n\n\t_findActive: function( selector ) {\n\t\treturn typeof selector === \"number\" ? this.headers.eq( selector ) : $();\n\t},\n\n\t_setupEvents: function( event ) {\n\t\tvar events = {\n\t\t\tkeydown: \"_keydown\"\n\t\t};\n\t\tif ( event ) {\n\t\t\t$.each( event.split( \" \" ), function( index, eventName ) {\n\t\t\t\tevents[ eventName ] = \"_eventHandler\";\n\t\t\t} );\n\t\t}\n\n\t\tthis._off( this.headers.add( this.headers.next() ) );\n\t\tthis._on( this.headers, events );\n\t\tthis._on( this.headers.next(), { keydown: \"_panelKeyDown\" } );\n\t\tthis._hoverable( this.headers );\n\t\tthis._focusable( this.headers );\n\t},\n\n\t_eventHandler: function( event ) {\n\t\tvar activeChildren, clickedChildren,\n\t\t\toptions = this.options,\n\t\t\tactive = this.active,\n\t\t\tclicked = $( event.currentTarget ),\n\t\t\tclickedIsActive = clicked[ 0 ] === active[ 0 ],\n\t\t\tcollapsing = clickedIsActive && options.collapsible,\n\t\t\ttoShow = collapsing ? $() : clicked.next(),\n\t\t\ttoHide = active.next(),\n\t\t\teventData = {\n\t\t\t\toldHeader: active,\n\t\t\t\toldPanel: toHide,\n\t\t\t\tnewHeader: collapsing ? $() : clicked,\n\t\t\t\tnewPanel: toShow\n\t\t\t};\n\n\t\tevent.preventDefault();\n\n\t\tif (\n\n\t\t\t\t// click on active header, but not collapsible\n\t\t\t\t( clickedIsActive && !options.collapsible ) ||\n\n\t\t\t\t// allow canceling activation\n\t\t\t\t( this._trigger( \"beforeActivate\", event, eventData ) === false ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\toptions.active = collapsing ? false : this.headers.index( clicked );\n\n\t\t// When the call to ._toggle() comes after the class changes\n\t\t// it causes a very odd bug in IE 8 (see #6720)\n\t\tthis.active = clickedIsActive ? $() : clicked;\n\t\tthis._toggle( eventData );\n\n\t\t// Switch classes\n\t\t// corner classes on the previously active header stay after the animation\n\t\tthis._removeClass( active, \"ui-accordion-header-active\", \"ui-state-active\" );\n\t\tif ( options.icons ) {\n\t\t\tactiveChildren = active.children( \".ui-accordion-header-icon\" );\n\t\t\tthis._removeClass( activeChildren, null, options.icons.activeHeader )\n\t\t\t\t._addClass( activeChildren, null, options.icons.header );\n\t\t}\n\n\t\tif ( !clickedIsActive ) {\n\t\t\tthis._removeClass( clicked, \"ui-accordion-header-collapsed\" )\n\t\t\t\t._addClass( clicked, \"ui-accordion-header-active\", \"ui-state-active\" );\n\t\t\tif ( options.icons ) {\n\t\t\t\tclickedChildren = clicked.children( \".ui-accordion-header-icon\" );\n\t\t\t\tthis._removeClass( clickedChildren, null, options.icons.header )\n\t\t\t\t\t._addClass( clickedChildren, null, options.icons.activeHeader );\n\t\t\t}\n\n\t\t\tthis._addClass( clicked.next(), \"ui-accordion-content-active\" );\n\t\t}\n\t},\n\n\t_toggle: function( data ) {\n\t\tvar toShow = data.newPanel,\n\t\t\ttoHide = this.prevShow.length ? this.prevShow : data.oldPanel;\n\n\t\t// Handle activating a panel during the animation for another activation\n\t\tthis.prevShow.add( this.prevHide ).stop( true, true );\n\t\tthis.prevShow = toShow;\n\t\tthis.prevHide = toHide;\n\n\t\tif ( this.options.animate ) {\n\t\t\tthis._animate( toShow, toHide, data );\n\t\t} else {\n\t\t\ttoHide.hide();\n\t\t\ttoShow.show();\n\t\t\tthis._toggleComplete( data );\n\t\t}\n\n\t\ttoHide.attr( {\n\t\t\t\"aria-hidden\": \"true\"\n\t\t} );\n\t\ttoHide.prev().attr( {\n\t\t\t\"aria-selected\": \"false\",\n\t\t\t\"aria-expanded\": \"false\"\n\t\t} );\n\n\t\t// if we're switching panels, remove the old header from the tab order\n\t\t// if we're opening from collapsed state, remove the previous header from the tab order\n\t\t// if we're collapsing, then keep the collapsing header in the tab order\n\t\tif ( toShow.length && toHide.length ) {\n\t\t\ttoHide.prev().attr( {\n\t\t\t\t\"tabIndex\": -1,\n\t\t\t\t\"aria-expanded\": \"false\"\n\t\t\t} );\n\t\t} else if ( toShow.length ) {\n\t\t\tthis.headers.filter( function() {\n\t\t\t\treturn parseInt( $( this ).attr( \"tabIndex\" ), 10 ) === 0;\n\t\t\t} )\n\t\t\t\t.attr( \"tabIndex\", -1 );\n\t\t}\n\n\t\ttoShow\n\t\t\t.attr( \"aria-hidden\", \"false\" )\n\t\t\t.prev()\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-selected\": \"true\",\n\t\t\t\t\t\"aria-expanded\": \"true\",\n\t\t\t\t\ttabIndex: 0\n\t\t\t\t} );\n\t},\n\n\t_animate: function( toShow, toHide, data ) {\n\t\tvar total, easing, duration,\n\t\t\tthat = this,\n\t\t\tadjust = 0,\n\t\t\tboxSizing = toShow.css( \"box-sizing\" ),\n\t\t\tdown = toShow.length &&\n\t\t\t\t( !toHide.length || ( toShow.index() < toHide.index() ) ),\n\t\t\tanimate = this.options.animate || {},\n\t\t\toptions = down && animate.down || animate,\n\t\t\tcomplete = function() {\n\t\t\t\tthat._toggleComplete( data );\n\t\t\t};\n\n\t\tif ( typeof options === \"number\" ) {\n\t\t\tduration = options;\n\t\t}\n\t\tif ( typeof options === \"string\" ) {\n\t\t\teasing = options;\n\t\t}\n\n\t\t// fall back from options to animation in case of partial down settings\n\t\teasing = easing || options.easing || animate.easing;\n\t\tduration = duration || options.duration || animate.duration;\n\n\t\tif ( !toHide.length ) {\n\t\t\treturn toShow.animate( this.showProps, duration, easing, complete );\n\t\t}\n\t\tif ( !toShow.length ) {\n\t\t\treturn toHide.animate( this.hideProps, duration, easing, complete );\n\t\t}\n\n\t\ttotal = toShow.show().outerHeight();\n\t\ttoHide.animate( this.hideProps, {\n\t\t\tduration: duration,\n\t\t\teasing: easing,\n\t\t\tstep: function( now, fx ) {\n\t\t\t\tfx.now = Math.round( now );\n\t\t\t}\n\t\t} );\n\t\ttoShow\n\t\t\t.hide()\n\t\t\t.animate( this.showProps, {\n\t\t\t\tduration: duration,\n\t\t\t\teasing: easing,\n\t\t\t\tcomplete: complete,\n\t\t\t\tstep: function( now, fx ) {\n\t\t\t\t\tfx.now = Math.round( now );\n\t\t\t\t\tif ( fx.prop !== \"height\" ) {\n\t\t\t\t\t\tif ( boxSizing === \"content-box\" ) {\n\t\t\t\t\t\t\tadjust += fx.now;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if ( that.options.heightStyle !== \"content\" ) {\n\t\t\t\t\t\tfx.now = Math.round( total - toHide.outerHeight() - adjust );\n\t\t\t\t\t\tadjust = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t},\n\n\t_toggleComplete: function( data ) {\n\t\tvar toHide = data.oldPanel,\n\t\t\tprev = toHide.prev();\n\n\t\tthis._removeClass( toHide, \"ui-accordion-content-active\" );\n\t\tthis._removeClass( prev, \"ui-accordion-header-active\" )\n\t\t\t._addClass( prev, \"ui-accordion-header-collapsed\" );\n\n\t\t// Work around for rendering bug in IE (#5421)\n\t\tif ( toHide.length ) {\n\t\t\ttoHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;\n\t\t}\n\t\tthis._trigger( \"activate\", null, data );\n\t}\n} );\n\n\n/*!\n * jQuery UI Menu 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Menu\n//>>group: Widgets\n//>>description: Creates nestable menus.\n//>>docs: http://api.jqueryui.com/menu/\n//>>demos: http://jqueryui.com/menu/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/menu.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsMenu = $.widget( \"ui.menu\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<ul>\",\n\tdelay: 300,\n\toptions: {\n\t\ticons: {\n\t\t\tsubmenu: \"ui-icon-caret-1-e\"\n\t\t},\n\t\titems: \"> *\",\n\t\tmenus: \"ul\",\n\t\tposition: {\n\t\t\tmy: \"left top\",\n\t\t\tat: \"right top\"\n\t\t},\n\t\trole: \"menu\",\n\n\t\t// Callbacks\n\t\tblur: null,\n\t\tfocus: null,\n\t\tselect: null\n\t},\n\n\t_create: function() {\n\t\tthis.activeMenu = this.element;\n\n\t\t// Flag used to prevent firing of the click handler\n\t\t// as the event bubbles up through nested menus\n\t\tthis.mouseHandled = false;\n\t\tthis.element\n\t\t\t.uniqueId()\n\t\t\t.attr( {\n\t\t\t\trole: this.options.role,\n\t\t\t\ttabIndex: 0\n\t\t\t} );\n\n\t\tthis._addClass( \"ui-menu\", \"ui-widget ui-widget-content\" );\n\t\tthis._on( {\n\n\t\t\t// Prevent focus from sticking to links inside menu after clicking\n\t\t\t// them (focus should always stay on UL during navigation).\n\t\t\t\"mousedown .ui-menu-item\": function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t},\n\t\t\t\"click .ui-menu-item\": function( event ) {\n\t\t\t\tvar target = $( event.target );\n\t\t\t\tvar active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );\n\t\t\t\tif ( !this.mouseHandled && target.not( \".ui-state-disabled\" ).length ) {\n\t\t\t\t\tthis.select( event );\n\n\t\t\t\t\t// Only set the mouseHandled flag if the event will bubble, see #9469.\n\t\t\t\t\tif ( !event.isPropagationStopped() ) {\n\t\t\t\t\t\tthis.mouseHandled = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Open submenu on click\n\t\t\t\t\tif ( target.has( \".ui-menu\" ).length ) {\n\t\t\t\t\t\tthis.expand( event );\n\t\t\t\t\t} else if ( !this.element.is( \":focus\" ) &&\n\t\t\t\t\t\t\tactive.closest( \".ui-menu\" ).length ) {\n\n\t\t\t\t\t\t// Redirect focus to the menu\n\t\t\t\t\t\tthis.element.trigger( \"focus\", [ true ] );\n\n\t\t\t\t\t\t// If the active item is on the top level, let it stay active.\n\t\t\t\t\t\t// Otherwise, blur the active item since it is no longer visible.\n\t\t\t\t\t\tif ( this.active && this.active.parents( \".ui-menu\" ).length === 1 ) {\n\t\t\t\t\t\t\tclearTimeout( this.timer );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"mouseenter .ui-menu-item\": function( event ) {\n\n\t\t\t\t// Ignore mouse events while typeahead is active, see #10458.\n\t\t\t\t// Prevents focusing the wrong item when typeahead causes a scroll while the mouse\n\t\t\t\t// is over an item in the menu\n\t\t\t\tif ( this.previousFilter ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar actualTarget = $( event.target ).closest( \".ui-menu-item\" ),\n\t\t\t\t\ttarget = $( event.currentTarget );\n\n\t\t\t\t// Ignore bubbled events on parent items, see #11641\n\t\t\t\tif ( actualTarget[ 0 ] !== target[ 0 ] ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Remove ui-state-active class from siblings of the newly focused menu item\n\t\t\t\t// to avoid a jump caused by adjacent elements both having a class with a border\n\t\t\t\tthis._removeClass( target.siblings().children( \".ui-state-active\" ),\n\t\t\t\t\tnull, \"ui-state-active\" );\n\t\t\t\tthis.focus( event, target );\n\t\t\t},\n\t\t\tmouseleave: \"collapseAll\",\n\t\t\t\"mouseleave .ui-menu\": \"collapseAll\",\n\t\t\tfocus: function( event, keepActiveItem ) {\n\n\t\t\t\t// If there's already an active item, keep it active\n\t\t\t\t// If not, activate the first item\n\t\t\t\tvar item = this.active || this.element.find( this.options.items ).eq( 0 );\n\n\t\t\t\tif ( !keepActiveItem ) {\n\t\t\t\t\tthis.focus( event, item );\n\t\t\t\t}\n\t\t\t},\n\t\t\tblur: function( event ) {\n\t\t\t\tthis._delay( function() {\n\t\t\t\t\tvar notContained = !$.contains(\n\t\t\t\t\t\tthis.element[ 0 ],\n\t\t\t\t\t\t$.ui.safeActiveElement( this.document[ 0 ] )\n\t\t\t\t\t);\n\t\t\t\t\tif ( notContained ) {\n\t\t\t\t\t\tthis.collapseAll( event );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t},\n\t\t\tkeydown: \"_keydown\"\n\t\t} );\n\n\t\tthis.refresh();\n\n\t\t// Clicks outside of a menu collapse any open menus\n\t\tthis._on( this.document, {\n\t\t\tclick: function( event ) {\n\t\t\t\tif ( this._closeOnDocumentClick( event ) ) {\n\t\t\t\t\tthis.collapseAll( event );\n\t\t\t\t}\n\n\t\t\t\t// Reset the mouseHandled flag\n\t\t\t\tthis.mouseHandled = false;\n\t\t\t}\n\t\t} );\n\t},\n\n\t_destroy: function() {\n\t\tvar items = this.element.find( \".ui-menu-item\" )\n\t\t\t\t.removeAttr( \"role aria-disabled\" ),\n\t\t\tsubmenus = items.children( \".ui-menu-item-wrapper\" )\n\t\t\t\t.removeUniqueId()\n\t\t\t\t.removeAttr( \"tabIndex role aria-haspopup\" );\n\n\t\t// Destroy (sub)menus\n\t\tthis.element\n\t\t\t.removeAttr( \"aria-activedescendant\" )\n\t\t\t.find( \".ui-menu\" ).addBack()\n\t\t\t\t.removeAttr( \"role aria-labelledby aria-expanded aria-hidden aria-disabled \" +\n\t\t\t\t\t\"tabIndex\" )\n\t\t\t\t.removeUniqueId()\n\t\t\t\t.show();\n\n\t\tsubmenus.children().each( function() {\n\t\t\tvar elem = $( this );\n\t\t\tif ( elem.data( \"ui-menu-submenu-caret\" ) ) {\n\t\t\t\telem.remove();\n\t\t\t}\n\t\t} );\n\t},\n\n\t_keydown: function( event ) {\n\t\tvar match, prev, character, skip,\n\t\t\tpreventDefault = true;\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\tthis.previousPage( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\tthis.nextPage( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.HOME:\n\t\t\tthis._move( \"first\", \"first\", event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.END:\n\t\t\tthis._move( \"last\", \"last\", event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.UP:\n\t\t\tthis.previous( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.DOWN:\n\t\t\tthis.next( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.LEFT:\n\t\t\tthis.collapse( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.RIGHT:\n\t\t\tif ( this.active && !this.active.is( \".ui-state-disabled\" ) ) {\n\t\t\t\tthis.expand( event );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.ENTER:\n\t\tcase $.ui.keyCode.SPACE:\n\t\t\tthis._activate( event );\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.ESCAPE:\n\t\t\tthis.collapse( event );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpreventDefault = false;\n\t\t\tprev = this.previousFilter || \"\";\n\t\t\tskip = false;\n\n\t\t\t// Support number pad values\n\t\t\tcharacter = event.keyCode >= 96 && event.keyCode <= 105 ?\n\t\t\t\t( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );\n\n\t\t\tclearTimeout( this.filterTimer );\n\n\t\t\tif ( character === prev ) {\n\t\t\t\tskip = true;\n\t\t\t} else {\n\t\t\t\tcharacter = prev + character;\n\t\t\t}\n\n\t\t\tmatch = this._filterMenuItems( character );\n\t\t\tmatch = skip && match.index( this.active.next() ) !== -1 ?\n\t\t\t\tthis.active.nextAll( \".ui-menu-item\" ) :\n\t\t\t\tmatch;\n\n\t\t\t// If no matches on the current filter, reset to the last character pressed\n\t\t\t// to move down the menu to the first item that starts with that character\n\t\t\tif ( !match.length ) {\n\t\t\t\tcharacter = String.fromCharCode( event.keyCode );\n\t\t\t\tmatch = this._filterMenuItems( character );\n\t\t\t}\n\n\t\t\tif ( match.length ) {\n\t\t\t\tthis.focus( event, match );\n\t\t\t\tthis.previousFilter = character;\n\t\t\t\tthis.filterTimer = this._delay( function() {\n\t\t\t\t\tdelete this.previousFilter;\n\t\t\t\t}, 1000 );\n\t\t\t} else {\n\t\t\t\tdelete this.previousFilter;\n\t\t\t}\n\t\t}\n\n\t\tif ( preventDefault ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t},\n\n\t_activate: function( event ) {\n\t\tif ( this.active && !this.active.is( \".ui-state-disabled\" ) ) {\n\t\t\tif ( this.active.children( \"[aria-haspopup='true']\" ).length ) {\n\t\t\t\tthis.expand( event );\n\t\t\t} else {\n\t\t\t\tthis.select( event );\n\t\t\t}\n\t\t}\n\t},\n\n\trefresh: function() {\n\t\tvar menus, items, newSubmenus, newItems, newWrappers,\n\t\t\tthat = this,\n\t\t\ticon = this.options.icons.submenu,\n\t\t\tsubmenus = this.element.find( this.options.menus );\n\n\t\tthis._toggleClass( \"ui-menu-icons\", null, !!this.element.find( \".ui-icon\" ).length );\n\n\t\t// Initialize nested menus\n\t\tnewSubmenus = submenus.filter( \":not(.ui-menu)\" )\n\t\t\t.hide()\n\t\t\t.attr( {\n\t\t\t\trole: this.options.role,\n\t\t\t\t\"aria-hidden\": \"true\",\n\t\t\t\t\"aria-expanded\": \"false\"\n\t\t\t} )\n\t\t\t.each( function() {\n\t\t\t\tvar menu = $( this ),\n\t\t\t\t\titem = menu.prev(),\n\t\t\t\t\tsubmenuCaret = $( \"<span>\" ).data( \"ui-menu-submenu-caret\", true );\n\n\t\t\t\tthat._addClass( submenuCaret, \"ui-menu-icon\", \"ui-icon \" + icon );\n\t\t\t\titem\n\t\t\t\t\t.attr( \"aria-haspopup\", \"true\" )\n\t\t\t\t\t.prepend( submenuCaret );\n\t\t\t\tmenu.attr( \"aria-labelledby\", item.attr( \"id\" ) );\n\t\t\t} );\n\n\t\tthis._addClass( newSubmenus, \"ui-menu\", \"ui-widget ui-widget-content ui-front\" );\n\n\t\tmenus = submenus.add( this.element );\n\t\titems = menus.find( this.options.items );\n\n\t\t// Initialize menu-items containing spaces and/or dashes only as dividers\n\t\titems.not( \".ui-menu-item\" ).each( function() {\n\t\t\tvar item = $( this );\n\t\t\tif ( that._isDivider( item ) ) {\n\t\t\t\tthat._addClass( item, \"ui-menu-divider\", \"ui-widget-content\" );\n\t\t\t}\n\t\t} );\n\n\t\t// Don't refresh list items that are already adapted\n\t\tnewItems = items.not( \".ui-menu-item, .ui-menu-divider\" );\n\t\tnewWrappers = newItems.children()\n\t\t\t.not( \".ui-menu\" )\n\t\t\t\t.uniqueId()\n\t\t\t\t.attr( {\n\t\t\t\t\ttabIndex: -1,\n\t\t\t\t\trole: this._itemRole()\n\t\t\t\t} );\n\t\tthis._addClass( newItems, \"ui-menu-item\" )\n\t\t\t._addClass( newWrappers, \"ui-menu-item-wrapper\" );\n\n\t\t// Add aria-disabled attribute to any disabled menu item\n\t\titems.filter( \".ui-state-disabled\" ).attr( \"aria-disabled\", \"true\" );\n\n\t\t// If the active item has been removed, blur the menu\n\t\tif ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {\n\t\t\tthis.blur();\n\t\t}\n\t},\n\n\t_itemRole: function() {\n\t\treturn {\n\t\t\tmenu: \"menuitem\",\n\t\t\tlistbox: \"option\"\n\t\t}[ this.options.role ];\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"icons\" ) {\n\t\t\tvar icons = this.element.find( \".ui-menu-icon\" );\n\t\t\tthis._removeClass( icons, null, this.options.icons.submenu )\n\t\t\t\t._addClass( icons, null, value.submenu );\n\t\t}\n\t\tthis._super( key, value );\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.element.attr( \"aria-disabled\", String( value ) );\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t},\n\n\tfocus: function( event, item ) {\n\t\tvar nested, focused, activeParent;\n\t\tthis.blur( event, event && event.type === \"focus\" );\n\n\t\tthis._scrollIntoView( item );\n\n\t\tthis.active = item.first();\n\n\t\tfocused = this.active.children( \".ui-menu-item-wrapper\" );\n\t\tthis._addClass( focused, null, \"ui-state-active\" );\n\n\t\t// Only update aria-activedescendant if there's a role\n\t\t// otherwise we assume focus is managed elsewhere\n\t\tif ( this.options.role ) {\n\t\t\tthis.element.attr( \"aria-activedescendant\", focused.attr( \"id\" ) );\n\t\t}\n\n\t\t// Highlight active parent menu item, if any\n\t\tactiveParent = this.active\n\t\t\t.parent()\n\t\t\t\t.closest( \".ui-menu-item\" )\n\t\t\t\t\t.children( \".ui-menu-item-wrapper\" );\n\t\tthis._addClass( activeParent, null, \"ui-state-active\" );\n\n\t\tif ( event && event.type === \"keydown\" ) {\n\t\t\tthis._close();\n\t\t} else {\n\t\t\tthis.timer = this._delay( function() {\n\t\t\t\tthis._close();\n\t\t\t}, this.delay );\n\t\t}\n\n\t\tnested = item.children( \".ui-menu\" );\n\t\tif ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {\n\t\t\tthis._startOpening( nested );\n\t\t}\n\t\tthis.activeMenu = item.parent();\n\n\t\tthis._trigger( \"focus\", event, { item: item } );\n\t},\n\n\t_scrollIntoView: function( item ) {\n\t\tvar borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;\n\t\tif ( this._hasScroll() ) {\n\t\t\tborderTop = parseFloat( $.css( this.activeMenu[ 0 ], \"borderTopWidth\" ) ) || 0;\n\t\t\tpaddingTop = parseFloat( $.css( this.activeMenu[ 0 ], \"paddingTop\" ) ) || 0;\n\t\t\toffset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;\n\t\t\tscroll = this.activeMenu.scrollTop();\n\t\t\telementHeight = this.activeMenu.height();\n\t\t\titemHeight = item.outerHeight();\n\n\t\t\tif ( offset < 0 ) {\n\t\t\t\tthis.activeMenu.scrollTop( scroll + offset );\n\t\t\t} else if ( offset + itemHeight > elementHeight ) {\n\t\t\t\tthis.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );\n\t\t\t}\n\t\t}\n\t},\n\n\tblur: function( event, fromFocus ) {\n\t\tif ( !fromFocus ) {\n\t\t\tclearTimeout( this.timer );\n\t\t}\n\n\t\tif ( !this.active ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._removeClass( this.active.children( \".ui-menu-item-wrapper\" ),\n\t\t\tnull, \"ui-state-active\" );\n\n\t\tthis._trigger( \"blur\", event, { item: this.active } );\n\t\tthis.active = null;\n\t},\n\n\t_startOpening: function( submenu ) {\n\t\tclearTimeout( this.timer );\n\n\t\t// Don't open if already open fixes a Firefox bug that caused a .5 pixel\n\t\t// shift in the submenu position when mousing over the caret icon\n\t\tif ( submenu.attr( \"aria-hidden\" ) !== \"true\" ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.timer = this._delay( function() {\n\t\t\tthis._close();\n\t\t\tthis._open( submenu );\n\t\t}, this.delay );\n\t},\n\n\t_open: function( submenu ) {\n\t\tvar position = $.extend( {\n\t\t\tof: this.active\n\t\t}, this.options.position );\n\n\t\tclearTimeout( this.timer );\n\t\tthis.element.find( \".ui-menu\" ).not( submenu.parents( \".ui-menu\" ) )\n\t\t\t.hide()\n\t\t\t.attr( \"aria-hidden\", \"true\" );\n\n\t\tsubmenu\n\t\t\t.show()\n\t\t\t.removeAttr( \"aria-hidden\" )\n\t\t\t.attr( \"aria-expanded\", \"true\" )\n\t\t\t.position( position );\n\t},\n\n\tcollapseAll: function( event, all ) {\n\t\tclearTimeout( this.timer );\n\t\tthis.timer = this._delay( function() {\n\n\t\t\t// If we were passed an event, look for the submenu that contains the event\n\t\t\tvar currentMenu = all ? this.element :\n\t\t\t\t$( event && event.target ).closest( this.element.find( \".ui-menu\" ) );\n\n\t\t\t// If we found no valid submenu ancestor, use the main menu to close all\n\t\t\t// sub menus anyway\n\t\t\tif ( !currentMenu.length ) {\n\t\t\t\tcurrentMenu = this.element;\n\t\t\t}\n\n\t\t\tthis._close( currentMenu );\n\n\t\t\tthis.blur( event );\n\n\t\t\t// Work around active item staying active after menu is blurred\n\t\t\tthis._removeClass( currentMenu.find( \".ui-state-active\" ), null, \"ui-state-active\" );\n\n\t\t\tthis.activeMenu = currentMenu;\n\t\t}, this.delay );\n\t},\n\n\t// With no arguments, closes the currently active menu - if nothing is active\n\t// it closes all menus.  If passed an argument, it will search for menus BELOW\n\t_close: function( startMenu ) {\n\t\tif ( !startMenu ) {\n\t\t\tstartMenu = this.active ? this.active.parent() : this.element;\n\t\t}\n\n\t\tstartMenu.find( \".ui-menu\" )\n\t\t\t.hide()\n\t\t\t.attr( \"aria-hidden\", \"true\" )\n\t\t\t.attr( \"aria-expanded\", \"false\" );\n\t},\n\n\t_closeOnDocumentClick: function( event ) {\n\t\treturn !$( event.target ).closest( \".ui-menu\" ).length;\n\t},\n\n\t_isDivider: function( item ) {\n\n\t\t// Match hyphen, em dash, en dash\n\t\treturn !/[^\\-\\u2014\\u2013\\s]/.test( item.text() );\n\t},\n\n\tcollapse: function( event ) {\n\t\tvar newItem = this.active &&\n\t\t\tthis.active.parent().closest( \".ui-menu-item\", this.element );\n\t\tif ( newItem && newItem.length ) {\n\t\t\tthis._close();\n\t\t\tthis.focus( event, newItem );\n\t\t}\n\t},\n\n\texpand: function( event ) {\n\t\tvar newItem = this.active &&\n\t\t\tthis.active\n\t\t\t\t.children( \".ui-menu \" )\n\t\t\t\t\t.find( this.options.items )\n\t\t\t\t\t\t.first();\n\n\t\tif ( newItem && newItem.length ) {\n\t\t\tthis._open( newItem.parent() );\n\n\t\t\t// Delay so Firefox will not hide activedescendant change in expanding submenu from AT\n\t\t\tthis._delay( function() {\n\t\t\t\tthis.focus( event, newItem );\n\t\t\t} );\n\t\t}\n\t},\n\n\tnext: function( event ) {\n\t\tthis._move( \"next\", \"first\", event );\n\t},\n\n\tprevious: function( event ) {\n\t\tthis._move( \"prev\", \"last\", event );\n\t},\n\n\tisFirstItem: function() {\n\t\treturn this.active && !this.active.prevAll( \".ui-menu-item\" ).length;\n\t},\n\n\tisLastItem: function() {\n\t\treturn this.active && !this.active.nextAll( \".ui-menu-item\" ).length;\n\t},\n\n\t_move: function( direction, filter, event ) {\n\t\tvar next;\n\t\tif ( this.active ) {\n\t\t\tif ( direction === \"first\" || direction === \"last\" ) {\n\t\t\t\tnext = this.active\n\t\t\t\t\t[ direction === \"first\" ? \"prevAll\" : \"nextAll\" ]( \".ui-menu-item\" )\n\t\t\t\t\t.eq( -1 );\n\t\t\t} else {\n\t\t\t\tnext = this.active\n\t\t\t\t\t[ direction + \"All\" ]( \".ui-menu-item\" )\n\t\t\t\t\t.eq( 0 );\n\t\t\t}\n\t\t}\n\t\tif ( !next || !next.length || !this.active ) {\n\t\t\tnext = this.activeMenu.find( this.options.items )[ filter ]();\n\t\t}\n\n\t\tthis.focus( event, next );\n\t},\n\n\tnextPage: function( event ) {\n\t\tvar item, base, height;\n\n\t\tif ( !this.active ) {\n\t\t\tthis.next( event );\n\t\t\treturn;\n\t\t}\n\t\tif ( this.isLastItem() ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( this._hasScroll() ) {\n\t\t\tbase = this.active.offset().top;\n\t\t\theight = this.element.height();\n\t\t\tthis.active.nextAll( \".ui-menu-item\" ).each( function() {\n\t\t\t\titem = $( this );\n\t\t\t\treturn item.offset().top - base - height < 0;\n\t\t\t} );\n\n\t\t\tthis.focus( event, item );\n\t\t} else {\n\t\t\tthis.focus( event, this.activeMenu.find( this.options.items )\n\t\t\t\t[ !this.active ? \"first\" : \"last\" ]() );\n\t\t}\n\t},\n\n\tpreviousPage: function( event ) {\n\t\tvar item, base, height;\n\t\tif ( !this.active ) {\n\t\t\tthis.next( event );\n\t\t\treturn;\n\t\t}\n\t\tif ( this.isFirstItem() ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( this._hasScroll() ) {\n\t\t\tbase = this.active.offset().top;\n\t\t\theight = this.element.height();\n\t\t\tthis.active.prevAll( \".ui-menu-item\" ).each( function() {\n\t\t\t\titem = $( this );\n\t\t\t\treturn item.offset().top - base + height > 0;\n\t\t\t} );\n\n\t\t\tthis.focus( event, item );\n\t\t} else {\n\t\t\tthis.focus( event, this.activeMenu.find( this.options.items ).first() );\n\t\t}\n\t},\n\n\t_hasScroll: function() {\n\t\treturn this.element.outerHeight() < this.element.prop( \"scrollHeight\" );\n\t},\n\n\tselect: function( event ) {\n\n\t\t// TODO: It should never be possible to not have an active item at this\n\t\t// point, but the tests don't trigger mouseenter before click.\n\t\tthis.active = this.active || $( event.target ).closest( \".ui-menu-item\" );\n\t\tvar ui = { item: this.active };\n\t\tif ( !this.active.has( \".ui-menu\" ).length ) {\n\t\t\tthis.collapseAll( event, true );\n\t\t}\n\t\tthis._trigger( \"select\", event, ui );\n\t},\n\n\t_filterMenuItems: function( character ) {\n\t\tvar escapedCharacter = character.replace( /[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, \"\\\\$&\" ),\n\t\t\tregex = new RegExp( \"^\" + escapedCharacter, \"i\" );\n\n\t\treturn this.activeMenu\n\t\t\t.find( this.options.items )\n\n\t\t\t\t// Only match on items, not dividers or other content (#10571)\n\t\t\t\t.filter( \".ui-menu-item\" )\n\t\t\t\t\t.filter( function() {\n\t\t\t\t\t\treturn regex.test(\n\t\t\t\t\t\t\t$.trim( $( this ).children( \".ui-menu-item-wrapper\" ).text() ) );\n\t\t\t\t\t} );\n\t}\n} );\n\n\n/*!\n * jQuery UI Autocomplete 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Autocomplete\n//>>group: Widgets\n//>>description: Lists suggested words as the user is typing.\n//>>docs: http://api.jqueryui.com/autocomplete/\n//>>demos: http://jqueryui.com/autocomplete/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/autocomplete.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.autocomplete\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<input>\",\n\toptions: {\n\t\tappendTo: null,\n\t\tautoFocus: false,\n\t\tdelay: 300,\n\t\tminLength: 1,\n\t\tposition: {\n\t\t\tmy: \"left top\",\n\t\t\tat: \"left bottom\",\n\t\t\tcollision: \"none\"\n\t\t},\n\t\tsource: null,\n\n\t\t// Callbacks\n\t\tchange: null,\n\t\tclose: null,\n\t\tfocus: null,\n\t\topen: null,\n\t\tresponse: null,\n\t\tsearch: null,\n\t\tselect: null\n\t},\n\n\trequestIndex: 0,\n\tpending: 0,\n\n\t_create: function() {\n\n\t\t// Some browsers only repeat keydown events, not keypress events,\n\t\t// so we use the suppressKeyPress flag to determine if we've already\n\t\t// handled the keydown event. #7269\n\t\t// Unfortunately the code for & in keypress is the same as the up arrow,\n\t\t// so we use the suppressKeyPressRepeat flag to avoid handling keypress\n\t\t// events when we know the keydown event was used to modify the\n\t\t// search term. #7799\n\t\tvar suppressKeyPress, suppressKeyPressRepeat, suppressInput,\n\t\t\tnodeName = this.element[ 0 ].nodeName.toLowerCase(),\n\t\t\tisTextarea = nodeName === \"textarea\",\n\t\t\tisInput = nodeName === \"input\";\n\n\t\t// Textareas are always multi-line\n\t\t// Inputs are always single-line, even if inside a contentEditable element\n\t\t// IE also treats inputs as contentEditable\n\t\t// All other element types are determined by whether or not they're contentEditable\n\t\tthis.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );\n\n\t\tthis.valueMethod = this.element[ isTextarea || isInput ? \"val\" : \"text\" ];\n\t\tthis.isNewMenu = true;\n\n\t\tthis._addClass( \"ui-autocomplete-input\" );\n\t\tthis.element.attr( \"autocomplete\", \"off\" );\n\n\t\tthis._on( this.element, {\n\t\t\tkeydown: function( event ) {\n\t\t\t\tif ( this.element.prop( \"readOnly\" ) ) {\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tsuppressInput = true;\n\t\t\t\t\tsuppressKeyPressRepeat = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tsuppressKeyPress = false;\n\t\t\t\tsuppressInput = false;\n\t\t\t\tsuppressKeyPressRepeat = false;\n\t\t\t\tvar keyCode = $.ui.keyCode;\n\t\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase keyCode.PAGE_UP:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._move( \"previousPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.PAGE_DOWN:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._move( \"nextPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.UP:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._keyEvent( \"previous\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.DOWN:\n\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\tthis._keyEvent( \"next\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.ENTER:\n\n\t\t\t\t\t// when menu is open and has focus\n\t\t\t\t\tif ( this.menu.active ) {\n\n\t\t\t\t\t\t// #6055 - Opera still allows the keypress to occur\n\t\t\t\t\t\t// which causes forms to submit\n\t\t\t\t\t\tsuppressKeyPress = true;\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tthis.menu.select( event );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.TAB:\n\t\t\t\t\tif ( this.menu.active ) {\n\t\t\t\t\t\tthis.menu.select( event );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.ESCAPE:\n\t\t\t\t\tif ( this.menu.element.is( \":visible\" ) ) {\n\t\t\t\t\t\tif ( !this.isMultiLine ) {\n\t\t\t\t\t\t\tthis._value( this.term );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.close( event );\n\n\t\t\t\t\t\t// Different browsers have different default behavior for escape\n\t\t\t\t\t\t// Single press can mean undo or clear\n\t\t\t\t\t\t// Double press in IE means clear the whole form\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tsuppressKeyPressRepeat = true;\n\n\t\t\t\t\t// search timeout should be triggered before the input value is changed\n\t\t\t\t\tthis._searchTimeout( event );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tkeypress: function( event ) {\n\t\t\t\tif ( suppressKeyPress ) {\n\t\t\t\t\tsuppressKeyPress = false;\n\t\t\t\t\tif ( !this.isMultiLine || this.menu.element.is( \":visible\" ) ) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif ( suppressKeyPressRepeat ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Replicate some key handlers to allow them to repeat in Firefox and Opera\n\t\t\t\tvar keyCode = $.ui.keyCode;\n\t\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase keyCode.PAGE_UP:\n\t\t\t\t\tthis._move( \"previousPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.PAGE_DOWN:\n\t\t\t\t\tthis._move( \"nextPage\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.UP:\n\t\t\t\t\tthis._keyEvent( \"previous\", event );\n\t\t\t\t\tbreak;\n\t\t\t\tcase keyCode.DOWN:\n\t\t\t\t\tthis._keyEvent( \"next\", event );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tinput: function( event ) {\n\t\t\t\tif ( suppressInput ) {\n\t\t\t\t\tsuppressInput = false;\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._searchTimeout( event );\n\t\t\t},\n\t\t\tfocus: function() {\n\t\t\t\tthis.selectedItem = null;\n\t\t\t\tthis.previous = this._value();\n\t\t\t},\n\t\t\tblur: function( event ) {\n\t\t\t\tif ( this.cancelBlur ) {\n\t\t\t\t\tdelete this.cancelBlur;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tclearTimeout( this.searching );\n\t\t\t\tthis.close( event );\n\t\t\t\tthis._change( event );\n\t\t\t}\n\t\t} );\n\n\t\tthis._initSource();\n\t\tthis.menu = $( \"<ul>\" )\n\t\t\t.appendTo( this._appendTo() )\n\t\t\t.menu( {\n\n\t\t\t\t// disable ARIA support, the live region takes care of that\n\t\t\t\trole: null\n\t\t\t} )\n\t\t\t.hide()\n\t\t\t.menu( \"instance\" );\n\n\t\tthis._addClass( this.menu.element, \"ui-autocomplete\", \"ui-front\" );\n\t\tthis._on( this.menu.element, {\n\t\t\tmousedown: function( event ) {\n\n\t\t\t\t// prevent moving focus out of the text field\n\t\t\t\tevent.preventDefault();\n\n\t\t\t\t// IE doesn't prevent moving focus even with event.preventDefault()\n\t\t\t\t// so we set a flag to know when we should ignore the blur event\n\t\t\t\tthis.cancelBlur = true;\n\t\t\t\tthis._delay( function() {\n\t\t\t\t\tdelete this.cancelBlur;\n\n\t\t\t\t\t// Support: IE 8 only\n\t\t\t\t\t// Right clicking a menu item or selecting text from the menu items will\n\t\t\t\t\t// result in focus moving out of the input. However, we've already received\n\t\t\t\t\t// and ignored the blur event because of the cancelBlur flag set above. So\n\t\t\t\t\t// we restore focus to ensure that the menu closes properly based on the user's\n\t\t\t\t\t// next actions.\n\t\t\t\t\tif ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {\n\t\t\t\t\t\tthis.element.trigger( \"focus\" );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t},\n\t\t\tmenufocus: function( event, ui ) {\n\t\t\t\tvar label, item;\n\n\t\t\t\t// support: Firefox\n\t\t\t\t// Prevent accidental activation of menu items in Firefox (#7024 #9118)\n\t\t\t\tif ( this.isNewMenu ) {\n\t\t\t\t\tthis.isNewMenu = false;\n\t\t\t\t\tif ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {\n\t\t\t\t\t\tthis.menu.blur();\n\n\t\t\t\t\t\tthis.document.one( \"mousemove\", function() {\n\t\t\t\t\t\t\t$( event.target ).trigger( event.originalEvent );\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\titem = ui.item.data( \"ui-autocomplete-item\" );\n\t\t\t\tif ( false !== this._trigger( \"focus\", event, { item: item } ) ) {\n\n\t\t\t\t\t// use value to match what will end up in the input, if it was a key event\n\t\t\t\t\tif ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {\n\t\t\t\t\t\tthis._value( item.value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Announce the value in the liveRegion\n\t\t\t\tlabel = ui.item.attr( \"aria-label\" ) || item.value;\n\t\t\t\tif ( label && $.trim( label ).length ) {\n\t\t\t\t\tthis.liveRegion.children().hide();\n\t\t\t\t\t$( \"<div>\" ).text( label ).appendTo( this.liveRegion );\n\t\t\t\t}\n\t\t\t},\n\t\t\tmenuselect: function( event, ui ) {\n\t\t\t\tvar item = ui.item.data( \"ui-autocomplete-item\" ),\n\t\t\t\t\tprevious = this.previous;\n\n\t\t\t\t// Only trigger when focus was lost (click on menu)\n\t\t\t\tif ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {\n\t\t\t\t\tthis.element.trigger( \"focus\" );\n\t\t\t\t\tthis.previous = previous;\n\n\t\t\t\t\t// #6109 - IE triggers two focus events and the second\n\t\t\t\t\t// is asynchronous, so we need to reset the previous\n\t\t\t\t\t// term synchronously and asynchronously :-(\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tthis.previous = previous;\n\t\t\t\t\t\tthis.selectedItem = item;\n\t\t\t\t\t} );\n\t\t\t\t}\n\n\t\t\t\tif ( false !== this._trigger( \"select\", event, { item: item } ) ) {\n\t\t\t\t\tthis._value( item.value );\n\t\t\t\t}\n\n\t\t\t\t// reset the term after the select event\n\t\t\t\t// this allows custom select handling to work properly\n\t\t\t\tthis.term = this._value();\n\n\t\t\t\tthis.close( event );\n\t\t\t\tthis.selectedItem = item;\n\t\t\t}\n\t\t} );\n\n\t\tthis.liveRegion = $( \"<div>\", {\n\t\t\trole: \"status\",\n\t\t\t\"aria-live\": \"assertive\",\n\t\t\t\"aria-relevant\": \"additions\"\n\t\t} )\n\t\t\t.appendTo( this.document[ 0 ].body );\n\n\t\tthis._addClass( this.liveRegion, null, \"ui-helper-hidden-accessible\" );\n\n\t\t// Turning off autocomplete prevents the browser from remembering the\n\t\t// value when navigating through history, so we re-enable autocomplete\n\t\t// if the page is unloaded before the widget is destroyed. #7790\n\t\tthis._on( this.window, {\n\t\t\tbeforeunload: function() {\n\t\t\t\tthis.element.removeAttr( \"autocomplete\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_destroy: function() {\n\t\tclearTimeout( this.searching );\n\t\tthis.element.removeAttr( \"autocomplete\" );\n\t\tthis.menu.element.remove();\n\t\tthis.liveRegion.remove();\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tthis._super( key, value );\n\t\tif ( key === \"source\" ) {\n\t\t\tthis._initSource();\n\t\t}\n\t\tif ( key === \"appendTo\" ) {\n\t\t\tthis.menu.element.appendTo( this._appendTo() );\n\t\t}\n\t\tif ( key === \"disabled\" && value && this.xhr ) {\n\t\t\tthis.xhr.abort();\n\t\t}\n\t},\n\n\t_isEventTargetInWidget: function( event ) {\n\t\tvar menuElement = this.menu.element[ 0 ];\n\n\t\treturn event.target === this.element[ 0 ] ||\n\t\t\tevent.target === menuElement ||\n\t\t\t$.contains( menuElement, event.target );\n\t},\n\n\t_closeOnClickOutside: function( event ) {\n\t\tif ( !this._isEventTargetInWidget( event ) ) {\n\t\t\tthis.close();\n\t\t}\n\t},\n\n\t_appendTo: function() {\n\t\tvar element = this.options.appendTo;\n\n\t\tif ( element ) {\n\t\t\telement = element.jquery || element.nodeType ?\n\t\t\t\t$( element ) :\n\t\t\t\tthis.document.find( element ).eq( 0 );\n\t\t}\n\n\t\tif ( !element || !element[ 0 ] ) {\n\t\t\telement = this.element.closest( \".ui-front, dialog\" );\n\t\t}\n\n\t\tif ( !element.length ) {\n\t\t\telement = this.document[ 0 ].body;\n\t\t}\n\n\t\treturn element;\n\t},\n\n\t_initSource: function() {\n\t\tvar array, url,\n\t\t\tthat = this;\n\t\tif ( $.isArray( this.options.source ) ) {\n\t\t\tarray = this.options.source;\n\t\t\tthis.source = function( request, response ) {\n\t\t\t\tresponse( $.ui.autocomplete.filter( array, request.term ) );\n\t\t\t};\n\t\t} else if ( typeof this.options.source === \"string\" ) {\n\t\t\turl = this.options.source;\n\t\t\tthis.source = function( request, response ) {\n\t\t\t\tif ( that.xhr ) {\n\t\t\t\t\tthat.xhr.abort();\n\t\t\t\t}\n\t\t\t\tthat.xhr = $.ajax( {\n\t\t\t\t\turl: url,\n\t\t\t\t\tdata: request,\n\t\t\t\t\tdataType: \"json\",\n\t\t\t\t\tsuccess: function( data ) {\n\t\t\t\t\t\tresponse( data );\n\t\t\t\t\t},\n\t\t\t\t\terror: function() {\n\t\t\t\t\t\tresponse( [] );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t};\n\t\t} else {\n\t\t\tthis.source = this.options.source;\n\t\t}\n\t},\n\n\t_searchTimeout: function( event ) {\n\t\tclearTimeout( this.searching );\n\t\tthis.searching = this._delay( function() {\n\n\t\t\t// Search if the value has changed, or if the user retypes the same value (see #7434)\n\t\t\tvar equalValues = this.term === this._value(),\n\t\t\t\tmenuVisible = this.menu.element.is( \":visible\" ),\n\t\t\t\tmodifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;\n\n\t\t\tif ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {\n\t\t\t\tthis.selectedItem = null;\n\t\t\t\tthis.search( null, event );\n\t\t\t}\n\t\t}, this.options.delay );\n\t},\n\n\tsearch: function( value, event ) {\n\t\tvalue = value != null ? value : this._value();\n\n\t\t// Always save the actual value, not the one passed as an argument\n\t\tthis.term = this._value();\n\n\t\tif ( value.length < this.options.minLength ) {\n\t\t\treturn this.close( event );\n\t\t}\n\n\t\tif ( this._trigger( \"search\", event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn this._search( value );\n\t},\n\n\t_search: function( value ) {\n\t\tthis.pending++;\n\t\tthis._addClass( \"ui-autocomplete-loading\" );\n\t\tthis.cancelSearch = false;\n\n\t\tthis.source( { term: value }, this._response() );\n\t},\n\n\t_response: function() {\n\t\tvar index = ++this.requestIndex;\n\n\t\treturn $.proxy( function( content ) {\n\t\t\tif ( index === this.requestIndex ) {\n\t\t\t\tthis.__response( content );\n\t\t\t}\n\n\t\t\tthis.pending--;\n\t\t\tif ( !this.pending ) {\n\t\t\t\tthis._removeClass( \"ui-autocomplete-loading\" );\n\t\t\t}\n\t\t}, this );\n\t},\n\n\t__response: function( content ) {\n\t\tif ( content ) {\n\t\t\tcontent = this._normalize( content );\n\t\t}\n\t\tthis._trigger( \"response\", null, { content: content } );\n\t\tif ( !this.options.disabled && content && content.length && !this.cancelSearch ) {\n\t\t\tthis._suggest( content );\n\t\t\tthis._trigger( \"open\" );\n\t\t} else {\n\n\t\t\t// use ._close() instead of .close() so we don't cancel future searches\n\t\t\tthis._close();\n\t\t}\n\t},\n\n\tclose: function( event ) {\n\t\tthis.cancelSearch = true;\n\t\tthis._close( event );\n\t},\n\n\t_close: function( event ) {\n\n\t\t// Remove the handler that closes the menu on outside clicks\n\t\tthis._off( this.document, \"mousedown\" );\n\n\t\tif ( this.menu.element.is( \":visible\" ) ) {\n\t\t\tthis.menu.element.hide();\n\t\t\tthis.menu.blur();\n\t\t\tthis.isNewMenu = true;\n\t\t\tthis._trigger( \"close\", event );\n\t\t}\n\t},\n\n\t_change: function( event ) {\n\t\tif ( this.previous !== this._value() ) {\n\t\t\tthis._trigger( \"change\", event, { item: this.selectedItem } );\n\t\t}\n\t},\n\n\t_normalize: function( items ) {\n\n\t\t// assume all items have the right format when the first item is complete\n\t\tif ( items.length && items[ 0 ].label && items[ 0 ].value ) {\n\t\t\treturn items;\n\t\t}\n\t\treturn $.map( items, function( item ) {\n\t\t\tif ( typeof item === \"string\" ) {\n\t\t\t\treturn {\n\t\t\t\t\tlabel: item,\n\t\t\t\t\tvalue: item\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn $.extend( {}, item, {\n\t\t\t\tlabel: item.label || item.value,\n\t\t\t\tvalue: item.value || item.label\n\t\t\t} );\n\t\t} );\n\t},\n\n\t_suggest: function( items ) {\n\t\tvar ul = this.menu.element.empty();\n\t\tthis._renderMenu( ul, items );\n\t\tthis.isNewMenu = true;\n\t\tthis.menu.refresh();\n\n\t\t// Size and position menu\n\t\tul.show();\n\t\tthis._resizeMenu();\n\t\tul.position( $.extend( {\n\t\t\tof: this.element\n\t\t}, this.options.position ) );\n\n\t\tif ( this.options.autoFocus ) {\n\t\t\tthis.menu.next();\n\t\t}\n\n\t\t// Listen for interactions outside of the widget (#6642)\n\t\tthis._on( this.document, {\n\t\t\tmousedown: \"_closeOnClickOutside\"\n\t\t} );\n\t},\n\n\t_resizeMenu: function() {\n\t\tvar ul = this.menu.element;\n\t\tul.outerWidth( Math.max(\n\n\t\t\t// Firefox wraps long text (possibly a rounding bug)\n\t\t\t// so we add 1px to avoid the wrapping (#7513)\n\t\t\tul.width( \"\" ).outerWidth() + 1,\n\t\t\tthis.element.outerWidth()\n\t\t) );\n\t},\n\n\t_renderMenu: function( ul, items ) {\n\t\tvar that = this;\n\t\t$.each( items, function( index, item ) {\n\t\t\tthat._renderItemData( ul, item );\n\t\t} );\n\t},\n\n\t_renderItemData: function( ul, item ) {\n\t\treturn this._renderItem( ul, item ).data( \"ui-autocomplete-item\", item );\n\t},\n\n\t_renderItem: function( ul, item ) {\n\t\treturn $( \"<li>\" )\n\t\t\t.append( $( \"<div>\" ).text( item.label ) )\n\t\t\t.appendTo( ul );\n\t},\n\n\t_move: function( direction, event ) {\n\t\tif ( !this.menu.element.is( \":visible\" ) ) {\n\t\t\tthis.search( null, event );\n\t\t\treturn;\n\t\t}\n\t\tif ( this.menu.isFirstItem() && /^previous/.test( direction ) ||\n\t\t\t\tthis.menu.isLastItem() && /^next/.test( direction ) ) {\n\n\t\t\tif ( !this.isMultiLine ) {\n\t\t\t\tthis._value( this.term );\n\t\t\t}\n\n\t\t\tthis.menu.blur();\n\t\t\treturn;\n\t\t}\n\t\tthis.menu[ direction ]( event );\n\t},\n\n\twidget: function() {\n\t\treturn this.menu.element;\n\t},\n\n\t_value: function() {\n\t\treturn this.valueMethod.apply( this.element, arguments );\n\t},\n\n\t_keyEvent: function( keyEvent, event ) {\n\t\tif ( !this.isMultiLine || this.menu.element.is( \":visible\" ) ) {\n\t\t\tthis._move( keyEvent, event );\n\n\t\t\t// Prevents moving cursor to beginning/end of the text field in some browsers\n\t\t\tevent.preventDefault();\n\t\t}\n\t},\n\n\t// Support: Chrome <=50\n\t// We should be able to just use this.element.prop( \"isContentEditable\" )\n\t// but hidden elements always report false in Chrome.\n\t// https://code.google.com/p/chromium/issues/detail?id=313082\n\t_isContentEditable: function( element ) {\n\t\tif ( !element.length ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar editable = element.prop( \"contentEditable\" );\n\n\t\tif ( editable === \"inherit\" ) {\n\t\t  return this._isContentEditable( element.parent() );\n\t\t}\n\n\t\treturn editable === \"true\";\n\t}\n} );\n\n$.extend( $.ui.autocomplete, {\n\tescapeRegex: function( value ) {\n\t\treturn value.replace( /[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, \"\\\\$&\" );\n\t},\n\tfilter: function( array, term ) {\n\t\tvar matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), \"i\" );\n\t\treturn $.grep( array, function( value ) {\n\t\t\treturn matcher.test( value.label || value.value || value );\n\t\t} );\n\t}\n} );\n\n// Live region extension, adding a `messages` option\n// NOTE: This is an experimental API. We are still investigating\n// a full solution for string manipulation and internationalization.\n$.widget( \"ui.autocomplete\", $.ui.autocomplete, {\n\toptions: {\n\t\tmessages: {\n\t\t\tnoResults: \"No search results.\",\n\t\t\tresults: function( amount ) {\n\t\t\t\treturn amount + ( amount > 1 ? \" results are\" : \" result is\" ) +\n\t\t\t\t\t\" available, use up and down arrow keys to navigate.\";\n\t\t\t}\n\t\t}\n\t},\n\n\t__response: function( content ) {\n\t\tvar message;\n\t\tthis._superApply( arguments );\n\t\tif ( this.options.disabled || this.cancelSearch ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( content && content.length ) {\n\t\t\tmessage = this.options.messages.results( content.length );\n\t\t} else {\n\t\t\tmessage = this.options.messages.noResults;\n\t\t}\n\t\tthis.liveRegion.children().hide();\n\t\t$( \"<div>\" ).text( message ).appendTo( this.liveRegion );\n\t}\n} );\n\nvar widgetsAutocomplete = $.ui.autocomplete;\n\n\n/*!\n * jQuery UI Controlgroup 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Controlgroup\n//>>group: Widgets\n//>>description: Visually groups form control widgets\n//>>docs: http://api.jqueryui.com/controlgroup/\n//>>demos: http://jqueryui.com/controlgroup/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/controlgroup.css\n//>>css.theme: ../../themes/base/theme.css\n\n\nvar controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g;\n\nvar widgetsControlgroup = $.widget( \"ui.controlgroup\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<div>\",\n\toptions: {\n\t\tdirection: \"horizontal\",\n\t\tdisabled: null,\n\t\tonlyVisible: true,\n\t\titems: {\n\t\t\t\"button\": \"input[type=button], input[type=submit], input[type=reset], button, a\",\n\t\t\t\"controlgroupLabel\": \".ui-controlgroup-label\",\n\t\t\t\"checkboxradio\": \"input[type='checkbox'], input[type='radio']\",\n\t\t\t\"selectmenu\": \"select\",\n\t\t\t\"spinner\": \".ui-spinner-input\"\n\t\t}\n\t},\n\n\t_create: function() {\n\t\tthis._enhance();\n\t},\n\n\t// To support the enhanced option in jQuery Mobile, we isolate DOM manipulation\n\t_enhance: function() {\n\t\tthis.element.attr( \"role\", \"toolbar\" );\n\t\tthis.refresh();\n\t},\n\n\t_destroy: function() {\n\t\tthis._callChildMethod( \"destroy\" );\n\t\tthis.childWidgets.removeData( \"ui-controlgroup-data\" );\n\t\tthis.element.removeAttr( \"role\" );\n\t\tif ( this.options.items.controlgroupLabel ) {\n\t\t\tthis.element\n\t\t\t\t.find( this.options.items.controlgroupLabel )\n\t\t\t\t.find( \".ui-controlgroup-label-contents\" )\n\t\t\t\t.contents().unwrap();\n\t\t}\n\t},\n\n\t_initWidgets: function() {\n\t\tvar that = this,\n\t\t\tchildWidgets = [];\n\n\t\t// First we iterate over each of the items options\n\t\t$.each( this.options.items, function( widget, selector ) {\n\t\t\tvar labels;\n\t\t\tvar options = {};\n\n\t\t\t// Make sure the widget has a selector set\n\t\t\tif ( !selector ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( widget === \"controlgroupLabel\" ) {\n\t\t\t\tlabels = that.element.find( selector );\n\t\t\t\tlabels.each( function() {\n\t\t\t\t\tvar element = $( this );\n\n\t\t\t\t\tif ( element.children( \".ui-controlgroup-label-contents\" ).length ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\telement.contents()\n\t\t\t\t\t\t.wrapAll( \"<span class='ui-controlgroup-label-contents'></span>\" );\n\t\t\t\t} );\n\t\t\t\tthat._addClass( labels, null, \"ui-widget ui-widget-content ui-state-default\" );\n\t\t\t\tchildWidgets = childWidgets.concat( labels.get() );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Make sure the widget actually exists\n\t\t\tif ( !$.fn[ widget ] ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// We assume everything is in the middle to start because we can't determine\n\t\t\t// first / last elements until all enhancments are done.\n\t\t\tif ( that[ \"_\" + widget + \"Options\" ] ) {\n\t\t\t\toptions = that[ \"_\" + widget + \"Options\" ]( \"middle\" );\n\t\t\t} else {\n\t\t\t\toptions = { classes: {} };\n\t\t\t}\n\n\t\t\t// Find instances of this widget inside controlgroup and init them\n\t\t\tthat.element\n\t\t\t\t.find( selector )\n\t\t\t\t.each( function() {\n\t\t\t\t\tvar element = $( this );\n\t\t\t\t\tvar instance = element[ widget ]( \"instance\" );\n\n\t\t\t\t\t// We need to clone the default options for this type of widget to avoid\n\t\t\t\t\t// polluting the variable options which has a wider scope than a single widget.\n\t\t\t\t\tvar instanceOptions = $.widget.extend( {}, options );\n\n\t\t\t\t\t// If the button is the child of a spinner ignore it\n\t\t\t\t\t// TODO: Find a more generic solution\n\t\t\t\t\tif ( widget === \"button\" && element.parent( \".ui-spinner\" ).length ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create the widget if it doesn't exist\n\t\t\t\t\tif ( !instance ) {\n\t\t\t\t\t\tinstance = element[ widget ]()[ widget ]( \"instance\" );\n\t\t\t\t\t}\n\t\t\t\t\tif ( instance ) {\n\t\t\t\t\t\tinstanceOptions.classes =\n\t\t\t\t\t\t\tthat._resolveClassesValues( instanceOptions.classes, instance );\n\t\t\t\t\t}\n\t\t\t\t\telement[ widget ]( instanceOptions );\n\n\t\t\t\t\t// Store an instance of the controlgroup to be able to reference\n\t\t\t\t\t// from the outermost element for changing options and refresh\n\t\t\t\t\tvar widgetElement = element[ widget ]( \"widget\" );\n\t\t\t\t\t$.data( widgetElement[ 0 ], \"ui-controlgroup-data\",\n\t\t\t\t\t\tinstance ? instance : element[ widget ]( \"instance\" ) );\n\n\t\t\t\t\tchildWidgets.push( widgetElement[ 0 ] );\n\t\t\t\t} );\n\t\t} );\n\n\t\tthis.childWidgets = $( $.unique( childWidgets ) );\n\t\tthis._addClass( this.childWidgets, \"ui-controlgroup-item\" );\n\t},\n\n\t_callChildMethod: function( method ) {\n\t\tthis.childWidgets.each( function() {\n\t\t\tvar element = $( this ),\n\t\t\t\tdata = element.data( \"ui-controlgroup-data\" );\n\t\t\tif ( data && data[ method ] ) {\n\t\t\t\tdata[ method ]();\n\t\t\t}\n\t\t} );\n\t},\n\n\t_updateCornerClass: function( element, position ) {\n\t\tvar remove = \"ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all\";\n\t\tvar add = this._buildSimpleOptions( position, \"label\" ).classes.label;\n\n\t\tthis._removeClass( element, null, remove );\n\t\tthis._addClass( element, null, add );\n\t},\n\n\t_buildSimpleOptions: function( position, key ) {\n\t\tvar direction = this.options.direction === \"vertical\";\n\t\tvar result = {\n\t\t\tclasses: {}\n\t\t};\n\t\tresult.classes[ key ] = {\n\t\t\t\"middle\": \"\",\n\t\t\t\"first\": \"ui-corner-\" + ( direction ? \"top\" : \"left\" ),\n\t\t\t\"last\": \"ui-corner-\" + ( direction ? \"bottom\" : \"right\" ),\n\t\t\t\"only\": \"ui-corner-all\"\n\t\t}[ position ];\n\n\t\treturn result;\n\t},\n\n\t_spinnerOptions: function( position ) {\n\t\tvar options = this._buildSimpleOptions( position, \"ui-spinner\" );\n\n\t\toptions.classes[ \"ui-spinner-up\" ] = \"\";\n\t\toptions.classes[ \"ui-spinner-down\" ] = \"\";\n\n\t\treturn options;\n\t},\n\n\t_buttonOptions: function( position ) {\n\t\treturn this._buildSimpleOptions( position, \"ui-button\" );\n\t},\n\n\t_checkboxradioOptions: function( position ) {\n\t\treturn this._buildSimpleOptions( position, \"ui-checkboxradio-label\" );\n\t},\n\n\t_selectmenuOptions: function( position ) {\n\t\tvar direction = this.options.direction === \"vertical\";\n\t\treturn {\n\t\t\twidth: direction ? \"auto\" : false,\n\t\t\tclasses: {\n\t\t\t\tmiddle: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": \"\",\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"\"\n\t\t\t\t},\n\t\t\t\tfirst: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": \"ui-corner-\" + ( direction ? \"top\" : \"tl\" ),\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-\" + ( direction ? \"top\" : \"left\" )\n\t\t\t\t},\n\t\t\t\tlast: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": direction ? \"\" : \"ui-corner-tr\",\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-\" + ( direction ? \"bottom\" : \"right\" )\n\t\t\t\t},\n\t\t\t\tonly: {\n\t\t\t\t\t\"ui-selectmenu-button-open\": \"ui-corner-top\",\n\t\t\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-all\"\n\t\t\t\t}\n\n\t\t\t}[ position ]\n\t\t};\n\t},\n\n\t_resolveClassesValues: function( classes, instance ) {\n\t\tvar result = {};\n\t\t$.each( classes, function( key ) {\n\t\t\tvar current = instance.options.classes[ key ] || \"\";\n\t\t\tcurrent = $.trim( current.replace( controlgroupCornerRegex, \"\" ) );\n\t\t\tresult[ key ] = ( current + \" \" + classes[ key ] ).replace( /\\s+/g, \" \" );\n\t\t} );\n\t\treturn result;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"direction\" ) {\n\t\t\tthis._removeClass( \"ui-controlgroup-\" + this.options.direction );\n\t\t}\n\n\t\tthis._super( key, value );\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._callChildMethod( value ? \"disable\" : \"enable\" );\n\t\t\treturn;\n\t\t}\n\n\t\tthis.refresh();\n\t},\n\n\trefresh: function() {\n\t\tvar children,\n\t\t\tthat = this;\n\n\t\tthis._addClass( \"ui-controlgroup ui-controlgroup-\" + this.options.direction );\n\n\t\tif ( this.options.direction === \"horizontal\" ) {\n\t\t\tthis._addClass( null, \"ui-helper-clearfix\" );\n\t\t}\n\t\tthis._initWidgets();\n\n\t\tchildren = this.childWidgets;\n\n\t\t// We filter here because we need to track all childWidgets not just the visible ones\n\t\tif ( this.options.onlyVisible ) {\n\t\t\tchildren = children.filter( \":visible\" );\n\t\t}\n\n\t\tif ( children.length ) {\n\n\t\t\t// We do this last because we need to make sure all enhancment is done\n\t\t\t// before determining first and last\n\t\t\t$.each( [ \"first\", \"last\" ], function( index, value ) {\n\t\t\t\tvar instance = children[ value ]().data( \"ui-controlgroup-data\" );\n\n\t\t\t\tif ( instance && that[ \"_\" + instance.widgetName + \"Options\" ] ) {\n\t\t\t\t\tvar options = that[ \"_\" + instance.widgetName + \"Options\" ](\n\t\t\t\t\t\tchildren.length === 1 ? \"only\" : value\n\t\t\t\t\t);\n\t\t\t\t\toptions.classes = that._resolveClassesValues( options.classes, instance );\n\t\t\t\t\tinstance.element[ instance.widgetName ]( options );\n\t\t\t\t} else {\n\t\t\t\t\tthat._updateCornerClass( children[ value ](), value );\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Finally call the refresh method on each of the child widgets.\n\t\t\tthis._callChildMethod( \"refresh\" );\n\t\t}\n\t}\n} );\n\n/*!\n * jQuery UI Checkboxradio 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Checkboxradio\n//>>group: Widgets\n//>>description: Enhances a form with multiple themeable checkboxes or radio buttons.\n//>>docs: http://api.jqueryui.com/checkboxradio/\n//>>demos: http://jqueryui.com/checkboxradio/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/button.css\n//>>css.structure: ../../themes/base/checkboxradio.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.checkboxradio\", [ $.ui.formResetMixin, {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tdisabled: null,\n\t\tlabel: null,\n\t\ticon: true,\n\t\tclasses: {\n\t\t\t\"ui-checkboxradio-label\": \"ui-corner-all\",\n\t\t\t\"ui-checkboxradio-icon\": \"ui-corner-all\"\n\t\t}\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar disabled, labels;\n\t\tvar that = this;\n\t\tvar options = this._super() || {};\n\n\t\t// We read the type here, because it makes more sense to throw a element type error first,\n\t\t// rather then the error for lack of a label. Often if its the wrong type, it\n\t\t// won't have a label (e.g. calling on a div, btn, etc)\n\t\tthis._readType();\n\n\t\tlabels = this.element.labels();\n\n\t\t// If there are multiple labels, use the last one\n\t\tthis.label = $( labels[ labels.length - 1 ] );\n\t\tif ( !this.label.length ) {\n\t\t\t$.error( \"No label found for checkboxradio widget\" );\n\t\t}\n\n\t\tthis.originalLabel = \"\";\n\n\t\t// We need to get the label text but this may also need to make sure it does not contain the\n\t\t// input itself.\n\t\tthis.label.contents().not( this.element[ 0 ] ).each( function() {\n\n\t\t\t// The label contents could be text, html, or a mix. We concat each element to get a\n\t\t\t// string representation of the label, without the input as part of it.\n\t\t\tthat.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML;\n\t\t} );\n\n\t\t// Set the label option if we found label text\n\t\tif ( this.originalLabel ) {\n\t\t\toptions.label = this.originalLabel;\n\t\t}\n\n\t\tdisabled = this.element[ 0 ].disabled;\n\t\tif ( disabled != null ) {\n\t\t\toptions.disabled = disabled;\n\t\t}\n\t\treturn options;\n\t},\n\n\t_create: function() {\n\t\tvar checked = this.element[ 0 ].checked;\n\n\t\tthis._bindFormResetHandler();\n\n\t\tif ( this.options.disabled == null ) {\n\t\t\tthis.options.disabled = this.element[ 0 ].disabled;\n\t\t}\n\n\t\tthis._setOption( \"disabled\", this.options.disabled );\n\t\tthis._addClass( \"ui-checkboxradio\", \"ui-helper-hidden-accessible\" );\n\t\tthis._addClass( this.label, \"ui-checkboxradio-label\", \"ui-button ui-widget\" );\n\n\t\tif ( this.type === \"radio\" ) {\n\t\t\tthis._addClass( this.label, \"ui-checkboxradio-radio-label\" );\n\t\t}\n\n\t\tif ( this.options.label && this.options.label !== this.originalLabel ) {\n\t\t\tthis._updateLabel();\n\t\t} else if ( this.originalLabel ) {\n\t\t\tthis.options.label = this.originalLabel;\n\t\t}\n\n\t\tthis._enhance();\n\n\t\tif ( checked ) {\n\t\t\tthis._addClass( this.label, \"ui-checkboxradio-checked\", \"ui-state-active\" );\n\t\t\tif ( this.icon ) {\n\t\t\t\tthis._addClass( this.icon, null, \"ui-state-hover\" );\n\t\t\t}\n\t\t}\n\n\t\tthis._on( {\n\t\t\tchange: \"_toggleClasses\",\n\t\t\tfocus: function() {\n\t\t\t\tthis._addClass( this.label, null, \"ui-state-focus ui-visual-focus\" );\n\t\t\t},\n\t\t\tblur: function() {\n\t\t\t\tthis._removeClass( this.label, null, \"ui-state-focus ui-visual-focus\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_readType: function() {\n\t\tvar nodeName = this.element[ 0 ].nodeName.toLowerCase();\n\t\tthis.type = this.element[ 0 ].type;\n\t\tif ( nodeName !== \"input\" || !/radio|checkbox/.test( this.type ) ) {\n\t\t\t$.error( \"Can't create checkboxradio on element.nodeName=\" + nodeName +\n\t\t\t\t\" and element.type=\" + this.type );\n\t\t}\n\t},\n\n\t// Support jQuery Mobile enhanced option\n\t_enhance: function() {\n\t\tthis._updateIcon( this.element[ 0 ].checked );\n\t},\n\n\twidget: function() {\n\t\treturn this.label;\n\t},\n\n\t_getRadioGroup: function() {\n\t\tvar group;\n\t\tvar name = this.element[ 0 ].name;\n\t\tvar nameSelector = \"input[name='\" + $.ui.escapeSelector( name ) + \"']\";\n\n\t\tif ( !name ) {\n\t\t\treturn $( [] );\n\t\t}\n\n\t\tif ( this.form.length ) {\n\t\t\tgroup = $( this.form[ 0 ].elements ).filter( nameSelector );\n\t\t} else {\n\n\t\t\t// Not inside a form, check all inputs that also are not inside a form\n\t\t\tgroup = $( nameSelector ).filter( function() {\n\t\t\t\treturn $( this ).form().length === 0;\n\t\t\t} );\n\t\t}\n\n\t\treturn group.not( this.element );\n\t},\n\n\t_toggleClasses: function() {\n\t\tvar checked = this.element[ 0 ].checked;\n\t\tthis._toggleClass( this.label, \"ui-checkboxradio-checked\", \"ui-state-active\", checked );\n\n\t\tif ( this.options.icon && this.type === \"checkbox\" ) {\n\t\t\tthis._toggleClass( this.icon, null, \"ui-icon-check ui-state-checked\", checked )\n\t\t\t\t._toggleClass( this.icon, null, \"ui-icon-blank\", !checked );\n\t\t}\n\n\t\tif ( this.type === \"radio\" ) {\n\t\t\tthis._getRadioGroup()\n\t\t\t\t.each( function() {\n\t\t\t\t\tvar instance = $( this ).checkboxradio( \"instance\" );\n\n\t\t\t\t\tif ( instance ) {\n\t\t\t\t\t\tinstance._removeClass( instance.label,\n\t\t\t\t\t\t\t\"ui-checkboxradio-checked\", \"ui-state-active\" );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tthis._unbindFormResetHandler();\n\n\t\tif ( this.icon ) {\n\t\t\tthis.icon.remove();\n\t\t\tthis.iconSpace.remove();\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\n\t\t// We don't allow the value to be set to nothing\n\t\tif ( key === \"label\" && !value ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._toggleClass( this.label, null, \"ui-state-disabled\", value );\n\t\t\tthis.element[ 0 ].disabled = value;\n\n\t\t\t// Don't refresh when setting disabled\n\t\t\treturn;\n\t\t}\n\t\tthis.refresh();\n\t},\n\n\t_updateIcon: function( checked ) {\n\t\tvar toAdd = \"ui-icon ui-icon-background \";\n\n\t\tif ( this.options.icon ) {\n\t\t\tif ( !this.icon ) {\n\t\t\t\tthis.icon = $( \"<span>\" );\n\t\t\t\tthis.iconSpace = $( \"<span> </span>\" );\n\t\t\t\tthis._addClass( this.iconSpace, \"ui-checkboxradio-icon-space\" );\n\t\t\t}\n\n\t\t\tif ( this.type === \"checkbox\" ) {\n\t\t\t\ttoAdd += checked ? \"ui-icon-check ui-state-checked\" : \"ui-icon-blank\";\n\t\t\t\tthis._removeClass( this.icon, null, checked ? \"ui-icon-blank\" : \"ui-icon-check\" );\n\t\t\t} else {\n\t\t\t\ttoAdd += \"ui-icon-blank\";\n\t\t\t}\n\t\t\tthis._addClass( this.icon, \"ui-checkboxradio-icon\", toAdd );\n\t\t\tif ( !checked ) {\n\t\t\t\tthis._removeClass( this.icon, null, \"ui-icon-check ui-state-checked\" );\n\t\t\t}\n\t\t\tthis.icon.prependTo( this.label ).after( this.iconSpace );\n\t\t} else if ( this.icon !== undefined ) {\n\t\t\tthis.icon.remove();\n\t\t\tthis.iconSpace.remove();\n\t\t\tdelete this.icon;\n\t\t}\n\t},\n\n\t_updateLabel: function() {\n\n\t\t// Remove the contents of the label ( minus the icon, icon space, and input )\n\t\tvar contents = this.label.contents().not( this.element[ 0 ] );\n\t\tif ( this.icon ) {\n\t\t\tcontents = contents.not( this.icon[ 0 ] );\n\t\t}\n\t\tif ( this.iconSpace ) {\n\t\t\tcontents = contents.not( this.iconSpace[ 0 ] );\n\t\t}\n\t\tcontents.remove();\n\n\t\tthis.label.append( this.options.label );\n\t},\n\n\trefresh: function() {\n\t\tvar checked = this.element[ 0 ].checked,\n\t\t\tisDisabled = this.element[ 0 ].disabled;\n\n\t\tthis._updateIcon( checked );\n\t\tthis._toggleClass( this.label, \"ui-checkboxradio-checked\", \"ui-state-active\", checked );\n\t\tif ( this.options.label !== null ) {\n\t\t\tthis._updateLabel();\n\t\t}\n\n\t\tif ( isDisabled !== this.options.disabled ) {\n\t\t\tthis._setOptions( { \"disabled\": isDisabled } );\n\t\t}\n\t}\n\n} ] );\n\nvar widgetsCheckboxradio = $.ui.checkboxradio;\n\n\n/*!\n * jQuery UI Button 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Button\n//>>group: Widgets\n//>>description: Enhances a form with themeable buttons.\n//>>docs: http://api.jqueryui.com/button/\n//>>demos: http://jqueryui.com/button/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/button.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.button\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<button>\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-button\": \"ui-corner-all\"\n\t\t},\n\t\tdisabled: null,\n\t\ticon: null,\n\t\ticonPosition: \"beginning\",\n\t\tlabel: null,\n\t\tshowLabel: true\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar disabled,\n\n\t\t\t// This is to support cases like in jQuery Mobile where the base widget does have\n\t\t\t// an implementation of _getCreateOptions\n\t\t\toptions = this._super() || {};\n\n\t\tthis.isInput = this.element.is( \"input\" );\n\n\t\tdisabled = this.element[ 0 ].disabled;\n\t\tif ( disabled != null ) {\n\t\t\toptions.disabled = disabled;\n\t\t}\n\n\t\tthis.originalLabel = this.isInput ? this.element.val() : this.element.html();\n\t\tif ( this.originalLabel ) {\n\t\t\toptions.label = this.originalLabel;\n\t\t}\n\n\t\treturn options;\n\t},\n\n\t_create: function() {\n\t\tif ( !this.option.showLabel & !this.options.icon ) {\n\t\t\tthis.options.showLabel = true;\n\t\t}\n\n\t\t// We have to check the option again here even though we did in _getCreateOptions,\n\t\t// because null may have been passed on init which would override what was set in\n\t\t// _getCreateOptions\n\t\tif ( this.options.disabled == null ) {\n\t\t\tthis.options.disabled = this.element[ 0 ].disabled || false;\n\t\t}\n\n\t\tthis.hasTitle = !!this.element.attr( \"title\" );\n\n\t\t// Check to see if the label needs to be set or if its already correct\n\t\tif ( this.options.label && this.options.label !== this.originalLabel ) {\n\t\t\tif ( this.isInput ) {\n\t\t\t\tthis.element.val( this.options.label );\n\t\t\t} else {\n\t\t\t\tthis.element.html( this.options.label );\n\t\t\t}\n\t\t}\n\t\tthis._addClass( \"ui-button\", \"ui-widget\" );\n\t\tthis._setOption( \"disabled\", this.options.disabled );\n\t\tthis._enhance();\n\n\t\tif ( this.element.is( \"a\" ) ) {\n\t\t\tthis._on( {\n\t\t\t\t\"keyup\": function( event ) {\n\t\t\t\t\tif ( event.keyCode === $.ui.keyCode.SPACE ) {\n\t\t\t\t\t\tevent.preventDefault();\n\n\t\t\t\t\t\t// Support: PhantomJS <= 1.9, IE 8 Only\n\t\t\t\t\t\t// If a native click is available use it so we actually cause navigation\n\t\t\t\t\t\t// otherwise just trigger a click event\n\t\t\t\t\t\tif ( this.element[ 0 ].click ) {\n\t\t\t\t\t\t\tthis.element[ 0 ].click();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.element.trigger( \"click\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\t},\n\n\t_enhance: function() {\n\t\tif ( !this.element.is( \"button\" ) ) {\n\t\t\tthis.element.attr( \"role\", \"button\" );\n\t\t}\n\n\t\tif ( this.options.icon ) {\n\t\t\tthis._updateIcon( \"icon\", this.options.icon );\n\t\t\tthis._updateTooltip();\n\t\t}\n\t},\n\n\t_updateTooltip: function() {\n\t\tthis.title = this.element.attr( \"title\" );\n\n\t\tif ( !this.options.showLabel && !this.title ) {\n\t\t\tthis.element.attr( \"title\", this.options.label );\n\t\t}\n\t},\n\n\t_updateIcon: function( option, value ) {\n\t\tvar icon = option !== \"iconPosition\",\n\t\t\tposition = icon ? this.options.iconPosition : value,\n\t\t\tdisplayBlock = position === \"top\" || position === \"bottom\";\n\n\t\t// Create icon\n\t\tif ( !this.icon ) {\n\t\t\tthis.icon = $( \"<span>\" );\n\n\t\t\tthis._addClass( this.icon, \"ui-button-icon\", \"ui-icon\" );\n\n\t\t\tif ( !this.options.showLabel ) {\n\t\t\t\tthis._addClass( \"ui-button-icon-only\" );\n\t\t\t}\n\t\t} else if ( icon ) {\n\n\t\t\t// If we are updating the icon remove the old icon class\n\t\t\tthis._removeClass( this.icon, null, this.options.icon );\n\t\t}\n\n\t\t// If we are updating the icon add the new icon class\n\t\tif ( icon ) {\n\t\t\tthis._addClass( this.icon, null, value );\n\t\t}\n\n\t\tthis._attachIcon( position );\n\n\t\t// If the icon is on top or bottom we need to add the ui-widget-icon-block class and remove\n\t\t// the iconSpace if there is one.\n\t\tif ( displayBlock ) {\n\t\t\tthis._addClass( this.icon, null, \"ui-widget-icon-block\" );\n\t\t\tif ( this.iconSpace ) {\n\t\t\t\tthis.iconSpace.remove();\n\t\t\t}\n\t\t} else {\n\n\t\t\t// Position is beginning or end so remove the ui-widget-icon-block class and add the\n\t\t\t// space if it does not exist\n\t\t\tif ( !this.iconSpace ) {\n\t\t\t\tthis.iconSpace = $( \"<span> </span>\" );\n\t\t\t\tthis._addClass( this.iconSpace, \"ui-button-icon-space\" );\n\t\t\t}\n\t\t\tthis._removeClass( this.icon, null, \"ui-wiget-icon-block\" );\n\t\t\tthis._attachIconSpace( position );\n\t\t}\n\t},\n\n\t_destroy: function() {\n\t\tthis.element.removeAttr( \"role\" );\n\n\t\tif ( this.icon ) {\n\t\t\tthis.icon.remove();\n\t\t}\n\t\tif ( this.iconSpace ) {\n\t\t\tthis.iconSpace.remove();\n\t\t}\n\t\tif ( !this.hasTitle ) {\n\t\t\tthis.element.removeAttr( \"title\" );\n\t\t}\n\t},\n\n\t_attachIconSpace: function( iconPosition ) {\n\t\tthis.icon[ /^(?:end|bottom)/.test( iconPosition ) ? \"before\" : \"after\" ]( this.iconSpace );\n\t},\n\n\t_attachIcon: function( iconPosition ) {\n\t\tthis.element[ /^(?:end|bottom)/.test( iconPosition ) ? \"append\" : \"prepend\" ]( this.icon );\n\t},\n\n\t_setOptions: function( options ) {\n\t\tvar newShowLabel = options.showLabel === undefined ?\n\t\t\t\tthis.options.showLabel :\n\t\t\t\toptions.showLabel,\n\t\t\tnewIcon = options.icon === undefined ? this.options.icon : options.icon;\n\n\t\tif ( !newShowLabel && !newIcon ) {\n\t\t\toptions.showLabel = true;\n\t\t}\n\t\tthis._super( options );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"icon\" ) {\n\t\t\tif ( value ) {\n\t\t\t\tthis._updateIcon( key, value );\n\t\t\t} else if ( this.icon ) {\n\t\t\t\tthis.icon.remove();\n\t\t\t\tif ( this.iconSpace ) {\n\t\t\t\t\tthis.iconSpace.remove();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"iconPosition\" ) {\n\t\t\tthis._updateIcon( key, value );\n\t\t}\n\n\t\t// Make sure we can't end up with a button that has neither text nor icon\n\t\tif ( key === \"showLabel\" ) {\n\t\t\t\tthis._toggleClass( \"ui-button-icon-only\", null, !value );\n\t\t\t\tthis._updateTooltip();\n\t\t}\n\n\t\tif ( key === \"label\" ) {\n\t\t\tif ( this.isInput ) {\n\t\t\t\tthis.element.val( value );\n\t\t\t} else {\n\n\t\t\t\t// If there is an icon, append it, else nothing then append the value\n\t\t\t\t// this avoids removal of the icon when setting label text\n\t\t\t\tthis.element.html( value );\n\t\t\t\tif ( this.icon ) {\n\t\t\t\t\tthis._attachIcon( this.options.iconPosition );\n\t\t\t\t\tthis._attachIconSpace( this.options.iconPosition );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\tthis._toggleClass( null, \"ui-state-disabled\", value );\n\t\t\tthis.element[ 0 ].disabled = value;\n\t\t\tif ( value ) {\n\t\t\t\tthis.element.blur();\n\t\t\t}\n\t\t}\n\t},\n\n\trefresh: function() {\n\n\t\t// Make sure to only check disabled if its an element that supports this otherwise\n\t\t// check for the disabled class to determine state\n\t\tvar isDisabled = this.element.is( \"input, button\" ) ?\n\t\t\tthis.element[ 0 ].disabled : this.element.hasClass( \"ui-button-disabled\" );\n\n\t\tif ( isDisabled !== this.options.disabled ) {\n\t\t\tthis._setOptions( { disabled: isDisabled } );\n\t\t}\n\n\t\tthis._updateTooltip();\n\t}\n} );\n\n// DEPRECATED\nif ( $.uiBackCompat !== false ) {\n\n\t// Text and Icons options\n\t$.widget( \"ui.button\", $.ui.button, {\n\t\toptions: {\n\t\t\ttext: true,\n\t\t\ticons: {\n\t\t\t\tprimary: null,\n\t\t\t\tsecondary: null\n\t\t\t}\n\t\t},\n\n\t\t_create: function() {\n\t\t\tif ( this.options.showLabel && !this.options.text ) {\n\t\t\t\tthis.options.showLabel = this.options.text;\n\t\t\t}\n\t\t\tif ( !this.options.showLabel && this.options.text ) {\n\t\t\t\tthis.options.text = this.options.showLabel;\n\t\t\t}\n\t\t\tif ( !this.options.icon && ( this.options.icons.primary ||\n\t\t\t\t\tthis.options.icons.secondary ) ) {\n\t\t\t\tif ( this.options.icons.primary ) {\n\t\t\t\t\tthis.options.icon = this.options.icons.primary;\n\t\t\t\t} else {\n\t\t\t\t\tthis.options.icon = this.options.icons.secondary;\n\t\t\t\t\tthis.options.iconPosition = \"end\";\n\t\t\t\t}\n\t\t\t} else if ( this.options.icon ) {\n\t\t\t\tthis.options.icons.primary = this.options.icon;\n\t\t\t}\n\t\t\tthis._super();\n\t\t},\n\n\t\t_setOption: function( key, value ) {\n\t\t\tif ( key === \"text\" ) {\n\t\t\t\tthis._super( \"showLabel\", value );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( key === \"showLabel\" ) {\n\t\t\t\tthis.options.text = value;\n\t\t\t}\n\t\t\tif ( key === \"icon\" ) {\n\t\t\t\tthis.options.icons.primary = value;\n\t\t\t}\n\t\t\tif ( key === \"icons\" ) {\n\t\t\t\tif ( value.primary ) {\n\t\t\t\t\tthis._super( \"icon\", value.primary );\n\t\t\t\t\tthis._super( \"iconPosition\", \"beginning\" );\n\t\t\t\t} else if ( value.secondary ) {\n\t\t\t\t\tthis._super( \"icon\", value.secondary );\n\t\t\t\t\tthis._super( \"iconPosition\", \"end\" );\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._superApply( arguments );\n\t\t}\n\t} );\n\n\t$.fn.button = ( function( orig ) {\n\t\treturn function() {\n\t\t\tif ( !this.length || ( this.length && this[ 0 ].tagName !== \"INPUT\" ) ||\n\t\t\t\t\t( this.length && this[ 0 ].tagName === \"INPUT\" && (\n\t\t\t\t\t\tthis.attr( \"type\" ) !== \"checkbox\" && this.attr( \"type\" ) !== \"radio\"\n\t\t\t\t\t) ) ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t}\n\t\t\tif ( !$.ui.checkboxradio ) {\n\t\t\t\t$.error( \"Checkboxradio widget missing\" );\n\t\t\t}\n\t\t\tif ( arguments.length === 0 ) {\n\t\t\t\treturn this.checkboxradio( {\n\t\t\t\t\t\"icon\": false\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn this.checkboxradio.apply( this, arguments );\n\t\t};\n\t} )( $.fn.button );\n\n\t$.fn.buttonset = function() {\n\t\tif ( !$.ui.controlgroup ) {\n\t\t\t$.error( \"Controlgroup widget missing\" );\n\t\t}\n\t\tif ( arguments[ 0 ] === \"option\" && arguments[ 1 ] === \"items\" && arguments[ 2 ] ) {\n\t\t\treturn this.controlgroup.apply( this,\n\t\t\t\t[ arguments[ 0 ], \"items.button\", arguments[ 2 ] ] );\n\t\t}\n\t\tif ( arguments[ 0 ] === \"option\" && arguments[ 1 ] === \"items\" ) {\n\t\t\treturn this.controlgroup.apply( this, [ arguments[ 0 ], \"items.button\" ] );\n\t\t}\n\t\tif ( typeof arguments[ 0 ] === \"object\" && arguments[ 0 ].items ) {\n\t\t\targuments[ 0 ].items = {\n\t\t\t\tbutton: arguments[ 0 ].items\n\t\t\t};\n\t\t}\n\t\treturn this.controlgroup.apply( this, arguments );\n\t};\n}\n\nvar widgetsButton = $.ui.button;\n\n\n// jscs:disable maximumLineLength\n/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */\n/*!\n * jQuery UI Datepicker 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Datepicker\n//>>group: Widgets\n//>>description: Displays a calendar from an input or inline for selecting dates.\n//>>docs: http://api.jqueryui.com/datepicker/\n//>>demos: http://jqueryui.com/datepicker/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/datepicker.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.extend( $.ui, { datepicker: { version: \"1.12.1\" } } );\n\nvar datepicker_instActive;\n\nfunction datepicker_getZindex( elem ) {\n\tvar position, value;\n\twhile ( elem.length && elem[ 0 ] !== document ) {\n\n\t\t// Ignore z-index if position is set to a value where z-index is ignored by the browser\n\t\t// This makes behavior of this function consistent across browsers\n\t\t// WebKit always returns auto if the element is positioned\n\t\tposition = elem.css( \"position\" );\n\t\tif ( position === \"absolute\" || position === \"relative\" || position === \"fixed\" ) {\n\n\t\t\t// IE returns 0 when zIndex is not specified\n\t\t\t// other browsers return a string\n\t\t\t// we ignore the case of nested elements with an explicit value of 0\n\t\t\t// <div style=\"z-index: -10;\"><div style=\"z-index: 0;\"></div></div>\n\t\t\tvalue = parseInt( elem.css( \"zIndex\" ), 10 );\n\t\t\tif ( !isNaN( value ) && value !== 0 ) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\telem = elem.parent();\n\t}\n\n\treturn 0;\n}\n/* Date picker manager.\n   Use the singleton instance of this class, $.datepicker, to interact with the date picker.\n   Settings for (groups of) date pickers are maintained in an instance object,\n   allowing multiple different settings on the same page. */\n\nfunction Datepicker() {\n\tthis._curInst = null; // The current instance in use\n\tthis._keyEvent = false; // If the last event was a key event\n\tthis._disabledInputs = []; // List of date picker inputs that have been disabled\n\tthis._datepickerShowing = false; // True if the popup picker is showing , false if not\n\tthis._inDialog = false; // True if showing within a \"dialog\", false if not\n\tthis._mainDivId = \"ui-datepicker-div\"; // The ID of the main datepicker division\n\tthis._inlineClass = \"ui-datepicker-inline\"; // The name of the inline marker class\n\tthis._appendClass = \"ui-datepicker-append\"; // The name of the append marker class\n\tthis._triggerClass = \"ui-datepicker-trigger\"; // The name of the trigger marker class\n\tthis._dialogClass = \"ui-datepicker-dialog\"; // The name of the dialog marker class\n\tthis._disableClass = \"ui-datepicker-disabled\"; // The name of the disabled covering marker class\n\tthis._unselectableClass = \"ui-datepicker-unselectable\"; // The name of the unselectable cell marker class\n\tthis._currentClass = \"ui-datepicker-current-day\"; // The name of the current day marker class\n\tthis._dayOverClass = \"ui-datepicker-days-cell-over\"; // The name of the day hover marker class\n\tthis.regional = []; // Available regional settings, indexed by language code\n\tthis.regional[ \"\" ] = { // Default regional settings\n\t\tcloseText: \"Done\", // Display text for close link\n\t\tprevText: \"Prev\", // Display text for previous month link\n\t\tnextText: \"Next\", // Display text for next month link\n\t\tcurrentText: \"Today\", // Display text for current month link\n\t\tmonthNames: [ \"January\",\"February\",\"March\",\"April\",\"May\",\"June\",\n\t\t\t\"July\",\"August\",\"September\",\"October\",\"November\",\"December\" ], // Names of months for drop-down and formatting\n\t\tmonthNamesShort: [ \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\" ], // For formatting\n\t\tdayNames: [ \"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\" ], // For formatting\n\t\tdayNamesShort: [ \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\" ], // For formatting\n\t\tdayNamesMin: [ \"Su\",\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\",\"Sa\" ], // Column headings for days starting at Sunday\n\t\tweekHeader: \"Wk\", // Column header for week of the year\n\t\tdateFormat: \"mm/dd/yy\", // See format options on parseDate\n\t\tfirstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...\n\t\tisRTL: false, // True if right-to-left language, false if left-to-right\n\t\tshowMonthAfterYear: false, // True if the year select precedes month, false for month then year\n\t\tyearSuffix: \"\" // Additional text to append to the year in the month headers\n\t};\n\tthis._defaults = { // Global defaults for all the date picker instances\n\t\tshowOn: \"focus\", // \"focus\" for popup on focus,\n\t\t\t// \"button\" for trigger button, or \"both\" for either\n\t\tshowAnim: \"fadeIn\", // Name of jQuery animation for popup\n\t\tshowOptions: {}, // Options for enhanced animations\n\t\tdefaultDate: null, // Used when field is blank: actual date,\n\t\t\t// +/-number for offset from today, null for today\n\t\tappendText: \"\", // Display text following the input box, e.g. showing the format\n\t\tbuttonText: \"...\", // Text for trigger button\n\t\tbuttonImage: \"\", // URL for trigger button image\n\t\tbuttonImageOnly: false, // True if the image appears alone, false if it appears on a button\n\t\thideIfNoPrevNext: false, // True to hide next/previous month links\n\t\t\t// if not applicable, false to just disable them\n\t\tnavigationAsDateFormat: false, // True if date formatting applied to prev/today/next links\n\t\tgotoCurrent: false, // True if today link goes back to current selection instead\n\t\tchangeMonth: false, // True if month can be selected directly, false if only prev/next\n\t\tchangeYear: false, // True if year can be selected directly, false if only prev/next\n\t\tyearRange: \"c-10:c+10\", // Range of years to display in drop-down,\n\t\t\t// either relative to today's year (-nn:+nn), relative to currently displayed year\n\t\t\t// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)\n\t\tshowOtherMonths: false, // True to show dates in other months, false to leave blank\n\t\tselectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable\n\t\tshowWeek: false, // True to show week of the year, false to not show it\n\t\tcalculateWeek: this.iso8601Week, // How to calculate the week of the year,\n\t\t\t// takes a Date and returns the number of the week for it\n\t\tshortYearCutoff: \"+10\", // Short year values < this are in the current century,\n\t\t\t// > this are in the previous century,\n\t\t\t// string value starting with \"+\" for current year + value\n\t\tminDate: null, // The earliest selectable date, or null for no limit\n\t\tmaxDate: null, // The latest selectable date, or null for no limit\n\t\tduration: \"fast\", // Duration of display/closure\n\t\tbeforeShowDay: null, // Function that takes a date and returns an array with\n\t\t\t// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or \"\",\n\t\t\t// [2] = cell title (optional), e.g. $.datepicker.noWeekends\n\t\tbeforeShow: null, // Function that takes an input field and\n\t\t\t// returns a set of custom settings for the date picker\n\t\tonSelect: null, // Define a callback function when a date is selected\n\t\tonChangeMonthYear: null, // Define a callback function when the month or year is changed\n\t\tonClose: null, // Define a callback function when the datepicker is closed\n\t\tnumberOfMonths: 1, // Number of months to show at a time\n\t\tshowCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)\n\t\tstepMonths: 1, // Number of months to step back/forward\n\t\tstepBigMonths: 12, // Number of months to step back/forward for the big links\n\t\taltField: \"\", // Selector for an alternate field to store selected dates into\n\t\taltFormat: \"\", // The date format to use for the alternate field\n\t\tconstrainInput: true, // The input is constrained by the current date format\n\t\tshowButtonPanel: false, // True to show button panel, false to not show it\n\t\tautoSize: false, // True to size the input for the date format, false to leave as is\n\t\tdisabled: false // The initial disabled state\n\t};\n\t$.extend( this._defaults, this.regional[ \"\" ] );\n\tthis.regional.en = $.extend( true, {}, this.regional[ \"\" ] );\n\tthis.regional[ \"en-US\" ] = $.extend( true, {}, this.regional.en );\n\tthis.dpDiv = datepicker_bindHover( $( \"<div id='\" + this._mainDivId + \"' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>\" ) );\n}\n\n$.extend( Datepicker.prototype, {\n\t/* Class name added to elements to indicate already configured with a date picker. */\n\tmarkerClassName: \"hasDatepicker\",\n\n\t//Keep track of the maximum number of rows displayed (see #7043)\n\tmaxRows: 4,\n\n\t// TODO rename to \"widget\" when switching to widget factory\n\t_widgetDatepicker: function() {\n\t\treturn this.dpDiv;\n\t},\n\n\t/* Override the default settings for all instances of the date picker.\n\t * @param  settings  object - the new settings to use as defaults (anonymous object)\n\t * @return the manager object\n\t */\n\tsetDefaults: function( settings ) {\n\t\tdatepicker_extendRemove( this._defaults, settings || {} );\n\t\treturn this;\n\t},\n\n\t/* Attach the date picker to a jQuery selection.\n\t * @param  target\telement - the target input field or division or span\n\t * @param  settings  object - the new settings to use for this date picker instance (anonymous)\n\t */\n\t_attachDatepicker: function( target, settings ) {\n\t\tvar nodeName, inline, inst;\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\tinline = ( nodeName === \"div\" || nodeName === \"span\" );\n\t\tif ( !target.id ) {\n\t\t\tthis.uuid += 1;\n\t\t\ttarget.id = \"dp\" + this.uuid;\n\t\t}\n\t\tinst = this._newInst( $( target ), inline );\n\t\tinst.settings = $.extend( {}, settings || {} );\n\t\tif ( nodeName === \"input\" ) {\n\t\t\tthis._connectDatepicker( target, inst );\n\t\t} else if ( inline ) {\n\t\t\tthis._inlineDatepicker( target, inst );\n\t\t}\n\t},\n\n\t/* Create a new instance object. */\n\t_newInst: function( target, inline ) {\n\t\tvar id = target[ 0 ].id.replace( /([^A-Za-z0-9_\\-])/g, \"\\\\\\\\$1\" ); // escape jQuery meta chars\n\t\treturn { id: id, input: target, // associated target\n\t\t\tselectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection\n\t\t\tdrawMonth: 0, drawYear: 0, // month being drawn\n\t\t\tinline: inline, // is datepicker inline or not\n\t\t\tdpDiv: ( !inline ? this.dpDiv : // presentation div\n\t\t\tdatepicker_bindHover( $( \"<div class='\" + this._inlineClass + \" ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>\" ) ) ) };\n\t},\n\n\t/* Attach the date picker to an input field. */\n\t_connectDatepicker: function( target, inst ) {\n\t\tvar input = $( target );\n\t\tinst.append = $( [] );\n\t\tinst.trigger = $( [] );\n\t\tif ( input.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\t\tthis._attachments( input, inst );\n\t\tinput.addClass( this.markerClassName ).on( \"keydown\", this._doKeyDown ).\n\t\t\ton( \"keypress\", this._doKeyPress ).on( \"keyup\", this._doKeyUp );\n\t\tthis._autoSize( inst );\n\t\t$.data( target, \"datepicker\", inst );\n\n\t\t//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)\n\t\tif ( inst.settings.disabled ) {\n\t\t\tthis._disableDatepicker( target );\n\t\t}\n\t},\n\n\t/* Make attachments based on settings. */\n\t_attachments: function( input, inst ) {\n\t\tvar showOn, buttonText, buttonImage,\n\t\t\tappendText = this._get( inst, \"appendText\" ),\n\t\t\tisRTL = this._get( inst, \"isRTL\" );\n\n\t\tif ( inst.append ) {\n\t\t\tinst.append.remove();\n\t\t}\n\t\tif ( appendText ) {\n\t\t\tinst.append = $( \"<span class='\" + this._appendClass + \"'>\" + appendText + \"</span>\" );\n\t\t\tinput[ isRTL ? \"before\" : \"after\" ]( inst.append );\n\t\t}\n\n\t\tinput.off( \"focus\", this._showDatepicker );\n\n\t\tif ( inst.trigger ) {\n\t\t\tinst.trigger.remove();\n\t\t}\n\n\t\tshowOn = this._get( inst, \"showOn\" );\n\t\tif ( showOn === \"focus\" || showOn === \"both\" ) { // pop-up date picker when in the marked field\n\t\t\tinput.on( \"focus\", this._showDatepicker );\n\t\t}\n\t\tif ( showOn === \"button\" || showOn === \"both\" ) { // pop-up date picker when button clicked\n\t\t\tbuttonText = this._get( inst, \"buttonText\" );\n\t\t\tbuttonImage = this._get( inst, \"buttonImage\" );\n\t\t\tinst.trigger = $( this._get( inst, \"buttonImageOnly\" ) ?\n\t\t\t\t$( \"<img/>\" ).addClass( this._triggerClass ).\n\t\t\t\t\tattr( { src: buttonImage, alt: buttonText, title: buttonText } ) :\n\t\t\t\t$( \"<button type='button'></button>\" ).addClass( this._triggerClass ).\n\t\t\t\t\thtml( !buttonImage ? buttonText : $( \"<img/>\" ).attr(\n\t\t\t\t\t{ src:buttonImage, alt:buttonText, title:buttonText } ) ) );\n\t\t\tinput[ isRTL ? \"before\" : \"after\" ]( inst.trigger );\n\t\t\tinst.trigger.on( \"click\", function() {\n\t\t\t\tif ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) {\n\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t} else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) {\n\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t\t$.datepicker._showDatepicker( input[ 0 ] );\n\t\t\t\t} else {\n\t\t\t\t\t$.datepicker._showDatepicker( input[ 0 ] );\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t} );\n\t\t}\n\t},\n\n\t/* Apply the maximum length for the date format. */\n\t_autoSize: function( inst ) {\n\t\tif ( this._get( inst, \"autoSize\" ) && !inst.inline ) {\n\t\t\tvar findMax, max, maxI, i,\n\t\t\t\tdate = new Date( 2009, 12 - 1, 20 ), // Ensure double digits\n\t\t\t\tdateFormat = this._get( inst, \"dateFormat\" );\n\n\t\t\tif ( dateFormat.match( /[DM]/ ) ) {\n\t\t\t\tfindMax = function( names ) {\n\t\t\t\t\tmax = 0;\n\t\t\t\t\tmaxI = 0;\n\t\t\t\t\tfor ( i = 0; i < names.length; i++ ) {\n\t\t\t\t\t\tif ( names[ i ].length > max ) {\n\t\t\t\t\t\t\tmax = names[ i ].length;\n\t\t\t\t\t\t\tmaxI = i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn maxI;\n\t\t\t\t};\n\t\t\t\tdate.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ?\n\t\t\t\t\t\"monthNames\" : \"monthNamesShort\" ) ) ) );\n\t\t\t\tdate.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ?\n\t\t\t\t\t\"dayNames\" : \"dayNamesShort\" ) ) ) + 20 - date.getDay() );\n\t\t\t}\n\t\t\tinst.input.attr( \"size\", this._formatDate( inst, date ).length );\n\t\t}\n\t},\n\n\t/* Attach an inline date picker to a div. */\n\t_inlineDatepicker: function( target, inst ) {\n\t\tvar divSpan = $( target );\n\t\tif ( divSpan.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\t\tdivSpan.addClass( this.markerClassName ).append( inst.dpDiv );\n\t\t$.data( target, \"datepicker\", inst );\n\t\tthis._setDate( inst, this._getDefaultDate( inst ), true );\n\t\tthis._updateDatepicker( inst );\n\t\tthis._updateAlternate( inst );\n\n\t\t//If disabled option is true, disable the datepicker before showing it (see ticket #5665)\n\t\tif ( inst.settings.disabled ) {\n\t\t\tthis._disableDatepicker( target );\n\t\t}\n\n\t\t// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements\n\t\t// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height\n\t\tinst.dpDiv.css( \"display\", \"block\" );\n\t},\n\n\t/* Pop-up the date picker in a \"dialog\" box.\n\t * @param  input element - ignored\n\t * @param  date\tstring or Date - the initial date to display\n\t * @param  onSelect  function - the function to call when a date is selected\n\t * @param  settings  object - update the dialog date picker instance's settings (anonymous object)\n\t * @param  pos int[2] - coordinates for the dialog's position within the screen or\n\t *\t\t\t\t\tevent - with x/y coordinates or\n\t *\t\t\t\t\tleave empty for default (screen centre)\n\t * @return the manager object\n\t */\n\t_dialogDatepicker: function( input, date, onSelect, settings, pos ) {\n\t\tvar id, browserWidth, browserHeight, scrollX, scrollY,\n\t\t\tinst = this._dialogInst; // internal instance\n\n\t\tif ( !inst ) {\n\t\t\tthis.uuid += 1;\n\t\t\tid = \"dp\" + this.uuid;\n\t\t\tthis._dialogInput = $( \"<input type='text' id='\" + id +\n\t\t\t\t\"' style='position: absolute; top: -100px; width: 0px;'/>\" );\n\t\t\tthis._dialogInput.on( \"keydown\", this._doKeyDown );\n\t\t\t$( \"body\" ).append( this._dialogInput );\n\t\t\tinst = this._dialogInst = this._newInst( this._dialogInput, false );\n\t\t\tinst.settings = {};\n\t\t\t$.data( this._dialogInput[ 0 ], \"datepicker\", inst );\n\t\t}\n\t\tdatepicker_extendRemove( inst.settings, settings || {} );\n\t\tdate = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date );\n\t\tthis._dialogInput.val( date );\n\n\t\tthis._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null );\n\t\tif ( !this._pos ) {\n\t\t\tbrowserWidth = document.documentElement.clientWidth;\n\t\t\tbrowserHeight = document.documentElement.clientHeight;\n\t\t\tscrollX = document.documentElement.scrollLeft || document.body.scrollLeft;\n\t\t\tscrollY = document.documentElement.scrollTop || document.body.scrollTop;\n\t\t\tthis._pos = // should use actual width/height below\n\t\t\t\t[ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ];\n\t\t}\n\n\t\t// Move input on screen for focus, but hidden behind dialog\n\t\tthis._dialogInput.css( \"left\", ( this._pos[ 0 ] + 20 ) + \"px\" ).css( \"top\", this._pos[ 1 ] + \"px\" );\n\t\tinst.settings.onSelect = onSelect;\n\t\tthis._inDialog = true;\n\t\tthis.dpDiv.addClass( this._dialogClass );\n\t\tthis._showDatepicker( this._dialogInput[ 0 ] );\n\t\tif ( $.blockUI ) {\n\t\t\t$.blockUI( this.dpDiv );\n\t\t}\n\t\t$.data( this._dialogInput[ 0 ], \"datepicker\", inst );\n\t\treturn this;\n\t},\n\n\t/* Detach a datepicker from its control.\n\t * @param  target\telement - the target input field or division or span\n\t */\n\t_destroyDatepicker: function( target ) {\n\t\tvar nodeName,\n\t\t\t$target = $( target ),\n\t\t\tinst = $.data( target, \"datepicker\" );\n\n\t\tif ( !$target.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\t$.removeData( target, \"datepicker\" );\n\t\tif ( nodeName === \"input\" ) {\n\t\t\tinst.append.remove();\n\t\t\tinst.trigger.remove();\n\t\t\t$target.removeClass( this.markerClassName ).\n\t\t\t\toff( \"focus\", this._showDatepicker ).\n\t\t\t\toff( \"keydown\", this._doKeyDown ).\n\t\t\t\toff( \"keypress\", this._doKeyPress ).\n\t\t\t\toff( \"keyup\", this._doKeyUp );\n\t\t} else if ( nodeName === \"div\" || nodeName === \"span\" ) {\n\t\t\t$target.removeClass( this.markerClassName ).empty();\n\t\t}\n\n\t\tif ( datepicker_instActive === inst ) {\n\t\t\tdatepicker_instActive = null;\n\t\t}\n\t},\n\n\t/* Enable the date picker to a jQuery selection.\n\t * @param  target\telement - the target input field or division or span\n\t */\n\t_enableDatepicker: function( target ) {\n\t\tvar nodeName, inline,\n\t\t\t$target = $( target ),\n\t\t\tinst = $.data( target, \"datepicker\" );\n\n\t\tif ( !$target.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\tif ( nodeName === \"input\" ) {\n\t\t\ttarget.disabled = false;\n\t\t\tinst.trigger.filter( \"button\" ).\n\t\t\t\teach( function() { this.disabled = false; } ).end().\n\t\t\t\tfilter( \"img\" ).css( { opacity: \"1.0\", cursor: \"\" } );\n\t\t} else if ( nodeName === \"div\" || nodeName === \"span\" ) {\n\t\t\tinline = $target.children( \".\" + this._inlineClass );\n\t\t\tinline.children().removeClass( \"ui-state-disabled\" );\n\t\t\tinline.find( \"select.ui-datepicker-month, select.ui-datepicker-year\" ).\n\t\t\t\tprop( \"disabled\", false );\n\t\t}\n\t\tthis._disabledInputs = $.map( this._disabledInputs,\n\t\t\tfunction( value ) { return ( value === target ? null : value ); } ); // delete entry\n\t},\n\n\t/* Disable the date picker to a jQuery selection.\n\t * @param  target\telement - the target input field or division or span\n\t */\n\t_disableDatepicker: function( target ) {\n\t\tvar nodeName, inline,\n\t\t\t$target = $( target ),\n\t\t\tinst = $.data( target, \"datepicker\" );\n\n\t\tif ( !$target.hasClass( this.markerClassName ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnodeName = target.nodeName.toLowerCase();\n\t\tif ( nodeName === \"input\" ) {\n\t\t\ttarget.disabled = true;\n\t\t\tinst.trigger.filter( \"button\" ).\n\t\t\t\teach( function() { this.disabled = true; } ).end().\n\t\t\t\tfilter( \"img\" ).css( { opacity: \"0.5\", cursor: \"default\" } );\n\t\t} else if ( nodeName === \"div\" || nodeName === \"span\" ) {\n\t\t\tinline = $target.children( \".\" + this._inlineClass );\n\t\t\tinline.children().addClass( \"ui-state-disabled\" );\n\t\t\tinline.find( \"select.ui-datepicker-month, select.ui-datepicker-year\" ).\n\t\t\t\tprop( \"disabled\", true );\n\t\t}\n\t\tthis._disabledInputs = $.map( this._disabledInputs,\n\t\t\tfunction( value ) { return ( value === target ? null : value ); } ); // delete entry\n\t\tthis._disabledInputs[ this._disabledInputs.length ] = target;\n\t},\n\n\t/* Is the first field in a jQuery collection disabled as a datepicker?\n\t * @param  target\telement - the target input field or division or span\n\t * @return boolean - true if disabled, false if enabled\n\t */\n\t_isDisabledDatepicker: function( target ) {\n\t\tif ( !target ) {\n\t\t\treturn false;\n\t\t}\n\t\tfor ( var i = 0; i < this._disabledInputs.length; i++ ) {\n\t\t\tif ( this._disabledInputs[ i ] === target ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t},\n\n\t/* Retrieve the instance data for the target control.\n\t * @param  target  element - the target input field or division or span\n\t * @return  object - the associated instance data\n\t * @throws  error if a jQuery problem getting data\n\t */\n\t_getInst: function( target ) {\n\t\ttry {\n\t\t\treturn $.data( target, \"datepicker\" );\n\t\t}\n\t\tcatch ( err ) {\n\t\t\tthrow \"Missing instance data for this datepicker\";\n\t\t}\n\t},\n\n\t/* Update or retrieve the settings for a date picker attached to an input field or division.\n\t * @param  target  element - the target input field or division or span\n\t * @param  name\tobject - the new settings to update or\n\t *\t\t\t\tstring - the name of the setting to change or retrieve,\n\t *\t\t\t\twhen retrieving also \"all\" for all instance settings or\n\t *\t\t\t\t\"defaults\" for all global defaults\n\t * @param  value   any - the new value for the setting\n\t *\t\t\t\t(omit if above is an object or to retrieve a value)\n\t */\n\t_optionDatepicker: function( target, name, value ) {\n\t\tvar settings, date, minDate, maxDate,\n\t\t\tinst = this._getInst( target );\n\n\t\tif ( arguments.length === 2 && typeof name === \"string\" ) {\n\t\t\treturn ( name === \"defaults\" ? $.extend( {}, $.datepicker._defaults ) :\n\t\t\t\t( inst ? ( name === \"all\" ? $.extend( {}, inst.settings ) :\n\t\t\t\tthis._get( inst, name ) ) : null ) );\n\t\t}\n\n\t\tsettings = name || {};\n\t\tif ( typeof name === \"string\" ) {\n\t\t\tsettings = {};\n\t\t\tsettings[ name ] = value;\n\t\t}\n\n\t\tif ( inst ) {\n\t\t\tif ( this._curInst === inst ) {\n\t\t\t\tthis._hideDatepicker();\n\t\t\t}\n\n\t\t\tdate = this._getDateDatepicker( target, true );\n\t\t\tminDate = this._getMinMaxDate( inst, \"min\" );\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" );\n\t\t\tdatepicker_extendRemove( inst.settings, settings );\n\n\t\t\t// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided\n\t\t\tif ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) {\n\t\t\t\tinst.settings.minDate = this._formatDate( inst, minDate );\n\t\t\t}\n\t\t\tif ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) {\n\t\t\t\tinst.settings.maxDate = this._formatDate( inst, maxDate );\n\t\t\t}\n\t\t\tif ( \"disabled\" in settings ) {\n\t\t\t\tif ( settings.disabled ) {\n\t\t\t\t\tthis._disableDatepicker( target );\n\t\t\t\t} else {\n\t\t\t\t\tthis._enableDatepicker( target );\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._attachments( $( target ), inst );\n\t\t\tthis._autoSize( inst );\n\t\t\tthis._setDate( inst, date );\n\t\t\tthis._updateAlternate( inst );\n\t\t\tthis._updateDatepicker( inst );\n\t\t}\n\t},\n\n\t// Change method deprecated\n\t_changeDatepicker: function( target, name, value ) {\n\t\tthis._optionDatepicker( target, name, value );\n\t},\n\n\t/* Redraw the date picker attached to an input field or division.\n\t * @param  target  element - the target input field or division or span\n\t */\n\t_refreshDatepicker: function( target ) {\n\t\tvar inst = this._getInst( target );\n\t\tif ( inst ) {\n\t\t\tthis._updateDatepicker( inst );\n\t\t}\n\t},\n\n\t/* Set the dates for a jQuery selection.\n\t * @param  target element - the target input field or division or span\n\t * @param  date\tDate - the new date\n\t */\n\t_setDateDatepicker: function( target, date ) {\n\t\tvar inst = this._getInst( target );\n\t\tif ( inst ) {\n\t\t\tthis._setDate( inst, date );\n\t\t\tthis._updateDatepicker( inst );\n\t\t\tthis._updateAlternate( inst );\n\t\t}\n\t},\n\n\t/* Get the date(s) for the first entry in a jQuery selection.\n\t * @param  target element - the target input field or division or span\n\t * @param  noDefault boolean - true if no default date is to be used\n\t * @return Date - the current date\n\t */\n\t_getDateDatepicker: function( target, noDefault ) {\n\t\tvar inst = this._getInst( target );\n\t\tif ( inst && !inst.inline ) {\n\t\t\tthis._setDateFromField( inst, noDefault );\n\t\t}\n\t\treturn ( inst ? this._getDate( inst ) : null );\n\t},\n\n\t/* Handle keystrokes. */\n\t_doKeyDown: function( event ) {\n\t\tvar onSelect, dateStr, sel,\n\t\t\tinst = $.datepicker._getInst( event.target ),\n\t\t\thandled = true,\n\t\t\tisRTL = inst.dpDiv.is( \".ui-datepicker-rtl\" );\n\n\t\tinst._keyEvent = true;\n\t\tif ( $.datepicker._datepickerShowing ) {\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase 9: $.datepicker._hideDatepicker();\n\t\t\t\t\t\thandled = false;\n\t\t\t\t\t\tbreak; // hide on tab out\n\t\t\t\tcase 13: sel = $( \"td.\" + $.datepicker._dayOverClass + \":not(.\" +\n\t\t\t\t\t\t\t\t\t$.datepicker._currentClass + \")\", inst.dpDiv );\n\t\t\t\t\t\tif ( sel[ 0 ] ) {\n\t\t\t\t\t\t\t$.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tonSelect = $.datepicker._get( inst, \"onSelect\" );\n\t\t\t\t\t\tif ( onSelect ) {\n\t\t\t\t\t\t\tdateStr = $.datepicker._formatDate( inst );\n\n\t\t\t\t\t\t\t// Trigger custom callback\n\t\t\t\t\t\t\tonSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn false; // don't submit the form\n\t\t\t\tcase 27: $.datepicker._hideDatepicker();\n\t\t\t\t\t\tbreak; // hide on escape\n\t\t\t\tcase 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\tbreak; // previous month/year on page up/+ ctrl\n\t\t\t\tcase 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\tbreak; // next month/year on page down/+ ctrl\n\t\t\t\tcase 35: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._clearDate( event.target );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // clear on ctrl or command +end\n\t\t\t\tcase 36: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._gotoToday( event.target );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // current on ctrl or command +home\n\t\t\t\tcase 37: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\n\t\t\t\t\t\t// -1 day on ctrl or command +left\n\t\t\t\t\t\tif ( event.originalEvent.altKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t\t-$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// next month/year on alt +left on Mac\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase 38: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, -7, \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // -1 week on ctrl or command +up\n\t\t\t\tcase 39: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\n\t\t\t\t\t\t// +1 day on ctrl or command +right\n\t\t\t\t\t\tif ( event.originalEvent.altKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, ( event.ctrlKey ?\n\t\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepBigMonths\" ) :\n\t\t\t\t\t\t\t\t+$.datepicker._get( inst, \"stepMonths\" ) ), \"M\" );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// next month/year on alt +right\n\t\t\t\t\t\tbreak;\n\t\t\t\tcase 40: if ( event.ctrlKey || event.metaKey ) {\n\t\t\t\t\t\t\t$.datepicker._adjustDate( event.target, +7, \"D\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandled = event.ctrlKey || event.metaKey;\n\t\t\t\t\t\tbreak; // +1 week on ctrl or command +down\n\t\t\t\tdefault: handled = false;\n\t\t\t}\n\t\t} else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home\n\t\t\t$.datepicker._showDatepicker( this );\n\t\t} else {\n\t\t\thandled = false;\n\t\t}\n\n\t\tif ( handled ) {\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\t\t}\n\t},\n\n\t/* Filter entered characters - based on date format. */\n\t_doKeyPress: function( event ) {\n\t\tvar chars, chr,\n\t\t\tinst = $.datepicker._getInst( event.target );\n\n\t\tif ( $.datepicker._get( inst, \"constrainInput\" ) ) {\n\t\t\tchars = $.datepicker._possibleChars( $.datepicker._get( inst, \"dateFormat\" ) );\n\t\t\tchr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode );\n\t\t\treturn event.ctrlKey || event.metaKey || ( chr < \" \" || !chars || chars.indexOf( chr ) > -1 );\n\t\t}\n\t},\n\n\t/* Synchronise manual entry and field/alternate field. */\n\t_doKeyUp: function( event ) {\n\t\tvar date,\n\t\t\tinst = $.datepicker._getInst( event.target );\n\n\t\tif ( inst.input.val() !== inst.lastVal ) {\n\t\t\ttry {\n\t\t\t\tdate = $.datepicker.parseDate( $.datepicker._get( inst, \"dateFormat\" ),\n\t\t\t\t\t( inst.input ? inst.input.val() : null ),\n\t\t\t\t\t$.datepicker._getFormatConfig( inst ) );\n\n\t\t\t\tif ( date ) { // only if valid\n\t\t\t\t\t$.datepicker._setDateFromField( inst );\n\t\t\t\t\t$.datepicker._updateAlternate( inst );\n\t\t\t\t\t$.datepicker._updateDatepicker( inst );\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch ( err ) {\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t},\n\n\t/* Pop-up the date picker for a given input field.\n\t * If false returned from beforeShow event handler do not show.\n\t * @param  input  element - the input field attached to the date picker or\n\t *\t\t\t\t\tevent - if triggered by focus\n\t */\n\t_showDatepicker: function( input ) {\n\t\tinput = input.target || input;\n\t\tif ( input.nodeName.toLowerCase() !== \"input\" ) { // find from button/image trigger\n\t\t\tinput = $( \"input\", input.parentNode )[ 0 ];\n\t\t}\n\n\t\tif ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here\n\t\t\treturn;\n\t\t}\n\n\t\tvar inst, beforeShow, beforeShowSettings, isFixed,\n\t\t\toffset, showAnim, duration;\n\n\t\tinst = $.datepicker._getInst( input );\n\t\tif ( $.datepicker._curInst && $.datepicker._curInst !== inst ) {\n\t\t\t$.datepicker._curInst.dpDiv.stop( true, true );\n\t\t\tif ( inst && $.datepicker._datepickerShowing ) {\n\t\t\t\t$.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] );\n\t\t\t}\n\t\t}\n\n\t\tbeforeShow = $.datepicker._get( inst, \"beforeShow\" );\n\t\tbeforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {};\n\t\tif ( beforeShowSettings === false ) {\n\t\t\treturn;\n\t\t}\n\t\tdatepicker_extendRemove( inst.settings, beforeShowSettings );\n\n\t\tinst.lastVal = null;\n\t\t$.datepicker._lastInput = input;\n\t\t$.datepicker._setDateFromField( inst );\n\n\t\tif ( $.datepicker._inDialog ) { // hide cursor\n\t\t\tinput.value = \"\";\n\t\t}\n\t\tif ( !$.datepicker._pos ) { // position below input\n\t\t\t$.datepicker._pos = $.datepicker._findPos( input );\n\t\t\t$.datepicker._pos[ 1 ] += input.offsetHeight; // add the height\n\t\t}\n\n\t\tisFixed = false;\n\t\t$( input ).parents().each( function() {\n\t\t\tisFixed |= $( this ).css( \"position\" ) === \"fixed\";\n\t\t\treturn !isFixed;\n\t\t} );\n\n\t\toffset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] };\n\t\t$.datepicker._pos = null;\n\n\t\t//to avoid flashes on Firefox\n\t\tinst.dpDiv.empty();\n\n\t\t// determine sizing offscreen\n\t\tinst.dpDiv.css( { position: \"absolute\", display: \"block\", top: \"-1000px\" } );\n\t\t$.datepicker._updateDatepicker( inst );\n\n\t\t// fix width for dynamic number of date pickers\n\t\t// and adjust position before showing\n\t\toffset = $.datepicker._checkOffset( inst, offset, isFixed );\n\t\tinst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ?\n\t\t\t\"static\" : ( isFixed ? \"fixed\" : \"absolute\" ) ), display: \"none\",\n\t\t\tleft: offset.left + \"px\", top: offset.top + \"px\" } );\n\n\t\tif ( !inst.inline ) {\n\t\t\tshowAnim = $.datepicker._get( inst, \"showAnim\" );\n\t\t\tduration = $.datepicker._get( inst, \"duration\" );\n\t\t\tinst.dpDiv.css( \"z-index\", datepicker_getZindex( $( input ) ) + 1 );\n\t\t\t$.datepicker._datepickerShowing = true;\n\n\t\t\tif ( $.effects && $.effects.effect[ showAnim ] ) {\n\t\t\t\tinst.dpDiv.show( showAnim, $.datepicker._get( inst, \"showOptions\" ), duration );\n\t\t\t} else {\n\t\t\t\tinst.dpDiv[ showAnim || \"show\" ]( showAnim ? duration : null );\n\t\t\t}\n\n\t\t\tif ( $.datepicker._shouldFocusInput( inst ) ) {\n\t\t\t\tinst.input.trigger( \"focus\" );\n\t\t\t}\n\n\t\t\t$.datepicker._curInst = inst;\n\t\t}\n\t},\n\n\t/* Generate the date picker content. */\n\t_updateDatepicker: function( inst ) {\n\t\tthis.maxRows = 4; //Reset the max number of rows being displayed (see #7043)\n\t\tdatepicker_instActive = inst; // for delegate hover events\n\t\tinst.dpDiv.empty().append( this._generateHTML( inst ) );\n\t\tthis._attachHandlers( inst );\n\n\t\tvar origyearshtml,\n\t\t\tnumMonths = this._getNumberOfMonths( inst ),\n\t\t\tcols = numMonths[ 1 ],\n\t\t\twidth = 17,\n\t\t\tactiveCell = inst.dpDiv.find( \".\" + this._dayOverClass + \" a\" );\n\n\t\tif ( activeCell.length > 0 ) {\n\t\t\tdatepicker_handleMouseover.apply( activeCell.get( 0 ) );\n\t\t}\n\n\t\tinst.dpDiv.removeClass( \"ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4\" ).width( \"\" );\n\t\tif ( cols > 1 ) {\n\t\t\tinst.dpDiv.addClass( \"ui-datepicker-multi-\" + cols ).css( \"width\", ( width * cols ) + \"em\" );\n\t\t}\n\t\tinst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? \"add\" : \"remove\" ) +\n\t\t\t\"Class\" ]( \"ui-datepicker-multi\" );\n\t\tinst.dpDiv[ ( this._get( inst, \"isRTL\" ) ? \"add\" : \"remove\" ) +\n\t\t\t\"Class\" ]( \"ui-datepicker-rtl\" );\n\n\t\tif ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {\n\t\t\tinst.input.trigger( \"focus\" );\n\t\t}\n\n\t\t// Deffered render of the years select (to avoid flashes on Firefox)\n\t\tif ( inst.yearshtml ) {\n\t\t\torigyearshtml = inst.yearshtml;\n\t\t\tsetTimeout( function() {\n\n\t\t\t\t//assure that inst.yearshtml didn't change.\n\t\t\t\tif ( origyearshtml === inst.yearshtml && inst.yearshtml ) {\n\t\t\t\t\tinst.dpDiv.find( \"select.ui-datepicker-year:first\" ).replaceWith( inst.yearshtml );\n\t\t\t\t}\n\t\t\t\torigyearshtml = inst.yearshtml = null;\n\t\t\t}, 0 );\n\t\t}\n\t},\n\n\t// #6694 - don't focus the input if it's already focused\n\t// this breaks the change event in IE\n\t// Support: IE and jQuery <1.9\n\t_shouldFocusInput: function( inst ) {\n\t\treturn inst.input && inst.input.is( \":visible\" ) && !inst.input.is( \":disabled\" ) && !inst.input.is( \":focus\" );\n\t},\n\n\t/* Check positioning to remain on screen. */\n\t_checkOffset: function( inst, offset, isFixed ) {\n\t\tvar dpWidth = inst.dpDiv.outerWidth(),\n\t\t\tdpHeight = inst.dpDiv.outerHeight(),\n\t\t\tinputWidth = inst.input ? inst.input.outerWidth() : 0,\n\t\t\tinputHeight = inst.input ? inst.input.outerHeight() : 0,\n\t\t\tviewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ),\n\t\t\tviewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() );\n\n\t\toffset.left -= ( this._get( inst, \"isRTL\" ) ? ( dpWidth - inputWidth ) : 0 );\n\t\toffset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0;\n\t\toffset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0;\n\n\t\t// Now check if datepicker is showing outside window viewport - move to a better place if so.\n\t\toffset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ?\n\t\t\tMath.abs( offset.left + dpWidth - viewWidth ) : 0 );\n\t\toffset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ?\n\t\t\tMath.abs( dpHeight + inputHeight ) : 0 );\n\n\t\treturn offset;\n\t},\n\n\t/* Find an object's position on the screen. */\n\t_findPos: function( obj ) {\n\t\tvar position,\n\t\t\tinst = this._getInst( obj ),\n\t\t\tisRTL = this._get( inst, \"isRTL\" );\n\n\t\twhile ( obj && ( obj.type === \"hidden\" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) {\n\t\t\tobj = obj[ isRTL ? \"previousSibling\" : \"nextSibling\" ];\n\t\t}\n\n\t\tposition = $( obj ).offset();\n\t\treturn [ position.left, position.top ];\n\t},\n\n\t/* Hide the date picker from view.\n\t * @param  input  element - the input field attached to the date picker\n\t */\n\t_hideDatepicker: function( input ) {\n\t\tvar showAnim, duration, postProcess, onClose,\n\t\t\tinst = this._curInst;\n\n\t\tif ( !inst || ( input && inst !== $.data( input, \"datepicker\" ) ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this._datepickerShowing ) {\n\t\t\tshowAnim = this._get( inst, \"showAnim\" );\n\t\t\tduration = this._get( inst, \"duration\" );\n\t\t\tpostProcess = function() {\n\t\t\t\t$.datepicker._tidyDialog( inst );\n\t\t\t};\n\n\t\t\t// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed\n\t\t\tif ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {\n\t\t\t\tinst.dpDiv.hide( showAnim, $.datepicker._get( inst, \"showOptions\" ), duration, postProcess );\n\t\t\t} else {\n\t\t\t\tinst.dpDiv[ ( showAnim === \"slideDown\" ? \"slideUp\" :\n\t\t\t\t\t( showAnim === \"fadeIn\" ? \"fadeOut\" : \"hide\" ) ) ]( ( showAnim ? duration : null ), postProcess );\n\t\t\t}\n\n\t\t\tif ( !showAnim ) {\n\t\t\t\tpostProcess();\n\t\t\t}\n\t\t\tthis._datepickerShowing = false;\n\n\t\t\tonClose = this._get( inst, \"onClose\" );\n\t\t\tif ( onClose ) {\n\t\t\t\tonClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : \"\" ), inst ] );\n\t\t\t}\n\n\t\t\tthis._lastInput = null;\n\t\t\tif ( this._inDialog ) {\n\t\t\t\tthis._dialogInput.css( { position: \"absolute\", left: \"0\", top: \"-100px\" } );\n\t\t\t\tif ( $.blockUI ) {\n\t\t\t\t\t$.unblockUI();\n\t\t\t\t\t$( \"body\" ).append( this.dpDiv );\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._inDialog = false;\n\t\t}\n\t},\n\n\t/* Tidy up after a dialog display. */\n\t_tidyDialog: function( inst ) {\n\t\tinst.dpDiv.removeClass( this._dialogClass ).off( \".ui-datepicker-calendar\" );\n\t},\n\n\t/* Close date picker if clicked elsewhere. */\n\t_checkExternalClick: function( event ) {\n\t\tif ( !$.datepicker._curInst ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar $target = $( event.target ),\n\t\t\tinst = $.datepicker._getInst( $target[ 0 ] );\n\n\t\tif ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId &&\n\t\t\t\t$target.parents( \"#\" + $.datepicker._mainDivId ).length === 0 &&\n\t\t\t\t!$target.hasClass( $.datepicker.markerClassName ) &&\n\t\t\t\t!$target.closest( \".\" + $.datepicker._triggerClass ).length &&\n\t\t\t\t$.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) ||\n\t\t\t( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) {\n\t\t\t\t$.datepicker._hideDatepicker();\n\t\t}\n\t},\n\n\t/* Adjust one of the date sub-fields. */\n\t_adjustDate: function( id, offset, period ) {\n\t\tvar target = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tif ( this._isDisabledDatepicker( target[ 0 ] ) ) {\n\t\t\treturn;\n\t\t}\n\t\tthis._adjustInstDate( inst, offset +\n\t\t\t( period === \"M\" ? this._get( inst, \"showCurrentAtPos\" ) : 0 ), // undo positioning\n\t\t\tperiod );\n\t\tthis._updateDatepicker( inst );\n\t},\n\n\t/* Action for current link. */\n\t_gotoToday: function( id ) {\n\t\tvar date,\n\t\t\ttarget = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tif ( this._get( inst, \"gotoCurrent\" ) && inst.currentDay ) {\n\t\t\tinst.selectedDay = inst.currentDay;\n\t\t\tinst.drawMonth = inst.selectedMonth = inst.currentMonth;\n\t\t\tinst.drawYear = inst.selectedYear = inst.currentYear;\n\t\t} else {\n\t\t\tdate = new Date();\n\t\t\tinst.selectedDay = date.getDate();\n\t\t\tinst.drawMonth = inst.selectedMonth = date.getMonth();\n\t\t\tinst.drawYear = inst.selectedYear = date.getFullYear();\n\t\t}\n\t\tthis._notifyChange( inst );\n\t\tthis._adjustDate( target );\n\t},\n\n\t/* Action for selecting a new month/year. */\n\t_selectMonthYear: function( id, select, period ) {\n\t\tvar target = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tinst[ \"selected\" + ( period === \"M\" ? \"Month\" : \"Year\" ) ] =\n\t\tinst[ \"draw\" + ( period === \"M\" ? \"Month\" : \"Year\" ) ] =\n\t\t\tparseInt( select.options[ select.selectedIndex ].value, 10 );\n\n\t\tthis._notifyChange( inst );\n\t\tthis._adjustDate( target );\n\t},\n\n\t/* Action for selecting a day. */\n\t_selectDay: function( id, month, year, td ) {\n\t\tvar inst,\n\t\t\ttarget = $( id );\n\n\t\tif ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tinst = this._getInst( target[ 0 ] );\n\t\tinst.selectedDay = inst.currentDay = $( \"a\", td ).html();\n\t\tinst.selectedMonth = inst.currentMonth = month;\n\t\tinst.selectedYear = inst.currentYear = year;\n\t\tthis._selectDate( id, this._formatDate( inst,\n\t\t\tinst.currentDay, inst.currentMonth, inst.currentYear ) );\n\t},\n\n\t/* Erase the input field and hide the date picker. */\n\t_clearDate: function( id ) {\n\t\tvar target = $( id );\n\t\tthis._selectDate( target, \"\" );\n\t},\n\n\t/* Update the input field with the selected date. */\n\t_selectDate: function( id, dateStr ) {\n\t\tvar onSelect,\n\t\t\ttarget = $( id ),\n\t\t\tinst = this._getInst( target[ 0 ] );\n\n\t\tdateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) );\n\t\tif ( inst.input ) {\n\t\t\tinst.input.val( dateStr );\n\t\t}\n\t\tthis._updateAlternate( inst );\n\n\t\tonSelect = this._get( inst, \"onSelect\" );\n\t\tif ( onSelect ) {\n\t\t\tonSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );  // trigger custom callback\n\t\t} else if ( inst.input ) {\n\t\t\tinst.input.trigger( \"change\" ); // fire the change event\n\t\t}\n\n\t\tif ( inst.inline ) {\n\t\t\tthis._updateDatepicker( inst );\n\t\t} else {\n\t\t\tthis._hideDatepicker();\n\t\t\tthis._lastInput = inst.input[ 0 ];\n\t\t\tif ( typeof( inst.input[ 0 ] ) !== \"object\" ) {\n\t\t\t\tinst.input.trigger( \"focus\" ); // restore focus\n\t\t\t}\n\t\t\tthis._lastInput = null;\n\t\t}\n\t},\n\n\t/* Update any alternate field to synchronise with the main field. */\n\t_updateAlternate: function( inst ) {\n\t\tvar altFormat, date, dateStr,\n\t\t\taltField = this._get( inst, \"altField\" );\n\n\t\tif ( altField ) { // update alternate field too\n\t\t\taltFormat = this._get( inst, \"altFormat\" ) || this._get( inst, \"dateFormat\" );\n\t\t\tdate = this._getDate( inst );\n\t\t\tdateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) );\n\t\t\t$( altField ).val( dateStr );\n\t\t}\n\t},\n\n\t/* Set as beforeShowDay function to prevent selection of weekends.\n\t * @param  date  Date - the date to customise\n\t * @return [boolean, string] - is this date selectable?, what is its CSS class?\n\t */\n\tnoWeekends: function( date ) {\n\t\tvar day = date.getDay();\n\t\treturn [ ( day > 0 && day < 6 ), \"\" ];\n\t},\n\n\t/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.\n\t * @param  date  Date - the date to get the week for\n\t * @return  number - the number of the week within the year that contains this date\n\t */\n\tiso8601Week: function( date ) {\n\t\tvar time,\n\t\t\tcheckDate = new Date( date.getTime() );\n\n\t\t// Find Thursday of this week starting on Monday\n\t\tcheckDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) );\n\n\t\ttime = checkDate.getTime();\n\t\tcheckDate.setMonth( 0 ); // Compare with Jan 1\n\t\tcheckDate.setDate( 1 );\n\t\treturn Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1;\n\t},\n\n\t/* Parse a string value into a date object.\n\t * See formatDate below for the possible formats.\n\t *\n\t * @param  format string - the expected format of the date\n\t * @param  value string - the date in the above format\n\t * @param  settings Object - attributes include:\n\t *\t\t\t\t\tshortYearCutoff  number - the cutoff year for determining the century (optional)\n\t *\t\t\t\t\tdayNamesShort\tstring[7] - abbreviated names of the days from Sunday (optional)\n\t *\t\t\t\t\tdayNames\t\tstring[7] - names of the days from Sunday (optional)\n\t *\t\t\t\t\tmonthNamesShort string[12] - abbreviated names of the months (optional)\n\t *\t\t\t\t\tmonthNames\t\tstring[12] - names of the months (optional)\n\t * @return  Date - the extracted date value or null if value is blank\n\t */\n\tparseDate: function( format, value, settings ) {\n\t\tif ( format == null || value == null ) {\n\t\t\tthrow \"Invalid arguments\";\n\t\t}\n\n\t\tvalue = ( typeof value === \"object\" ? value.toString() : value + \"\" );\n\t\tif ( value === \"\" ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tvar iFormat, dim, extra,\n\t\t\tiValue = 0,\n\t\t\tshortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff,\n\t\t\tshortYearCutoff = ( typeof shortYearCutoffTemp !== \"string\" ? shortYearCutoffTemp :\n\t\t\t\tnew Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ),\n\t\t\tdayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,\n\t\t\tdayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,\n\t\t\tmonthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,\n\t\t\tmonthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,\n\t\t\tyear = -1,\n\t\t\tmonth = -1,\n\t\t\tday = -1,\n\t\t\tdoy = -1,\n\t\t\tliteral = false,\n\t\t\tdate,\n\n\t\t\t// Check whether a format character is doubled\n\t\t\tlookAhead = function( match ) {\n\t\t\t\tvar matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );\n\t\t\t\tif ( matches ) {\n\t\t\t\t\tiFormat++;\n\t\t\t\t}\n\t\t\t\treturn matches;\n\t\t\t},\n\n\t\t\t// Extract a number from the string value\n\t\t\tgetNumber = function( match ) {\n\t\t\t\tvar isDoubled = lookAhead( match ),\n\t\t\t\t\tsize = ( match === \"@\" ? 14 : ( match === \"!\" ? 20 :\n\t\t\t\t\t( match === \"y\" && isDoubled ? 4 : ( match === \"o\" ? 3 : 2 ) ) ) ),\n\t\t\t\t\tminSize = ( match === \"y\" ? size : 1 ),\n\t\t\t\t\tdigits = new RegExp( \"^\\\\d{\" + minSize + \",\" + size + \"}\" ),\n\t\t\t\t\tnum = value.substring( iValue ).match( digits );\n\t\t\t\tif ( !num ) {\n\t\t\t\t\tthrow \"Missing number at position \" + iValue;\n\t\t\t\t}\n\t\t\t\tiValue += num[ 0 ].length;\n\t\t\t\treturn parseInt( num[ 0 ], 10 );\n\t\t\t},\n\n\t\t\t// Extract a name from the string value and convert to an index\n\t\t\tgetName = function( match, shortNames, longNames ) {\n\t\t\t\tvar index = -1,\n\t\t\t\t\tnames = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) {\n\t\t\t\t\t\treturn [ [ k, v ] ];\n\t\t\t\t\t} ).sort( function( a, b ) {\n\t\t\t\t\t\treturn -( a[ 1 ].length - b[ 1 ].length );\n\t\t\t\t\t} );\n\n\t\t\t\t$.each( names, function( i, pair ) {\n\t\t\t\t\tvar name = pair[ 1 ];\n\t\t\t\t\tif ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) {\n\t\t\t\t\t\tindex = pair[ 0 ];\n\t\t\t\t\t\tiValue += name.length;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\tif ( index !== -1 ) {\n\t\t\t\t\treturn index + 1;\n\t\t\t\t} else {\n\t\t\t\t\tthrow \"Unknown name at position \" + iValue;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// Confirm that a literal character matches the string value\n\t\t\tcheckLiteral = function() {\n\t\t\t\tif ( value.charAt( iValue ) !== format.charAt( iFormat ) ) {\n\t\t\t\t\tthrow \"Unexpected literal at position \" + iValue;\n\t\t\t\t}\n\t\t\t\tiValue++;\n\t\t\t};\n\n\t\tfor ( iFormat = 0; iFormat < format.length; iFormat++ ) {\n\t\t\tif ( literal ) {\n\t\t\t\tif ( format.charAt( iFormat ) === \"'\" && !lookAhead( \"'\" ) ) {\n\t\t\t\t\tliteral = false;\n\t\t\t\t} else {\n\t\t\t\t\tcheckLiteral();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch ( format.charAt( iFormat ) ) {\n\t\t\t\t\tcase \"d\":\n\t\t\t\t\t\tday = getNumber( \"d\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"D\":\n\t\t\t\t\t\tgetName( \"D\", dayNamesShort, dayNames );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"o\":\n\t\t\t\t\t\tdoy = getNumber( \"o\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"m\":\n\t\t\t\t\t\tmonth = getNumber( \"m\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"M\":\n\t\t\t\t\t\tmonth = getName( \"M\", monthNamesShort, monthNames );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"y\":\n\t\t\t\t\t\tyear = getNumber( \"y\" );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"@\":\n\t\t\t\t\t\tdate = new Date( getNumber( \"@\" ) );\n\t\t\t\t\t\tyear = date.getFullYear();\n\t\t\t\t\t\tmonth = date.getMonth() + 1;\n\t\t\t\t\t\tday = date.getDate();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"!\":\n\t\t\t\t\t\tdate = new Date( ( getNumber( \"!\" ) - this._ticksTo1970 ) / 10000 );\n\t\t\t\t\t\tyear = date.getFullYear();\n\t\t\t\t\t\tmonth = date.getMonth() + 1;\n\t\t\t\t\t\tday = date.getDate();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"'\":\n\t\t\t\t\t\tif ( lookAhead( \"'\" ) ) {\n\t\t\t\t\t\t\tcheckLiteral();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tliteral = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcheckLiteral();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( iValue < value.length ) {\n\t\t\textra = value.substr( iValue );\n\t\t\tif ( !/^\\s+/.test( extra ) ) {\n\t\t\t\tthrow \"Extra/unparsed characters found in date: \" + extra;\n\t\t\t}\n\t\t}\n\n\t\tif ( year === -1 ) {\n\t\t\tyear = new Date().getFullYear();\n\t\t} else if ( year < 100 ) {\n\t\t\tyear += new Date().getFullYear() - new Date().getFullYear() % 100 +\n\t\t\t\t( year <= shortYearCutoff ? 0 : -100 );\n\t\t}\n\n\t\tif ( doy > -1 ) {\n\t\t\tmonth = 1;\n\t\t\tday = doy;\n\t\t\tdo {\n\t\t\t\tdim = this._getDaysInMonth( year, month - 1 );\n\t\t\t\tif ( day <= dim ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmonth++;\n\t\t\t\tday -= dim;\n\t\t\t} while ( true );\n\t\t}\n\n\t\tdate = this._daylightSavingAdjust( new Date( year, month - 1, day ) );\n\t\tif ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) {\n\t\t\tthrow \"Invalid date\"; // E.g. 31/02/00\n\t\t}\n\t\treturn date;\n\t},\n\n\t/* Standard date formats. */\n\tATOM: \"yy-mm-dd\", // RFC 3339 (ISO 8601)\n\tCOOKIE: \"D, dd M yy\",\n\tISO_8601: \"yy-mm-dd\",\n\tRFC_822: \"D, d M y\",\n\tRFC_850: \"DD, dd-M-y\",\n\tRFC_1036: \"D, d M y\",\n\tRFC_1123: \"D, d M yy\",\n\tRFC_2822: \"D, d M yy\",\n\tRSS: \"D, d M y\", // RFC 822\n\tTICKS: \"!\",\n\tTIMESTAMP: \"@\",\n\tW3C: \"yy-mm-dd\", // ISO 8601\n\n\t_ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) +\n\t\tMath.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ),\n\n\t/* Format a date object into a string value.\n\t * The format can be combinations of the following:\n\t * d  - day of month (no leading zero)\n\t * dd - day of month (two digit)\n\t * o  - day of year (no leading zeros)\n\t * oo - day of year (three digit)\n\t * D  - day name short\n\t * DD - day name long\n\t * m  - month of year (no leading zero)\n\t * mm - month of year (two digit)\n\t * M  - month name short\n\t * MM - month name long\n\t * y  - year (two digit)\n\t * yy - year (four digit)\n\t * @ - Unix timestamp (ms since 01/01/1970)\n\t * ! - Windows ticks (100ns since 01/01/0001)\n\t * \"...\" - literal text\n\t * '' - single quote\n\t *\n\t * @param  format string - the desired format of the date\n\t * @param  date Date - the date value to format\n\t * @param  settings Object - attributes include:\n\t *\t\t\t\t\tdayNamesShort\tstring[7] - abbreviated names of the days from Sunday (optional)\n\t *\t\t\t\t\tdayNames\t\tstring[7] - names of the days from Sunday (optional)\n\t *\t\t\t\t\tmonthNamesShort string[12] - abbreviated names of the months (optional)\n\t *\t\t\t\t\tmonthNames\t\tstring[12] - names of the months (optional)\n\t * @return  string - the date in the above format\n\t */\n\tformatDate: function( format, date, settings ) {\n\t\tif ( !date ) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tvar iFormat,\n\t\t\tdayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,\n\t\t\tdayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,\n\t\t\tmonthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,\n\t\t\tmonthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,\n\n\t\t\t// Check whether a format character is doubled\n\t\t\tlookAhead = function( match ) {\n\t\t\t\tvar matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );\n\t\t\t\tif ( matches ) {\n\t\t\t\t\tiFormat++;\n\t\t\t\t}\n\t\t\t\treturn matches;\n\t\t\t},\n\n\t\t\t// Format a number, with leading zero if necessary\n\t\t\tformatNumber = function( match, value, len ) {\n\t\t\t\tvar num = \"\" + value;\n\t\t\t\tif ( lookAhead( match ) ) {\n\t\t\t\t\twhile ( num.length < len ) {\n\t\t\t\t\t\tnum = \"0\" + num;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn num;\n\t\t\t},\n\n\t\t\t// Format a name, short or long as requested\n\t\t\tformatName = function( match, value, shortNames, longNames ) {\n\t\t\t\treturn ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] );\n\t\t\t},\n\t\t\toutput = \"\",\n\t\t\tliteral = false;\n\n\t\tif ( date ) {\n\t\t\tfor ( iFormat = 0; iFormat < format.length; iFormat++ ) {\n\t\t\t\tif ( literal ) {\n\t\t\t\t\tif ( format.charAt( iFormat ) === \"'\" && !lookAhead( \"'\" ) ) {\n\t\t\t\t\t\tliteral = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\toutput += format.charAt( iFormat );\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tswitch ( format.charAt( iFormat ) ) {\n\t\t\t\t\t\tcase \"d\":\n\t\t\t\t\t\t\toutput += formatNumber( \"d\", date.getDate(), 2 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"D\":\n\t\t\t\t\t\t\toutput += formatName( \"D\", date.getDay(), dayNamesShort, dayNames );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"o\":\n\t\t\t\t\t\t\toutput += formatNumber( \"o\",\n\t\t\t\t\t\t\t\tMath.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"m\":\n\t\t\t\t\t\t\toutput += formatNumber( \"m\", date.getMonth() + 1, 2 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"M\":\n\t\t\t\t\t\t\toutput += formatName( \"M\", date.getMonth(), monthNamesShort, monthNames );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"y\":\n\t\t\t\t\t\t\toutput += ( lookAhead( \"y\" ) ? date.getFullYear() :\n\t\t\t\t\t\t\t\t( date.getFullYear() % 100 < 10 ? \"0\" : \"\" ) + date.getFullYear() % 100 );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"@\":\n\t\t\t\t\t\t\toutput += date.getTime();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"!\":\n\t\t\t\t\t\t\toutput += date.getTime() * 10000 + this._ticksTo1970;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"'\":\n\t\t\t\t\t\t\tif ( lookAhead( \"'\" ) ) {\n\t\t\t\t\t\t\t\toutput += \"'\";\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tliteral = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\toutput += format.charAt( iFormat );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn output;\n\t},\n\n\t/* Extract all possible characters from the date format. */\n\t_possibleChars: function( format ) {\n\t\tvar iFormat,\n\t\t\tchars = \"\",\n\t\t\tliteral = false,\n\n\t\t\t// Check whether a format character is doubled\n\t\t\tlookAhead = function( match ) {\n\t\t\t\tvar matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );\n\t\t\t\tif ( matches ) {\n\t\t\t\t\tiFormat++;\n\t\t\t\t}\n\t\t\t\treturn matches;\n\t\t\t};\n\n\t\tfor ( iFormat = 0; iFormat < format.length; iFormat++ ) {\n\t\t\tif ( literal ) {\n\t\t\t\tif ( format.charAt( iFormat ) === \"'\" && !lookAhead( \"'\" ) ) {\n\t\t\t\t\tliteral = false;\n\t\t\t\t} else {\n\t\t\t\t\tchars += format.charAt( iFormat );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch ( format.charAt( iFormat ) ) {\n\t\t\t\t\tcase \"d\": case \"m\": case \"y\": case \"@\":\n\t\t\t\t\t\tchars += \"0123456789\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"D\": case \"M\":\n\t\t\t\t\t\treturn null; // Accept anything\n\t\t\t\t\tcase \"'\":\n\t\t\t\t\t\tif ( lookAhead( \"'\" ) ) {\n\t\t\t\t\t\t\tchars += \"'\";\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tliteral = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tchars += format.charAt( iFormat );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn chars;\n\t},\n\n\t/* Get a setting value, defaulting if necessary. */\n\t_get: function( inst, name ) {\n\t\treturn inst.settings[ name ] !== undefined ?\n\t\t\tinst.settings[ name ] : this._defaults[ name ];\n\t},\n\n\t/* Parse existing date and initialise date picker. */\n\t_setDateFromField: function( inst, noDefault ) {\n\t\tif ( inst.input.val() === inst.lastVal ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar dateFormat = this._get( inst, \"dateFormat\" ),\n\t\t\tdates = inst.lastVal = inst.input ? inst.input.val() : null,\n\t\t\tdefaultDate = this._getDefaultDate( inst ),\n\t\t\tdate = defaultDate,\n\t\t\tsettings = this._getFormatConfig( inst );\n\n\t\ttry {\n\t\t\tdate = this.parseDate( dateFormat, dates, settings ) || defaultDate;\n\t\t} catch ( event ) {\n\t\t\tdates = ( noDefault ? \"\" : dates );\n\t\t}\n\t\tinst.selectedDay = date.getDate();\n\t\tinst.drawMonth = inst.selectedMonth = date.getMonth();\n\t\tinst.drawYear = inst.selectedYear = date.getFullYear();\n\t\tinst.currentDay = ( dates ? date.getDate() : 0 );\n\t\tinst.currentMonth = ( dates ? date.getMonth() : 0 );\n\t\tinst.currentYear = ( dates ? date.getFullYear() : 0 );\n\t\tthis._adjustInstDate( inst );\n\t},\n\n\t/* Retrieve the default date shown on opening. */\n\t_getDefaultDate: function( inst ) {\n\t\treturn this._restrictMinMax( inst,\n\t\t\tthis._determineDate( inst, this._get( inst, \"defaultDate\" ), new Date() ) );\n\t},\n\n\t/* A date may be specified as an exact value or a relative one. */\n\t_determineDate: function( inst, date, defaultDate ) {\n\t\tvar offsetNumeric = function( offset ) {\n\t\t\t\tvar date = new Date();\n\t\t\t\tdate.setDate( date.getDate() + offset );\n\t\t\t\treturn date;\n\t\t\t},\n\t\t\toffsetString = function( offset ) {\n\t\t\t\ttry {\n\t\t\t\t\treturn $.datepicker.parseDate( $.datepicker._get( inst, \"dateFormat\" ),\n\t\t\t\t\t\toffset, $.datepicker._getFormatConfig( inst ) );\n\t\t\t\t}\n\t\t\t\tcatch ( e ) {\n\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\n\t\t\t\tvar date = ( offset.toLowerCase().match( /^c/ ) ?\n\t\t\t\t\t$.datepicker._getDate( inst ) : null ) || new Date(),\n\t\t\t\t\tyear = date.getFullYear(),\n\t\t\t\t\tmonth = date.getMonth(),\n\t\t\t\t\tday = date.getDate(),\n\t\t\t\t\tpattern = /([+\\-]?[0-9]+)\\s*(d|D|w|W|m|M|y|Y)?/g,\n\t\t\t\t\tmatches = pattern.exec( offset );\n\n\t\t\t\twhile ( matches ) {\n\t\t\t\t\tswitch ( matches[ 2 ] || \"d\" ) {\n\t\t\t\t\t\tcase \"d\" : case \"D\" :\n\t\t\t\t\t\t\tday += parseInt( matches[ 1 ], 10 ); break;\n\t\t\t\t\t\tcase \"w\" : case \"W\" :\n\t\t\t\t\t\t\tday += parseInt( matches[ 1 ], 10 ) * 7; break;\n\t\t\t\t\t\tcase \"m\" : case \"M\" :\n\t\t\t\t\t\t\tmonth += parseInt( matches[ 1 ], 10 );\n\t\t\t\t\t\t\tday = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"y\": case \"Y\" :\n\t\t\t\t\t\t\tyear += parseInt( matches[ 1 ], 10 );\n\t\t\t\t\t\t\tday = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tmatches = pattern.exec( offset );\n\t\t\t\t}\n\t\t\t\treturn new Date( year, month, day );\n\t\t\t},\n\t\t\tnewDate = ( date == null || date === \"\" ? defaultDate : ( typeof date === \"string\" ? offsetString( date ) :\n\t\t\t\t( typeof date === \"number\" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) );\n\n\t\tnewDate = ( newDate && newDate.toString() === \"Invalid Date\" ? defaultDate : newDate );\n\t\tif ( newDate ) {\n\t\t\tnewDate.setHours( 0 );\n\t\t\tnewDate.setMinutes( 0 );\n\t\t\tnewDate.setSeconds( 0 );\n\t\t\tnewDate.setMilliseconds( 0 );\n\t\t}\n\t\treturn this._daylightSavingAdjust( newDate );\n\t},\n\n\t/* Handle switch to/from daylight saving.\n\t * Hours may be non-zero on daylight saving cut-over:\n\t * > 12 when midnight changeover, but then cannot generate\n\t * midnight datetime, so jump to 1AM, otherwise reset.\n\t * @param  date  (Date) the date to check\n\t * @return  (Date) the corrected date\n\t */\n\t_daylightSavingAdjust: function( date ) {\n\t\tif ( !date ) {\n\t\t\treturn null;\n\t\t}\n\t\tdate.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 );\n\t\treturn date;\n\t},\n\n\t/* Set the date(s) directly. */\n\t_setDate: function( inst, date, noChange ) {\n\t\tvar clear = !date,\n\t\t\torigMonth = inst.selectedMonth,\n\t\t\torigYear = inst.selectedYear,\n\t\t\tnewDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) );\n\n\t\tinst.selectedDay = inst.currentDay = newDate.getDate();\n\t\tinst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();\n\t\tinst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();\n\t\tif ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) {\n\t\t\tthis._notifyChange( inst );\n\t\t}\n\t\tthis._adjustInstDate( inst );\n\t\tif ( inst.input ) {\n\t\t\tinst.input.val( clear ? \"\" : this._formatDate( inst ) );\n\t\t}\n\t},\n\n\t/* Retrieve the date(s) directly. */\n\t_getDate: function( inst ) {\n\t\tvar startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === \"\" ) ? null :\n\t\t\tthis._daylightSavingAdjust( new Date(\n\t\t\tinst.currentYear, inst.currentMonth, inst.currentDay ) ) );\n\t\t\treturn startDate;\n\t},\n\n\t/* Attach the onxxx handlers.  These are declared statically so\n\t * they work with static code transformers like Caja.\n\t */\n\t_attachHandlers: function( inst ) {\n\t\tvar stepMonths = this._get( inst, \"stepMonths\" ),\n\t\t\tid = \"#\" + inst.id.replace( /\\\\\\\\/g, \"\\\\\" );\n\t\tinst.dpDiv.find( \"[data-handler]\" ).map( function() {\n\t\t\tvar handler = {\n\t\t\t\tprev: function() {\n\t\t\t\t\t$.datepicker._adjustDate( id, -stepMonths, \"M\" );\n\t\t\t\t},\n\t\t\t\tnext: function() {\n\t\t\t\t\t$.datepicker._adjustDate( id, +stepMonths, \"M\" );\n\t\t\t\t},\n\t\t\t\thide: function() {\n\t\t\t\t\t$.datepicker._hideDatepicker();\n\t\t\t\t},\n\t\t\t\ttoday: function() {\n\t\t\t\t\t$.datepicker._gotoToday( id );\n\t\t\t\t},\n\t\t\t\tselectDay: function() {\n\t\t\t\t\t$.datepicker._selectDay( id, +this.getAttribute( \"data-month\" ), +this.getAttribute( \"data-year\" ), this );\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t\tselectMonth: function() {\n\t\t\t\t\t$.datepicker._selectMonthYear( id, this, \"M\" );\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t\tselectYear: function() {\n\t\t\t\t\t$.datepicker._selectMonthYear( id, this, \"Y\" );\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t};\n\t\t\t$( this ).on( this.getAttribute( \"data-event\" ), handler[ this.getAttribute( \"data-handler\" ) ] );\n\t\t} );\n\t},\n\n\t/* Generate the HTML for the current state of the date picker. */\n\t_generateHTML: function( inst ) {\n\t\tvar maxDraw, prevText, prev, nextText, next, currentText, gotoDate,\n\t\t\tcontrols, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,\n\t\t\tmonthNames, monthNamesShort, beforeShowDay, showOtherMonths,\n\t\t\tselectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,\n\t\t\tcornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,\n\t\t\tprintDate, dRow, tbody, daySettings, otherMonth, unselectable,\n\t\t\ttempDate = new Date(),\n\t\t\ttoday = this._daylightSavingAdjust(\n\t\t\t\tnew Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time\n\t\t\tisRTL = this._get( inst, \"isRTL\" ),\n\t\t\tshowButtonPanel = this._get( inst, \"showButtonPanel\" ),\n\t\t\thideIfNoPrevNext = this._get( inst, \"hideIfNoPrevNext\" ),\n\t\t\tnavigationAsDateFormat = this._get( inst, \"navigationAsDateFormat\" ),\n\t\t\tnumMonths = this._getNumberOfMonths( inst ),\n\t\t\tshowCurrentAtPos = this._get( inst, \"showCurrentAtPos\" ),\n\t\t\tstepMonths = this._get( inst, \"stepMonths\" ),\n\t\t\tisMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ),\n\t\t\tcurrentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) :\n\t\t\t\tnew Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ),\n\t\t\tminDate = this._getMinMaxDate( inst, \"min\" ),\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" ),\n\t\t\tdrawMonth = inst.drawMonth - showCurrentAtPos,\n\t\t\tdrawYear = inst.drawYear;\n\n\t\tif ( drawMonth < 0 ) {\n\t\t\tdrawMonth += 12;\n\t\t\tdrawYear--;\n\t\t}\n\t\tif ( maxDate ) {\n\t\t\tmaxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(),\n\t\t\t\tmaxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) );\n\t\t\tmaxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw );\n\t\t\twhile ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) {\n\t\t\t\tdrawMonth--;\n\t\t\t\tif ( drawMonth < 0 ) {\n\t\t\t\t\tdrawMonth = 11;\n\t\t\t\t\tdrawYear--;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tinst.drawMonth = drawMonth;\n\t\tinst.drawYear = drawYear;\n\n\t\tprevText = this._get( inst, \"prevText\" );\n\t\tprevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText,\n\t\t\tthis._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ),\n\t\t\tthis._getFormatConfig( inst ) ) );\n\n\t\tprev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ?\n\t\t\t\"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'\" +\n\t\t\t\" title='\" + prevText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"e\" : \"w\" ) + \"'>\" + prevText + \"</span></a>\" :\n\t\t\t( hideIfNoPrevNext ? \"\" : \"<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='\" + prevText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"e\" : \"w\" ) + \"'>\" + prevText + \"</span></a>\" ) );\n\n\t\tnextText = this._get( inst, \"nextText\" );\n\t\tnextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText,\n\t\t\tthis._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ),\n\t\t\tthis._getFormatConfig( inst ) ) );\n\n\t\tnext = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ?\n\t\t\t\"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'\" +\n\t\t\t\" title='\" + nextText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"w\" : \"e\" ) + \"'>\" + nextText + \"</span></a>\" :\n\t\t\t( hideIfNoPrevNext ? \"\" : \"<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='\" + nextText + \"'><span class='ui-icon ui-icon-circle-triangle-\" + ( isRTL ? \"w\" : \"e\" ) + \"'>\" + nextText + \"</span></a>\" ) );\n\n\t\tcurrentText = this._get( inst, \"currentText\" );\n\t\tgotoDate = ( this._get( inst, \"gotoCurrent\" ) && inst.currentDay ? currentDate : today );\n\t\tcurrentText = ( !navigationAsDateFormat ? currentText :\n\t\t\tthis.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) );\n\n\t\tcontrols = ( !inst.inline ? \"<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>\" +\n\t\t\tthis._get( inst, \"closeText\" ) + \"</button>\" : \"\" );\n\n\t\tbuttonPanel = ( showButtonPanel ) ? \"<div class='ui-datepicker-buttonpane ui-widget-content'>\" + ( isRTL ? controls : \"\" ) +\n\t\t\t( this._isInRange( inst, gotoDate ) ? \"<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'\" +\n\t\t\t\">\" + currentText + \"</button>\" : \"\" ) + ( isRTL ? \"\" : controls ) + \"</div>\" : \"\";\n\n\t\tfirstDay = parseInt( this._get( inst, \"firstDay\" ), 10 );\n\t\tfirstDay = ( isNaN( firstDay ) ? 0 : firstDay );\n\n\t\tshowWeek = this._get( inst, \"showWeek\" );\n\t\tdayNames = this._get( inst, \"dayNames\" );\n\t\tdayNamesMin = this._get( inst, \"dayNamesMin\" );\n\t\tmonthNames = this._get( inst, \"monthNames\" );\n\t\tmonthNamesShort = this._get( inst, \"monthNamesShort\" );\n\t\tbeforeShowDay = this._get( inst, \"beforeShowDay\" );\n\t\tshowOtherMonths = this._get( inst, \"showOtherMonths\" );\n\t\tselectOtherMonths = this._get( inst, \"selectOtherMonths\" );\n\t\tdefaultDate = this._getDefaultDate( inst );\n\t\thtml = \"\";\n\n\t\tfor ( row = 0; row < numMonths[ 0 ]; row++ ) {\n\t\t\tgroup = \"\";\n\t\t\tthis.maxRows = 4;\n\t\t\tfor ( col = 0; col < numMonths[ 1 ]; col++ ) {\n\t\t\t\tselectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) );\n\t\t\t\tcornerClass = \" ui-corner-all\";\n\t\t\t\tcalender = \"\";\n\t\t\t\tif ( isMultiMonth ) {\n\t\t\t\t\tcalender += \"<div class='ui-datepicker-group\";\n\t\t\t\t\tif ( numMonths[ 1 ] > 1 ) {\n\t\t\t\t\t\tswitch ( col ) {\n\t\t\t\t\t\t\tcase 0: calender += \" ui-datepicker-group-first\";\n\t\t\t\t\t\t\t\tcornerClass = \" ui-corner-\" + ( isRTL ? \"right\" : \"left\" ); break;\n\t\t\t\t\t\t\tcase numMonths[ 1 ] - 1: calender += \" ui-datepicker-group-last\";\n\t\t\t\t\t\t\t\tcornerClass = \" ui-corner-\" + ( isRTL ? \"left\" : \"right\" ); break;\n\t\t\t\t\t\t\tdefault: calender += \" ui-datepicker-group-middle\"; cornerClass = \"\"; break;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcalender += \"'>\";\n\t\t\t\t}\n\t\t\t\tcalender += \"<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix\" + cornerClass + \"'>\" +\n\t\t\t\t\t( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : \"\" ) +\n\t\t\t\t\t( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : \"\" ) +\n\t\t\t\t\tthis._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate,\n\t\t\t\t\trow > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers\n\t\t\t\t\t\"</div><table class='ui-datepicker-calendar'><thead>\" +\n\t\t\t\t\t\"<tr>\";\n\t\t\t\tthead = ( showWeek ? \"<th class='ui-datepicker-week-col'>\" + this._get( inst, \"weekHeader\" ) + \"</th>\" : \"\" );\n\t\t\t\tfor ( dow = 0; dow < 7; dow++ ) { // days of the week\n\t\t\t\t\tday = ( dow + firstDay ) % 7;\n\t\t\t\t\tthead += \"<th scope='col'\" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? \" class='ui-datepicker-week-end'\" : \"\" ) + \">\" +\n\t\t\t\t\t\t\"<span title='\" + dayNames[ day ] + \"'>\" + dayNamesMin[ day ] + \"</span></th>\";\n\t\t\t\t}\n\t\t\t\tcalender += thead + \"</tr></thead><tbody>\";\n\t\t\t\tdaysInMonth = this._getDaysInMonth( drawYear, drawMonth );\n\t\t\t\tif ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) {\n\t\t\t\t\tinst.selectedDay = Math.min( inst.selectedDay, daysInMonth );\n\t\t\t\t}\n\t\t\t\tleadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7;\n\t\t\t\tcurRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate\n\t\t\t\tnumRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043)\n\t\t\t\tthis.maxRows = numRows;\n\t\t\t\tprintDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) );\n\t\t\t\tfor ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows\n\t\t\t\t\tcalender += \"<tr>\";\n\t\t\t\t\ttbody = ( !showWeek ? \"\" : \"<td class='ui-datepicker-week-col'>\" +\n\t\t\t\t\t\tthis._get( inst, \"calculateWeek\" )( printDate ) + \"</td>\" );\n\t\t\t\t\tfor ( dow = 0; dow < 7; dow++ ) { // create date picker days\n\t\t\t\t\t\tdaySettings = ( beforeShowDay ?\n\t\t\t\t\t\t\tbeforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, \"\" ] );\n\t\t\t\t\t\totherMonth = ( printDate.getMonth() !== drawMonth );\n\t\t\t\t\t\tunselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] ||\n\t\t\t\t\t\t\t( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate );\n\t\t\t\t\t\ttbody += \"<td class='\" +\n\t\t\t\t\t\t\t( ( dow + firstDay + 6 ) % 7 >= 5 ? \" ui-datepicker-week-end\" : \"\" ) + // highlight weekends\n\t\t\t\t\t\t\t( otherMonth ? \" ui-datepicker-other-month\" : \"\" ) + // highlight days from other months\n\t\t\t\t\t\t\t( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key\n\t\t\t\t\t\t\t( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ?\n\n\t\t\t\t\t\t\t// or defaultDate is current printedDate and defaultDate is selectedDate\n\t\t\t\t\t\t\t\" \" + this._dayOverClass : \"\" ) + // highlight selected day\n\t\t\t\t\t\t\t( unselectable ? \" \" + this._unselectableClass + \" ui-state-disabled\" : \"\" ) +  // highlight unselectable days\n\t\t\t\t\t\t\t( otherMonth && !showOtherMonths ? \"\" : \" \" + daySettings[ 1 ] + // highlight custom dates\n\t\t\t\t\t\t\t( printDate.getTime() === currentDate.getTime() ? \" \" + this._currentClass : \"\" ) + // highlight selected day\n\t\t\t\t\t\t\t( printDate.getTime() === today.getTime() ? \" ui-datepicker-today\" : \"\" ) ) + \"'\" + // highlight today (if different)\n\t\t\t\t\t\t\t( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? \" title='\" + daySettings[ 2 ].replace( /'/g, \"&#39;\" ) + \"'\" : \"\" ) + // cell title\n\t\t\t\t\t\t\t( unselectable ? \"\" : \" data-handler='selectDay' data-event='click' data-month='\" + printDate.getMonth() + \"' data-year='\" + printDate.getFullYear() + \"'\" ) + \">\" + // actions\n\t\t\t\t\t\t\t( otherMonth && !showOtherMonths ? \"&#xa0;\" : // display for other months\n\t\t\t\t\t\t\t( unselectable ? \"<span class='ui-state-default'>\" + printDate.getDate() + \"</span>\" : \"<a class='ui-state-default\" +\n\t\t\t\t\t\t\t( printDate.getTime() === today.getTime() ? \" ui-state-highlight\" : \"\" ) +\n\t\t\t\t\t\t\t( printDate.getTime() === currentDate.getTime() ? \" ui-state-active\" : \"\" ) + // highlight selected day\n\t\t\t\t\t\t\t( otherMonth ? \" ui-priority-secondary\" : \"\" ) + // distinguish dates from other months\n\t\t\t\t\t\t\t\"' href='#'>\" + printDate.getDate() + \"</a>\" ) ) + \"</td>\"; // display selectable date\n\t\t\t\t\t\tprintDate.setDate( printDate.getDate() + 1 );\n\t\t\t\t\t\tprintDate = this._daylightSavingAdjust( printDate );\n\t\t\t\t\t}\n\t\t\t\t\tcalender += tbody + \"</tr>\";\n\t\t\t\t}\n\t\t\t\tdrawMonth++;\n\t\t\t\tif ( drawMonth > 11 ) {\n\t\t\t\t\tdrawMonth = 0;\n\t\t\t\t\tdrawYear++;\n\t\t\t\t}\n\t\t\t\tcalender += \"</tbody></table>\" + ( isMultiMonth ? \"</div>\" +\n\t\t\t\t\t\t\t( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? \"<div class='ui-datepicker-row-break'></div>\" : \"\" ) : \"\" );\n\t\t\t\tgroup += calender;\n\t\t\t}\n\t\t\thtml += group;\n\t\t}\n\t\thtml += buttonPanel;\n\t\tinst._keyEvent = false;\n\t\treturn html;\n\t},\n\n\t/* Generate the month and year header. */\n\t_generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate,\n\t\t\tsecondary, monthNames, monthNamesShort ) {\n\n\t\tvar inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,\n\t\t\tchangeMonth = this._get( inst, \"changeMonth\" ),\n\t\t\tchangeYear = this._get( inst, \"changeYear\" ),\n\t\t\tshowMonthAfterYear = this._get( inst, \"showMonthAfterYear\" ),\n\t\t\thtml = \"<div class='ui-datepicker-title'>\",\n\t\t\tmonthHtml = \"\";\n\n\t\t// Month selection\n\t\tif ( secondary || !changeMonth ) {\n\t\t\tmonthHtml += \"<span class='ui-datepicker-month'>\" + monthNames[ drawMonth ] + \"</span>\";\n\t\t} else {\n\t\t\tinMinYear = ( minDate && minDate.getFullYear() === drawYear );\n\t\t\tinMaxYear = ( maxDate && maxDate.getFullYear() === drawYear );\n\t\t\tmonthHtml += \"<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>\";\n\t\t\tfor ( month = 0; month < 12; month++ ) {\n\t\t\t\tif ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) {\n\t\t\t\t\tmonthHtml += \"<option value='\" + month + \"'\" +\n\t\t\t\t\t\t( month === drawMonth ? \" selected='selected'\" : \"\" ) +\n\t\t\t\t\t\t\">\" + monthNamesShort[ month ] + \"</option>\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tmonthHtml += \"</select>\";\n\t\t}\n\n\t\tif ( !showMonthAfterYear ) {\n\t\t\thtml += monthHtml + ( secondary || !( changeMonth && changeYear ) ? \"&#xa0;\" : \"\" );\n\t\t}\n\n\t\t// Year selection\n\t\tif ( !inst.yearshtml ) {\n\t\t\tinst.yearshtml = \"\";\n\t\t\tif ( secondary || !changeYear ) {\n\t\t\t\thtml += \"<span class='ui-datepicker-year'>\" + drawYear + \"</span>\";\n\t\t\t} else {\n\n\t\t\t\t// determine range of years to display\n\t\t\t\tyears = this._get( inst, \"yearRange\" ).split( \":\" );\n\t\t\t\tthisYear = new Date().getFullYear();\n\t\t\t\tdetermineYear = function( value ) {\n\t\t\t\t\tvar year = ( value.match( /c[+\\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) :\n\t\t\t\t\t\t( value.match( /[+\\-].*/ ) ? thisYear + parseInt( value, 10 ) :\n\t\t\t\t\t\tparseInt( value, 10 ) ) );\n\t\t\t\t\treturn ( isNaN( year ) ? thisYear : year );\n\t\t\t\t};\n\t\t\t\tyear = determineYear( years[ 0 ] );\n\t\t\t\tendYear = Math.max( year, determineYear( years[ 1 ] || \"\" ) );\n\t\t\t\tyear = ( minDate ? Math.max( year, minDate.getFullYear() ) : year );\n\t\t\t\tendYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear );\n\t\t\t\tinst.yearshtml += \"<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>\";\n\t\t\t\tfor ( ; year <= endYear; year++ ) {\n\t\t\t\t\tinst.yearshtml += \"<option value='\" + year + \"'\" +\n\t\t\t\t\t\t( year === drawYear ? \" selected='selected'\" : \"\" ) +\n\t\t\t\t\t\t\">\" + year + \"</option>\";\n\t\t\t\t}\n\t\t\t\tinst.yearshtml += \"</select>\";\n\n\t\t\t\thtml += inst.yearshtml;\n\t\t\t\tinst.yearshtml = null;\n\t\t\t}\n\t\t}\n\n\t\thtml += this._get( inst, \"yearSuffix\" );\n\t\tif ( showMonthAfterYear ) {\n\t\t\thtml += ( secondary || !( changeMonth && changeYear ) ? \"&#xa0;\" : \"\" ) + monthHtml;\n\t\t}\n\t\thtml += \"</div>\"; // Close datepicker_header\n\t\treturn html;\n\t},\n\n\t/* Adjust one of the date sub-fields. */\n\t_adjustInstDate: function( inst, offset, period ) {\n\t\tvar year = inst.selectedYear + ( period === \"Y\" ? offset : 0 ),\n\t\t\tmonth = inst.selectedMonth + ( period === \"M\" ? offset : 0 ),\n\t\t\tday = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === \"D\" ? offset : 0 ),\n\t\t\tdate = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) );\n\n\t\tinst.selectedDay = date.getDate();\n\t\tinst.drawMonth = inst.selectedMonth = date.getMonth();\n\t\tinst.drawYear = inst.selectedYear = date.getFullYear();\n\t\tif ( period === \"M\" || period === \"Y\" ) {\n\t\t\tthis._notifyChange( inst );\n\t\t}\n\t},\n\n\t/* Ensure a date is within any min/max bounds. */\n\t_restrictMinMax: function( inst, date ) {\n\t\tvar minDate = this._getMinMaxDate( inst, \"min\" ),\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" ),\n\t\t\tnewDate = ( minDate && date < minDate ? minDate : date );\n\t\treturn ( maxDate && newDate > maxDate ? maxDate : newDate );\n\t},\n\n\t/* Notify change of month/year. */\n\t_notifyChange: function( inst ) {\n\t\tvar onChange = this._get( inst, \"onChangeMonthYear\" );\n\t\tif ( onChange ) {\n\t\t\tonChange.apply( ( inst.input ? inst.input[ 0 ] : null ),\n\t\t\t\t[ inst.selectedYear, inst.selectedMonth + 1, inst ] );\n\t\t}\n\t},\n\n\t/* Determine the number of months to show. */\n\t_getNumberOfMonths: function( inst ) {\n\t\tvar numMonths = this._get( inst, \"numberOfMonths\" );\n\t\treturn ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === \"number\" ? [ 1, numMonths ] : numMonths ) );\n\t},\n\n\t/* Determine the current maximum date - ensure no time components are set. */\n\t_getMinMaxDate: function( inst, minMax ) {\n\t\treturn this._determineDate( inst, this._get( inst, minMax + \"Date\" ), null );\n\t},\n\n\t/* Find the number of days in a given month. */\n\t_getDaysInMonth: function( year, month ) {\n\t\treturn 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate();\n\t},\n\n\t/* Find the day of the week of the first of a month. */\n\t_getFirstDayOfMonth: function( year, month ) {\n\t\treturn new Date( year, month, 1 ).getDay();\n\t},\n\n\t/* Determines if we should allow a \"next/prev\" month display change. */\n\t_canAdjustMonth: function( inst, offset, curYear, curMonth ) {\n\t\tvar numMonths = this._getNumberOfMonths( inst ),\n\t\t\tdate = this._daylightSavingAdjust( new Date( curYear,\n\t\t\tcurMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) );\n\n\t\tif ( offset < 0 ) {\n\t\t\tdate.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) );\n\t\t}\n\t\treturn this._isInRange( inst, date );\n\t},\n\n\t/* Is the given date in the accepted range? */\n\t_isInRange: function( inst, date ) {\n\t\tvar yearSplit, currentYear,\n\t\t\tminDate = this._getMinMaxDate( inst, \"min\" ),\n\t\t\tmaxDate = this._getMinMaxDate( inst, \"max\" ),\n\t\t\tminYear = null,\n\t\t\tmaxYear = null,\n\t\t\tyears = this._get( inst, \"yearRange\" );\n\t\t\tif ( years ) {\n\t\t\t\tyearSplit = years.split( \":\" );\n\t\t\t\tcurrentYear = new Date().getFullYear();\n\t\t\t\tminYear = parseInt( yearSplit[ 0 ], 10 );\n\t\t\t\tmaxYear = parseInt( yearSplit[ 1 ], 10 );\n\t\t\t\tif ( yearSplit[ 0 ].match( /[+\\-].*/ ) ) {\n\t\t\t\t\tminYear += currentYear;\n\t\t\t\t}\n\t\t\t\tif ( yearSplit[ 1 ].match( /[+\\-].*/ ) ) {\n\t\t\t\t\tmaxYear += currentYear;\n\t\t\t\t}\n\t\t\t}\n\n\t\treturn ( ( !minDate || date.getTime() >= minDate.getTime() ) &&\n\t\t\t( !maxDate || date.getTime() <= maxDate.getTime() ) &&\n\t\t\t( !minYear || date.getFullYear() >= minYear ) &&\n\t\t\t( !maxYear || date.getFullYear() <= maxYear ) );\n\t},\n\n\t/* Provide the configuration settings for formatting/parsing. */\n\t_getFormatConfig: function( inst ) {\n\t\tvar shortYearCutoff = this._get( inst, \"shortYearCutoff\" );\n\t\tshortYearCutoff = ( typeof shortYearCutoff !== \"string\" ? shortYearCutoff :\n\t\t\tnew Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) );\n\t\treturn { shortYearCutoff: shortYearCutoff,\n\t\t\tdayNamesShort: this._get( inst, \"dayNamesShort\" ), dayNames: this._get( inst, \"dayNames\" ),\n\t\t\tmonthNamesShort: this._get( inst, \"monthNamesShort\" ), monthNames: this._get( inst, \"monthNames\" ) };\n\t},\n\n\t/* Format the given date for display. */\n\t_formatDate: function( inst, day, month, year ) {\n\t\tif ( !day ) {\n\t\t\tinst.currentDay = inst.selectedDay;\n\t\t\tinst.currentMonth = inst.selectedMonth;\n\t\t\tinst.currentYear = inst.selectedYear;\n\t\t}\n\t\tvar date = ( day ? ( typeof day === \"object\" ? day :\n\t\t\tthis._daylightSavingAdjust( new Date( year, month, day ) ) ) :\n\t\t\tthis._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) );\n\t\treturn this.formatDate( this._get( inst, \"dateFormat\" ), date, this._getFormatConfig( inst ) );\n\t}\n} );\n\n/*\n * Bind hover events for datepicker elements.\n * Done via delegate so the binding only occurs once in the lifetime of the parent div.\n * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.\n */\nfunction datepicker_bindHover( dpDiv ) {\n\tvar selector = \"button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a\";\n\treturn dpDiv.on( \"mouseout\", selector, function() {\n\t\t\t$( this ).removeClass( \"ui-state-hover\" );\n\t\t\tif ( this.className.indexOf( \"ui-datepicker-prev\" ) !== -1 ) {\n\t\t\t\t$( this ).removeClass( \"ui-datepicker-prev-hover\" );\n\t\t\t}\n\t\t\tif ( this.className.indexOf( \"ui-datepicker-next\" ) !== -1 ) {\n\t\t\t\t$( this ).removeClass( \"ui-datepicker-next-hover\" );\n\t\t\t}\n\t\t} )\n\t\t.on( \"mouseover\", selector, datepicker_handleMouseover );\n}\n\nfunction datepicker_handleMouseover() {\n\tif ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) {\n\t\t$( this ).parents( \".ui-datepicker-calendar\" ).find( \"a\" ).removeClass( \"ui-state-hover\" );\n\t\t$( this ).addClass( \"ui-state-hover\" );\n\t\tif ( this.className.indexOf( \"ui-datepicker-prev\" ) !== -1 ) {\n\t\t\t$( this ).addClass( \"ui-datepicker-prev-hover\" );\n\t\t}\n\t\tif ( this.className.indexOf( \"ui-datepicker-next\" ) !== -1 ) {\n\t\t\t$( this ).addClass( \"ui-datepicker-next-hover\" );\n\t\t}\n\t}\n}\n\n/* jQuery extend now ignores nulls! */\nfunction datepicker_extendRemove( target, props ) {\n\t$.extend( target, props );\n\tfor ( var name in props ) {\n\t\tif ( props[ name ] == null ) {\n\t\t\ttarget[ name ] = props[ name ];\n\t\t}\n\t}\n\treturn target;\n}\n\n/* Invoke the datepicker functionality.\n   @param  options  string - a command, optionally followed by additional parameters or\n\t\t\t\t\tObject - settings for attaching new datepicker functionality\n   @return  jQuery object */\n$.fn.datepicker = function( options ) {\n\n\t/* Verify an empty collection wasn't passed - Fixes #6976 */\n\tif ( !this.length ) {\n\t\treturn this;\n\t}\n\n\t/* Initialise the date picker. */\n\tif ( !$.datepicker.initialized ) {\n\t\t$( document ).on( \"mousedown\", $.datepicker._checkExternalClick );\n\t\t$.datepicker.initialized = true;\n\t}\n\n\t/* Append datepicker main container to body if not exist. */\n\tif ( $( \"#\" + $.datepicker._mainDivId ).length === 0 ) {\n\t\t$( \"body\" ).append( $.datepicker.dpDiv );\n\t}\n\n\tvar otherArgs = Array.prototype.slice.call( arguments, 1 );\n\tif ( typeof options === \"string\" && ( options === \"isDisabled\" || options === \"getDate\" || options === \"widget\" ) ) {\n\t\treturn $.datepicker[ \"_\" + options + \"Datepicker\" ].\n\t\t\tapply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );\n\t}\n\tif ( options === \"option\" && arguments.length === 2 && typeof arguments[ 1 ] === \"string\" ) {\n\t\treturn $.datepicker[ \"_\" + options + \"Datepicker\" ].\n\t\t\tapply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );\n\t}\n\treturn this.each( function() {\n\t\ttypeof options === \"string\" ?\n\t\t\t$.datepicker[ \"_\" + options + \"Datepicker\" ].\n\t\t\t\tapply( $.datepicker, [ this ].concat( otherArgs ) ) :\n\t\t\t$.datepicker._attachDatepicker( this, options );\n\t} );\n};\n\n$.datepicker = new Datepicker(); // singleton instance\n$.datepicker.initialized = false;\n$.datepicker.uuid = new Date().getTime();\n$.datepicker.version = \"1.12.1\";\n\nvar widgetsDatepicker = $.datepicker;\n\n\n/*!\n * jQuery UI Dialog 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Dialog\n//>>group: Widgets\n//>>description: Displays customizable dialog windows.\n//>>docs: http://api.jqueryui.com/dialog/\n//>>demos: http://jqueryui.com/dialog/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/dialog.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.dialog\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tappendTo: \"body\",\n\t\tautoOpen: true,\n\t\tbuttons: [],\n\t\tclasses: {\n\t\t\t\"ui-dialog\": \"ui-corner-all\",\n\t\t\t\"ui-dialog-titlebar\": \"ui-corner-all\"\n\t\t},\n\t\tcloseOnEscape: true,\n\t\tcloseText: \"Close\",\n\t\tdraggable: true,\n\t\thide: null,\n\t\theight: \"auto\",\n\t\tmaxHeight: null,\n\t\tmaxWidth: null,\n\t\tminHeight: 150,\n\t\tminWidth: 150,\n\t\tmodal: false,\n\t\tposition: {\n\t\t\tmy: \"center\",\n\t\t\tat: \"center\",\n\t\t\tof: window,\n\t\t\tcollision: \"fit\",\n\n\t\t\t// Ensure the titlebar is always visible\n\t\t\tusing: function( pos ) {\n\t\t\t\tvar topOffset = $( this ).css( pos ).offset().top;\n\t\t\t\tif ( topOffset < 0 ) {\n\t\t\t\t\t$( this ).css( \"top\", pos.top - topOffset );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tresizable: true,\n\t\tshow: null,\n\t\ttitle: null,\n\t\twidth: 300,\n\n\t\t// Callbacks\n\t\tbeforeClose: null,\n\t\tclose: null,\n\t\tdrag: null,\n\t\tdragStart: null,\n\t\tdragStop: null,\n\t\tfocus: null,\n\t\topen: null,\n\t\tresize: null,\n\t\tresizeStart: null,\n\t\tresizeStop: null\n\t},\n\n\tsizeRelatedOptions: {\n\t\tbuttons: true,\n\t\theight: true,\n\t\tmaxHeight: true,\n\t\tmaxWidth: true,\n\t\tminHeight: true,\n\t\tminWidth: true,\n\t\twidth: true\n\t},\n\n\tresizableRelatedOptions: {\n\t\tmaxHeight: true,\n\t\tmaxWidth: true,\n\t\tminHeight: true,\n\t\tminWidth: true\n\t},\n\n\t_create: function() {\n\t\tthis.originalCss = {\n\t\t\tdisplay: this.element[ 0 ].style.display,\n\t\t\twidth: this.element[ 0 ].style.width,\n\t\t\tminHeight: this.element[ 0 ].style.minHeight,\n\t\t\tmaxHeight: this.element[ 0 ].style.maxHeight,\n\t\t\theight: this.element[ 0 ].style.height\n\t\t};\n\t\tthis.originalPosition = {\n\t\t\tparent: this.element.parent(),\n\t\t\tindex: this.element.parent().children().index( this.element )\n\t\t};\n\t\tthis.originalTitle = this.element.attr( \"title\" );\n\t\tif ( this.options.title == null && this.originalTitle != null ) {\n\t\t\tthis.options.title = this.originalTitle;\n\t\t}\n\n\t\t// Dialogs can't be disabled\n\t\tif ( this.options.disabled ) {\n\t\t\tthis.options.disabled = false;\n\t\t}\n\n\t\tthis._createWrapper();\n\n\t\tthis.element\n\t\t\t.show()\n\t\t\t.removeAttr( \"title\" )\n\t\t\t.appendTo( this.uiDialog );\n\n\t\tthis._addClass( \"ui-dialog-content\", \"ui-widget-content\" );\n\n\t\tthis._createTitlebar();\n\t\tthis._createButtonPane();\n\n\t\tif ( this.options.draggable && $.fn.draggable ) {\n\t\t\tthis._makeDraggable();\n\t\t}\n\t\tif ( this.options.resizable && $.fn.resizable ) {\n\t\t\tthis._makeResizable();\n\t\t}\n\n\t\tthis._isOpen = false;\n\n\t\tthis._trackFocus();\n\t},\n\n\t_init: function() {\n\t\tif ( this.options.autoOpen ) {\n\t\t\tthis.open();\n\t\t}\n\t},\n\n\t_appendTo: function() {\n\t\tvar element = this.options.appendTo;\n\t\tif ( element && ( element.jquery || element.nodeType ) ) {\n\t\t\treturn $( element );\n\t\t}\n\t\treturn this.document.find( element || \"body\" ).eq( 0 );\n\t},\n\n\t_destroy: function() {\n\t\tvar next,\n\t\t\toriginalPosition = this.originalPosition;\n\n\t\tthis._untrackInstance();\n\t\tthis._destroyOverlay();\n\n\t\tthis.element\n\t\t\t.removeUniqueId()\n\t\t\t.css( this.originalCss )\n\n\t\t\t// Without detaching first, the following becomes really slow\n\t\t\t.detach();\n\n\t\tthis.uiDialog.remove();\n\n\t\tif ( this.originalTitle ) {\n\t\t\tthis.element.attr( \"title\", this.originalTitle );\n\t\t}\n\n\t\tnext = originalPosition.parent.children().eq( originalPosition.index );\n\n\t\t// Don't try to place the dialog next to itself (#8613)\n\t\tif ( next.length && next[ 0 ] !== this.element[ 0 ] ) {\n\t\t\tnext.before( this.element );\n\t\t} else {\n\t\t\toriginalPosition.parent.append( this.element );\n\t\t}\n\t},\n\n\twidget: function() {\n\t\treturn this.uiDialog;\n\t},\n\n\tdisable: $.noop,\n\tenable: $.noop,\n\n\tclose: function( event ) {\n\t\tvar that = this;\n\n\t\tif ( !this._isOpen || this._trigger( \"beforeClose\", event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._isOpen = false;\n\t\tthis._focusedElement = null;\n\t\tthis._destroyOverlay();\n\t\tthis._untrackInstance();\n\n\t\tif ( !this.opener.filter( \":focusable\" ).trigger( \"focus\" ).length ) {\n\n\t\t\t// Hiding a focused element doesn't trigger blur in WebKit\n\t\t\t// so in case we have nothing to focus on, explicitly blur the active element\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=47182\n\t\t\t$.ui.safeBlur( $.ui.safeActiveElement( this.document[ 0 ] ) );\n\t\t}\n\n\t\tthis._hide( this.uiDialog, this.options.hide, function() {\n\t\t\tthat._trigger( \"close\", event );\n\t\t} );\n\t},\n\n\tisOpen: function() {\n\t\treturn this._isOpen;\n\t},\n\n\tmoveToTop: function() {\n\t\tthis._moveToTop();\n\t},\n\n\t_moveToTop: function( event, silent ) {\n\t\tvar moved = false,\n\t\t\tzIndices = this.uiDialog.siblings( \".ui-front:visible\" ).map( function() {\n\t\t\t\treturn +$( this ).css( \"z-index\" );\n\t\t\t} ).get(),\n\t\t\tzIndexMax = Math.max.apply( null, zIndices );\n\n\t\tif ( zIndexMax >= +this.uiDialog.css( \"z-index\" ) ) {\n\t\t\tthis.uiDialog.css( \"z-index\", zIndexMax + 1 );\n\t\t\tmoved = true;\n\t\t}\n\n\t\tif ( moved && !silent ) {\n\t\t\tthis._trigger( \"focus\", event );\n\t\t}\n\t\treturn moved;\n\t},\n\n\topen: function() {\n\t\tvar that = this;\n\t\tif ( this._isOpen ) {\n\t\t\tif ( this._moveToTop() ) {\n\t\t\t\tthis._focusTabbable();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tthis._isOpen = true;\n\t\tthis.opener = $( $.ui.safeActiveElement( this.document[ 0 ] ) );\n\n\t\tthis._size();\n\t\tthis._position();\n\t\tthis._createOverlay();\n\t\tthis._moveToTop( null, true );\n\n\t\t// Ensure the overlay is moved to the top with the dialog, but only when\n\t\t// opening. The overlay shouldn't move after the dialog is open so that\n\t\t// modeless dialogs opened after the modal dialog stack properly.\n\t\tif ( this.overlay ) {\n\t\t\tthis.overlay.css( \"z-index\", this.uiDialog.css( \"z-index\" ) - 1 );\n\t\t}\n\n\t\tthis._show( this.uiDialog, this.options.show, function() {\n\t\t\tthat._focusTabbable();\n\t\t\tthat._trigger( \"focus\" );\n\t\t} );\n\n\t\t// Track the dialog immediately upon openening in case a focus event\n\t\t// somehow occurs outside of the dialog before an element inside the\n\t\t// dialog is focused (#10152)\n\t\tthis._makeFocusTarget();\n\n\t\tthis._trigger( \"open\" );\n\t},\n\n\t_focusTabbable: function() {\n\n\t\t// Set focus to the first match:\n\t\t// 1. An element that was focused previously\n\t\t// 2. First element inside the dialog matching [autofocus]\n\t\t// 3. Tabbable element inside the content element\n\t\t// 4. Tabbable element inside the buttonpane\n\t\t// 5. The close button\n\t\t// 6. The dialog itself\n\t\tvar hasFocus = this._focusedElement;\n\t\tif ( !hasFocus ) {\n\t\t\thasFocus = this.element.find( \"[autofocus]\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.element.find( \":tabbable\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.uiDialogButtonPane.find( \":tabbable\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.uiDialogTitlebarClose.filter( \":tabbable\" );\n\t\t}\n\t\tif ( !hasFocus.length ) {\n\t\t\thasFocus = this.uiDialog;\n\t\t}\n\t\thasFocus.eq( 0 ).trigger( \"focus\" );\n\t},\n\n\t_keepFocus: function( event ) {\n\t\tfunction checkFocus() {\n\t\t\tvar activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),\n\t\t\t\tisActive = this.uiDialog[ 0 ] === activeElement ||\n\t\t\t\t\t$.contains( this.uiDialog[ 0 ], activeElement );\n\t\t\tif ( !isActive ) {\n\t\t\t\tthis._focusTabbable();\n\t\t\t}\n\t\t}\n\t\tevent.preventDefault();\n\t\tcheckFocus.call( this );\n\n\t\t// support: IE\n\t\t// IE <= 8 doesn't prevent moving focus even with event.preventDefault()\n\t\t// so we check again later\n\t\tthis._delay( checkFocus );\n\t},\n\n\t_createWrapper: function() {\n\t\tthis.uiDialog = $( \"<div>\" )\n\t\t\t.hide()\n\t\t\t.attr( {\n\n\t\t\t\t// Setting tabIndex makes the div focusable\n\t\t\t\ttabIndex: -1,\n\t\t\t\trole: \"dialog\"\n\t\t\t} )\n\t\t\t.appendTo( this._appendTo() );\n\n\t\tthis._addClass( this.uiDialog, \"ui-dialog\", \"ui-widget ui-widget-content ui-front\" );\n\t\tthis._on( this.uiDialog, {\n\t\t\tkeydown: function( event ) {\n\t\t\t\tif ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&\n\t\t\t\t\t\tevent.keyCode === $.ui.keyCode.ESCAPE ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tthis.close( event );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Prevent tabbing out of dialogs\n\t\t\t\tif ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvar tabbables = this.uiDialog.find( \":tabbable\" ),\n\t\t\t\t\tfirst = tabbables.filter( \":first\" ),\n\t\t\t\t\tlast = tabbables.filter( \":last\" );\n\n\t\t\t\tif ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) &&\n\t\t\t\t\t\t!event.shiftKey ) {\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tfirst.trigger( \"focus\" );\n\t\t\t\t\t} );\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t} else if ( ( event.target === first[ 0 ] ||\n\t\t\t\t\t\tevent.target === this.uiDialog[ 0 ] ) && event.shiftKey ) {\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tlast.trigger( \"focus\" );\n\t\t\t\t\t} );\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t},\n\t\t\tmousedown: function( event ) {\n\t\t\t\tif ( this._moveToTop( event ) ) {\n\t\t\t\t\tthis._focusTabbable();\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\n\t\t// We assume that any existing aria-describedby attribute means\n\t\t// that the dialog content is marked up properly\n\t\t// otherwise we brute force the content as the description\n\t\tif ( !this.element.find( \"[aria-describedby]\" ).length ) {\n\t\t\tthis.uiDialog.attr( {\n\t\t\t\t\"aria-describedby\": this.element.uniqueId().attr( \"id\" )\n\t\t\t} );\n\t\t}\n\t},\n\n\t_createTitlebar: function() {\n\t\tvar uiDialogTitle;\n\n\t\tthis.uiDialogTitlebar = $( \"<div>\" );\n\t\tthis._addClass( this.uiDialogTitlebar,\n\t\t\t\"ui-dialog-titlebar\", \"ui-widget-header ui-helper-clearfix\" );\n\t\tthis._on( this.uiDialogTitlebar, {\n\t\t\tmousedown: function( event ) {\n\n\t\t\t\t// Don't prevent click on close button (#8838)\n\t\t\t\t// Focusing a dialog that is partially scrolled out of view\n\t\t\t\t// causes the browser to scroll it into view, preventing the click event\n\t\t\t\tif ( !$( event.target ).closest( \".ui-dialog-titlebar-close\" ) ) {\n\n\t\t\t\t\t// Dialog isn't getting focus when dragging (#8063)\n\t\t\t\t\tthis.uiDialog.trigger( \"focus\" );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\n\t\t// Support: IE\n\t\t// Use type=\"button\" to prevent enter keypresses in textboxes from closing the\n\t\t// dialog in IE (#9312)\n\t\tthis.uiDialogTitlebarClose = $( \"<button type='button'></button>\" )\n\t\t\t.button( {\n\t\t\t\tlabel: $( \"<a>\" ).text( this.options.closeText ).html(),\n\t\t\t\ticon: \"ui-icon-closethick\",\n\t\t\t\tshowLabel: false\n\t\t\t} )\n\t\t\t.appendTo( this.uiDialogTitlebar );\n\n\t\tthis._addClass( this.uiDialogTitlebarClose, \"ui-dialog-titlebar-close\" );\n\t\tthis._on( this.uiDialogTitlebarClose, {\n\t\t\tclick: function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.close( event );\n\t\t\t}\n\t\t} );\n\n\t\tuiDialogTitle = $( \"<span>\" ).uniqueId().prependTo( this.uiDialogTitlebar );\n\t\tthis._addClass( uiDialogTitle, \"ui-dialog-title\" );\n\t\tthis._title( uiDialogTitle );\n\n\t\tthis.uiDialogTitlebar.prependTo( this.uiDialog );\n\n\t\tthis.uiDialog.attr( {\n\t\t\t\"aria-labelledby\": uiDialogTitle.attr( \"id\" )\n\t\t} );\n\t},\n\n\t_title: function( title ) {\n\t\tif ( this.options.title ) {\n\t\t\ttitle.text( this.options.title );\n\t\t} else {\n\t\t\ttitle.html( \"&#160;\" );\n\t\t}\n\t},\n\n\t_createButtonPane: function() {\n\t\tthis.uiDialogButtonPane = $( \"<div>\" );\n\t\tthis._addClass( this.uiDialogButtonPane, \"ui-dialog-buttonpane\",\n\t\t\t\"ui-widget-content ui-helper-clearfix\" );\n\n\t\tthis.uiButtonSet = $( \"<div>\" )\n\t\t\t.appendTo( this.uiDialogButtonPane );\n\t\tthis._addClass( this.uiButtonSet, \"ui-dialog-buttonset\" );\n\n\t\tthis._createButtons();\n\t},\n\n\t_createButtons: function() {\n\t\tvar that = this,\n\t\t\tbuttons = this.options.buttons;\n\n\t\t// If we already have a button pane, remove it\n\t\tthis.uiDialogButtonPane.remove();\n\t\tthis.uiButtonSet.empty();\n\n\t\tif ( $.isEmptyObject( buttons ) || ( $.isArray( buttons ) && !buttons.length ) ) {\n\t\t\tthis._removeClass( this.uiDialog, \"ui-dialog-buttons\" );\n\t\t\treturn;\n\t\t}\n\n\t\t$.each( buttons, function( name, props ) {\n\t\t\tvar click, buttonOptions;\n\t\t\tprops = $.isFunction( props ) ?\n\t\t\t\t{ click: props, text: name } :\n\t\t\t\tprops;\n\n\t\t\t// Default to a non-submitting button\n\t\t\tprops = $.extend( { type: \"button\" }, props );\n\n\t\t\t// Change the context for the click callback to be the main element\n\t\t\tclick = props.click;\n\t\t\tbuttonOptions = {\n\t\t\t\ticon: props.icon,\n\t\t\t\ticonPosition: props.iconPosition,\n\t\t\t\tshowLabel: props.showLabel,\n\n\t\t\t\t// Deprecated options\n\t\t\t\ticons: props.icons,\n\t\t\t\ttext: props.text\n\t\t\t};\n\n\t\t\tdelete props.click;\n\t\t\tdelete props.icon;\n\t\t\tdelete props.iconPosition;\n\t\t\tdelete props.showLabel;\n\n\t\t\t// Deprecated options\n\t\t\tdelete props.icons;\n\t\t\tif ( typeof props.text === \"boolean\" ) {\n\t\t\t\tdelete props.text;\n\t\t\t}\n\n\t\t\t$( \"<button></button>\", props )\n\t\t\t\t.button( buttonOptions )\n\t\t\t\t.appendTo( that.uiButtonSet )\n\t\t\t\t.on( \"click\", function() {\n\t\t\t\t\tclick.apply( that.element[ 0 ], arguments );\n\t\t\t\t} );\n\t\t} );\n\t\tthis._addClass( this.uiDialog, \"ui-dialog-buttons\" );\n\t\tthis.uiDialogButtonPane.appendTo( this.uiDialog );\n\t},\n\n\t_makeDraggable: function() {\n\t\tvar that = this,\n\t\t\toptions = this.options;\n\n\t\tfunction filteredUi( ui ) {\n\t\t\treturn {\n\t\t\t\tposition: ui.position,\n\t\t\t\toffset: ui.offset\n\t\t\t};\n\t\t}\n\n\t\tthis.uiDialog.draggable( {\n\t\t\tcancel: \".ui-dialog-content, .ui-dialog-titlebar-close\",\n\t\t\thandle: \".ui-dialog-titlebar\",\n\t\t\tcontainment: \"document\",\n\t\t\tstart: function( event, ui ) {\n\t\t\t\tthat._addClass( $( this ), \"ui-dialog-dragging\" );\n\t\t\t\tthat._blockFrames();\n\t\t\t\tthat._trigger( \"dragStart\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tdrag: function( event, ui ) {\n\t\t\t\tthat._trigger( \"drag\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tstop: function( event, ui ) {\n\t\t\t\tvar left = ui.offset.left - that.document.scrollLeft(),\n\t\t\t\t\ttop = ui.offset.top - that.document.scrollTop();\n\n\t\t\t\toptions.position = {\n\t\t\t\t\tmy: \"left top\",\n\t\t\t\t\tat: \"left\" + ( left >= 0 ? \"+\" : \"\" ) + left + \" \" +\n\t\t\t\t\t\t\"top\" + ( top >= 0 ? \"+\" : \"\" ) + top,\n\t\t\t\t\tof: that.window\n\t\t\t\t};\n\t\t\t\tthat._removeClass( $( this ), \"ui-dialog-dragging\" );\n\t\t\t\tthat._unblockFrames();\n\t\t\t\tthat._trigger( \"dragStop\", event, filteredUi( ui ) );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_makeResizable: function() {\n\t\tvar that = this,\n\t\t\toptions = this.options,\n\t\t\thandles = options.resizable,\n\n\t\t\t// .ui-resizable has position: relative defined in the stylesheet\n\t\t\t// but dialogs have to use absolute or fixed positioning\n\t\t\tposition = this.uiDialog.css( \"position\" ),\n\t\t\tresizeHandles = typeof handles === \"string\" ?\n\t\t\t\thandles :\n\t\t\t\t\"n,e,s,w,se,sw,ne,nw\";\n\n\t\tfunction filteredUi( ui ) {\n\t\t\treturn {\n\t\t\t\toriginalPosition: ui.originalPosition,\n\t\t\t\toriginalSize: ui.originalSize,\n\t\t\t\tposition: ui.position,\n\t\t\t\tsize: ui.size\n\t\t\t};\n\t\t}\n\n\t\tthis.uiDialog.resizable( {\n\t\t\tcancel: \".ui-dialog-content\",\n\t\t\tcontainment: \"document\",\n\t\t\talsoResize: this.element,\n\t\t\tmaxWidth: options.maxWidth,\n\t\t\tmaxHeight: options.maxHeight,\n\t\t\tminWidth: options.minWidth,\n\t\t\tminHeight: this._minHeight(),\n\t\t\thandles: resizeHandles,\n\t\t\tstart: function( event, ui ) {\n\t\t\t\tthat._addClass( $( this ), \"ui-dialog-resizing\" );\n\t\t\t\tthat._blockFrames();\n\t\t\t\tthat._trigger( \"resizeStart\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tresize: function( event, ui ) {\n\t\t\t\tthat._trigger( \"resize\", event, filteredUi( ui ) );\n\t\t\t},\n\t\t\tstop: function( event, ui ) {\n\t\t\t\tvar offset = that.uiDialog.offset(),\n\t\t\t\t\tleft = offset.left - that.document.scrollLeft(),\n\t\t\t\t\ttop = offset.top - that.document.scrollTop();\n\n\t\t\t\toptions.height = that.uiDialog.height();\n\t\t\t\toptions.width = that.uiDialog.width();\n\t\t\t\toptions.position = {\n\t\t\t\t\tmy: \"left top\",\n\t\t\t\t\tat: \"left\" + ( left >= 0 ? \"+\" : \"\" ) + left + \" \" +\n\t\t\t\t\t\t\"top\" + ( top >= 0 ? \"+\" : \"\" ) + top,\n\t\t\t\t\tof: that.window\n\t\t\t\t};\n\t\t\t\tthat._removeClass( $( this ), \"ui-dialog-resizing\" );\n\t\t\t\tthat._unblockFrames();\n\t\t\t\tthat._trigger( \"resizeStop\", event, filteredUi( ui ) );\n\t\t\t}\n\t\t} )\n\t\t\t.css( \"position\", position );\n\t},\n\n\t_trackFocus: function() {\n\t\tthis._on( this.widget(), {\n\t\t\tfocusin: function( event ) {\n\t\t\t\tthis._makeFocusTarget();\n\t\t\t\tthis._focusedElement = $( event.target );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_makeFocusTarget: function() {\n\t\tthis._untrackInstance();\n\t\tthis._trackingInstances().unshift( this );\n\t},\n\n\t_untrackInstance: function() {\n\t\tvar instances = this._trackingInstances(),\n\t\t\texists = $.inArray( this, instances );\n\t\tif ( exists !== -1 ) {\n\t\t\tinstances.splice( exists, 1 );\n\t\t}\n\t},\n\n\t_trackingInstances: function() {\n\t\tvar instances = this.document.data( \"ui-dialog-instances\" );\n\t\tif ( !instances ) {\n\t\t\tinstances = [];\n\t\t\tthis.document.data( \"ui-dialog-instances\", instances );\n\t\t}\n\t\treturn instances;\n\t},\n\n\t_minHeight: function() {\n\t\tvar options = this.options;\n\n\t\treturn options.height === \"auto\" ?\n\t\t\toptions.minHeight :\n\t\t\tMath.min( options.minHeight, options.height );\n\t},\n\n\t_position: function() {\n\n\t\t// Need to show the dialog to get the actual offset in the position plugin\n\t\tvar isVisible = this.uiDialog.is( \":visible\" );\n\t\tif ( !isVisible ) {\n\t\t\tthis.uiDialog.show();\n\t\t}\n\t\tthis.uiDialog.position( this.options.position );\n\t\tif ( !isVisible ) {\n\t\t\tthis.uiDialog.hide();\n\t\t}\n\t},\n\n\t_setOptions: function( options ) {\n\t\tvar that = this,\n\t\t\tresize = false,\n\t\t\tresizableOptions = {};\n\n\t\t$.each( options, function( key, value ) {\n\t\t\tthat._setOption( key, value );\n\n\t\t\tif ( key in that.sizeRelatedOptions ) {\n\t\t\t\tresize = true;\n\t\t\t}\n\t\t\tif ( key in that.resizableRelatedOptions ) {\n\t\t\t\tresizableOptions[ key ] = value;\n\t\t\t}\n\t\t} );\n\n\t\tif ( resize ) {\n\t\t\tthis._size();\n\t\t\tthis._position();\n\t\t}\n\t\tif ( this.uiDialog.is( \":data(ui-resizable)\" ) ) {\n\t\t\tthis.uiDialog.resizable( \"option\", resizableOptions );\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar isDraggable, isResizable,\n\t\t\tuiDialog = this.uiDialog;\n\n\t\tif ( key === \"disabled\" ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"appendTo\" ) {\n\t\t\tthis.uiDialog.appendTo( this._appendTo() );\n\t\t}\n\n\t\tif ( key === \"buttons\" ) {\n\t\t\tthis._createButtons();\n\t\t}\n\n\t\tif ( key === \"closeText\" ) {\n\t\t\tthis.uiDialogTitlebarClose.button( {\n\n\t\t\t\t// Ensure that we always pass a string\n\t\t\t\tlabel: $( \"<a>\" ).text( \"\" + this.options.closeText ).html()\n\t\t\t} );\n\t\t}\n\n\t\tif ( key === \"draggable\" ) {\n\t\t\tisDraggable = uiDialog.is( \":data(ui-draggable)\" );\n\t\t\tif ( isDraggable && !value ) {\n\t\t\t\tuiDialog.draggable( \"destroy\" );\n\t\t\t}\n\n\t\t\tif ( !isDraggable && value ) {\n\t\t\t\tthis._makeDraggable();\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"position\" ) {\n\t\t\tthis._position();\n\t\t}\n\n\t\tif ( key === \"resizable\" ) {\n\n\t\t\t// currently resizable, becoming non-resizable\n\t\t\tisResizable = uiDialog.is( \":data(ui-resizable)\" );\n\t\t\tif ( isResizable && !value ) {\n\t\t\t\tuiDialog.resizable( \"destroy\" );\n\t\t\t}\n\n\t\t\t// Currently resizable, changing handles\n\t\t\tif ( isResizable && typeof value === \"string\" ) {\n\t\t\t\tuiDialog.resizable( \"option\", \"handles\", value );\n\t\t\t}\n\n\t\t\t// Currently non-resizable, becoming resizable\n\t\t\tif ( !isResizable && value !== false ) {\n\t\t\t\tthis._makeResizable();\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"title\" ) {\n\t\t\tthis._title( this.uiDialogTitlebar.find( \".ui-dialog-title\" ) );\n\t\t}\n\t},\n\n\t_size: function() {\n\n\t\t// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content\n\t\t// divs will both have width and height set, so we need to reset them\n\t\tvar nonContentHeight, minContentHeight, maxContentHeight,\n\t\t\toptions = this.options;\n\n\t\t// Reset content sizing\n\t\tthis.element.show().css( {\n\t\t\twidth: \"auto\",\n\t\t\tminHeight: 0,\n\t\t\tmaxHeight: \"none\",\n\t\t\theight: 0\n\t\t} );\n\n\t\tif ( options.minWidth > options.width ) {\n\t\t\toptions.width = options.minWidth;\n\t\t}\n\n\t\t// Reset wrapper sizing\n\t\t// determine the height of all the non-content elements\n\t\tnonContentHeight = this.uiDialog.css( {\n\t\t\theight: \"auto\",\n\t\t\twidth: options.width\n\t\t} )\n\t\t\t.outerHeight();\n\t\tminContentHeight = Math.max( 0, options.minHeight - nonContentHeight );\n\t\tmaxContentHeight = typeof options.maxHeight === \"number\" ?\n\t\t\tMath.max( 0, options.maxHeight - nonContentHeight ) :\n\t\t\t\"none\";\n\n\t\tif ( options.height === \"auto\" ) {\n\t\t\tthis.element.css( {\n\t\t\t\tminHeight: minContentHeight,\n\t\t\t\tmaxHeight: maxContentHeight,\n\t\t\t\theight: \"auto\"\n\t\t\t} );\n\t\t} else {\n\t\t\tthis.element.height( Math.max( 0, options.height - nonContentHeight ) );\n\t\t}\n\n\t\tif ( this.uiDialog.is( \":data(ui-resizable)\" ) ) {\n\t\t\tthis.uiDialog.resizable( \"option\", \"minHeight\", this._minHeight() );\n\t\t}\n\t},\n\n\t_blockFrames: function() {\n\t\tthis.iframeBlocks = this.document.find( \"iframe\" ).map( function() {\n\t\t\tvar iframe = $( this );\n\n\t\t\treturn $( \"<div>\" )\n\t\t\t\t.css( {\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\twidth: iframe.outerWidth(),\n\t\t\t\t\theight: iframe.outerHeight()\n\t\t\t\t} )\n\t\t\t\t.appendTo( iframe.parent() )\n\t\t\t\t.offset( iframe.offset() )[ 0 ];\n\t\t} );\n\t},\n\n\t_unblockFrames: function() {\n\t\tif ( this.iframeBlocks ) {\n\t\t\tthis.iframeBlocks.remove();\n\t\t\tdelete this.iframeBlocks;\n\t\t}\n\t},\n\n\t_allowInteraction: function( event ) {\n\t\tif ( $( event.target ).closest( \".ui-dialog\" ).length ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// TODO: Remove hack when datepicker implements\n\t\t// the .ui-front logic (#8989)\n\t\treturn !!$( event.target ).closest( \".ui-datepicker\" ).length;\n\t},\n\n\t_createOverlay: function() {\n\t\tif ( !this.options.modal ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// We use a delay in case the overlay is created from an\n\t\t// event that we're going to be cancelling (#2804)\n\t\tvar isOpening = true;\n\t\tthis._delay( function() {\n\t\t\tisOpening = false;\n\t\t} );\n\n\t\tif ( !this.document.data( \"ui-dialog-overlays\" ) ) {\n\n\t\t\t// Prevent use of anchors and inputs\n\t\t\t// Using _on() for an event handler shared across many instances is\n\t\t\t// safe because the dialogs stack and must be closed in reverse order\n\t\t\tthis._on( this.document, {\n\t\t\t\tfocusin: function( event ) {\n\t\t\t\t\tif ( isOpening ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( !this._allowInteraction( event ) ) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tthis._trackingInstances()[ 0 ]._focusTabbable();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\tthis.overlay = $( \"<div>\" )\n\t\t\t.appendTo( this._appendTo() );\n\n\t\tthis._addClass( this.overlay, null, \"ui-widget-overlay ui-front\" );\n\t\tthis._on( this.overlay, {\n\t\t\tmousedown: \"_keepFocus\"\n\t\t} );\n\t\tthis.document.data( \"ui-dialog-overlays\",\n\t\t\t( this.document.data( \"ui-dialog-overlays\" ) || 0 ) + 1 );\n\t},\n\n\t_destroyOverlay: function() {\n\t\tif ( !this.options.modal ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this.overlay ) {\n\t\t\tvar overlays = this.document.data( \"ui-dialog-overlays\" ) - 1;\n\n\t\t\tif ( !overlays ) {\n\t\t\t\tthis._off( this.document, \"focusin\" );\n\t\t\t\tthis.document.removeData( \"ui-dialog-overlays\" );\n\t\t\t} else {\n\t\t\t\tthis.document.data( \"ui-dialog-overlays\", overlays );\n\t\t\t}\n\n\t\t\tthis.overlay.remove();\n\t\t\tthis.overlay = null;\n\t\t}\n\t}\n} );\n\n// DEPRECATED\n// TODO: switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for dialogClass option\n\t$.widget( \"ui.dialog\", $.ui.dialog, {\n\t\toptions: {\n\t\t\tdialogClass: \"\"\n\t\t},\n\t\t_createWrapper: function() {\n\t\t\tthis._super();\n\t\t\tthis.uiDialog.addClass( this.options.dialogClass );\n\t\t},\n\t\t_setOption: function( key, value ) {\n\t\t\tif ( key === \"dialogClass\" ) {\n\t\t\t\tthis.uiDialog\n\t\t\t\t\t.removeClass( this.options.dialogClass )\n\t\t\t\t\t.addClass( value );\n\t\t\t}\n\t\t\tthis._superApply( arguments );\n\t\t}\n\t} );\n}\n\nvar widgetsDialog = $.ui.dialog;\n\n\n/*!\n * jQuery UI Progressbar 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Progressbar\n//>>group: Widgets\n// jscs:disable maximumLineLength\n//>>description: Displays a status indicator for loading state, standard percentage, and other progress indicators.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/progressbar/\n//>>demos: http://jqueryui.com/progressbar/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/progressbar.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsProgressbar = $.widget( \"ui.progressbar\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-progressbar\": \"ui-corner-all\",\n\t\t\t\"ui-progressbar-value\": \"ui-corner-left\",\n\t\t\t\"ui-progressbar-complete\": \"ui-corner-right\"\n\t\t},\n\t\tmax: 100,\n\t\tvalue: 0,\n\n\t\tchange: null,\n\t\tcomplete: null\n\t},\n\n\tmin: 0,\n\n\t_create: function() {\n\n\t\t// Constrain initial value\n\t\tthis.oldValue = this.options.value = this._constrainedValue();\n\n\t\tthis.element.attr( {\n\n\t\t\t// Only set static values; aria-valuenow and aria-valuemax are\n\t\t\t// set inside _refreshValue()\n\t\t\trole: \"progressbar\",\n\t\t\t\"aria-valuemin\": this.min\n\t\t} );\n\t\tthis._addClass( \"ui-progressbar\", \"ui-widget ui-widget-content\" );\n\n\t\tthis.valueDiv = $( \"<div>\" ).appendTo( this.element );\n\t\tthis._addClass( this.valueDiv, \"ui-progressbar-value\", \"ui-widget-header\" );\n\t\tthis._refreshValue();\n\t},\n\n\t_destroy: function() {\n\t\tthis.element.removeAttr( \"role aria-valuemin aria-valuemax aria-valuenow\" );\n\n\t\tthis.valueDiv.remove();\n\t},\n\n\tvalue: function( newValue ) {\n\t\tif ( newValue === undefined ) {\n\t\t\treturn this.options.value;\n\t\t}\n\n\t\tthis.options.value = this._constrainedValue( newValue );\n\t\tthis._refreshValue();\n\t},\n\n\t_constrainedValue: function( newValue ) {\n\t\tif ( newValue === undefined ) {\n\t\t\tnewValue = this.options.value;\n\t\t}\n\n\t\tthis.indeterminate = newValue === false;\n\n\t\t// Sanitize value\n\t\tif ( typeof newValue !== \"number\" ) {\n\t\t\tnewValue = 0;\n\t\t}\n\n\t\treturn this.indeterminate ? false :\n\t\t\tMath.min( this.options.max, Math.max( this.min, newValue ) );\n\t},\n\n\t_setOptions: function( options ) {\n\n\t\t// Ensure \"value\" option is set after other values (like max)\n\t\tvar value = options.value;\n\t\tdelete options.value;\n\n\t\tthis._super( options );\n\n\t\tthis.options.value = this._constrainedValue( value );\n\t\tthis._refreshValue();\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"max\" ) {\n\n\t\t\t// Don't allow a max less than min\n\t\t\tvalue = Math.max( this.min, value );\n\t\t}\n\t\tthis._super( key, value );\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.element.attr( \"aria-disabled\", value );\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t},\n\n\t_percentage: function() {\n\t\treturn this.indeterminate ?\n\t\t\t100 :\n\t\t\t100 * ( this.options.value - this.min ) / ( this.options.max - this.min );\n\t},\n\n\t_refreshValue: function() {\n\t\tvar value = this.options.value,\n\t\t\tpercentage = this._percentage();\n\n\t\tthis.valueDiv\n\t\t\t.toggle( this.indeterminate || value > this.min )\n\t\t\t.width( percentage.toFixed( 0 ) + \"%\" );\n\n\t\tthis\n\t\t\t._toggleClass( this.valueDiv, \"ui-progressbar-complete\", null,\n\t\t\t\tvalue === this.options.max )\n\t\t\t._toggleClass( \"ui-progressbar-indeterminate\", null, this.indeterminate );\n\n\t\tif ( this.indeterminate ) {\n\t\t\tthis.element.removeAttr( \"aria-valuenow\" );\n\t\t\tif ( !this.overlayDiv ) {\n\t\t\t\tthis.overlayDiv = $( \"<div>\" ).appendTo( this.valueDiv );\n\t\t\t\tthis._addClass( this.overlayDiv, \"ui-progressbar-overlay\" );\n\t\t\t}\n\t\t} else {\n\t\t\tthis.element.attr( {\n\t\t\t\t\"aria-valuemax\": this.options.max,\n\t\t\t\t\"aria-valuenow\": value\n\t\t\t} );\n\t\t\tif ( this.overlayDiv ) {\n\t\t\t\tthis.overlayDiv.remove();\n\t\t\t\tthis.overlayDiv = null;\n\t\t\t}\n\t\t}\n\n\t\tif ( this.oldValue !== value ) {\n\t\t\tthis.oldValue = value;\n\t\t\tthis._trigger( \"change\" );\n\t\t}\n\t\tif ( value === this.options.max ) {\n\t\t\tthis._trigger( \"complete\" );\n\t\t}\n\t}\n} );\n\n\n/*!\n * jQuery UI Selectmenu 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Selectmenu\n//>>group: Widgets\n// jscs:disable maximumLineLength\n//>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/selectmenu/\n//>>demos: http://jqueryui.com/selectmenu/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsSelectmenu = $.widget( \"ui.selectmenu\", [ $.ui.formResetMixin, {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<select>\",\n\toptions: {\n\t\tappendTo: null,\n\t\tclasses: {\n\t\t\t\"ui-selectmenu-button-open\": \"ui-corner-top\",\n\t\t\t\"ui-selectmenu-button-closed\": \"ui-corner-all\"\n\t\t},\n\t\tdisabled: null,\n\t\ticons: {\n\t\t\tbutton: \"ui-icon-triangle-1-s\"\n\t\t},\n\t\tposition: {\n\t\t\tmy: \"left top\",\n\t\t\tat: \"left bottom\",\n\t\t\tcollision: \"none\"\n\t\t},\n\t\twidth: false,\n\n\t\t// Callbacks\n\t\tchange: null,\n\t\tclose: null,\n\t\tfocus: null,\n\t\topen: null,\n\t\tselect: null\n\t},\n\n\t_create: function() {\n\t\tvar selectmenuId = this.element.uniqueId().attr( \"id\" );\n\t\tthis.ids = {\n\t\t\telement: selectmenuId,\n\t\t\tbutton: selectmenuId + \"-button\",\n\t\t\tmenu: selectmenuId + \"-menu\"\n\t\t};\n\n\t\tthis._drawButton();\n\t\tthis._drawMenu();\n\t\tthis._bindFormResetHandler();\n\n\t\tthis._rendered = false;\n\t\tthis.menuItems = $();\n\t},\n\n\t_drawButton: function() {\n\t\tvar icon,\n\t\t\tthat = this,\n\t\t\titem = this._parseOption(\n\t\t\t\tthis.element.find( \"option:selected\" ),\n\t\t\t\tthis.element[ 0 ].selectedIndex\n\t\t\t);\n\n\t\t// Associate existing label with the new button\n\t\tthis.labels = this.element.labels().attr( \"for\", this.ids.button );\n\t\tthis._on( this.labels, {\n\t\t\tclick: function( event ) {\n\t\t\t\tthis.button.focus();\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t} );\n\n\t\t// Hide original select element\n\t\tthis.element.hide();\n\n\t\t// Create button\n\t\tthis.button = $( \"<span>\", {\n\t\t\ttabindex: this.options.disabled ? -1 : 0,\n\t\t\tid: this.ids.button,\n\t\t\trole: \"combobox\",\n\t\t\t\"aria-expanded\": \"false\",\n\t\t\t\"aria-autocomplete\": \"list\",\n\t\t\t\"aria-owns\": this.ids.menu,\n\t\t\t\"aria-haspopup\": \"true\",\n\t\t\ttitle: this.element.attr( \"title\" )\n\t\t} )\n\t\t\t.insertAfter( this.element );\n\n\t\tthis._addClass( this.button, \"ui-selectmenu-button ui-selectmenu-button-closed\",\n\t\t\t\"ui-button ui-widget\" );\n\n\t\ticon = $( \"<span>\" ).appendTo( this.button );\n\t\tthis._addClass( icon, \"ui-selectmenu-icon\", \"ui-icon \" + this.options.icons.button );\n\t\tthis.buttonItem = this._renderButtonItem( item )\n\t\t\t.appendTo( this.button );\n\n\t\tif ( this.options.width !== false ) {\n\t\t\tthis._resizeButton();\n\t\t}\n\n\t\tthis._on( this.button, this._buttonEvents );\n\t\tthis.button.one( \"focusin\", function() {\n\n\t\t\t// Delay rendering the menu items until the button receives focus.\n\t\t\t// The menu may have already been rendered via a programmatic open.\n\t\t\tif ( !that._rendered ) {\n\t\t\t\tthat._refreshMenu();\n\t\t\t}\n\t\t} );\n\t},\n\n\t_drawMenu: function() {\n\t\tvar that = this;\n\n\t\t// Create menu\n\t\tthis.menu = $( \"<ul>\", {\n\t\t\t\"aria-hidden\": \"true\",\n\t\t\t\"aria-labelledby\": this.ids.button,\n\t\t\tid: this.ids.menu\n\t\t} );\n\n\t\t// Wrap menu\n\t\tthis.menuWrap = $( \"<div>\" ).append( this.menu );\n\t\tthis._addClass( this.menuWrap, \"ui-selectmenu-menu\", \"ui-front\" );\n\t\tthis.menuWrap.appendTo( this._appendTo() );\n\n\t\t// Initialize menu widget\n\t\tthis.menuInstance = this.menu\n\t\t\t.menu( {\n\t\t\t\tclasses: {\n\t\t\t\t\t\"ui-menu\": \"ui-corner-bottom\"\n\t\t\t\t},\n\t\t\t\trole: \"listbox\",\n\t\t\t\tselect: function( event, ui ) {\n\t\t\t\t\tevent.preventDefault();\n\n\t\t\t\t\t// Support: IE8\n\t\t\t\t\t// If the item was selected via a click, the text selection\n\t\t\t\t\t// will be destroyed in IE\n\t\t\t\t\tthat._setSelection();\n\n\t\t\t\t\tthat._select( ui.item.data( \"ui-selectmenu-item\" ), event );\n\t\t\t\t},\n\t\t\t\tfocus: function( event, ui ) {\n\t\t\t\t\tvar item = ui.item.data( \"ui-selectmenu-item\" );\n\n\t\t\t\t\t// Prevent inital focus from firing and check if its a newly focused item\n\t\t\t\t\tif ( that.focusIndex != null && item.index !== that.focusIndex ) {\n\t\t\t\t\t\tthat._trigger( \"focus\", event, { item: item } );\n\t\t\t\t\t\tif ( !that.isOpen ) {\n\t\t\t\t\t\t\tthat._select( item, event );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthat.focusIndex = item.index;\n\n\t\t\t\t\tthat.button.attr( \"aria-activedescendant\",\n\t\t\t\t\t\tthat.menuItems.eq( item.index ).attr( \"id\" ) );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.menu( \"instance\" );\n\n\t\t// Don't close the menu on mouseleave\n\t\tthis.menuInstance._off( this.menu, \"mouseleave\" );\n\n\t\t// Cancel the menu's collapseAll on document click\n\t\tthis.menuInstance._closeOnDocumentClick = function() {\n\t\t\treturn false;\n\t\t};\n\n\t\t// Selects often contain empty items, but never contain dividers\n\t\tthis.menuInstance._isDivider = function() {\n\t\t\treturn false;\n\t\t};\n\t},\n\n\trefresh: function() {\n\t\tthis._refreshMenu();\n\t\tthis.buttonItem.replaceWith(\n\t\t\tthis.buttonItem = this._renderButtonItem(\n\n\t\t\t\t// Fall back to an empty object in case there are no options\n\t\t\t\tthis._getSelectedItem().data( \"ui-selectmenu-item\" ) || {}\n\t\t\t)\n\t\t);\n\t\tif ( this.options.width === null ) {\n\t\t\tthis._resizeButton();\n\t\t}\n\t},\n\n\t_refreshMenu: function() {\n\t\tvar item,\n\t\t\toptions = this.element.find( \"option\" );\n\n\t\tthis.menu.empty();\n\n\t\tthis._parseOptions( options );\n\t\tthis._renderMenu( this.menu, this.items );\n\n\t\tthis.menuInstance.refresh();\n\t\tthis.menuItems = this.menu.find( \"li\" )\n\t\t\t.not( \".ui-selectmenu-optgroup\" )\n\t\t\t\t.find( \".ui-menu-item-wrapper\" );\n\n\t\tthis._rendered = true;\n\n\t\tif ( !options.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\titem = this._getSelectedItem();\n\n\t\t// Update the menu to have the correct item focused\n\t\tthis.menuInstance.focus( null, item );\n\t\tthis._setAria( item.data( \"ui-selectmenu-item\" ) );\n\n\t\t// Set disabled state\n\t\tthis._setOption( \"disabled\", this.element.prop( \"disabled\" ) );\n\t},\n\n\topen: function( event ) {\n\t\tif ( this.options.disabled ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If this is the first time the menu is being opened, render the items\n\t\tif ( !this._rendered ) {\n\t\t\tthis._refreshMenu();\n\t\t} else {\n\n\t\t\t// Menu clears focus on close, reset focus to selected item\n\t\t\tthis._removeClass( this.menu.find( \".ui-state-active\" ), null, \"ui-state-active\" );\n\t\t\tthis.menuInstance.focus( null, this._getSelectedItem() );\n\t\t}\n\n\t\t// If there are no options, don't open the menu\n\t\tif ( !this.menuItems.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.isOpen = true;\n\t\tthis._toggleAttr();\n\t\tthis._resizeMenu();\n\t\tthis._position();\n\n\t\tthis._on( this.document, this._documentClick );\n\n\t\tthis._trigger( \"open\", event );\n\t},\n\n\t_position: function() {\n\t\tthis.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );\n\t},\n\n\tclose: function( event ) {\n\t\tif ( !this.isOpen ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.isOpen = false;\n\t\tthis._toggleAttr();\n\n\t\tthis.range = null;\n\t\tthis._off( this.document );\n\n\t\tthis._trigger( \"close\", event );\n\t},\n\n\twidget: function() {\n\t\treturn this.button;\n\t},\n\n\tmenuWidget: function() {\n\t\treturn this.menu;\n\t},\n\n\t_renderButtonItem: function( item ) {\n\t\tvar buttonItem = $( \"<span>\" );\n\n\t\tthis._setText( buttonItem, item.label );\n\t\tthis._addClass( buttonItem, \"ui-selectmenu-text\" );\n\n\t\treturn buttonItem;\n\t},\n\n\t_renderMenu: function( ul, items ) {\n\t\tvar that = this,\n\t\t\tcurrentOptgroup = \"\";\n\n\t\t$.each( items, function( index, item ) {\n\t\t\tvar li;\n\n\t\t\tif ( item.optgroup !== currentOptgroup ) {\n\t\t\t\tli = $( \"<li>\", {\n\t\t\t\t\ttext: item.optgroup\n\t\t\t\t} );\n\t\t\t\tthat._addClass( li, \"ui-selectmenu-optgroup\", \"ui-menu-divider\" +\n\t\t\t\t\t( item.element.parent( \"optgroup\" ).prop( \"disabled\" ) ?\n\t\t\t\t\t\t\" ui-state-disabled\" :\n\t\t\t\t\t\t\"\" ) );\n\n\t\t\t\tli.appendTo( ul );\n\n\t\t\t\tcurrentOptgroup = item.optgroup;\n\t\t\t}\n\n\t\t\tthat._renderItemData( ul, item );\n\t\t} );\n\t},\n\n\t_renderItemData: function( ul, item ) {\n\t\treturn this._renderItem( ul, item ).data( \"ui-selectmenu-item\", item );\n\t},\n\n\t_renderItem: function( ul, item ) {\n\t\tvar li = $( \"<li>\" ),\n\t\t\twrapper = $( \"<div>\", {\n\t\t\t\ttitle: item.element.attr( \"title\" )\n\t\t\t} );\n\n\t\tif ( item.disabled ) {\n\t\t\tthis._addClass( li, null, \"ui-state-disabled\" );\n\t\t}\n\t\tthis._setText( wrapper, item.label );\n\n\t\treturn li.append( wrapper ).appendTo( ul );\n\t},\n\n\t_setText: function( element, value ) {\n\t\tif ( value ) {\n\t\t\telement.text( value );\n\t\t} else {\n\t\t\telement.html( \"&#160;\" );\n\t\t}\n\t},\n\n\t_move: function( direction, event ) {\n\t\tvar item, next,\n\t\t\tfilter = \".ui-menu-item\";\n\n\t\tif ( this.isOpen ) {\n\t\t\titem = this.menuItems.eq( this.focusIndex ).parent( \"li\" );\n\t\t} else {\n\t\t\titem = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( \"li\" );\n\t\t\tfilter += \":not(.ui-state-disabled)\";\n\t\t}\n\n\t\tif ( direction === \"first\" || direction === \"last\" ) {\n\t\t\tnext = item[ direction === \"first\" ? \"prevAll\" : \"nextAll\" ]( filter ).eq( -1 );\n\t\t} else {\n\t\t\tnext = item[ direction + \"All\" ]( filter ).eq( 0 );\n\t\t}\n\n\t\tif ( next.length ) {\n\t\t\tthis.menuInstance.focus( event, next );\n\t\t}\n\t},\n\n\t_getSelectedItem: function() {\n\t\treturn this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( \"li\" );\n\t},\n\n\t_toggle: function( event ) {\n\t\tthis[ this.isOpen ? \"close\" : \"open\" ]( event );\n\t},\n\n\t_setSelection: function() {\n\t\tvar selection;\n\n\t\tif ( !this.range ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( window.getSelection ) {\n\t\t\tselection = window.getSelection();\n\t\t\tselection.removeAllRanges();\n\t\t\tselection.addRange( this.range );\n\n\t\t// Support: IE8\n\t\t} else {\n\t\t\tthis.range.select();\n\t\t}\n\n\t\t// Support: IE\n\t\t// Setting the text selection kills the button focus in IE, but\n\t\t// restoring the focus doesn't kill the selection.\n\t\tthis.button.focus();\n\t},\n\n\t_documentClick: {\n\t\tmousedown: function( event ) {\n\t\t\tif ( !this.isOpen ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( !$( event.target ).closest( \".ui-selectmenu-menu, #\" +\n\t\t\t\t\t$.ui.escapeSelector( this.ids.button ) ).length ) {\n\t\t\t\tthis.close( event );\n\t\t\t}\n\t\t}\n\t},\n\n\t_buttonEvents: {\n\n\t\t// Prevent text selection from being reset when interacting with the selectmenu (#10144)\n\t\tmousedown: function() {\n\t\t\tvar selection;\n\n\t\t\tif ( window.getSelection ) {\n\t\t\t\tselection = window.getSelection();\n\t\t\t\tif ( selection.rangeCount ) {\n\t\t\t\t\tthis.range = selection.getRangeAt( 0 );\n\t\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t} else {\n\t\t\t\tthis.range = document.selection.createRange();\n\t\t\t}\n\t\t},\n\n\t\tclick: function( event ) {\n\t\t\tthis._setSelection();\n\t\t\tthis._toggle( event );\n\t\t},\n\n\t\tkeydown: function( event ) {\n\t\t\tvar preventDefault = true;\n\t\t\tswitch ( event.keyCode ) {\n\t\t\tcase $.ui.keyCode.TAB:\n\t\t\tcase $.ui.keyCode.ESCAPE:\n\t\t\t\tthis.close( event );\n\t\t\t\tpreventDefault = false;\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.ENTER:\n\t\t\t\tif ( this.isOpen ) {\n\t\t\t\t\tthis._selectFocusedItem( event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.UP:\n\t\t\t\tif ( event.altKey ) {\n\t\t\t\t\tthis._toggle( event );\n\t\t\t\t} else {\n\t\t\t\t\tthis._move( \"prev\", event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.DOWN:\n\t\t\t\tif ( event.altKey ) {\n\t\t\t\t\tthis._toggle( event );\n\t\t\t\t} else {\n\t\t\t\t\tthis._move( \"next\", event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.SPACE:\n\t\t\t\tif ( this.isOpen ) {\n\t\t\t\t\tthis._selectFocusedItem( event );\n\t\t\t\t} else {\n\t\t\t\t\tthis._toggle( event );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.LEFT:\n\t\t\t\tthis._move( \"prev\", event );\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.RIGHT:\n\t\t\t\tthis._move( \"next\", event );\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.HOME:\n\t\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\t\tthis._move( \"first\", event );\n\t\t\t\tbreak;\n\t\t\tcase $.ui.keyCode.END:\n\t\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\t\tthis._move( \"last\", event );\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthis.menu.trigger( event );\n\t\t\t\tpreventDefault = false;\n\t\t\t}\n\n\t\t\tif ( preventDefault ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t}\n\t},\n\n\t_selectFocusedItem: function( event ) {\n\t\tvar item = this.menuItems.eq( this.focusIndex ).parent( \"li\" );\n\t\tif ( !item.hasClass( \"ui-state-disabled\" ) ) {\n\t\t\tthis._select( item.data( \"ui-selectmenu-item\" ), event );\n\t\t}\n\t},\n\n\t_select: function( item, event ) {\n\t\tvar oldIndex = this.element[ 0 ].selectedIndex;\n\n\t\t// Change native select element\n\t\tthis.element[ 0 ].selectedIndex = item.index;\n\t\tthis.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );\n\t\tthis._setAria( item );\n\t\tthis._trigger( \"select\", event, { item: item } );\n\n\t\tif ( item.index !== oldIndex ) {\n\t\t\tthis._trigger( \"change\", event, { item: item } );\n\t\t}\n\n\t\tthis.close( event );\n\t},\n\n\t_setAria: function( item ) {\n\t\tvar id = this.menuItems.eq( item.index ).attr( \"id\" );\n\n\t\tthis.button.attr( {\n\t\t\t\"aria-labelledby\": id,\n\t\t\t\"aria-activedescendant\": id\n\t\t} );\n\t\tthis.menu.attr( \"aria-activedescendant\", id );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"icons\" ) {\n\t\t\tvar icon = this.button.find( \"span.ui-icon\" );\n\t\t\tthis._removeClass( icon, null, this.options.icons.button )\n\t\t\t\t._addClass( icon, null, value.button );\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"appendTo\" ) {\n\t\t\tthis.menuWrap.appendTo( this._appendTo() );\n\t\t}\n\n\t\tif ( key === \"width\" ) {\n\t\t\tthis._resizeButton();\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis.menuInstance.option( \"disabled\", value );\n\t\tthis.button.attr( \"aria-disabled\", value );\n\t\tthis._toggleClass( this.button, null, \"ui-state-disabled\", value );\n\n\t\tthis.element.prop( \"disabled\", value );\n\t\tif ( value ) {\n\t\t\tthis.button.attr( \"tabindex\", -1 );\n\t\t\tthis.close();\n\t\t} else {\n\t\t\tthis.button.attr( \"tabindex\", 0 );\n\t\t}\n\t},\n\n\t_appendTo: function() {\n\t\tvar element = this.options.appendTo;\n\n\t\tif ( element ) {\n\t\t\telement = element.jquery || element.nodeType ?\n\t\t\t\t$( element ) :\n\t\t\t\tthis.document.find( element ).eq( 0 );\n\t\t}\n\n\t\tif ( !element || !element[ 0 ] ) {\n\t\t\telement = this.element.closest( \".ui-front, dialog\" );\n\t\t}\n\n\t\tif ( !element.length ) {\n\t\t\telement = this.document[ 0 ].body;\n\t\t}\n\n\t\treturn element;\n\t},\n\n\t_toggleAttr: function() {\n\t\tthis.button.attr( \"aria-expanded\", this.isOpen );\n\n\t\t// We can't use two _toggleClass() calls here, because we need to make sure\n\t\t// we always remove classes first and add them second, otherwise if both classes have the\n\t\t// same theme class, it will be removed after we add it.\n\t\tthis._removeClass( this.button, \"ui-selectmenu-button-\" +\n\t\t\t( this.isOpen ? \"closed\" : \"open\" ) )\n\t\t\t._addClass( this.button, \"ui-selectmenu-button-\" +\n\t\t\t\t( this.isOpen ? \"open\" : \"closed\" ) )\n\t\t\t._toggleClass( this.menuWrap, \"ui-selectmenu-open\", null, this.isOpen );\n\n\t\tthis.menu.attr( \"aria-hidden\", !this.isOpen );\n\t},\n\n\t_resizeButton: function() {\n\t\tvar width = this.options.width;\n\n\t\t// For `width: false`, just remove inline style and stop\n\t\tif ( width === false ) {\n\t\t\tthis.button.css( \"width\", \"\" );\n\t\t\treturn;\n\t\t}\n\n\t\t// For `width: null`, match the width of the original element\n\t\tif ( width === null ) {\n\t\t\twidth = this.element.show().outerWidth();\n\t\t\tthis.element.hide();\n\t\t}\n\n\t\tthis.button.outerWidth( width );\n\t},\n\n\t_resizeMenu: function() {\n\t\tthis.menu.outerWidth( Math.max(\n\t\t\tthis.button.outerWidth(),\n\n\t\t\t// Support: IE10\n\t\t\t// IE10 wraps long text (possibly a rounding bug)\n\t\t\t// so we add 1px to avoid the wrapping\n\t\t\tthis.menu.width( \"\" ).outerWidth() + 1\n\t\t) );\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar options = this._super();\n\n\t\toptions.disabled = this.element.prop( \"disabled\" );\n\n\t\treturn options;\n\t},\n\n\t_parseOptions: function( options ) {\n\t\tvar that = this,\n\t\t\tdata = [];\n\t\toptions.each( function( index, item ) {\n\t\t\tdata.push( that._parseOption( $( item ), index ) );\n\t\t} );\n\t\tthis.items = data;\n\t},\n\n\t_parseOption: function( option, index ) {\n\t\tvar optgroup = option.parent( \"optgroup\" );\n\n\t\treturn {\n\t\t\telement: option,\n\t\t\tindex: index,\n\t\t\tvalue: option.val(),\n\t\t\tlabel: option.text(),\n\t\t\toptgroup: optgroup.attr( \"label\" ) || \"\",\n\t\t\tdisabled: optgroup.prop( \"disabled\" ) || option.prop( \"disabled\" )\n\t\t};\n\t},\n\n\t_destroy: function() {\n\t\tthis._unbindFormResetHandler();\n\t\tthis.menuWrap.remove();\n\t\tthis.button.remove();\n\t\tthis.element.show();\n\t\tthis.element.removeUniqueId();\n\t\tthis.labels.attr( \"for\", this.ids.element );\n\t}\n} ] );\n\n\n/*!\n * jQuery UI Slider 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Slider\n//>>group: Widgets\n//>>description: Displays a flexible slider with ranges and accessibility via keyboard.\n//>>docs: http://api.jqueryui.com/slider/\n//>>demos: http://jqueryui.com/slider/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/slider.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nvar widgetsSlider = $.widget( \"ui.slider\", $.ui.mouse, {\n\tversion: \"1.12.1\",\n\twidgetEventPrefix: \"slide\",\n\n\toptions: {\n\t\tanimate: false,\n\t\tclasses: {\n\t\t\t\"ui-slider\": \"ui-corner-all\",\n\t\t\t\"ui-slider-handle\": \"ui-corner-all\",\n\n\t\t\t// Note: ui-widget-header isn't the most fittingly semantic framework class for this\n\t\t\t// element, but worked best visually with a variety of themes\n\t\t\t\"ui-slider-range\": \"ui-corner-all ui-widget-header\"\n\t\t},\n\t\tdistance: 0,\n\t\tmax: 100,\n\t\tmin: 0,\n\t\torientation: \"horizontal\",\n\t\trange: false,\n\t\tstep: 1,\n\t\tvalue: 0,\n\t\tvalues: null,\n\n\t\t// Callbacks\n\t\tchange: null,\n\t\tslide: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\n\t// Number of pages in a slider\n\t// (how many times can you page up/down to go through the whole range)\n\tnumPages: 5,\n\n\t_create: function() {\n\t\tthis._keySliding = false;\n\t\tthis._mouseSliding = false;\n\t\tthis._animateOff = true;\n\t\tthis._handleIndex = null;\n\t\tthis._detectOrientation();\n\t\tthis._mouseInit();\n\t\tthis._calculateNewMax();\n\n\t\tthis._addClass( \"ui-slider ui-slider-\" + this.orientation,\n\t\t\t\"ui-widget ui-widget-content\" );\n\n\t\tthis._refresh();\n\n\t\tthis._animateOff = false;\n\t},\n\n\t_refresh: function() {\n\t\tthis._createRange();\n\t\tthis._createHandles();\n\t\tthis._setupEvents();\n\t\tthis._refreshValue();\n\t},\n\n\t_createHandles: function() {\n\t\tvar i, handleCount,\n\t\t\toptions = this.options,\n\t\t\texistingHandles = this.element.find( \".ui-slider-handle\" ),\n\t\t\thandle = \"<span tabindex='0'></span>\",\n\t\t\thandles = [];\n\n\t\thandleCount = ( options.values && options.values.length ) || 1;\n\n\t\tif ( existingHandles.length > handleCount ) {\n\t\t\texistingHandles.slice( handleCount ).remove();\n\t\t\texistingHandles = existingHandles.slice( 0, handleCount );\n\t\t}\n\n\t\tfor ( i = existingHandles.length; i < handleCount; i++ ) {\n\t\t\thandles.push( handle );\n\t\t}\n\n\t\tthis.handles = existingHandles.add( $( handles.join( \"\" ) ).appendTo( this.element ) );\n\n\t\tthis._addClass( this.handles, \"ui-slider-handle\", \"ui-state-default\" );\n\n\t\tthis.handle = this.handles.eq( 0 );\n\n\t\tthis.handles.each( function( i ) {\n\t\t\t$( this )\n\t\t\t\t.data( \"ui-slider-handle-index\", i )\n\t\t\t\t.attr( \"tabIndex\", 0 );\n\t\t} );\n\t},\n\n\t_createRange: function() {\n\t\tvar options = this.options;\n\n\t\tif ( options.range ) {\n\t\t\tif ( options.range === true ) {\n\t\t\t\tif ( !options.values ) {\n\t\t\t\t\toptions.values = [ this._valueMin(), this._valueMin() ];\n\t\t\t\t} else if ( options.values.length && options.values.length !== 2 ) {\n\t\t\t\t\toptions.values = [ options.values[ 0 ], options.values[ 0 ] ];\n\t\t\t\t} else if ( $.isArray( options.values ) ) {\n\t\t\t\t\toptions.values = options.values.slice( 0 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !this.range || !this.range.length ) {\n\t\t\t\tthis.range = $( \"<div>\" )\n\t\t\t\t\t.appendTo( this.element );\n\n\t\t\t\tthis._addClass( this.range, \"ui-slider-range\" );\n\t\t\t} else {\n\t\t\t\tthis._removeClass( this.range, \"ui-slider-range-min ui-slider-range-max\" );\n\n\t\t\t\t// Handle range switching from true to min/max\n\t\t\t\tthis.range.css( {\n\t\t\t\t\t\"left\": \"\",\n\t\t\t\t\t\"bottom\": \"\"\n\t\t\t\t} );\n\t\t\t}\n\t\t\tif ( options.range === \"min\" || options.range === \"max\" ) {\n\t\t\t\tthis._addClass( this.range, \"ui-slider-range-\" + options.range );\n\t\t\t}\n\t\t} else {\n\t\t\tif ( this.range ) {\n\t\t\t\tthis.range.remove();\n\t\t\t}\n\t\t\tthis.range = null;\n\t\t}\n\t},\n\n\t_setupEvents: function() {\n\t\tthis._off( this.handles );\n\t\tthis._on( this.handles, this._handleEvents );\n\t\tthis._hoverable( this.handles );\n\t\tthis._focusable( this.handles );\n\t},\n\n\t_destroy: function() {\n\t\tthis.handles.remove();\n\t\tif ( this.range ) {\n\t\t\tthis.range.remove();\n\t\t}\n\n\t\tthis._mouseDestroy();\n\t},\n\n\t_mouseCapture: function( event ) {\n\t\tvar position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,\n\t\t\tthat = this,\n\t\t\to = this.options;\n\n\t\tif ( o.disabled ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.elementSize = {\n\t\t\twidth: this.element.outerWidth(),\n\t\t\theight: this.element.outerHeight()\n\t\t};\n\t\tthis.elementOffset = this.element.offset();\n\n\t\tposition = { x: event.pageX, y: event.pageY };\n\t\tnormValue = this._normValueFromMouse( position );\n\t\tdistance = this._valueMax() - this._valueMin() + 1;\n\t\tthis.handles.each( function( i ) {\n\t\t\tvar thisDistance = Math.abs( normValue - that.values( i ) );\n\t\t\tif ( ( distance > thisDistance ) ||\n\t\t\t\t( distance === thisDistance &&\n\t\t\t\t\t( i === that._lastChangedValue || that.values( i ) === o.min ) ) ) {\n\t\t\t\tdistance = thisDistance;\n\t\t\t\tclosestHandle = $( this );\n\t\t\t\tindex = i;\n\t\t\t}\n\t\t} );\n\n\t\tallowed = this._start( event, index );\n\t\tif ( allowed === false ) {\n\t\t\treturn false;\n\t\t}\n\t\tthis._mouseSliding = true;\n\n\t\tthis._handleIndex = index;\n\n\t\tthis._addClass( closestHandle, null, \"ui-state-active\" );\n\t\tclosestHandle.trigger( \"focus\" );\n\n\t\toffset = closestHandle.offset();\n\t\tmouseOverHandle = !$( event.target ).parents().addBack().is( \".ui-slider-handle\" );\n\t\tthis._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {\n\t\t\tleft: event.pageX - offset.left - ( closestHandle.width() / 2 ),\n\t\t\ttop: event.pageY - offset.top -\n\t\t\t\t( closestHandle.height() / 2 ) -\n\t\t\t\t( parseInt( closestHandle.css( \"borderTopWidth\" ), 10 ) || 0 ) -\n\t\t\t\t( parseInt( closestHandle.css( \"borderBottomWidth\" ), 10 ) || 0 ) +\n\t\t\t\t( parseInt( closestHandle.css( \"marginTop\" ), 10 ) || 0 )\n\t\t};\n\n\t\tif ( !this.handles.hasClass( \"ui-state-hover\" ) ) {\n\t\t\tthis._slide( event, index, normValue );\n\t\t}\n\t\tthis._animateOff = true;\n\t\treturn true;\n\t},\n\n\t_mouseStart: function() {\n\t\treturn true;\n\t},\n\n\t_mouseDrag: function( event ) {\n\t\tvar position = { x: event.pageX, y: event.pageY },\n\t\t\tnormValue = this._normValueFromMouse( position );\n\n\t\tthis._slide( event, this._handleIndex, normValue );\n\n\t\treturn false;\n\t},\n\n\t_mouseStop: function( event ) {\n\t\tthis._removeClass( this.handles, null, \"ui-state-active\" );\n\t\tthis._mouseSliding = false;\n\n\t\tthis._stop( event, this._handleIndex );\n\t\tthis._change( event, this._handleIndex );\n\n\t\tthis._handleIndex = null;\n\t\tthis._clickOffset = null;\n\t\tthis._animateOff = false;\n\n\t\treturn false;\n\t},\n\n\t_detectOrientation: function() {\n\t\tthis.orientation = ( this.options.orientation === \"vertical\" ) ? \"vertical\" : \"horizontal\";\n\t},\n\n\t_normValueFromMouse: function( position ) {\n\t\tvar pixelTotal,\n\t\t\tpixelMouse,\n\t\t\tpercentMouse,\n\t\t\tvalueTotal,\n\t\t\tvalueMouse;\n\n\t\tif ( this.orientation === \"horizontal\" ) {\n\t\t\tpixelTotal = this.elementSize.width;\n\t\t\tpixelMouse = position.x - this.elementOffset.left -\n\t\t\t\t( this._clickOffset ? this._clickOffset.left : 0 );\n\t\t} else {\n\t\t\tpixelTotal = this.elementSize.height;\n\t\t\tpixelMouse = position.y - this.elementOffset.top -\n\t\t\t\t( this._clickOffset ? this._clickOffset.top : 0 );\n\t\t}\n\n\t\tpercentMouse = ( pixelMouse / pixelTotal );\n\t\tif ( percentMouse > 1 ) {\n\t\t\tpercentMouse = 1;\n\t\t}\n\t\tif ( percentMouse < 0 ) {\n\t\t\tpercentMouse = 0;\n\t\t}\n\t\tif ( this.orientation === \"vertical\" ) {\n\t\t\tpercentMouse = 1 - percentMouse;\n\t\t}\n\n\t\tvalueTotal = this._valueMax() - this._valueMin();\n\t\tvalueMouse = this._valueMin() + percentMouse * valueTotal;\n\n\t\treturn this._trimAlignValue( valueMouse );\n\t},\n\n\t_uiHash: function( index, value, values ) {\n\t\tvar uiHash = {\n\t\t\thandle: this.handles[ index ],\n\t\t\thandleIndex: index,\n\t\t\tvalue: value !== undefined ? value : this.value()\n\t\t};\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\tuiHash.value = value !== undefined ? value : this.values( index );\n\t\t\tuiHash.values = values || this.values();\n\t\t}\n\n\t\treturn uiHash;\n\t},\n\n\t_hasMultipleValues: function() {\n\t\treturn this.options.values && this.options.values.length;\n\t},\n\n\t_start: function( event, index ) {\n\t\treturn this._trigger( \"start\", event, this._uiHash( index ) );\n\t},\n\n\t_slide: function( event, index, newVal ) {\n\t\tvar allowed, otherVal,\n\t\t\tcurrentValue = this.value(),\n\t\t\tnewValues = this.values();\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\totherVal = this.values( index ? 0 : 1 );\n\t\t\tcurrentValue = this.values( index );\n\n\t\t\tif ( this.options.values.length === 2 && this.options.range === true ) {\n\t\t\t\tnewVal =  index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal );\n\t\t\t}\n\n\t\t\tnewValues[ index ] = newVal;\n\t\t}\n\n\t\tif ( newVal === currentValue ) {\n\t\t\treturn;\n\t\t}\n\n\t\tallowed = this._trigger( \"slide\", event, this._uiHash( index, newVal, newValues ) );\n\n\t\t// A slide can be canceled by returning false from the slide callback\n\t\tif ( allowed === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\tthis.values( index, newVal );\n\t\t} else {\n\t\t\tthis.value( newVal );\n\t\t}\n\t},\n\n\t_stop: function( event, index ) {\n\t\tthis._trigger( \"stop\", event, this._uiHash( index ) );\n\t},\n\n\t_change: function( event, index ) {\n\t\tif ( !this._keySliding && !this._mouseSliding ) {\n\n\t\t\t//store the last changed value index for reference when handles overlap\n\t\t\tthis._lastChangedValue = index;\n\t\t\tthis._trigger( \"change\", event, this._uiHash( index ) );\n\t\t}\n\t},\n\n\tvalue: function( newValue ) {\n\t\tif ( arguments.length ) {\n\t\t\tthis.options.value = this._trimAlignValue( newValue );\n\t\t\tthis._refreshValue();\n\t\t\tthis._change( null, 0 );\n\t\t\treturn;\n\t\t}\n\n\t\treturn this._value();\n\t},\n\n\tvalues: function( index, newValue ) {\n\t\tvar vals,\n\t\t\tnewValues,\n\t\t\ti;\n\n\t\tif ( arguments.length > 1 ) {\n\t\t\tthis.options.values[ index ] = this._trimAlignValue( newValue );\n\t\t\tthis._refreshValue();\n\t\t\tthis._change( null, index );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( arguments.length ) {\n\t\t\tif ( $.isArray( arguments[ 0 ] ) ) {\n\t\t\t\tvals = this.options.values;\n\t\t\t\tnewValues = arguments[ 0 ];\n\t\t\t\tfor ( i = 0; i < vals.length; i += 1 ) {\n\t\t\t\t\tvals[ i ] = this._trimAlignValue( newValues[ i ] );\n\t\t\t\t\tthis._change( null, i );\n\t\t\t\t}\n\t\t\t\tthis._refreshValue();\n\t\t\t} else {\n\t\t\t\tif ( this._hasMultipleValues() ) {\n\t\t\t\t\treturn this._values( index );\n\t\t\t\t} else {\n\t\t\t\t\treturn this.value();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\treturn this._values();\n\t\t}\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar i,\n\t\t\tvalsLength = 0;\n\n\t\tif ( key === \"range\" && this.options.range === true ) {\n\t\t\tif ( value === \"min\" ) {\n\t\t\t\tthis.options.value = this._values( 0 );\n\t\t\t\tthis.options.values = null;\n\t\t\t} else if ( value === \"max\" ) {\n\t\t\t\tthis.options.value = this._values( this.options.values.length - 1 );\n\t\t\t\tthis.options.values = null;\n\t\t\t}\n\t\t}\n\n\t\tif ( $.isArray( this.options.values ) ) {\n\t\t\tvalsLength = this.options.values.length;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tswitch ( key ) {\n\t\t\tcase \"orientation\":\n\t\t\t\tthis._detectOrientation();\n\t\t\t\tthis._removeClass( \"ui-slider-horizontal ui-slider-vertical\" )\n\t\t\t\t\t._addClass( \"ui-slider-\" + this.orientation );\n\t\t\t\tthis._refreshValue();\n\t\t\t\tif ( this.options.range ) {\n\t\t\t\t\tthis._refreshRange( value );\n\t\t\t\t}\n\n\t\t\t\t// Reset positioning from previous orientation\n\t\t\t\tthis.handles.css( value === \"horizontal\" ? \"bottom\" : \"left\", \"\" );\n\t\t\t\tbreak;\n\t\t\tcase \"value\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._refreshValue();\n\t\t\t\tthis._change( null, 0 );\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t\tcase \"values\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._refreshValue();\n\n\t\t\t\t// Start from the last handle to prevent unreachable handles (#9046)\n\t\t\t\tfor ( i = valsLength - 1; i >= 0; i-- ) {\n\t\t\t\t\tthis._change( null, i );\n\t\t\t\t}\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t\tcase \"step\":\n\t\t\tcase \"min\":\n\t\t\tcase \"max\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._calculateNewMax();\n\t\t\t\tthis._refreshValue();\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t\tcase \"range\":\n\t\t\t\tthis._animateOff = true;\n\t\t\t\tthis._refresh();\n\t\t\t\tthis._animateOff = false;\n\t\t\t\tbreak;\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis._toggleClass( null, \"ui-state-disabled\", !!value );\n\t},\n\n\t//internal value getter\n\t// _value() returns value trimmed by min and max, aligned by step\n\t_value: function() {\n\t\tvar val = this.options.value;\n\t\tval = this._trimAlignValue( val );\n\n\t\treturn val;\n\t},\n\n\t//internal values getter\n\t// _values() returns array of values trimmed by min and max, aligned by step\n\t// _values( index ) returns single value trimmed by min and max, aligned by step\n\t_values: function( index ) {\n\t\tvar val,\n\t\t\tvals,\n\t\t\ti;\n\n\t\tif ( arguments.length ) {\n\t\t\tval = this.options.values[ index ];\n\t\t\tval = this._trimAlignValue( val );\n\n\t\t\treturn val;\n\t\t} else if ( this._hasMultipleValues() ) {\n\n\t\t\t// .slice() creates a copy of the array\n\t\t\t// this copy gets trimmed by min and max and then returned\n\t\t\tvals = this.options.values.slice();\n\t\t\tfor ( i = 0; i < vals.length; i += 1 ) {\n\t\t\t\tvals[ i ] = this._trimAlignValue( vals[ i ] );\n\t\t\t}\n\n\t\t\treturn vals;\n\t\t} else {\n\t\t\treturn [];\n\t\t}\n\t},\n\n\t// Returns the step-aligned value that val is closest to, between (inclusive) min and max\n\t_trimAlignValue: function( val ) {\n\t\tif ( val <= this._valueMin() ) {\n\t\t\treturn this._valueMin();\n\t\t}\n\t\tif ( val >= this._valueMax() ) {\n\t\t\treturn this._valueMax();\n\t\t}\n\t\tvar step = ( this.options.step > 0 ) ? this.options.step : 1,\n\t\t\tvalModStep = ( val - this._valueMin() ) % step,\n\t\t\talignValue = val - valModStep;\n\n\t\tif ( Math.abs( valModStep ) * 2 >= step ) {\n\t\t\talignValue += ( valModStep > 0 ) ? step : ( -step );\n\t\t}\n\n\t\t// Since JavaScript has problems with large floats, round\n\t\t// the final value to 5 digits after the decimal point (see #4124)\n\t\treturn parseFloat( alignValue.toFixed( 5 ) );\n\t},\n\n\t_calculateNewMax: function() {\n\t\tvar max = this.options.max,\n\t\t\tmin = this._valueMin(),\n\t\t\tstep = this.options.step,\n\t\t\taboveMin = Math.round( ( max - min ) / step ) * step;\n\t\tmax = aboveMin + min;\n\t\tif ( max > this.options.max ) {\n\n\t\t\t//If max is not divisible by step, rounding off may increase its value\n\t\t\tmax -= step;\n\t\t}\n\t\tthis.max = parseFloat( max.toFixed( this._precision() ) );\n\t},\n\n\t_precision: function() {\n\t\tvar precision = this._precisionOf( this.options.step );\n\t\tif ( this.options.min !== null ) {\n\t\t\tprecision = Math.max( precision, this._precisionOf( this.options.min ) );\n\t\t}\n\t\treturn precision;\n\t},\n\n\t_precisionOf: function( num ) {\n\t\tvar str = num.toString(),\n\t\t\tdecimal = str.indexOf( \".\" );\n\t\treturn decimal === -1 ? 0 : str.length - decimal - 1;\n\t},\n\n\t_valueMin: function() {\n\t\treturn this.options.min;\n\t},\n\n\t_valueMax: function() {\n\t\treturn this.max;\n\t},\n\n\t_refreshRange: function( orientation ) {\n\t\tif ( orientation === \"vertical\" ) {\n\t\t\tthis.range.css( { \"width\": \"\", \"left\": \"\" } );\n\t\t}\n\t\tif ( orientation === \"horizontal\" ) {\n\t\t\tthis.range.css( { \"height\": \"\", \"bottom\": \"\" } );\n\t\t}\n\t},\n\n\t_refreshValue: function() {\n\t\tvar lastValPercent, valPercent, value, valueMin, valueMax,\n\t\t\toRange = this.options.range,\n\t\t\to = this.options,\n\t\t\tthat = this,\n\t\t\tanimate = ( !this._animateOff ) ? o.animate : false,\n\t\t\t_set = {};\n\n\t\tif ( this._hasMultipleValues() ) {\n\t\t\tthis.handles.each( function( i ) {\n\t\t\t\tvalPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() -\n\t\t\t\t\tthat._valueMin() ) * 100;\n\t\t\t\t_set[ that.orientation === \"horizontal\" ? \"left\" : \"bottom\" ] = valPercent + \"%\";\n\t\t\t\t$( this ).stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( _set, o.animate );\n\t\t\t\tif ( that.options.range === true ) {\n\t\t\t\t\tif ( that.orientation === \"horizontal\" ) {\n\t\t\t\t\t\tif ( i === 0 ) {\n\t\t\t\t\t\t\tthat.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\tleft: valPercent + \"%\"\n\t\t\t\t\t\t\t}, o.animate );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( i === 1 ) {\n\t\t\t\t\t\t\tthat.range[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\twidth: ( valPercent - lastValPercent ) + \"%\"\n\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tqueue: false,\n\t\t\t\t\t\t\t\tduration: o.animate\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif ( i === 0 ) {\n\t\t\t\t\t\t\tthat.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\tbottom: ( valPercent ) + \"%\"\n\t\t\t\t\t\t\t}, o.animate );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( i === 1 ) {\n\t\t\t\t\t\t\tthat.range[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\t\t\t\theight: ( valPercent - lastValPercent ) + \"%\"\n\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tqueue: false,\n\t\t\t\t\t\t\t\tduration: o.animate\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlastValPercent = valPercent;\n\t\t\t} );\n\t\t} else {\n\t\t\tvalue = this.value();\n\t\t\tvalueMin = this._valueMin();\n\t\t\tvalueMax = this._valueMax();\n\t\t\tvalPercent = ( valueMax !== valueMin ) ?\n\t\t\t\t\t( value - valueMin ) / ( valueMax - valueMin ) * 100 :\n\t\t\t\t\t0;\n\t\t\t_set[ this.orientation === \"horizontal\" ? \"left\" : \"bottom\" ] = valPercent + \"%\";\n\t\t\tthis.handle.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( _set, o.animate );\n\n\t\t\tif ( oRange === \"min\" && this.orientation === \"horizontal\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\twidth: valPercent + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t\tif ( oRange === \"max\" && this.orientation === \"horizontal\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\twidth: ( 100 - valPercent ) + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t\tif ( oRange === \"min\" && this.orientation === \"vertical\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\theight: valPercent + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t\tif ( oRange === \"max\" && this.orientation === \"vertical\" ) {\n\t\t\t\tthis.range.stop( 1, 1 )[ animate ? \"animate\" : \"css\" ]( {\n\t\t\t\t\theight: ( 100 - valPercent ) + \"%\"\n\t\t\t\t}, o.animate );\n\t\t\t}\n\t\t}\n\t},\n\n\t_handleEvents: {\n\t\tkeydown: function( event ) {\n\t\t\tvar allowed, curVal, newVal, step,\n\t\t\t\tindex = $( event.target ).data( \"ui-slider-handle-index\" );\n\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase $.ui.keyCode.HOME:\n\t\t\t\tcase $.ui.keyCode.END:\n\t\t\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\t\tcase $.ui.keyCode.UP:\n\t\t\t\tcase $.ui.keyCode.RIGHT:\n\t\t\t\tcase $.ui.keyCode.DOWN:\n\t\t\t\tcase $.ui.keyCode.LEFT:\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tif ( !this._keySliding ) {\n\t\t\t\t\t\tthis._keySliding = true;\n\t\t\t\t\t\tthis._addClass( $( event.target ), null, \"ui-state-active\" );\n\t\t\t\t\t\tallowed = this._start( event, index );\n\t\t\t\t\t\tif ( allowed === false ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tstep = this.options.step;\n\t\t\tif ( this._hasMultipleValues() ) {\n\t\t\t\tcurVal = newVal = this.values( index );\n\t\t\t} else {\n\t\t\t\tcurVal = newVal = this.value();\n\t\t\t}\n\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase $.ui.keyCode.HOME:\n\t\t\t\t\tnewVal = this._valueMin();\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.END:\n\t\t\t\t\tnewVal = this._valueMax();\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.PAGE_UP:\n\t\t\t\t\tnewVal = this._trimAlignValue(\n\t\t\t\t\t\tcurVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages )\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.PAGE_DOWN:\n\t\t\t\t\tnewVal = this._trimAlignValue(\n\t\t\t\t\t\tcurVal - ( ( this._valueMax() - this._valueMin() ) / this.numPages ) );\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.UP:\n\t\t\t\tcase $.ui.keyCode.RIGHT:\n\t\t\t\t\tif ( curVal === this._valueMax() ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tnewVal = this._trimAlignValue( curVal + step );\n\t\t\t\t\tbreak;\n\t\t\t\tcase $.ui.keyCode.DOWN:\n\t\t\t\tcase $.ui.keyCode.LEFT:\n\t\t\t\t\tif ( curVal === this._valueMin() ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tnewVal = this._trimAlignValue( curVal - step );\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tthis._slide( event, index, newVal );\n\t\t},\n\t\tkeyup: function( event ) {\n\t\t\tvar index = $( event.target ).data( \"ui-slider-handle-index\" );\n\n\t\t\tif ( this._keySliding ) {\n\t\t\t\tthis._keySliding = false;\n\t\t\t\tthis._stop( event, index );\n\t\t\t\tthis._change( event, index );\n\t\t\t\tthis._removeClass( $( event.target ), null, \"ui-state-active\" );\n\t\t\t}\n\t\t}\n\t}\n} );\n\n\n/*!\n * jQuery UI Spinner 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Spinner\n//>>group: Widgets\n//>>description: Displays buttons to easily input numbers via the keyboard or mouse.\n//>>docs: http://api.jqueryui.com/spinner/\n//>>demos: http://jqueryui.com/spinner/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/spinner.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\nfunction spinnerModifer( fn ) {\n\treturn function() {\n\t\tvar previous = this.element.val();\n\t\tfn.apply( this, arguments );\n\t\tthis._refresh();\n\t\tif ( previous !== this.element.val() ) {\n\t\t\tthis._trigger( \"change\" );\n\t\t}\n\t};\n}\n\n$.widget( \"ui.spinner\", {\n\tversion: \"1.12.1\",\n\tdefaultElement: \"<input>\",\n\twidgetEventPrefix: \"spin\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-spinner\": \"ui-corner-all\",\n\t\t\t\"ui-spinner-down\": \"ui-corner-br\",\n\t\t\t\"ui-spinner-up\": \"ui-corner-tr\"\n\t\t},\n\t\tculture: null,\n\t\ticons: {\n\t\t\tdown: \"ui-icon-triangle-1-s\",\n\t\t\tup: \"ui-icon-triangle-1-n\"\n\t\t},\n\t\tincremental: true,\n\t\tmax: null,\n\t\tmin: null,\n\t\tnumberFormat: null,\n\t\tpage: 10,\n\t\tstep: 1,\n\n\t\tchange: null,\n\t\tspin: null,\n\t\tstart: null,\n\t\tstop: null\n\t},\n\n\t_create: function() {\n\n\t\t// handle string values that need to be parsed\n\t\tthis._setOption( \"max\", this.options.max );\n\t\tthis._setOption( \"min\", this.options.min );\n\t\tthis._setOption( \"step\", this.options.step );\n\n\t\t// Only format if there is a value, prevents the field from being marked\n\t\t// as invalid in Firefox, see #9573.\n\t\tif ( this.value() !== \"\" ) {\n\n\t\t\t// Format the value, but don't constrain.\n\t\t\tthis._value( this.element.val(), true );\n\t\t}\n\n\t\tthis._draw();\n\t\tthis._on( this._events );\n\t\tthis._refresh();\n\n\t\t// Turning off autocomplete prevents the browser from remembering the\n\t\t// value when navigating through history, so we re-enable autocomplete\n\t\t// if the page is unloaded before the widget is destroyed. #7790\n\t\tthis._on( this.window, {\n\t\t\tbeforeunload: function() {\n\t\t\t\tthis.element.removeAttr( \"autocomplete\" );\n\t\t\t}\n\t\t} );\n\t},\n\n\t_getCreateOptions: function() {\n\t\tvar options = this._super();\n\t\tvar element = this.element;\n\n\t\t$.each( [ \"min\", \"max\", \"step\" ], function( i, option ) {\n\t\t\tvar value = element.attr( option );\n\t\t\tif ( value != null && value.length ) {\n\t\t\t\toptions[ option ] = value;\n\t\t\t}\n\t\t} );\n\n\t\treturn options;\n\t},\n\n\t_events: {\n\t\tkeydown: function( event ) {\n\t\t\tif ( this._start( event ) && this._keydown( event ) ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t},\n\t\tkeyup: \"_stop\",\n\t\tfocus: function() {\n\t\t\tthis.previous = this.element.val();\n\t\t},\n\t\tblur: function( event ) {\n\t\t\tif ( this.cancelBlur ) {\n\t\t\t\tdelete this.cancelBlur;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis._stop();\n\t\t\tthis._refresh();\n\t\t\tif ( this.previous !== this.element.val() ) {\n\t\t\t\tthis._trigger( \"change\", event );\n\t\t\t}\n\t\t},\n\t\tmousewheel: function( event, delta ) {\n\t\t\tif ( !delta ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( !this.spinning && !this._start( event ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event );\n\t\t\tclearTimeout( this.mousewheelTimer );\n\t\t\tthis.mousewheelTimer = this._delay( function() {\n\t\t\t\tif ( this.spinning ) {\n\t\t\t\t\tthis._stop( event );\n\t\t\t\t}\n\t\t\t}, 100 );\n\t\t\tevent.preventDefault();\n\t\t},\n\t\t\"mousedown .ui-spinner-button\": function( event ) {\n\t\t\tvar previous;\n\n\t\t\t// We never want the buttons to have focus; whenever the user is\n\t\t\t// interacting with the spinner, the focus should be on the input.\n\t\t\t// If the input is focused then this.previous is properly set from\n\t\t\t// when the input first received focus. If the input is not focused\n\t\t\t// then we need to set this.previous based on the value before spinning.\n\t\t\tprevious = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ?\n\t\t\t\tthis.previous : this.element.val();\n\t\t\tfunction checkFocus() {\n\t\t\t\tvar isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] );\n\t\t\t\tif ( !isActive ) {\n\t\t\t\t\tthis.element.trigger( \"focus\" );\n\t\t\t\t\tthis.previous = previous;\n\n\t\t\t\t\t// support: IE\n\t\t\t\t\t// IE sets focus asynchronously, so we need to check if focus\n\t\t\t\t\t// moved off of the input because the user clicked on the button.\n\t\t\t\t\tthis._delay( function() {\n\t\t\t\t\t\tthis.previous = previous;\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ensure focus is on (or stays on) the text field\n\t\t\tevent.preventDefault();\n\t\t\tcheckFocus.call( this );\n\n\t\t\t// Support: IE\n\t\t\t// IE doesn't prevent moving focus even with event.preventDefault()\n\t\t\t// so we set a flag to know when we should ignore the blur event\n\t\t\t// and check (again) if focus moved off of the input.\n\t\t\tthis.cancelBlur = true;\n\t\t\tthis._delay( function() {\n\t\t\t\tdelete this.cancelBlur;\n\t\t\t\tcheckFocus.call( this );\n\t\t\t} );\n\n\t\t\tif ( this._start( event ) === false ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis._repeat( null, $( event.currentTarget )\n\t\t\t\t.hasClass( \"ui-spinner-up\" ) ? 1 : -1, event );\n\t\t},\n\t\t\"mouseup .ui-spinner-button\": \"_stop\",\n\t\t\"mouseenter .ui-spinner-button\": function( event ) {\n\n\t\t\t// button will add ui-state-active if mouse was down while mouseleave and kept down\n\t\t\tif ( !$( event.currentTarget ).hasClass( \"ui-state-active\" ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( this._start( event ) === false ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis._repeat( null, $( event.currentTarget )\n\t\t\t\t.hasClass( \"ui-spinner-up\" ) ? 1 : -1, event );\n\t\t},\n\n\t\t// TODO: do we really want to consider this a stop?\n\t\t// shouldn't we just stop the repeater and wait until mouseup before\n\t\t// we trigger the stop event?\n\t\t\"mouseleave .ui-spinner-button\": \"_stop\"\n\t},\n\n\t// Support mobile enhanced option and make backcompat more sane\n\t_enhance: function() {\n\t\tthis.uiSpinner = this.element\n\t\t\t.attr( \"autocomplete\", \"off\" )\n\t\t\t.wrap( \"<span>\" )\n\t\t\t.parent()\n\n\t\t\t\t// Add buttons\n\t\t\t\t.append(\n\t\t\t\t\t\"<a></a><a></a>\"\n\t\t\t\t);\n\t},\n\n\t_draw: function() {\n\t\tthis._enhance();\n\n\t\tthis._addClass( this.uiSpinner, \"ui-spinner\", \"ui-widget ui-widget-content\" );\n\t\tthis._addClass( \"ui-spinner-input\" );\n\n\t\tthis.element.attr( \"role\", \"spinbutton\" );\n\n\t\t// Button bindings\n\t\tthis.buttons = this.uiSpinner.children( \"a\" )\n\t\t\t.attr( \"tabIndex\", -1 )\n\t\t\t.attr( \"aria-hidden\", true )\n\t\t\t.button( {\n\t\t\t\tclasses: {\n\t\t\t\t\t\"ui-button\": \"\"\n\t\t\t\t}\n\t\t\t} );\n\n\t\t// TODO: Right now button does not support classes this is already updated in button PR\n\t\tthis._removeClass( this.buttons, \"ui-corner-all\" );\n\n\t\tthis._addClass( this.buttons.first(), \"ui-spinner-button ui-spinner-up\" );\n\t\tthis._addClass( this.buttons.last(), \"ui-spinner-button ui-spinner-down\" );\n\t\tthis.buttons.first().button( {\n\t\t\t\"icon\": this.options.icons.up,\n\t\t\t\"showLabel\": false\n\t\t} );\n\t\tthis.buttons.last().button( {\n\t\t\t\"icon\": this.options.icons.down,\n\t\t\t\"showLabel\": false\n\t\t} );\n\n\t\t// IE 6 doesn't understand height: 50% for the buttons\n\t\t// unless the wrapper has an explicit height\n\t\tif ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) &&\n\t\t\t\tthis.uiSpinner.height() > 0 ) {\n\t\t\tthis.uiSpinner.height( this.uiSpinner.height() );\n\t\t}\n\t},\n\n\t_keydown: function( event ) {\n\t\tvar options = this.options,\n\t\t\tkeyCode = $.ui.keyCode;\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase keyCode.UP:\n\t\t\tthis._repeat( null, 1, event );\n\t\t\treturn true;\n\t\tcase keyCode.DOWN:\n\t\t\tthis._repeat( null, -1, event );\n\t\t\treturn true;\n\t\tcase keyCode.PAGE_UP:\n\t\t\tthis._repeat( null, options.page, event );\n\t\t\treturn true;\n\t\tcase keyCode.PAGE_DOWN:\n\t\t\tthis._repeat( null, -options.page, event );\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_start: function( event ) {\n\t\tif ( !this.spinning && this._trigger( \"start\", event ) === false ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( !this.counter ) {\n\t\t\tthis.counter = 1;\n\t\t}\n\t\tthis.spinning = true;\n\t\treturn true;\n\t},\n\n\t_repeat: function( i, steps, event ) {\n\t\ti = i || 500;\n\n\t\tclearTimeout( this.timer );\n\t\tthis.timer = this._delay( function() {\n\t\t\tthis._repeat( 40, steps, event );\n\t\t}, i );\n\n\t\tthis._spin( steps * this.options.step, event );\n\t},\n\n\t_spin: function( step, event ) {\n\t\tvar value = this.value() || 0;\n\n\t\tif ( !this.counter ) {\n\t\t\tthis.counter = 1;\n\t\t}\n\n\t\tvalue = this._adjustValue( value + step * this._increment( this.counter ) );\n\n\t\tif ( !this.spinning || this._trigger( \"spin\", event, { value: value } ) !== false ) {\n\t\t\tthis._value( value );\n\t\t\tthis.counter++;\n\t\t}\n\t},\n\n\t_increment: function( i ) {\n\t\tvar incremental = this.options.incremental;\n\n\t\tif ( incremental ) {\n\t\t\treturn $.isFunction( incremental ) ?\n\t\t\t\tincremental( i ) :\n\t\t\t\tMath.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );\n\t\t}\n\n\t\treturn 1;\n\t},\n\n\t_precision: function() {\n\t\tvar precision = this._precisionOf( this.options.step );\n\t\tif ( this.options.min !== null ) {\n\t\t\tprecision = Math.max( precision, this._precisionOf( this.options.min ) );\n\t\t}\n\t\treturn precision;\n\t},\n\n\t_precisionOf: function( num ) {\n\t\tvar str = num.toString(),\n\t\t\tdecimal = str.indexOf( \".\" );\n\t\treturn decimal === -1 ? 0 : str.length - decimal - 1;\n\t},\n\n\t_adjustValue: function( value ) {\n\t\tvar base, aboveMin,\n\t\t\toptions = this.options;\n\n\t\t// Make sure we're at a valid step\n\t\t// - find out where we are relative to the base (min or 0)\n\t\tbase = options.min !== null ? options.min : 0;\n\t\taboveMin = value - base;\n\n\t\t// - round to the nearest step\n\t\taboveMin = Math.round( aboveMin / options.step ) * options.step;\n\n\t\t// - rounding is based on 0, so adjust back to our base\n\t\tvalue = base + aboveMin;\n\n\t\t// Fix precision from bad JS floating point math\n\t\tvalue = parseFloat( value.toFixed( this._precision() ) );\n\n\t\t// Clamp the value\n\t\tif ( options.max !== null && value > options.max ) {\n\t\t\treturn options.max;\n\t\t}\n\t\tif ( options.min !== null && value < options.min ) {\n\t\t\treturn options.min;\n\t\t}\n\n\t\treturn value;\n\t},\n\n\t_stop: function( event ) {\n\t\tif ( !this.spinning ) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout( this.timer );\n\t\tclearTimeout( this.mousewheelTimer );\n\t\tthis.counter = 0;\n\t\tthis.spinning = false;\n\t\tthis._trigger( \"stop\", event );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar prevValue, first, last;\n\n\t\tif ( key === \"culture\" || key === \"numberFormat\" ) {\n\t\t\tprevValue = this._parse( this.element.val() );\n\t\t\tthis.options[ key ] = value;\n\t\t\tthis.element.val( this._format( prevValue ) );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( key === \"max\" || key === \"min\" || key === \"step\" ) {\n\t\t\tif ( typeof value === \"string\" ) {\n\t\t\t\tvalue = this._parse( value );\n\t\t\t}\n\t\t}\n\t\tif ( key === \"icons\" ) {\n\t\t\tfirst = this.buttons.first().find( \".ui-icon\" );\n\t\t\tthis._removeClass( first, null, this.options.icons.up );\n\t\t\tthis._addClass( first, null, value.up );\n\t\t\tlast = this.buttons.last().find( \".ui-icon\" );\n\t\t\tthis._removeClass( last, null, this.options.icons.down );\n\t\t\tthis._addClass( last, null, value.down );\n\t\t}\n\n\t\tthis._super( key, value );\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis._super( value );\n\n\t\tthis._toggleClass( this.uiSpinner, null, \"ui-state-disabled\", !!value );\n\t\tthis.element.prop( \"disabled\", !!value );\n\t\tthis.buttons.button( value ? \"disable\" : \"enable\" );\n\t},\n\n\t_setOptions: spinnerModifer( function( options ) {\n\t\tthis._super( options );\n\t} ),\n\n\t_parse: function( val ) {\n\t\tif ( typeof val === \"string\" && val !== \"\" ) {\n\t\t\tval = window.Globalize && this.options.numberFormat ?\n\t\t\t\tGlobalize.parseFloat( val, 10, this.options.culture ) : +val;\n\t\t}\n\t\treturn val === \"\" || isNaN( val ) ? null : val;\n\t},\n\n\t_format: function( value ) {\n\t\tif ( value === \"\" ) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn window.Globalize && this.options.numberFormat ?\n\t\t\tGlobalize.format( value, this.options.numberFormat, this.options.culture ) :\n\t\t\tvalue;\n\t},\n\n\t_refresh: function() {\n\t\tthis.element.attr( {\n\t\t\t\"aria-valuemin\": this.options.min,\n\t\t\t\"aria-valuemax\": this.options.max,\n\n\t\t\t// TODO: what should we do with values that can't be parsed?\n\t\t\t\"aria-valuenow\": this._parse( this.element.val() )\n\t\t} );\n\t},\n\n\tisValid: function() {\n\t\tvar value = this.value();\n\n\t\t// Null is invalid\n\t\tif ( value === null ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// If value gets adjusted, it's invalid\n\t\treturn value === this._adjustValue( value );\n\t},\n\n\t// Update the value without triggering change\n\t_value: function( value, allowAny ) {\n\t\tvar parsed;\n\t\tif ( value !== \"\" ) {\n\t\t\tparsed = this._parse( value );\n\t\t\tif ( parsed !== null ) {\n\t\t\t\tif ( !allowAny ) {\n\t\t\t\t\tparsed = this._adjustValue( parsed );\n\t\t\t\t}\n\t\t\t\tvalue = this._format( parsed );\n\t\t\t}\n\t\t}\n\t\tthis.element.val( value );\n\t\tthis._refresh();\n\t},\n\n\t_destroy: function() {\n\t\tthis.element\n\t\t\t.prop( \"disabled\", false )\n\t\t\t.removeAttr( \"autocomplete role aria-valuemin aria-valuemax aria-valuenow\" );\n\n\t\tthis.uiSpinner.replaceWith( this.element );\n\t},\n\n\tstepUp: spinnerModifer( function( steps ) {\n\t\tthis._stepUp( steps );\n\t} ),\n\t_stepUp: function( steps ) {\n\t\tif ( this._start() ) {\n\t\t\tthis._spin( ( steps || 1 ) * this.options.step );\n\t\t\tthis._stop();\n\t\t}\n\t},\n\n\tstepDown: spinnerModifer( function( steps ) {\n\t\tthis._stepDown( steps );\n\t} ),\n\t_stepDown: function( steps ) {\n\t\tif ( this._start() ) {\n\t\t\tthis._spin( ( steps || 1 ) * -this.options.step );\n\t\t\tthis._stop();\n\t\t}\n\t},\n\n\tpageUp: spinnerModifer( function( pages ) {\n\t\tthis._stepUp( ( pages || 1 ) * this.options.page );\n\t} ),\n\n\tpageDown: spinnerModifer( function( pages ) {\n\t\tthis._stepDown( ( pages || 1 ) * this.options.page );\n\t} ),\n\n\tvalue: function( newVal ) {\n\t\tif ( !arguments.length ) {\n\t\t\treturn this._parse( this.element.val() );\n\t\t}\n\t\tspinnerModifer( this._value ).call( this, newVal );\n\t},\n\n\twidget: function() {\n\t\treturn this.uiSpinner;\n\t}\n} );\n\n// DEPRECATED\n// TODO: switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for spinner html extension points\n\t$.widget( \"ui.spinner\", $.ui.spinner, {\n\t\t_enhance: function() {\n\t\t\tthis.uiSpinner = this.element\n\t\t\t\t.attr( \"autocomplete\", \"off\" )\n\t\t\t\t.wrap( this._uiSpinnerHtml() )\n\t\t\t\t.parent()\n\n\t\t\t\t\t// Add buttons\n\t\t\t\t\t.append( this._buttonHtml() );\n\t\t},\n\t\t_uiSpinnerHtml: function() {\n\t\t\treturn \"<span>\";\n\t\t},\n\n\t\t_buttonHtml: function() {\n\t\t\treturn \"<a></a><a></a>\";\n\t\t}\n\t} );\n}\n\nvar widgetsSpinner = $.ui.spinner;\n\n\n/*!\n * jQuery UI Tabs 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Tabs\n//>>group: Widgets\n//>>description: Transforms a set of container elements into a tab structure.\n//>>docs: http://api.jqueryui.com/tabs/\n//>>demos: http://jqueryui.com/tabs/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/tabs.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.tabs\", {\n\tversion: \"1.12.1\",\n\tdelay: 300,\n\toptions: {\n\t\tactive: null,\n\t\tclasses: {\n\t\t\t\"ui-tabs\": \"ui-corner-all\",\n\t\t\t\"ui-tabs-nav\": \"ui-corner-all\",\n\t\t\t\"ui-tabs-panel\": \"ui-corner-bottom\",\n\t\t\t\"ui-tabs-tab\": \"ui-corner-top\"\n\t\t},\n\t\tcollapsible: false,\n\t\tevent: \"click\",\n\t\theightStyle: \"content\",\n\t\thide: null,\n\t\tshow: null,\n\n\t\t// Callbacks\n\t\tactivate: null,\n\t\tbeforeActivate: null,\n\t\tbeforeLoad: null,\n\t\tload: null\n\t},\n\n\t_isLocal: ( function() {\n\t\tvar rhash = /#.*$/;\n\n\t\treturn function( anchor ) {\n\t\t\tvar anchorUrl, locationUrl;\n\n\t\t\tanchorUrl = anchor.href.replace( rhash, \"\" );\n\t\t\tlocationUrl = location.href.replace( rhash, \"\" );\n\n\t\t\t// Decoding may throw an error if the URL isn't UTF-8 (#9518)\n\t\t\ttry {\n\t\t\t\tanchorUrl = decodeURIComponent( anchorUrl );\n\t\t\t} catch ( error ) {}\n\t\t\ttry {\n\t\t\t\tlocationUrl = decodeURIComponent( locationUrl );\n\t\t\t} catch ( error ) {}\n\n\t\t\treturn anchor.hash.length > 1 && anchorUrl === locationUrl;\n\t\t};\n\t} )(),\n\n\t_create: function() {\n\t\tvar that = this,\n\t\t\toptions = this.options;\n\n\t\tthis.running = false;\n\n\t\tthis._addClass( \"ui-tabs\", \"ui-widget ui-widget-content\" );\n\t\tthis._toggleClass( \"ui-tabs-collapsible\", null, options.collapsible );\n\n\t\tthis._processTabs();\n\t\toptions.active = this._initialActive();\n\n\t\t// Take disabling tabs via class attribute from HTML\n\t\t// into account and update option properly.\n\t\tif ( $.isArray( options.disabled ) ) {\n\t\t\toptions.disabled = $.unique( options.disabled.concat(\n\t\t\t\t$.map( this.tabs.filter( \".ui-state-disabled\" ), function( li ) {\n\t\t\t\t\treturn that.tabs.index( li );\n\t\t\t\t} )\n\t\t\t) ).sort();\n\t\t}\n\n\t\t// Check for length avoids error when initializing empty list\n\t\tif ( this.options.active !== false && this.anchors.length ) {\n\t\t\tthis.active = this._findActive( options.active );\n\t\t} else {\n\t\t\tthis.active = $();\n\t\t}\n\n\t\tthis._refresh();\n\n\t\tif ( this.active.length ) {\n\t\t\tthis.load( options.active );\n\t\t}\n\t},\n\n\t_initialActive: function() {\n\t\tvar active = this.options.active,\n\t\t\tcollapsible = this.options.collapsible,\n\t\t\tlocationHash = location.hash.substring( 1 );\n\n\t\tif ( active === null ) {\n\n\t\t\t// check the fragment identifier in the URL\n\t\t\tif ( locationHash ) {\n\t\t\t\tthis.tabs.each( function( i, tab ) {\n\t\t\t\t\tif ( $( tab ).attr( \"aria-controls\" ) === locationHash ) {\n\t\t\t\t\t\tactive = i;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t// Check for a tab marked active via a class\n\t\t\tif ( active === null ) {\n\t\t\t\tactive = this.tabs.index( this.tabs.filter( \".ui-tabs-active\" ) );\n\t\t\t}\n\n\t\t\t// No active tab, set to false\n\t\t\tif ( active === null || active === -1 ) {\n\t\t\t\tactive = this.tabs.length ? 0 : false;\n\t\t\t}\n\t\t}\n\n\t\t// Handle numbers: negative, out of range\n\t\tif ( active !== false ) {\n\t\t\tactive = this.tabs.index( this.tabs.eq( active ) );\n\t\t\tif ( active === -1 ) {\n\t\t\t\tactive = collapsible ? false : 0;\n\t\t\t}\n\t\t}\n\n\t\t// Don't allow collapsible: false and active: false\n\t\tif ( !collapsible && active === false && this.anchors.length ) {\n\t\t\tactive = 0;\n\t\t}\n\n\t\treturn active;\n\t},\n\n\t_getCreateEventData: function() {\n\t\treturn {\n\t\t\ttab: this.active,\n\t\t\tpanel: !this.active.length ? $() : this._getPanelForTab( this.active )\n\t\t};\n\t},\n\n\t_tabKeydown: function( event ) {\n\t\tvar focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( \"li\" ),\n\t\t\tselectedIndex = this.tabs.index( focusedTab ),\n\t\t\tgoingForward = true;\n\n\t\tif ( this._handlePageNav( event ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch ( event.keyCode ) {\n\t\tcase $.ui.keyCode.RIGHT:\n\t\tcase $.ui.keyCode.DOWN:\n\t\t\tselectedIndex++;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.UP:\n\t\tcase $.ui.keyCode.LEFT:\n\t\t\tgoingForward = false;\n\t\t\tselectedIndex--;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.END:\n\t\t\tselectedIndex = this.anchors.length - 1;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.HOME:\n\t\t\tselectedIndex = 0;\n\t\t\tbreak;\n\t\tcase $.ui.keyCode.SPACE:\n\n\t\t\t// Activate only, no collapsing\n\t\t\tevent.preventDefault();\n\t\t\tclearTimeout( this.activating );\n\t\t\tthis._activate( selectedIndex );\n\t\t\treturn;\n\t\tcase $.ui.keyCode.ENTER:\n\n\t\t\t// Toggle (cancel delayed activation, allow collapsing)\n\t\t\tevent.preventDefault();\n\t\t\tclearTimeout( this.activating );\n\n\t\t\t// Determine if we should collapse or activate\n\t\t\tthis._activate( selectedIndex === this.options.active ? false : selectedIndex );\n\t\t\treturn;\n\t\tdefault:\n\t\t\treturn;\n\t\t}\n\n\t\t// Focus the appropriate tab, based on which key was pressed\n\t\tevent.preventDefault();\n\t\tclearTimeout( this.activating );\n\t\tselectedIndex = this._focusNextTab( selectedIndex, goingForward );\n\n\t\t// Navigating with control/command key will prevent automatic activation\n\t\tif ( !event.ctrlKey && !event.metaKey ) {\n\n\t\t\t// Update aria-selected immediately so that AT think the tab is already selected.\n\t\t\t// Otherwise AT may confuse the user by stating that they need to activate the tab,\n\t\t\t// but the tab will already be activated by the time the announcement finishes.\n\t\t\tfocusedTab.attr( \"aria-selected\", \"false\" );\n\t\t\tthis.tabs.eq( selectedIndex ).attr( \"aria-selected\", \"true\" );\n\n\t\t\tthis.activating = this._delay( function() {\n\t\t\t\tthis.option( \"active\", selectedIndex );\n\t\t\t}, this.delay );\n\t\t}\n\t},\n\n\t_panelKeydown: function( event ) {\n\t\tif ( this._handlePageNav( event ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+up moves focus to the current tab\n\t\tif ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {\n\t\t\tevent.preventDefault();\n\t\t\tthis.active.trigger( \"focus\" );\n\t\t}\n\t},\n\n\t// Alt+page up/down moves focus to the previous/next tab (and activates)\n\t_handlePageNav: function( event ) {\n\t\tif ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {\n\t\t\tthis._activate( this._focusNextTab( this.options.active - 1, false ) );\n\t\t\treturn true;\n\t\t}\n\t\tif ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {\n\t\t\tthis._activate( this._focusNextTab( this.options.active + 1, true ) );\n\t\t\treturn true;\n\t\t}\n\t},\n\n\t_findNextTab: function( index, goingForward ) {\n\t\tvar lastTabIndex = this.tabs.length - 1;\n\n\t\tfunction constrain() {\n\t\t\tif ( index > lastTabIndex ) {\n\t\t\t\tindex = 0;\n\t\t\t}\n\t\t\tif ( index < 0 ) {\n\t\t\t\tindex = lastTabIndex;\n\t\t\t}\n\t\t\treturn index;\n\t\t}\n\n\t\twhile ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {\n\t\t\tindex = goingForward ? index + 1 : index - 1;\n\t\t}\n\n\t\treturn index;\n\t},\n\n\t_focusNextTab: function( index, goingForward ) {\n\t\tindex = this._findNextTab( index, goingForward );\n\t\tthis.tabs.eq( index ).trigger( \"focus\" );\n\t\treturn index;\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tif ( key === \"active\" ) {\n\n\t\t\t// _activate() will handle invalid values and update this.options\n\t\t\tthis._activate( value );\n\t\t\treturn;\n\t\t}\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"collapsible\" ) {\n\t\t\tthis._toggleClass( \"ui-tabs-collapsible\", null, value );\n\n\t\t\t// Setting collapsible: false while collapsed; open first panel\n\t\t\tif ( !value && this.options.active === false ) {\n\t\t\t\tthis._activate( 0 );\n\t\t\t}\n\t\t}\n\n\t\tif ( key === \"event\" ) {\n\t\t\tthis._setupEvents( value );\n\t\t}\n\n\t\tif ( key === \"heightStyle\" ) {\n\t\t\tthis._setupHeightStyle( value );\n\t\t}\n\t},\n\n\t_sanitizeSelector: function( hash ) {\n\t\treturn hash ? hash.replace( /[!\"$%&'()*+,.\\/:;<=>?@\\[\\]\\^`{|}~]/g, \"\\\\$&\" ) : \"\";\n\t},\n\n\trefresh: function() {\n\t\tvar options = this.options,\n\t\t\tlis = this.tablist.children( \":has(a[href])\" );\n\n\t\t// Get disabled tabs from class attribute from HTML\n\t\t// this will get converted to a boolean if needed in _refresh()\n\t\toptions.disabled = $.map( lis.filter( \".ui-state-disabled\" ), function( tab ) {\n\t\t\treturn lis.index( tab );\n\t\t} );\n\n\t\tthis._processTabs();\n\n\t\t// Was collapsed or no tabs\n\t\tif ( options.active === false || !this.anchors.length ) {\n\t\t\toptions.active = false;\n\t\t\tthis.active = $();\n\n\t\t// was active, but active tab is gone\n\t\t} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {\n\n\t\t\t// all remaining tabs are disabled\n\t\t\tif ( this.tabs.length === options.disabled.length ) {\n\t\t\t\toptions.active = false;\n\t\t\t\tthis.active = $();\n\n\t\t\t// activate previous tab\n\t\t\t} else {\n\t\t\t\tthis._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );\n\t\t\t}\n\n\t\t// was active, active tab still exists\n\t\t} else {\n\n\t\t\t// make sure active index is correct\n\t\t\toptions.active = this.tabs.index( this.active );\n\t\t}\n\n\t\tthis._refresh();\n\t},\n\n\t_refresh: function() {\n\t\tthis._setOptionDisabled( this.options.disabled );\n\t\tthis._setupEvents( this.options.event );\n\t\tthis._setupHeightStyle( this.options.heightStyle );\n\n\t\tthis.tabs.not( this.active ).attr( {\n\t\t\t\"aria-selected\": \"false\",\n\t\t\t\"aria-expanded\": \"false\",\n\t\t\ttabIndex: -1\n\t\t} );\n\t\tthis.panels.not( this._getPanelForTab( this.active ) )\n\t\t\t.hide()\n\t\t\t.attr( {\n\t\t\t\t\"aria-hidden\": \"true\"\n\t\t\t} );\n\n\t\t// Make sure one tab is in the tab order\n\t\tif ( !this.active.length ) {\n\t\t\tthis.tabs.eq( 0 ).attr( \"tabIndex\", 0 );\n\t\t} else {\n\t\t\tthis.active\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-selected\": \"true\",\n\t\t\t\t\t\"aria-expanded\": \"true\",\n\t\t\t\t\ttabIndex: 0\n\t\t\t\t} );\n\t\t\tthis._addClass( this.active, \"ui-tabs-active\", \"ui-state-active\" );\n\t\t\tthis._getPanelForTab( this.active )\n\t\t\t\t.show()\n\t\t\t\t.attr( {\n\t\t\t\t\t\"aria-hidden\": \"false\"\n\t\t\t\t} );\n\t\t}\n\t},\n\n\t_processTabs: function() {\n\t\tvar that = this,\n\t\t\tprevTabs = this.tabs,\n\t\t\tprevAnchors = this.anchors,\n\t\t\tprevPanels = this.panels;\n\n\t\tthis.tablist = this._getList().attr( \"role\", \"tablist\" );\n\t\tthis._addClass( this.tablist, \"ui-tabs-nav\",\n\t\t\t\"ui-helper-reset ui-helper-clearfix ui-widget-header\" );\n\n\t\t// Prevent users from focusing disabled tabs via click\n\t\tthis.tablist\n\t\t\t.on( \"mousedown\" + this.eventNamespace, \"> li\", function( event ) {\n\t\t\t\tif ( $( this ).is( \".ui-state-disabled\" ) ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t} )\n\n\t\t\t// Support: IE <9\n\t\t\t// Preventing the default action in mousedown doesn't prevent IE\n\t\t\t// from focusing the element, so if the anchor gets focused, blur.\n\t\t\t// We don't have to worry about focusing the previously focused\n\t\t\t// element since clicking on a non-focusable element should focus\n\t\t\t// the body anyway.\n\t\t\t.on( \"focus\" + this.eventNamespace, \".ui-tabs-anchor\", function() {\n\t\t\t\tif ( $( this ).closest( \"li\" ).is( \".ui-state-disabled\" ) ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t}\n\t\t\t} );\n\n\t\tthis.tabs = this.tablist.find( \"> li:has(a[href])\" )\n\t\t\t.attr( {\n\t\t\t\trole: \"tab\",\n\t\t\t\ttabIndex: -1\n\t\t\t} );\n\t\tthis._addClass( this.tabs, \"ui-tabs-tab\", \"ui-state-default\" );\n\n\t\tthis.anchors = this.tabs.map( function() {\n\t\t\treturn $( \"a\", this )[ 0 ];\n\t\t} )\n\t\t\t.attr( {\n\t\t\t\trole: \"presentation\",\n\t\t\t\ttabIndex: -1\n\t\t\t} );\n\t\tthis._addClass( this.anchors, \"ui-tabs-anchor\" );\n\n\t\tthis.panels = $();\n\n\t\tthis.anchors.each( function( i, anchor ) {\n\t\t\tvar selector, panel, panelId,\n\t\t\t\tanchorId = $( anchor ).uniqueId().attr( \"id\" ),\n\t\t\t\ttab = $( anchor ).closest( \"li\" ),\n\t\t\t\toriginalAriaControls = tab.attr( \"aria-controls\" );\n\n\t\t\t// Inline tab\n\t\t\tif ( that._isLocal( anchor ) ) {\n\t\t\t\tselector = anchor.hash;\n\t\t\t\tpanelId = selector.substring( 1 );\n\t\t\t\tpanel = that.element.find( that._sanitizeSelector( selector ) );\n\n\t\t\t// remote tab\n\t\t\t} else {\n\n\t\t\t\t// If the tab doesn't already have aria-controls,\n\t\t\t\t// generate an id by using a throw-away element\n\t\t\t\tpanelId = tab.attr( \"aria-controls\" ) || $( {} ).uniqueId()[ 0 ].id;\n\t\t\t\tselector = \"#\" + panelId;\n\t\t\t\tpanel = that.element.find( selector );\n\t\t\t\tif ( !panel.length ) {\n\t\t\t\t\tpanel = that._createPanel( panelId );\n\t\t\t\t\tpanel.insertAfter( that.panels[ i - 1 ] || that.tablist );\n\t\t\t\t}\n\t\t\t\tpanel.attr( \"aria-live\", \"polite\" );\n\t\t\t}\n\n\t\t\tif ( panel.length ) {\n\t\t\t\tthat.panels = that.panels.add( panel );\n\t\t\t}\n\t\t\tif ( originalAriaControls ) {\n\t\t\t\ttab.data( \"ui-tabs-aria-controls\", originalAriaControls );\n\t\t\t}\n\t\t\ttab.attr( {\n\t\t\t\t\"aria-controls\": panelId,\n\t\t\t\t\"aria-labelledby\": anchorId\n\t\t\t} );\n\t\t\tpanel.attr( \"aria-labelledby\", anchorId );\n\t\t} );\n\n\t\tthis.panels.attr( \"role\", \"tabpanel\" );\n\t\tthis._addClass( this.panels, \"ui-tabs-panel\", \"ui-widget-content\" );\n\n\t\t// Avoid memory leaks (#10056)\n\t\tif ( prevTabs ) {\n\t\t\tthis._off( prevTabs.not( this.tabs ) );\n\t\t\tthis._off( prevAnchors.not( this.anchors ) );\n\t\t\tthis._off( prevPanels.not( this.panels ) );\n\t\t}\n\t},\n\n\t// Allow overriding how to find the list for rare usage scenarios (#7715)\n\t_getList: function() {\n\t\treturn this.tablist || this.element.find( \"ol, ul\" ).eq( 0 );\n\t},\n\n\t_createPanel: function( id ) {\n\t\treturn $( \"<div>\" )\n\t\t\t.attr( \"id\", id )\n\t\t\t.data( \"ui-tabs-destroy\", true );\n\t},\n\n\t_setOptionDisabled: function( disabled ) {\n\t\tvar currentItem, li, i;\n\n\t\tif ( $.isArray( disabled ) ) {\n\t\t\tif ( !disabled.length ) {\n\t\t\t\tdisabled = false;\n\t\t\t} else if ( disabled.length === this.anchors.length ) {\n\t\t\t\tdisabled = true;\n\t\t\t}\n\t\t}\n\n\t\t// Disable tabs\n\t\tfor ( i = 0; ( li = this.tabs[ i ] ); i++ ) {\n\t\t\tcurrentItem = $( li );\n\t\t\tif ( disabled === true || $.inArray( i, disabled ) !== -1 ) {\n\t\t\t\tcurrentItem.attr( \"aria-disabled\", \"true\" );\n\t\t\t\tthis._addClass( currentItem, null, \"ui-state-disabled\" );\n\t\t\t} else {\n\t\t\t\tcurrentItem.removeAttr( \"aria-disabled\" );\n\t\t\t\tthis._removeClass( currentItem, null, \"ui-state-disabled\" );\n\t\t\t}\n\t\t}\n\n\t\tthis.options.disabled = disabled;\n\n\t\tthis._toggleClass( this.widget(), this.widgetFullName + \"-disabled\", null,\n\t\t\tdisabled === true );\n\t},\n\n\t_setupEvents: function( event ) {\n\t\tvar events = {};\n\t\tif ( event ) {\n\t\t\t$.each( event.split( \" \" ), function( index, eventName ) {\n\t\t\t\tevents[ eventName ] = \"_eventHandler\";\n\t\t\t} );\n\t\t}\n\n\t\tthis._off( this.anchors.add( this.tabs ).add( this.panels ) );\n\n\t\t// Always prevent the default action, even when disabled\n\t\tthis._on( true, this.anchors, {\n\t\t\tclick: function( event ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t} );\n\t\tthis._on( this.anchors, events );\n\t\tthis._on( this.tabs, { keydown: \"_tabKeydown\" } );\n\t\tthis._on( this.panels, { keydown: \"_panelKeydown\" } );\n\n\t\tthis._focusable( this.tabs );\n\t\tthis._hoverable( this.tabs );\n\t},\n\n\t_setupHeightStyle: function( heightStyle ) {\n\t\tvar maxHeight,\n\t\t\tparent = this.element.parent();\n\n\t\tif ( heightStyle === \"fill\" ) {\n\t\t\tmaxHeight = parent.height();\n\t\t\tmaxHeight -= this.element.outerHeight() - this.element.height();\n\n\t\t\tthis.element.siblings( \":visible\" ).each( function() {\n\t\t\t\tvar elem = $( this ),\n\t\t\t\t\tposition = elem.css( \"position\" );\n\n\t\t\t\tif ( position === \"absolute\" || position === \"fixed\" ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmaxHeight -= elem.outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.element.children().not( this.panels ).each( function() {\n\t\t\t\tmaxHeight -= $( this ).outerHeight( true );\n\t\t\t} );\n\n\t\t\tthis.panels.each( function() {\n\t\t\t\t$( this ).height( Math.max( 0, maxHeight -\n\t\t\t\t\t$( this ).innerHeight() + $( this ).height() ) );\n\t\t\t} )\n\t\t\t\t.css( \"overflow\", \"auto\" );\n\t\t} else if ( heightStyle === \"auto\" ) {\n\t\t\tmaxHeight = 0;\n\t\t\tthis.panels.each( function() {\n\t\t\t\tmaxHeight = Math.max( maxHeight, $( this ).height( \"\" ).height() );\n\t\t\t} ).height( maxHeight );\n\t\t}\n\t},\n\n\t_eventHandler: function( event ) {\n\t\tvar options = this.options,\n\t\t\tactive = this.active,\n\t\t\tanchor = $( event.currentTarget ),\n\t\t\ttab = anchor.closest( \"li\" ),\n\t\t\tclickedIsActive = tab[ 0 ] === active[ 0 ],\n\t\t\tcollapsing = clickedIsActive && options.collapsible,\n\t\t\ttoShow = collapsing ? $() : this._getPanelForTab( tab ),\n\t\t\ttoHide = !active.length ? $() : this._getPanelForTab( active ),\n\t\t\teventData = {\n\t\t\t\toldTab: active,\n\t\t\t\toldPanel: toHide,\n\t\t\t\tnewTab: collapsing ? $() : tab,\n\t\t\t\tnewPanel: toShow\n\t\t\t};\n\n\t\tevent.preventDefault();\n\n\t\tif ( tab.hasClass( \"ui-state-disabled\" ) ||\n\n\t\t\t\t// tab is already loading\n\t\t\t\ttab.hasClass( \"ui-tabs-loading\" ) ||\n\n\t\t\t\t// can't switch durning an animation\n\t\t\t\tthis.running ||\n\n\t\t\t\t// click on active header, but not collapsible\n\t\t\t\t( clickedIsActive && !options.collapsible ) ||\n\n\t\t\t\t// allow canceling activation\n\t\t\t\t( this._trigger( \"beforeActivate\", event, eventData ) === false ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\toptions.active = collapsing ? false : this.tabs.index( tab );\n\n\t\tthis.active = clickedIsActive ? $() : tab;\n\t\tif ( this.xhr ) {\n\t\t\tthis.xhr.abort();\n\t\t}\n\n\t\tif ( !toHide.length && !toShow.length ) {\n\t\t\t$.error( \"jQuery UI Tabs: Mismatching fragment identifier.\" );\n\t\t}\n\n\t\tif ( toShow.length ) {\n\t\t\tthis.load( this.tabs.index( tab ), event );\n\t\t}\n\t\tthis._toggle( event, eventData );\n\t},\n\n\t// Handles show/hide for selecting tabs\n\t_toggle: function( event, eventData ) {\n\t\tvar that = this,\n\t\t\ttoShow = eventData.newPanel,\n\t\t\ttoHide = eventData.oldPanel;\n\n\t\tthis.running = true;\n\n\t\tfunction complete() {\n\t\t\tthat.running = false;\n\t\t\tthat._trigger( \"activate\", event, eventData );\n\t\t}\n\n\t\tfunction show() {\n\t\t\tthat._addClass( eventData.newTab.closest( \"li\" ), \"ui-tabs-active\", \"ui-state-active\" );\n\n\t\t\tif ( toShow.length && that.options.show ) {\n\t\t\t\tthat._show( toShow, that.options.show, complete );\n\t\t\t} else {\n\t\t\t\ttoShow.show();\n\t\t\t\tcomplete();\n\t\t\t}\n\t\t}\n\n\t\t// Start out by hiding, then showing, then completing\n\t\tif ( toHide.length && this.options.hide ) {\n\t\t\tthis._hide( toHide, this.options.hide, function() {\n\t\t\t\tthat._removeClass( eventData.oldTab.closest( \"li\" ),\n\t\t\t\t\t\"ui-tabs-active\", \"ui-state-active\" );\n\t\t\t\tshow();\n\t\t\t} );\n\t\t} else {\n\t\t\tthis._removeClass( eventData.oldTab.closest( \"li\" ),\n\t\t\t\t\"ui-tabs-active\", \"ui-state-active\" );\n\t\t\ttoHide.hide();\n\t\t\tshow();\n\t\t}\n\n\t\ttoHide.attr( \"aria-hidden\", \"true\" );\n\t\teventData.oldTab.attr( {\n\t\t\t\"aria-selected\": \"false\",\n\t\t\t\"aria-expanded\": \"false\"\n\t\t} );\n\n\t\t// If we're switching tabs, remove the old tab from the tab order.\n\t\t// If we're opening from collapsed state, remove the previous tab from the tab order.\n\t\t// If we're collapsing, then keep the collapsing tab in the tab order.\n\t\tif ( toShow.length && toHide.length ) {\n\t\t\teventData.oldTab.attr( \"tabIndex\", -1 );\n\t\t} else if ( toShow.length ) {\n\t\t\tthis.tabs.filter( function() {\n\t\t\t\treturn $( this ).attr( \"tabIndex\" ) === 0;\n\t\t\t} )\n\t\t\t\t.attr( \"tabIndex\", -1 );\n\t\t}\n\n\t\ttoShow.attr( \"aria-hidden\", \"false\" );\n\t\teventData.newTab.attr( {\n\t\t\t\"aria-selected\": \"true\",\n\t\t\t\"aria-expanded\": \"true\",\n\t\t\ttabIndex: 0\n\t\t} );\n\t},\n\n\t_activate: function( index ) {\n\t\tvar anchor,\n\t\t\tactive = this._findActive( index );\n\n\t\t// Trying to activate the already active panel\n\t\tif ( active[ 0 ] === this.active[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Trying to collapse, simulate a click on the current active header\n\t\tif ( !active.length ) {\n\t\t\tactive = this.active;\n\t\t}\n\n\t\tanchor = active.find( \".ui-tabs-anchor\" )[ 0 ];\n\t\tthis._eventHandler( {\n\t\t\ttarget: anchor,\n\t\t\tcurrentTarget: anchor,\n\t\t\tpreventDefault: $.noop\n\t\t} );\n\t},\n\n\t_findActive: function( index ) {\n\t\treturn index === false ? $() : this.tabs.eq( index );\n\t},\n\n\t_getIndex: function( index ) {\n\n\t\t// meta-function to give users option to provide a href string instead of a numerical index.\n\t\tif ( typeof index === \"string\" ) {\n\t\t\tindex = this.anchors.index( this.anchors.filter( \"[href$='\" +\n\t\t\t\t$.ui.escapeSelector( index ) + \"']\" ) );\n\t\t}\n\n\t\treturn index;\n\t},\n\n\t_destroy: function() {\n\t\tif ( this.xhr ) {\n\t\t\tthis.xhr.abort();\n\t\t}\n\n\t\tthis.tablist\n\t\t\t.removeAttr( \"role\" )\n\t\t\t.off( this.eventNamespace );\n\n\t\tthis.anchors\n\t\t\t.removeAttr( \"role tabIndex\" )\n\t\t\t.removeUniqueId();\n\n\t\tthis.tabs.add( this.panels ).each( function() {\n\t\t\tif ( $.data( this, \"ui-tabs-destroy\" ) ) {\n\t\t\t\t$( this ).remove();\n\t\t\t} else {\n\t\t\t\t$( this ).removeAttr( \"role tabIndex \" +\n\t\t\t\t\t\"aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded\" );\n\t\t\t}\n\t\t} );\n\n\t\tthis.tabs.each( function() {\n\t\t\tvar li = $( this ),\n\t\t\t\tprev = li.data( \"ui-tabs-aria-controls\" );\n\t\t\tif ( prev ) {\n\t\t\t\tli\n\t\t\t\t\t.attr( \"aria-controls\", prev )\n\t\t\t\t\t.removeData( \"ui-tabs-aria-controls\" );\n\t\t\t} else {\n\t\t\t\tli.removeAttr( \"aria-controls\" );\n\t\t\t}\n\t\t} );\n\n\t\tthis.panels.show();\n\n\t\tif ( this.options.heightStyle !== \"content\" ) {\n\t\t\tthis.panels.css( \"height\", \"\" );\n\t\t}\n\t},\n\n\tenable: function( index ) {\n\t\tvar disabled = this.options.disabled;\n\t\tif ( disabled === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( index === undefined ) {\n\t\t\tdisabled = false;\n\t\t} else {\n\t\t\tindex = this._getIndex( index );\n\t\t\tif ( $.isArray( disabled ) ) {\n\t\t\t\tdisabled = $.map( disabled, function( num ) {\n\t\t\t\t\treturn num !== index ? num : null;\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tdisabled = $.map( this.tabs, function( li, num ) {\n\t\t\t\t\treturn num !== index ? num : null;\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\t\tthis._setOptionDisabled( disabled );\n\t},\n\n\tdisable: function( index ) {\n\t\tvar disabled = this.options.disabled;\n\t\tif ( disabled === true ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( index === undefined ) {\n\t\t\tdisabled = true;\n\t\t} else {\n\t\t\tindex = this._getIndex( index );\n\t\t\tif ( $.inArray( index, disabled ) !== -1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( $.isArray( disabled ) ) {\n\t\t\t\tdisabled = $.merge( [ index ], disabled ).sort();\n\t\t\t} else {\n\t\t\t\tdisabled = [ index ];\n\t\t\t}\n\t\t}\n\t\tthis._setOptionDisabled( disabled );\n\t},\n\n\tload: function( index, event ) {\n\t\tindex = this._getIndex( index );\n\t\tvar that = this,\n\t\t\ttab = this.tabs.eq( index ),\n\t\t\tanchor = tab.find( \".ui-tabs-anchor\" ),\n\t\t\tpanel = this._getPanelForTab( tab ),\n\t\t\teventData = {\n\t\t\t\ttab: tab,\n\t\t\t\tpanel: panel\n\t\t\t},\n\t\t\tcomplete = function( jqXHR, status ) {\n\t\t\t\tif ( status === \"abort\" ) {\n\t\t\t\t\tthat.panels.stop( false, true );\n\t\t\t\t}\n\n\t\t\t\tthat._removeClass( tab, \"ui-tabs-loading\" );\n\t\t\t\tpanel.removeAttr( \"aria-busy\" );\n\n\t\t\t\tif ( jqXHR === that.xhr ) {\n\t\t\t\t\tdelete that.xhr;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Not remote\n\t\tif ( this._isLocal( anchor[ 0 ] ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );\n\n\t\t// Support: jQuery <1.8\n\t\t// jQuery <1.8 returns false if the request is canceled in beforeSend,\n\t\t// but as of 1.8, $.ajax() always returns a jqXHR object.\n\t\tif ( this.xhr && this.xhr.statusText !== \"canceled\" ) {\n\t\t\tthis._addClass( tab, \"ui-tabs-loading\" );\n\t\t\tpanel.attr( \"aria-busy\", \"true\" );\n\n\t\t\tthis.xhr\n\t\t\t\t.done( function( response, status, jqXHR ) {\n\n\t\t\t\t\t// support: jQuery <1.8\n\t\t\t\t\t// http://bugs.jquery.com/ticket/11778\n\t\t\t\t\tsetTimeout( function() {\n\t\t\t\t\t\tpanel.html( response );\n\t\t\t\t\t\tthat._trigger( \"load\", event, eventData );\n\n\t\t\t\t\t\tcomplete( jqXHR, status );\n\t\t\t\t\t}, 1 );\n\t\t\t\t} )\n\t\t\t\t.fail( function( jqXHR, status ) {\n\n\t\t\t\t\t// support: jQuery <1.8\n\t\t\t\t\t// http://bugs.jquery.com/ticket/11778\n\t\t\t\t\tsetTimeout( function() {\n\t\t\t\t\t\tcomplete( jqXHR, status );\n\t\t\t\t\t}, 1 );\n\t\t\t\t} );\n\t\t}\n\t},\n\n\t_ajaxSettings: function( anchor, event, eventData ) {\n\t\tvar that = this;\n\t\treturn {\n\n\t\t\t// Support: IE <11 only\n\t\t\t// Strip any hash that exists to prevent errors with the Ajax request\n\t\t\turl: anchor.attr( \"href\" ).replace( /#.*$/, \"\" ),\n\t\t\tbeforeSend: function( jqXHR, settings ) {\n\t\t\t\treturn that._trigger( \"beforeLoad\", event,\n\t\t\t\t\t$.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );\n\t\t\t}\n\t\t};\n\t},\n\n\t_getPanelForTab: function( tab ) {\n\t\tvar id = $( tab ).attr( \"aria-controls\" );\n\t\treturn this.element.find( this._sanitizeSelector( \"#\" + id ) );\n\t}\n} );\n\n// DEPRECATED\n// TODO: Switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for ui-tab class (now ui-tabs-tab)\n\t$.widget( \"ui.tabs\", $.ui.tabs, {\n\t\t_processTabs: function() {\n\t\t\tthis._superApply( arguments );\n\t\t\tthis._addClass( this.tabs, \"ui-tab\" );\n\t\t}\n\t} );\n}\n\nvar widgetsTabs = $.ui.tabs;\n\n\n/*!\n * jQuery UI Tooltip 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Tooltip\n//>>group: Widgets\n//>>description: Shows additional information for any element on hover or focus.\n//>>docs: http://api.jqueryui.com/tooltip/\n//>>demos: http://jqueryui.com/tooltip/\n//>>css.structure: ../../themes/base/core.css\n//>>css.structure: ../../themes/base/tooltip.css\n//>>css.theme: ../../themes/base/theme.css\n\n\n\n$.widget( \"ui.tooltip\", {\n\tversion: \"1.12.1\",\n\toptions: {\n\t\tclasses: {\n\t\t\t\"ui-tooltip\": \"ui-corner-all ui-widget-shadow\"\n\t\t},\n\t\tcontent: function() {\n\n\t\t\t// support: IE<9, Opera in jQuery <1.7\n\t\t\t// .text() can't accept undefined, so coerce to a string\n\t\t\tvar title = $( this ).attr( \"title\" ) || \"\";\n\n\t\t\t// Escape title, since we're going from an attribute to raw HTML\n\t\t\treturn $( \"<a>\" ).text( title ).html();\n\t\t},\n\t\thide: true,\n\n\t\t// Disabled elements have inconsistent behavior across browsers (#8661)\n\t\titems: \"[title]:not([disabled])\",\n\t\tposition: {\n\t\t\tmy: \"left top+15\",\n\t\t\tat: \"left bottom\",\n\t\t\tcollision: \"flipfit flip\"\n\t\t},\n\t\tshow: true,\n\t\ttrack: false,\n\n\t\t// Callbacks\n\t\tclose: null,\n\t\topen: null\n\t},\n\n\t_addDescribedBy: function( elem, id ) {\n\t\tvar describedby = ( elem.attr( \"aria-describedby\" ) || \"\" ).split( /\\s+/ );\n\t\tdescribedby.push( id );\n\t\telem\n\t\t\t.data( \"ui-tooltip-id\", id )\n\t\t\t.attr( \"aria-describedby\", $.trim( describedby.join( \" \" ) ) );\n\t},\n\n\t_removeDescribedBy: function( elem ) {\n\t\tvar id = elem.data( \"ui-tooltip-id\" ),\n\t\t\tdescribedby = ( elem.attr( \"aria-describedby\" ) || \"\" ).split( /\\s+/ ),\n\t\t\tindex = $.inArray( id, describedby );\n\n\t\tif ( index !== -1 ) {\n\t\t\tdescribedby.splice( index, 1 );\n\t\t}\n\n\t\telem.removeData( \"ui-tooltip-id\" );\n\t\tdescribedby = $.trim( describedby.join( \" \" ) );\n\t\tif ( describedby ) {\n\t\t\telem.attr( \"aria-describedby\", describedby );\n\t\t} else {\n\t\t\telem.removeAttr( \"aria-describedby\" );\n\t\t}\n\t},\n\n\t_create: function() {\n\t\tthis._on( {\n\t\t\tmouseover: \"open\",\n\t\t\tfocusin: \"open\"\n\t\t} );\n\n\t\t// IDs of generated tooltips, needed for destroy\n\t\tthis.tooltips = {};\n\n\t\t// IDs of parent tooltips where we removed the title attribute\n\t\tthis.parents = {};\n\n\t\t// Append the aria-live region so tooltips announce correctly\n\t\tthis.liveRegion = $( \"<div>\" )\n\t\t\t.attr( {\n\t\t\t\trole: \"log\",\n\t\t\t\t\"aria-live\": \"assertive\",\n\t\t\t\t\"aria-relevant\": \"additions\"\n\t\t\t} )\n\t\t\t.appendTo( this.document[ 0 ].body );\n\t\tthis._addClass( this.liveRegion, null, \"ui-helper-hidden-accessible\" );\n\n\t\tthis.disabledTitles = $( [] );\n\t},\n\n\t_setOption: function( key, value ) {\n\t\tvar that = this;\n\n\t\tthis._super( key, value );\n\n\t\tif ( key === \"content\" ) {\n\t\t\t$.each( this.tooltips, function( id, tooltipData ) {\n\t\t\t\tthat._updateContent( tooltipData.element );\n\t\t\t} );\n\t\t}\n\t},\n\n\t_setOptionDisabled: function( value ) {\n\t\tthis[ value ? \"_disable\" : \"_enable\" ]();\n\t},\n\n\t_disable: function() {\n\t\tvar that = this;\n\n\t\t// Close open tooltips\n\t\t$.each( this.tooltips, function( id, tooltipData ) {\n\t\t\tvar event = $.Event( \"blur\" );\n\t\t\tevent.target = event.currentTarget = tooltipData.element[ 0 ];\n\t\t\tthat.close( event, true );\n\t\t} );\n\n\t\t// Remove title attributes to prevent native tooltips\n\t\tthis.disabledTitles = this.disabledTitles.add(\n\t\t\tthis.element.find( this.options.items ).addBack()\n\t\t\t\t.filter( function() {\n\t\t\t\t\tvar element = $( this );\n\t\t\t\t\tif ( element.is( \"[title]\" ) ) {\n\t\t\t\t\t\treturn element\n\t\t\t\t\t\t\t.data( \"ui-tooltip-title\", element.attr( \"title\" ) )\n\t\t\t\t\t\t\t.removeAttr( \"title\" );\n\t\t\t\t\t}\n\t\t\t\t} )\n\t\t);\n\t},\n\n\t_enable: function() {\n\n\t\t// restore title attributes\n\t\tthis.disabledTitles.each( function() {\n\t\t\tvar element = $( this );\n\t\t\tif ( element.data( \"ui-tooltip-title\" ) ) {\n\t\t\t\telement.attr( \"title\", element.data( \"ui-tooltip-title\" ) );\n\t\t\t}\n\t\t} );\n\t\tthis.disabledTitles = $( [] );\n\t},\n\n\topen: function( event ) {\n\t\tvar that = this,\n\t\t\ttarget = $( event ? event.target : this.element )\n\n\t\t\t\t// we need closest here due to mouseover bubbling,\n\t\t\t\t// but always pointing at the same event target\n\t\t\t\t.closest( this.options.items );\n\n\t\t// No element to show a tooltip for or the tooltip is already open\n\t\tif ( !target.length || target.data( \"ui-tooltip-id\" ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( target.attr( \"title\" ) ) {\n\t\t\ttarget.data( \"ui-tooltip-title\", target.attr( \"title\" ) );\n\t\t}\n\n\t\ttarget.data( \"ui-tooltip-open\", true );\n\n\t\t// Kill parent tooltips, custom or native, for hover\n\t\tif ( event && event.type === \"mouseover\" ) {\n\t\t\ttarget.parents().each( function() {\n\t\t\t\tvar parent = $( this ),\n\t\t\t\t\tblurEvent;\n\t\t\t\tif ( parent.data( \"ui-tooltip-open\" ) ) {\n\t\t\t\t\tblurEvent = $.Event( \"blur\" );\n\t\t\t\t\tblurEvent.target = blurEvent.currentTarget = this;\n\t\t\t\t\tthat.close( blurEvent, true );\n\t\t\t\t}\n\t\t\t\tif ( parent.attr( \"title\" ) ) {\n\t\t\t\t\tparent.uniqueId();\n\t\t\t\t\tthat.parents[ this.id ] = {\n\t\t\t\t\t\telement: this,\n\t\t\t\t\t\ttitle: parent.attr( \"title\" )\n\t\t\t\t\t};\n\t\t\t\t\tparent.attr( \"title\", \"\" );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\tthis._registerCloseHandlers( event, target );\n\t\tthis._updateContent( target, event );\n\t},\n\n\t_updateContent: function( target, event ) {\n\t\tvar content,\n\t\t\tcontentOption = this.options.content,\n\t\t\tthat = this,\n\t\t\teventType = event ? event.type : null;\n\n\t\tif ( typeof contentOption === \"string\" || contentOption.nodeType ||\n\t\t\t\tcontentOption.jquery ) {\n\t\t\treturn this._open( event, target, contentOption );\n\t\t}\n\n\t\tcontent = contentOption.call( target[ 0 ], function( response ) {\n\n\t\t\t// IE may instantly serve a cached response for ajax requests\n\t\t\t// delay this call to _open so the other call to _open runs first\n\t\t\tthat._delay( function() {\n\n\t\t\t\t// Ignore async response if tooltip was closed already\n\t\t\t\tif ( !target.data( \"ui-tooltip-open\" ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// JQuery creates a special event for focusin when it doesn't\n\t\t\t\t// exist natively. To improve performance, the native event\n\t\t\t\t// object is reused and the type is changed. Therefore, we can't\n\t\t\t\t// rely on the type being correct after the event finished\n\t\t\t\t// bubbling, so we set it back to the previous value. (#8740)\n\t\t\t\tif ( event ) {\n\t\t\t\t\tevent.type = eventType;\n\t\t\t\t}\n\t\t\t\tthis._open( event, target, response );\n\t\t\t} );\n\t\t} );\n\t\tif ( content ) {\n\t\t\tthis._open( event, target, content );\n\t\t}\n\t},\n\n\t_open: function( event, target, content ) {\n\t\tvar tooltipData, tooltip, delayedShow, a11yContent,\n\t\t\tpositionOption = $.extend( {}, this.options.position );\n\n\t\tif ( !content ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Content can be updated multiple times. If the tooltip already\n\t\t// exists, then just update the content and bail.\n\t\ttooltipData = this._find( target );\n\t\tif ( tooltipData ) {\n\t\t\ttooltipData.tooltip.find( \".ui-tooltip-content\" ).html( content );\n\t\t\treturn;\n\t\t}\n\n\t\t// If we have a title, clear it to prevent the native tooltip\n\t\t// we have to check first to avoid defining a title if none exists\n\t\t// (we don't want to cause an element to start matching [title])\n\t\t//\n\t\t// We use removeAttr only for key events, to allow IE to export the correct\n\t\t// accessible attributes. For mouse events, set to empty string to avoid\n\t\t// native tooltip showing up (happens only when removing inside mouseover).\n\t\tif ( target.is( \"[title]\" ) ) {\n\t\t\tif ( event && event.type === \"mouseover\" ) {\n\t\t\t\ttarget.attr( \"title\", \"\" );\n\t\t\t} else {\n\t\t\t\ttarget.removeAttr( \"title\" );\n\t\t\t}\n\t\t}\n\n\t\ttooltipData = this._tooltip( target );\n\t\ttooltip = tooltipData.tooltip;\n\t\tthis._addDescribedBy( target, tooltip.attr( \"id\" ) );\n\t\ttooltip.find( \".ui-tooltip-content\" ).html( content );\n\n\t\t// Support: Voiceover on OS X, JAWS on IE <= 9\n\t\t// JAWS announces deletions even when aria-relevant=\"additions\"\n\t\t// Voiceover will sometimes re-read the entire log region's contents from the beginning\n\t\tthis.liveRegion.children().hide();\n\t\ta11yContent = $( \"<div>\" ).html( tooltip.find( \".ui-tooltip-content\" ).html() );\n\t\ta11yContent.removeAttr( \"name\" ).find( \"[name]\" ).removeAttr( \"name\" );\n\t\ta11yContent.removeAttr( \"id\" ).find( \"[id]\" ).removeAttr( \"id\" );\n\t\ta11yContent.appendTo( this.liveRegion );\n\n\t\tfunction position( event ) {\n\t\t\tpositionOption.of = event;\n\t\t\tif ( tooltip.is( \":hidden\" ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttooltip.position( positionOption );\n\t\t}\n\t\tif ( this.options.track && event && /^mouse/.test( event.type ) ) {\n\t\t\tthis._on( this.document, {\n\t\t\t\tmousemove: position\n\t\t\t} );\n\n\t\t\t// trigger once to override element-relative positioning\n\t\t\tposition( event );\n\t\t} else {\n\t\t\ttooltip.position( $.extend( {\n\t\t\t\tof: target\n\t\t\t}, this.options.position ) );\n\t\t}\n\n\t\ttooltip.hide();\n\n\t\tthis._show( tooltip, this.options.show );\n\n\t\t// Handle tracking tooltips that are shown with a delay (#8644). As soon\n\t\t// as the tooltip is visible, position the tooltip using the most recent\n\t\t// event.\n\t\t// Adds the check to add the timers only when both delay and track options are set (#14682)\n\t\tif ( this.options.track && this.options.show && this.options.show.delay ) {\n\t\t\tdelayedShow = this.delayedShow = setInterval( function() {\n\t\t\t\tif ( tooltip.is( \":visible\" ) ) {\n\t\t\t\t\tposition( positionOption.of );\n\t\t\t\t\tclearInterval( delayedShow );\n\t\t\t\t}\n\t\t\t}, $.fx.interval );\n\t\t}\n\n\t\tthis._trigger( \"open\", event, { tooltip: tooltip } );\n\t},\n\n\t_registerCloseHandlers: function( event, target ) {\n\t\tvar events = {\n\t\t\tkeyup: function( event ) {\n\t\t\t\tif ( event.keyCode === $.ui.keyCode.ESCAPE ) {\n\t\t\t\t\tvar fakeEvent = $.Event( event );\n\t\t\t\t\tfakeEvent.currentTarget = target[ 0 ];\n\t\t\t\t\tthis.close( fakeEvent, true );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\t// Only bind remove handler for delegated targets. Non-delegated\n\t\t// tooltips will handle this in destroy.\n\t\tif ( target[ 0 ] !== this.element[ 0 ] ) {\n\t\t\tevents.remove = function() {\n\t\t\t\tthis._removeTooltip( this._find( target ).tooltip );\n\t\t\t};\n\t\t}\n\n\t\tif ( !event || event.type === \"mouseover\" ) {\n\t\t\tevents.mouseleave = \"close\";\n\t\t}\n\t\tif ( !event || event.type === \"focusin\" ) {\n\t\t\tevents.focusout = \"close\";\n\t\t}\n\t\tthis._on( true, target, events );\n\t},\n\n\tclose: function( event ) {\n\t\tvar tooltip,\n\t\t\tthat = this,\n\t\t\ttarget = $( event ? event.currentTarget : this.element ),\n\t\t\ttooltipData = this._find( target );\n\n\t\t// The tooltip may already be closed\n\t\tif ( !tooltipData ) {\n\n\t\t\t// We set ui-tooltip-open immediately upon open (in open()), but only set the\n\t\t\t// additional data once there's actually content to show (in _open()). So even if the\n\t\t\t// tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in\n\t\t\t// the period between open() and _open().\n\t\t\ttarget.removeData( \"ui-tooltip-open\" );\n\t\t\treturn;\n\t\t}\n\n\t\ttooltip = tooltipData.tooltip;\n\n\t\t// Disabling closes the tooltip, so we need to track when we're closing\n\t\t// to avoid an infinite loop in case the tooltip becomes disabled on close\n\t\tif ( tooltipData.closing ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clear the interval for delayed tracking tooltips\n\t\tclearInterval( this.delayedShow );\n\n\t\t// Only set title if we had one before (see comment in _open())\n\t\t// If the title attribute has changed since open(), don't restore\n\t\tif ( target.data( \"ui-tooltip-title\" ) && !target.attr( \"title\" ) ) {\n\t\t\ttarget.attr( \"title\", target.data( \"ui-tooltip-title\" ) );\n\t\t}\n\n\t\tthis._removeDescribedBy( target );\n\n\t\ttooltipData.hiding = true;\n\t\ttooltip.stop( true );\n\t\tthis._hide( tooltip, this.options.hide, function() {\n\t\t\tthat._removeTooltip( $( this ) );\n\t\t} );\n\n\t\ttarget.removeData( \"ui-tooltip-open\" );\n\t\tthis._off( target, \"mouseleave focusout keyup\" );\n\n\t\t// Remove 'remove' binding only on delegated targets\n\t\tif ( target[ 0 ] !== this.element[ 0 ] ) {\n\t\t\tthis._off( target, \"remove\" );\n\t\t}\n\t\tthis._off( this.document, \"mousemove\" );\n\n\t\tif ( event && event.type === \"mouseleave\" ) {\n\t\t\t$.each( this.parents, function( id, parent ) {\n\t\t\t\t$( parent.element ).attr( \"title\", parent.title );\n\t\t\t\tdelete that.parents[ id ];\n\t\t\t} );\n\t\t}\n\n\t\ttooltipData.closing = true;\n\t\tthis._trigger( \"close\", event, { tooltip: tooltip } );\n\t\tif ( !tooltipData.hiding ) {\n\t\t\ttooltipData.closing = false;\n\t\t}\n\t},\n\n\t_tooltip: function( element ) {\n\t\tvar tooltip = $( \"<div>\" ).attr( \"role\", \"tooltip\" ),\n\t\t\tcontent = $( \"<div>\" ).appendTo( tooltip ),\n\t\t\tid = tooltip.uniqueId().attr( \"id\" );\n\n\t\tthis._addClass( content, \"ui-tooltip-content\" );\n\t\tthis._addClass( tooltip, \"ui-tooltip\", \"ui-widget ui-widget-content\" );\n\n\t\ttooltip.appendTo( this._appendTo( element ) );\n\n\t\treturn this.tooltips[ id ] = {\n\t\t\telement: element,\n\t\t\ttooltip: tooltip\n\t\t};\n\t},\n\n\t_find: function( target ) {\n\t\tvar id = target.data( \"ui-tooltip-id\" );\n\t\treturn id ? this.tooltips[ id ] : null;\n\t},\n\n\t_removeTooltip: function( tooltip ) {\n\t\ttooltip.remove();\n\t\tdelete this.tooltips[ tooltip.attr( \"id\" ) ];\n\t},\n\n\t_appendTo: function( target ) {\n\t\tvar element = target.closest( \".ui-front, dialog\" );\n\n\t\tif ( !element.length ) {\n\t\t\telement = this.document[ 0 ].body;\n\t\t}\n\n\t\treturn element;\n\t},\n\n\t_destroy: function() {\n\t\tvar that = this;\n\n\t\t// Close open tooltips\n\t\t$.each( this.tooltips, function( id, tooltipData ) {\n\n\t\t\t// Delegate to close method to handle common cleanup\n\t\t\tvar event = $.Event( \"blur\" ),\n\t\t\t\telement = tooltipData.element;\n\t\t\tevent.target = event.currentTarget = element[ 0 ];\n\t\t\tthat.close( event, true );\n\n\t\t\t// Remove immediately; destroying an open tooltip doesn't use the\n\t\t\t// hide animation\n\t\t\t$( \"#\" + id ).remove();\n\n\t\t\t// Restore the title\n\t\t\tif ( element.data( \"ui-tooltip-title\" ) ) {\n\n\t\t\t\t// If the title attribute has changed since open(), don't restore\n\t\t\t\tif ( !element.attr( \"title\" ) ) {\n\t\t\t\t\telement.attr( \"title\", element.data( \"ui-tooltip-title\" ) );\n\t\t\t\t}\n\t\t\t\telement.removeData( \"ui-tooltip-title\" );\n\t\t\t}\n\t\t} );\n\t\tthis.liveRegion.remove();\n\t}\n} );\n\n// DEPRECATED\n// TODO: Switch return back to widget declaration at top of file when this is removed\nif ( $.uiBackCompat !== false ) {\n\n\t// Backcompat for tooltipClass option\n\t$.widget( \"ui.tooltip\", $.ui.tooltip, {\n\t\toptions: {\n\t\t\ttooltipClass: null\n\t\t},\n\t\t_tooltip: function() {\n\t\t\tvar tooltipData = this._superApply( arguments );\n\t\t\tif ( this.options.tooltipClass ) {\n\t\t\t\ttooltipData.tooltip.addClass( this.options.tooltipClass );\n\t\t\t}\n\t\t\treturn tooltipData;\n\t\t}\n\t} );\n}\n\nvar widgetsTooltip = $.ui.tooltip;\n\n\n/*!\n * jQuery UI Effects 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Effects Core\n//>>group: Effects\n// jscs:disable maximumLineLength\n//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/category/effects-core/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar dataSpace = \"ui-effects-\",\n\tdataSpaceStyle = \"ui-effects-style\",\n\tdataSpaceAnimated = \"ui-effects-animated\",\n\n\t// Create a local jQuery because jQuery Color relies on it and the\n\t// global may not exist with AMD and a custom build (#10199)\n\tjQuery = $;\n\n$.effects = {\n\teffect: {}\n};\n\n/*!\n * jQuery Color Animations v2.1.2\n * https://github.com/jquery/jquery-color\n *\n * Copyright 2014 jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * Date: Wed Jan 16 08:47:09 2013 -0600\n */\n( function( jQuery, undefined ) {\n\n\tvar stepHooks = \"backgroundColor borderBottomColor borderLeftColor borderRightColor \" +\n\t\t\"borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor\",\n\n\t// Plusequals test for += 100 -= 100\n\trplusequals = /^([\\-+])=\\s*(\\d+\\.?\\d*)/,\n\n\t// A set of RE's that can match strings and generate color tuples.\n\tstringParsers = [ {\n\t\t\tre: /rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*(?:,\\s*(\\d?(?:\\.\\d+)?)\\s*)?\\)/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\texecResult[ 1 ],\n\t\t\t\t\texecResult[ 2 ],\n\t\t\t\t\texecResult[ 3 ],\n\t\t\t\t\texecResult[ 4 ]\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\t\t\tre: /rgba?\\(\\s*(\\d+(?:\\.\\d+)?)\\%\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*(?:,\\s*(\\d?(?:\\.\\d+)?)\\s*)?\\)/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\texecResult[ 1 ] * 2.55,\n\t\t\t\t\texecResult[ 2 ] * 2.55,\n\t\t\t\t\texecResult[ 3 ] * 2.55,\n\t\t\t\t\texecResult[ 4 ]\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\n\t\t\t// This regex ignores A-F because it's compared against an already lowercased string\n\t\t\tre: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt( execResult[ 1 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 2 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 3 ], 16 )\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\n\t\t\t// This regex ignores A-F because it's compared against an already lowercased string\n\t\t\tre: /#([a-f0-9])([a-f0-9])([a-f0-9])/,\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),\n\t\t\t\t\tparseInt( execResult[ 3 ] + execResult[ 3 ], 16 )\n\t\t\t\t];\n\t\t\t}\n\t\t}, {\n\t\t\tre: /hsla?\\(\\s*(\\d+(?:\\.\\d+)?)\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*,\\s*(\\d+(?:\\.\\d+)?)\\%\\s*(?:,\\s*(\\d?(?:\\.\\d+)?)\\s*)?\\)/,\n\t\t\tspace: \"hsla\",\n\t\t\tparse: function( execResult ) {\n\t\t\t\treturn [\n\t\t\t\t\texecResult[ 1 ],\n\t\t\t\t\texecResult[ 2 ] / 100,\n\t\t\t\t\texecResult[ 3 ] / 100,\n\t\t\t\t\texecResult[ 4 ]\n\t\t\t\t];\n\t\t\t}\n\t\t} ],\n\n\t// JQuery.Color( )\n\tcolor = jQuery.Color = function( color, green, blue, alpha ) {\n\t\treturn new jQuery.Color.fn.parse( color, green, blue, alpha );\n\t},\n\tspaces = {\n\t\trgba: {\n\t\t\tprops: {\n\t\t\t\tred: {\n\t\t\t\t\tidx: 0,\n\t\t\t\t\ttype: \"byte\"\n\t\t\t\t},\n\t\t\t\tgreen: {\n\t\t\t\t\tidx: 1,\n\t\t\t\t\ttype: \"byte\"\n\t\t\t\t},\n\t\t\t\tblue: {\n\t\t\t\t\tidx: 2,\n\t\t\t\t\ttype: \"byte\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\thsla: {\n\t\t\tprops: {\n\t\t\t\thue: {\n\t\t\t\t\tidx: 0,\n\t\t\t\t\ttype: \"degrees\"\n\t\t\t\t},\n\t\t\t\tsaturation: {\n\t\t\t\t\tidx: 1,\n\t\t\t\t\ttype: \"percent\"\n\t\t\t\t},\n\t\t\t\tlightness: {\n\t\t\t\t\tidx: 2,\n\t\t\t\t\ttype: \"percent\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\tpropTypes = {\n\t\t\"byte\": {\n\t\t\tfloor: true,\n\t\t\tmax: 255\n\t\t},\n\t\t\"percent\": {\n\t\t\tmax: 1\n\t\t},\n\t\t\"degrees\": {\n\t\t\tmod: 360,\n\t\t\tfloor: true\n\t\t}\n\t},\n\tsupport = color.support = {},\n\n\t// Element for support tests\n\tsupportElem = jQuery( \"<p>\" )[ 0 ],\n\n\t// Colors = jQuery.Color.names\n\tcolors,\n\n\t// Local aliases of functions called often\n\teach = jQuery.each;\n\n// Determine rgba support immediately\nsupportElem.style.cssText = \"background-color:rgba(1,1,1,.5)\";\nsupport.rgba = supportElem.style.backgroundColor.indexOf( \"rgba\" ) > -1;\n\n// Define cache name and alpha properties\n// for rgba and hsla spaces\neach( spaces, function( spaceName, space ) {\n\tspace.cache = \"_\" + spaceName;\n\tspace.props.alpha = {\n\t\tidx: 3,\n\t\ttype: \"percent\",\n\t\tdef: 1\n\t};\n} );\n\nfunction clamp( value, prop, allowEmpty ) {\n\tvar type = propTypes[ prop.type ] || {};\n\n\tif ( value == null ) {\n\t\treturn ( allowEmpty || !prop.def ) ? null : prop.def;\n\t}\n\n\t// ~~ is an short way of doing floor for positive numbers\n\tvalue = type.floor ? ~~value : parseFloat( value );\n\n\t// IE will pass in empty strings as value for alpha,\n\t// which will hit this case\n\tif ( isNaN( value ) ) {\n\t\treturn prop.def;\n\t}\n\n\tif ( type.mod ) {\n\n\t\t// We add mod before modding to make sure that negatives values\n\t\t// get converted properly: -10 -> 350\n\t\treturn ( value + type.mod ) % type.mod;\n\t}\n\n\t// For now all property types without mod have min and max\n\treturn 0 > value ? 0 : type.max < value ? type.max : value;\n}\n\nfunction stringParse( string ) {\n\tvar inst = color(),\n\t\trgba = inst._rgba = [];\n\n\tstring = string.toLowerCase();\n\n\teach( stringParsers, function( i, parser ) {\n\t\tvar parsed,\n\t\t\tmatch = parser.re.exec( string ),\n\t\t\tvalues = match && parser.parse( match ),\n\t\t\tspaceName = parser.space || \"rgba\";\n\n\t\tif ( values ) {\n\t\t\tparsed = inst[ spaceName ]( values );\n\n\t\t\t// If this was an rgba parse the assignment might happen twice\n\t\t\t// oh well....\n\t\t\tinst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];\n\t\t\trgba = inst._rgba = parsed._rgba;\n\n\t\t\t// Exit each( stringParsers ) here because we matched\n\t\t\treturn false;\n\t\t}\n\t} );\n\n\t// Found a stringParser that handled it\n\tif ( rgba.length ) {\n\n\t\t// If this came from a parsed string, force \"transparent\" when alpha is 0\n\t\t// chrome, (and maybe others) return \"transparent\" as rgba(0,0,0,0)\n\t\tif ( rgba.join() === \"0,0,0,0\" ) {\n\t\t\tjQuery.extend( rgba, colors.transparent );\n\t\t}\n\t\treturn inst;\n\t}\n\n\t// Named colors\n\treturn colors[ string ];\n}\n\ncolor.fn = jQuery.extend( color.prototype, {\n\tparse: function( red, green, blue, alpha ) {\n\t\tif ( red === undefined ) {\n\t\t\tthis._rgba = [ null, null, null, null ];\n\t\t\treturn this;\n\t\t}\n\t\tif ( red.jquery || red.nodeType ) {\n\t\t\tred = jQuery( red ).css( green );\n\t\t\tgreen = undefined;\n\t\t}\n\n\t\tvar inst = this,\n\t\t\ttype = jQuery.type( red ),\n\t\t\trgba = this._rgba = [];\n\n\t\t// More than 1 argument specified - assume ( red, green, blue, alpha )\n\t\tif ( green !== undefined ) {\n\t\t\tred = [ red, green, blue, alpha ];\n\t\t\ttype = \"array\";\n\t\t}\n\n\t\tif ( type === \"string\" ) {\n\t\t\treturn this.parse( stringParse( red ) || colors._default );\n\t\t}\n\n\t\tif ( type === \"array\" ) {\n\t\t\teach( spaces.rgba.props, function( key, prop ) {\n\t\t\t\trgba[ prop.idx ] = clamp( red[ prop.idx ], prop );\n\t\t\t} );\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( type === \"object\" ) {\n\t\t\tif ( red instanceof color ) {\n\t\t\t\teach( spaces, function( spaceName, space ) {\n\t\t\t\t\tif ( red[ space.cache ] ) {\n\t\t\t\t\t\tinst[ space.cache ] = red[ space.cache ].slice();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\teach( spaces, function( spaceName, space ) {\n\t\t\t\t\tvar cache = space.cache;\n\t\t\t\t\teach( space.props, function( key, prop ) {\n\n\t\t\t\t\t\t// If the cache doesn't exist, and we know how to convert\n\t\t\t\t\t\tif ( !inst[ cache ] && space.to ) {\n\n\t\t\t\t\t\t\t// If the value was null, we don't need to copy it\n\t\t\t\t\t\t\t// if the key was alpha, we don't need to copy it either\n\t\t\t\t\t\t\tif ( key === \"alpha\" || red[ key ] == null ) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tinst[ cache ] = space.to( inst._rgba );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// This is the only case where we allow nulls for ALL properties.\n\t\t\t\t\t\t// call clamp with alwaysAllowEmpty\n\t\t\t\t\t\tinst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );\n\t\t\t\t\t} );\n\n\t\t\t\t\t// Everything defined but alpha?\n\t\t\t\t\tif ( inst[ cache ] &&\n\t\t\t\t\t\t\tjQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {\n\n\t\t\t\t\t\t// Use the default of 1\n\t\t\t\t\t\tinst[ cache ][ 3 ] = 1;\n\t\t\t\t\t\tif ( space.from ) {\n\t\t\t\t\t\t\tinst._rgba = space.from( inst[ cache ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t},\n\tis: function( compare ) {\n\t\tvar is = color( compare ),\n\t\t\tsame = true,\n\t\t\tinst = this;\n\n\t\teach( spaces, function( _, space ) {\n\t\t\tvar localCache,\n\t\t\t\tisCache = is[ space.cache ];\n\t\t\tif ( isCache ) {\n\t\t\t\tlocalCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];\n\t\t\t\teach( space.props, function( _, prop ) {\n\t\t\t\t\tif ( isCache[ prop.idx ] != null ) {\n\t\t\t\t\t\tsame = ( isCache[ prop.idx ] === localCache[ prop.idx ] );\n\t\t\t\t\t\treturn same;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn same;\n\t\t} );\n\t\treturn same;\n\t},\n\t_space: function() {\n\t\tvar used = [],\n\t\t\tinst = this;\n\t\teach( spaces, function( spaceName, space ) {\n\t\t\tif ( inst[ space.cache ] ) {\n\t\t\t\tused.push( spaceName );\n\t\t\t}\n\t\t} );\n\t\treturn used.pop();\n\t},\n\ttransition: function( other, distance ) {\n\t\tvar end = color( other ),\n\t\t\tspaceName = end._space(),\n\t\t\tspace = spaces[ spaceName ],\n\t\t\tstartColor = this.alpha() === 0 ? color( \"transparent\" ) : this,\n\t\t\tstart = startColor[ space.cache ] || space.to( startColor._rgba ),\n\t\t\tresult = start.slice();\n\n\t\tend = end[ space.cache ];\n\t\teach( space.props, function( key, prop ) {\n\t\t\tvar index = prop.idx,\n\t\t\t\tstartValue = start[ index ],\n\t\t\t\tendValue = end[ index ],\n\t\t\t\ttype = propTypes[ prop.type ] || {};\n\n\t\t\t// If null, don't override start value\n\t\t\tif ( endValue === null ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If null - use end\n\t\t\tif ( startValue === null ) {\n\t\t\t\tresult[ index ] = endValue;\n\t\t\t} else {\n\t\t\t\tif ( type.mod ) {\n\t\t\t\t\tif ( endValue - startValue > type.mod / 2 ) {\n\t\t\t\t\t\tstartValue += type.mod;\n\t\t\t\t\t} else if ( startValue - endValue > type.mod / 2 ) {\n\t\t\t\t\t\tstartValue -= type.mod;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresult[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );\n\t\t\t}\n\t\t} );\n\t\treturn this[ spaceName ]( result );\n\t},\n\tblend: function( opaque ) {\n\n\t\t// If we are already opaque - return ourself\n\t\tif ( this._rgba[ 3 ] === 1 ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tvar rgb = this._rgba.slice(),\n\t\t\ta = rgb.pop(),\n\t\t\tblend = color( opaque )._rgba;\n\n\t\treturn color( jQuery.map( rgb, function( v, i ) {\n\t\t\treturn ( 1 - a ) * blend[ i ] + a * v;\n\t\t} ) );\n\t},\n\ttoRgbaString: function() {\n\t\tvar prefix = \"rgba(\",\n\t\t\trgba = jQuery.map( this._rgba, function( v, i ) {\n\t\t\t\treturn v == null ? ( i > 2 ? 1 : 0 ) : v;\n\t\t\t} );\n\n\t\tif ( rgba[ 3 ] === 1 ) {\n\t\t\trgba.pop();\n\t\t\tprefix = \"rgb(\";\n\t\t}\n\n\t\treturn prefix + rgba.join() + \")\";\n\t},\n\ttoHslaString: function() {\n\t\tvar prefix = \"hsla(\",\n\t\t\thsla = jQuery.map( this.hsla(), function( v, i ) {\n\t\t\t\tif ( v == null ) {\n\t\t\t\t\tv = i > 2 ? 1 : 0;\n\t\t\t\t}\n\n\t\t\t\t// Catch 1 and 2\n\t\t\t\tif ( i && i < 3 ) {\n\t\t\t\t\tv = Math.round( v * 100 ) + \"%\";\n\t\t\t\t}\n\t\t\t\treturn v;\n\t\t\t} );\n\n\t\tif ( hsla[ 3 ] === 1 ) {\n\t\t\thsla.pop();\n\t\t\tprefix = \"hsl(\";\n\t\t}\n\t\treturn prefix + hsla.join() + \")\";\n\t},\n\ttoHexString: function( includeAlpha ) {\n\t\tvar rgba = this._rgba.slice(),\n\t\t\talpha = rgba.pop();\n\n\t\tif ( includeAlpha ) {\n\t\t\trgba.push( ~~( alpha * 255 ) );\n\t\t}\n\n\t\treturn \"#\" + jQuery.map( rgba, function( v ) {\n\n\t\t\t// Default to 0 when nulls exist\n\t\t\tv = ( v || 0 ).toString( 16 );\n\t\t\treturn v.length === 1 ? \"0\" + v : v;\n\t\t} ).join( \"\" );\n\t},\n\ttoString: function() {\n\t\treturn this._rgba[ 3 ] === 0 ? \"transparent\" : this.toRgbaString();\n\t}\n} );\ncolor.fn.parse.prototype = color.fn;\n\n// Hsla conversions adapted from:\n// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021\n\nfunction hue2rgb( p, q, h ) {\n\th = ( h + 1 ) % 1;\n\tif ( h * 6 < 1 ) {\n\t\treturn p + ( q - p ) * h * 6;\n\t}\n\tif ( h * 2 < 1 ) {\n\t\treturn q;\n\t}\n\tif ( h * 3 < 2 ) {\n\t\treturn p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6;\n\t}\n\treturn p;\n}\n\nspaces.hsla.to = function( rgba ) {\n\tif ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {\n\t\treturn [ null, null, null, rgba[ 3 ] ];\n\t}\n\tvar r = rgba[ 0 ] / 255,\n\t\tg = rgba[ 1 ] / 255,\n\t\tb = rgba[ 2 ] / 255,\n\t\ta = rgba[ 3 ],\n\t\tmax = Math.max( r, g, b ),\n\t\tmin = Math.min( r, g, b ),\n\t\tdiff = max - min,\n\t\tadd = max + min,\n\t\tl = add * 0.5,\n\t\th, s;\n\n\tif ( min === max ) {\n\t\th = 0;\n\t} else if ( r === max ) {\n\t\th = ( 60 * ( g - b ) / diff ) + 360;\n\t} else if ( g === max ) {\n\t\th = ( 60 * ( b - r ) / diff ) + 120;\n\t} else {\n\t\th = ( 60 * ( r - g ) / diff ) + 240;\n\t}\n\n\t// Chroma (diff) == 0 means greyscale which, by definition, saturation = 0%\n\t// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)\n\tif ( diff === 0 ) {\n\t\ts = 0;\n\t} else if ( l <= 0.5 ) {\n\t\ts = diff / add;\n\t} else {\n\t\ts = diff / ( 2 - add );\n\t}\n\treturn [ Math.round( h ) % 360, s, l, a == null ? 1 : a ];\n};\n\nspaces.hsla.from = function( hsla ) {\n\tif ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {\n\t\treturn [ null, null, null, hsla[ 3 ] ];\n\t}\n\tvar h = hsla[ 0 ] / 360,\n\t\ts = hsla[ 1 ],\n\t\tl = hsla[ 2 ],\n\t\ta = hsla[ 3 ],\n\t\tq = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,\n\t\tp = 2 * l - q;\n\n\treturn [\n\t\tMath.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),\n\t\tMath.round( hue2rgb( p, q, h ) * 255 ),\n\t\tMath.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),\n\t\ta\n\t];\n};\n\neach( spaces, function( spaceName, space ) {\n\tvar props = space.props,\n\t\tcache = space.cache,\n\t\tto = space.to,\n\t\tfrom = space.from;\n\n\t// Makes rgba() and hsla()\n\tcolor.fn[ spaceName ] = function( value ) {\n\n\t\t// Generate a cache for this space if it doesn't exist\n\t\tif ( to && !this[ cache ] ) {\n\t\t\tthis[ cache ] = to( this._rgba );\n\t\t}\n\t\tif ( value === undefined ) {\n\t\t\treturn this[ cache ].slice();\n\t\t}\n\n\t\tvar ret,\n\t\t\ttype = jQuery.type( value ),\n\t\t\tarr = ( type === \"array\" || type === \"object\" ) ? value : arguments,\n\t\t\tlocal = this[ cache ].slice();\n\n\t\teach( props, function( key, prop ) {\n\t\t\tvar val = arr[ type === \"object\" ? key : prop.idx ];\n\t\t\tif ( val == null ) {\n\t\t\t\tval = local[ prop.idx ];\n\t\t\t}\n\t\t\tlocal[ prop.idx ] = clamp( val, prop );\n\t\t} );\n\n\t\tif ( from ) {\n\t\t\tret = color( from( local ) );\n\t\t\tret[ cache ] = local;\n\t\t\treturn ret;\n\t\t} else {\n\t\t\treturn color( local );\n\t\t}\n\t};\n\n\t// Makes red() green() blue() alpha() hue() saturation() lightness()\n\teach( props, function( key, prop ) {\n\n\t\t// Alpha is included in more than one space\n\t\tif ( color.fn[ key ] ) {\n\t\t\treturn;\n\t\t}\n\t\tcolor.fn[ key ] = function( value ) {\n\t\t\tvar vtype = jQuery.type( value ),\n\t\t\t\tfn = ( key === \"alpha\" ? ( this._hsla ? \"hsla\" : \"rgba\" ) : spaceName ),\n\t\t\t\tlocal = this[ fn ](),\n\t\t\t\tcur = local[ prop.idx ],\n\t\t\t\tmatch;\n\n\t\t\tif ( vtype === \"undefined\" ) {\n\t\t\t\treturn cur;\n\t\t\t}\n\n\t\t\tif ( vtype === \"function\" ) {\n\t\t\t\tvalue = value.call( this, cur );\n\t\t\t\tvtype = jQuery.type( value );\n\t\t\t}\n\t\t\tif ( value == null && prop.empty ) {\n\t\t\t\treturn this;\n\t\t\t}\n\t\t\tif ( vtype === \"string\" ) {\n\t\t\t\tmatch = rplusequals.exec( value );\n\t\t\t\tif ( match ) {\n\t\t\t\t\tvalue = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === \"+\" ? 1 : -1 );\n\t\t\t\t}\n\t\t\t}\n\t\t\tlocal[ prop.idx ] = value;\n\t\t\treturn this[ fn ]( local );\n\t\t};\n\t} );\n} );\n\n// Add cssHook and .fx.step function for each named hook.\n// accept a space separated string of properties\ncolor.hook = function( hook ) {\n\tvar hooks = hook.split( \" \" );\n\teach( hooks, function( i, hook ) {\n\t\tjQuery.cssHooks[ hook ] = {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar parsed, curElem,\n\t\t\t\t\tbackgroundColor = \"\";\n\n\t\t\t\tif ( value !== \"transparent\" && ( jQuery.type( value ) !== \"string\" ||\n\t\t\t\t\t\t( parsed = stringParse( value ) ) ) ) {\n\t\t\t\t\tvalue = color( parsed || value );\n\t\t\t\t\tif ( !support.rgba && value._rgba[ 3 ] !== 1 ) {\n\t\t\t\t\t\tcurElem = hook === \"backgroundColor\" ? elem.parentNode : elem;\n\t\t\t\t\t\twhile (\n\t\t\t\t\t\t\t( backgroundColor === \"\" || backgroundColor === \"transparent\" ) &&\n\t\t\t\t\t\t\tcurElem && curElem.style\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tbackgroundColor = jQuery.css( curElem, \"backgroundColor\" );\n\t\t\t\t\t\t\t\tcurElem = curElem.parentNode;\n\t\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvalue = value.blend( backgroundColor && backgroundColor !== \"transparent\" ?\n\t\t\t\t\t\t\tbackgroundColor :\n\t\t\t\t\t\t\t\"_default\" );\n\t\t\t\t\t}\n\n\t\t\t\t\tvalue = value.toRgbaString();\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\telem.style[ hook ] = value;\n\t\t\t\t} catch ( e ) {\n\n\t\t\t\t\t// Wrapped to prevent IE from throwing errors on \"invalid\" values like\n\t\t\t\t\t// 'auto' or 'inherit'\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tjQuery.fx.step[ hook ] = function( fx ) {\n\t\t\tif ( !fx.colorInit ) {\n\t\t\t\tfx.start = color( fx.elem, hook );\n\t\t\t\tfx.end = color( fx.end );\n\t\t\t\tfx.colorInit = true;\n\t\t\t}\n\t\t\tjQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );\n\t\t};\n\t} );\n\n};\n\ncolor.hook( stepHooks );\n\njQuery.cssHooks.borderColor = {\n\texpand: function( value ) {\n\t\tvar expanded = {};\n\n\t\teach( [ \"Top\", \"Right\", \"Bottom\", \"Left\" ], function( i, part ) {\n\t\t\texpanded[ \"border\" + part + \"Color\" ] = value;\n\t\t} );\n\t\treturn expanded;\n\t}\n};\n\n// Basic color names only.\n// Usage of any of the other color names requires adding yourself or including\n// jquery.color.svg-names.js.\ncolors = jQuery.Color.names = {\n\n\t// 4.1. Basic color keywords\n\taqua: \"#00ffff\",\n\tblack: \"#000000\",\n\tblue: \"#0000ff\",\n\tfuchsia: \"#ff00ff\",\n\tgray: \"#808080\",\n\tgreen: \"#008000\",\n\tlime: \"#00ff00\",\n\tmaroon: \"#800000\",\n\tnavy: \"#000080\",\n\tolive: \"#808000\",\n\tpurple: \"#800080\",\n\tred: \"#ff0000\",\n\tsilver: \"#c0c0c0\",\n\tteal: \"#008080\",\n\twhite: \"#ffffff\",\n\tyellow: \"#ffff00\",\n\n\t// 4.2.3. \"transparent\" color keyword\n\ttransparent: [ null, null, null, 0 ],\n\n\t_default: \"#ffffff\"\n};\n\n} )( jQuery );\n\n/******************************************************************************/\n/****************************** CLASS ANIMATIONS ******************************/\n/******************************************************************************/\n( function() {\n\nvar classAnimationActions = [ \"add\", \"remove\", \"toggle\" ],\n\tshorthandStyles = {\n\t\tborder: 1,\n\t\tborderBottom: 1,\n\t\tborderColor: 1,\n\t\tborderLeft: 1,\n\t\tborderRight: 1,\n\t\tborderTop: 1,\n\t\tborderWidth: 1,\n\t\tmargin: 1,\n\t\tpadding: 1\n\t};\n\n$.each(\n\t[ \"borderLeftStyle\", \"borderRightStyle\", \"borderBottomStyle\", \"borderTopStyle\" ],\n\tfunction( _, prop ) {\n\t\t$.fx.step[ prop ] = function( fx ) {\n\t\t\tif ( fx.end !== \"none\" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {\n\t\t\t\tjQuery.style( fx.elem, prop, fx.end );\n\t\t\t\tfx.setAttr = true;\n\t\t\t}\n\t\t};\n\t}\n);\n\nfunction getElementStyles( elem ) {\n\tvar key, len,\n\t\tstyle = elem.ownerDocument.defaultView ?\n\t\t\telem.ownerDocument.defaultView.getComputedStyle( elem, null ) :\n\t\t\telem.currentStyle,\n\t\tstyles = {};\n\n\tif ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {\n\t\tlen = style.length;\n\t\twhile ( len-- ) {\n\t\t\tkey = style[ len ];\n\t\t\tif ( typeof style[ key ] === \"string\" ) {\n\t\t\t\tstyles[ $.camelCase( key ) ] = style[ key ];\n\t\t\t}\n\t\t}\n\n\t// Support: Opera, IE <9\n\t} else {\n\t\tfor ( key in style ) {\n\t\t\tif ( typeof style[ key ] === \"string\" ) {\n\t\t\t\tstyles[ key ] = style[ key ];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn styles;\n}\n\nfunction styleDifference( oldStyle, newStyle ) {\n\tvar diff = {},\n\t\tname, value;\n\n\tfor ( name in newStyle ) {\n\t\tvalue = newStyle[ name ];\n\t\tif ( oldStyle[ name ] !== value ) {\n\t\t\tif ( !shorthandStyles[ name ] ) {\n\t\t\t\tif ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {\n\t\t\t\t\tdiff[ name ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn diff;\n}\n\n// Support: jQuery <1.8\nif ( !$.fn.addBack ) {\n\t$.fn.addBack = function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter( selector )\n\t\t);\n\t};\n}\n\n$.effects.animateClass = function( value, duration, easing, callback ) {\n\tvar o = $.speed( duration, easing, callback );\n\n\treturn this.queue( function() {\n\t\tvar animated = $( this ),\n\t\t\tbaseClass = animated.attr( \"class\" ) || \"\",\n\t\t\tapplyClassChange,\n\t\t\tallAnimations = o.children ? animated.find( \"*\" ).addBack() : animated;\n\n\t\t// Map the animated objects to store the original styles.\n\t\tallAnimations = allAnimations.map( function() {\n\t\t\tvar el = $( this );\n\t\t\treturn {\n\t\t\t\tel: el,\n\t\t\t\tstart: getElementStyles( this )\n\t\t\t};\n\t\t} );\n\n\t\t// Apply class change\n\t\tapplyClassChange = function() {\n\t\t\t$.each( classAnimationActions, function( i, action ) {\n\t\t\t\tif ( value[ action ] ) {\n\t\t\t\t\tanimated[ action + \"Class\" ]( value[ action ] );\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\t\tapplyClassChange();\n\n\t\t// Map all animated objects again - calculate new styles and diff\n\t\tallAnimations = allAnimations.map( function() {\n\t\t\tthis.end = getElementStyles( this.el[ 0 ] );\n\t\t\tthis.diff = styleDifference( this.start, this.end );\n\t\t\treturn this;\n\t\t} );\n\n\t\t// Apply original class\n\t\tanimated.attr( \"class\", baseClass );\n\n\t\t// Map all animated objects again - this time collecting a promise\n\t\tallAnimations = allAnimations.map( function() {\n\t\t\tvar styleInfo = this,\n\t\t\t\tdfd = $.Deferred(),\n\t\t\t\topts = $.extend( {}, o, {\n\t\t\t\t\tqueue: false,\n\t\t\t\t\tcomplete: function() {\n\t\t\t\t\t\tdfd.resolve( styleInfo );\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\tthis.el.animate( this.diff, opts );\n\t\t\treturn dfd.promise();\n\t\t} );\n\n\t\t// Once all animations have completed:\n\t\t$.when.apply( $, allAnimations.get() ).done( function() {\n\n\t\t\t// Set the final class\n\t\t\tapplyClassChange();\n\n\t\t\t// For each animated element,\n\t\t\t// clear all css properties that were animated\n\t\t\t$.each( arguments, function() {\n\t\t\t\tvar el = this.el;\n\t\t\t\t$.each( this.diff, function( key ) {\n\t\t\t\t\tel.css( key, \"\" );\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t\t// This is guarnteed to be there if you use jQuery.speed()\n\t\t\t// it also handles dequeuing the next anim...\n\t\t\to.complete.call( animated[ 0 ] );\n\t\t} );\n\t} );\n};\n\n$.fn.extend( {\n\taddClass: ( function( orig ) {\n\t\treturn function( classNames, speed, easing, callback ) {\n\t\t\treturn speed ?\n\t\t\t\t$.effects.animateClass.call( this,\n\t\t\t\t\t{ add: classNames }, speed, easing, callback ) :\n\t\t\t\torig.apply( this, arguments );\n\t\t};\n\t} )( $.fn.addClass ),\n\n\tremoveClass: ( function( orig ) {\n\t\treturn function( classNames, speed, easing, callback ) {\n\t\t\treturn arguments.length > 1 ?\n\t\t\t\t$.effects.animateClass.call( this,\n\t\t\t\t\t{ remove: classNames }, speed, easing, callback ) :\n\t\t\t\torig.apply( this, arguments );\n\t\t};\n\t} )( $.fn.removeClass ),\n\n\ttoggleClass: ( function( orig ) {\n\t\treturn function( classNames, force, speed, easing, callback ) {\n\t\t\tif ( typeof force === \"boolean\" || force === undefined ) {\n\t\t\t\tif ( !speed ) {\n\n\t\t\t\t\t// Without speed parameter\n\t\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t\t} else {\n\t\t\t\t\treturn $.effects.animateClass.call( this,\n\t\t\t\t\t\t( force ? { add: classNames } : { remove: classNames } ),\n\t\t\t\t\t\tspeed, easing, callback );\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\t// Without force parameter\n\t\t\t\treturn $.effects.animateClass.call( this,\n\t\t\t\t\t{ toggle: classNames }, force, speed, easing );\n\t\t\t}\n\t\t};\n\t} )( $.fn.toggleClass ),\n\n\tswitchClass: function( remove, add, speed, easing, callback ) {\n\t\treturn $.effects.animateClass.call( this, {\n\t\t\tadd: add,\n\t\t\tremove: remove\n\t\t}, speed, easing, callback );\n\t}\n} );\n\n} )();\n\n/******************************************************************************/\n/*********************************** EFFECTS **********************************/\n/******************************************************************************/\n\n( function() {\n\nif ( $.expr && $.expr.filters && $.expr.filters.animated ) {\n\t$.expr.filters.animated = ( function( orig ) {\n\t\treturn function( elem ) {\n\t\t\treturn !!$( elem ).data( dataSpaceAnimated ) || orig( elem );\n\t\t};\n\t} )( $.expr.filters.animated );\n}\n\nif ( $.uiBackCompat !== false ) {\n\t$.extend( $.effects, {\n\n\t\t// Saves a set of properties in a data storage\n\t\tsave: function( element, set ) {\n\t\t\tvar i = 0, length = set.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( set[ i ] !== null ) {\n\t\t\t\t\telement.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t// Restores a set of previously saved properties from a data storage\n\t\trestore: function( element, set ) {\n\t\t\tvar val, i = 0, length = set.length;\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( set[ i ] !== null ) {\n\t\t\t\t\tval = element.data( dataSpace + set[ i ] );\n\t\t\t\t\telement.css( set[ i ], val );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tsetMode: function( el, mode ) {\n\t\t\tif ( mode === \"toggle\" ) {\n\t\t\t\tmode = el.is( \":hidden\" ) ? \"show\" : \"hide\";\n\t\t\t}\n\t\t\treturn mode;\n\t\t},\n\n\t\t// Wraps the element around a wrapper that copies position properties\n\t\tcreateWrapper: function( element ) {\n\n\t\t\t// If the element is already wrapped, return it\n\t\t\tif ( element.parent().is( \".ui-effects-wrapper\" ) ) {\n\t\t\t\treturn element.parent();\n\t\t\t}\n\n\t\t\t// Wrap the element\n\t\t\tvar props = {\n\t\t\t\t\twidth: element.outerWidth( true ),\n\t\t\t\t\theight: element.outerHeight( true ),\n\t\t\t\t\t\"float\": element.css( \"float\" )\n\t\t\t\t},\n\t\t\t\twrapper = $( \"<div></div>\" )\n\t\t\t\t\t.addClass( \"ui-effects-wrapper\" )\n\t\t\t\t\t.css( {\n\t\t\t\t\t\tfontSize: \"100%\",\n\t\t\t\t\t\tbackground: \"transparent\",\n\t\t\t\t\t\tborder: \"none\",\n\t\t\t\t\t\tmargin: 0,\n\t\t\t\t\t\tpadding: 0\n\t\t\t\t\t} ),\n\n\t\t\t\t// Store the size in case width/height are defined in % - Fixes #5245\n\t\t\t\tsize = {\n\t\t\t\t\twidth: element.width(),\n\t\t\t\t\theight: element.height()\n\t\t\t\t},\n\t\t\t\tactive = document.activeElement;\n\n\t\t\t// Support: Firefox\n\t\t\t// Firefox incorrectly exposes anonymous content\n\t\t\t// https://bugzilla.mozilla.org/show_bug.cgi?id=561664\n\t\t\ttry {\n\t\t\t\tactive.id;\n\t\t\t} catch ( e ) {\n\t\t\t\tactive = document.body;\n\t\t\t}\n\n\t\t\telement.wrap( wrapper );\n\n\t\t\t// Fixes #7595 - Elements lose focus when wrapped.\n\t\t\tif ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {\n\t\t\t\t$( active ).trigger( \"focus\" );\n\t\t\t}\n\n\t\t\t// Hotfix for jQuery 1.4 since some change in wrap() seems to actually\n\t\t\t// lose the reference to the wrapped element\n\t\t\twrapper = element.parent();\n\n\t\t\t// Transfer positioning properties to the wrapper\n\t\t\tif ( element.css( \"position\" ) === \"static\" ) {\n\t\t\t\twrapper.css( { position: \"relative\" } );\n\t\t\t\telement.css( { position: \"relative\" } );\n\t\t\t} else {\n\t\t\t\t$.extend( props, {\n\t\t\t\t\tposition: element.css( \"position\" ),\n\t\t\t\t\tzIndex: element.css( \"z-index\" )\n\t\t\t\t} );\n\t\t\t\t$.each( [ \"top\", \"left\", \"bottom\", \"right\" ], function( i, pos ) {\n\t\t\t\t\tprops[ pos ] = element.css( pos );\n\t\t\t\t\tif ( isNaN( parseInt( props[ pos ], 10 ) ) ) {\n\t\t\t\t\t\tprops[ pos ] = \"auto\";\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\telement.css( {\n\t\t\t\t\tposition: \"relative\",\n\t\t\t\t\ttop: 0,\n\t\t\t\t\tleft: 0,\n\t\t\t\t\tright: \"auto\",\n\t\t\t\t\tbottom: \"auto\"\n\t\t\t\t} );\n\t\t\t}\n\t\t\telement.css( size );\n\n\t\t\treturn wrapper.css( props ).show();\n\t\t},\n\n\t\tremoveWrapper: function( element ) {\n\t\t\tvar active = document.activeElement;\n\n\t\t\tif ( element.parent().is( \".ui-effects-wrapper\" ) ) {\n\t\t\t\telement.parent().replaceWith( element );\n\n\t\t\t\t// Fixes #7595 - Elements lose focus when wrapped.\n\t\t\t\tif ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {\n\t\t\t\t\t$( active ).trigger( \"focus\" );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn element;\n\t\t}\n\t} );\n}\n\n$.extend( $.effects, {\n\tversion: \"1.12.1\",\n\n\tdefine: function( name, mode, effect ) {\n\t\tif ( !effect ) {\n\t\t\teffect = mode;\n\t\t\tmode = \"effect\";\n\t\t}\n\n\t\t$.effects.effect[ name ] = effect;\n\t\t$.effects.effect[ name ].mode = mode;\n\n\t\treturn effect;\n\t},\n\n\tscaledDimensions: function( element, percent, direction ) {\n\t\tif ( percent === 0 ) {\n\t\t\treturn {\n\t\t\t\theight: 0,\n\t\t\t\twidth: 0,\n\t\t\t\touterHeight: 0,\n\t\t\t\touterWidth: 0\n\t\t\t};\n\t\t}\n\n\t\tvar x = direction !== \"horizontal\" ? ( ( percent || 100 ) / 100 ) : 1,\n\t\t\ty = direction !== \"vertical\" ? ( ( percent || 100 ) / 100 ) : 1;\n\n\t\treturn {\n\t\t\theight: element.height() * y,\n\t\t\twidth: element.width() * x,\n\t\t\touterHeight: element.outerHeight() * y,\n\t\t\touterWidth: element.outerWidth() * x\n\t\t};\n\n\t},\n\n\tclipToBox: function( animation ) {\n\t\treturn {\n\t\t\twidth: animation.clip.right - animation.clip.left,\n\t\t\theight: animation.clip.bottom - animation.clip.top,\n\t\t\tleft: animation.clip.left,\n\t\t\ttop: animation.clip.top\n\t\t};\n\t},\n\n\t// Injects recently queued functions to be first in line (after \"inprogress\")\n\tunshift: function( element, queueLength, count ) {\n\t\tvar queue = element.queue();\n\n\t\tif ( queueLength > 1 ) {\n\t\t\tqueue.splice.apply( queue,\n\t\t\t\t[ 1, 0 ].concat( queue.splice( queueLength, count ) ) );\n\t\t}\n\t\telement.dequeue();\n\t},\n\n\tsaveStyle: function( element ) {\n\t\telement.data( dataSpaceStyle, element[ 0 ].style.cssText );\n\t},\n\n\trestoreStyle: function( element ) {\n\t\telement[ 0 ].style.cssText = element.data( dataSpaceStyle ) || \"\";\n\t\telement.removeData( dataSpaceStyle );\n\t},\n\n\tmode: function( element, mode ) {\n\t\tvar hidden = element.is( \":hidden\" );\n\n\t\tif ( mode === \"toggle\" ) {\n\t\t\tmode = hidden ? \"show\" : \"hide\";\n\t\t}\n\t\tif ( hidden ? mode === \"hide\" : mode === \"show\" ) {\n\t\t\tmode = \"none\";\n\t\t}\n\t\treturn mode;\n\t},\n\n\t// Translates a [top,left] array into a baseline value\n\tgetBaseline: function( origin, original ) {\n\t\tvar y, x;\n\n\t\tswitch ( origin[ 0 ] ) {\n\t\tcase \"top\":\n\t\t\ty = 0;\n\t\t\tbreak;\n\t\tcase \"middle\":\n\t\t\ty = 0.5;\n\t\t\tbreak;\n\t\tcase \"bottom\":\n\t\t\ty = 1;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ty = origin[ 0 ] / original.height;\n\t\t}\n\n\t\tswitch ( origin[ 1 ] ) {\n\t\tcase \"left\":\n\t\t\tx = 0;\n\t\t\tbreak;\n\t\tcase \"center\":\n\t\t\tx = 0.5;\n\t\t\tbreak;\n\t\tcase \"right\":\n\t\t\tx = 1;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tx = origin[ 1 ] / original.width;\n\t\t}\n\n\t\treturn {\n\t\t\tx: x,\n\t\t\ty: y\n\t\t};\n\t},\n\n\t// Creates a placeholder element so that the original element can be made absolute\n\tcreatePlaceholder: function( element ) {\n\t\tvar placeholder,\n\t\t\tcssPosition = element.css( \"position\" ),\n\t\t\tposition = element.position();\n\n\t\t// Lock in margins first to account for form elements, which\n\t\t// will change margin if you explicitly set height\n\t\t// see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380\n\t\t// Support: Safari\n\t\telement.css( {\n\t\t\tmarginTop: element.css( \"marginTop\" ),\n\t\t\tmarginBottom: element.css( \"marginBottom\" ),\n\t\t\tmarginLeft: element.css( \"marginLeft\" ),\n\t\t\tmarginRight: element.css( \"marginRight\" )\n\t\t} )\n\t\t.outerWidth( element.outerWidth() )\n\t\t.outerHeight( element.outerHeight() );\n\n\t\tif ( /^(static|relative)/.test( cssPosition ) ) {\n\t\t\tcssPosition = \"absolute\";\n\n\t\t\tplaceholder = $( \"<\" + element[ 0 ].nodeName + \">\" ).insertAfter( element ).css( {\n\n\t\t\t\t// Convert inline to inline block to account for inline elements\n\t\t\t\t// that turn to inline block based on content (like img)\n\t\t\t\tdisplay: /^(inline|ruby)/.test( element.css( \"display\" ) ) ?\n\t\t\t\t\t\"inline-block\" :\n\t\t\t\t\t\"block\",\n\t\t\t\tvisibility: \"hidden\",\n\n\t\t\t\t// Margins need to be set to account for margin collapse\n\t\t\t\tmarginTop: element.css( \"marginTop\" ),\n\t\t\t\tmarginBottom: element.css( \"marginBottom\" ),\n\t\t\t\tmarginLeft: element.css( \"marginLeft\" ),\n\t\t\t\tmarginRight: element.css( \"marginRight\" ),\n\t\t\t\t\"float\": element.css( \"float\" )\n\t\t\t} )\n\t\t\t.outerWidth( element.outerWidth() )\n\t\t\t.outerHeight( element.outerHeight() )\n\t\t\t.addClass( \"ui-effects-placeholder\" );\n\n\t\t\telement.data( dataSpace + \"placeholder\", placeholder );\n\t\t}\n\n\t\telement.css( {\n\t\t\tposition: cssPosition,\n\t\t\tleft: position.left,\n\t\t\ttop: position.top\n\t\t} );\n\n\t\treturn placeholder;\n\t},\n\n\tremovePlaceholder: function( element ) {\n\t\tvar dataKey = dataSpace + \"placeholder\",\n\t\t\t\tplaceholder = element.data( dataKey );\n\n\t\tif ( placeholder ) {\n\t\t\tplaceholder.remove();\n\t\t\telement.removeData( dataKey );\n\t\t}\n\t},\n\n\t// Removes a placeholder if it exists and restores\n\t// properties that were modified during placeholder creation\n\tcleanUp: function( element ) {\n\t\t$.effects.restoreStyle( element );\n\t\t$.effects.removePlaceholder( element );\n\t},\n\n\tsetTransition: function( element, list, factor, value ) {\n\t\tvalue = value || {};\n\t\t$.each( list, function( i, x ) {\n\t\t\tvar unit = element.cssUnit( x );\n\t\t\tif ( unit[ 0 ] > 0 ) {\n\t\t\t\tvalue[ x ] = unit[ 0 ] * factor + unit[ 1 ];\n\t\t\t}\n\t\t} );\n\t\treturn value;\n\t}\n} );\n\n// Return an effect options object for the given parameters:\nfunction _normalizeArguments( effect, options, speed, callback ) {\n\n\t// Allow passing all options as the first parameter\n\tif ( $.isPlainObject( effect ) ) {\n\t\toptions = effect;\n\t\teffect = effect.effect;\n\t}\n\n\t// Convert to an object\n\teffect = { effect: effect };\n\n\t// Catch (effect, null, ...)\n\tif ( options == null ) {\n\t\toptions = {};\n\t}\n\n\t// Catch (effect, callback)\n\tif ( $.isFunction( options ) ) {\n\t\tcallback = options;\n\t\tspeed = null;\n\t\toptions = {};\n\t}\n\n\t// Catch (effect, speed, ?)\n\tif ( typeof options === \"number\" || $.fx.speeds[ options ] ) {\n\t\tcallback = speed;\n\t\tspeed = options;\n\t\toptions = {};\n\t}\n\n\t// Catch (effect, options, callback)\n\tif ( $.isFunction( speed ) ) {\n\t\tcallback = speed;\n\t\tspeed = null;\n\t}\n\n\t// Add options to effect\n\tif ( options ) {\n\t\t$.extend( effect, options );\n\t}\n\n\tspeed = speed || options.duration;\n\teffect.duration = $.fx.off ? 0 :\n\t\ttypeof speed === \"number\" ? speed :\n\t\tspeed in $.fx.speeds ? $.fx.speeds[ speed ] :\n\t\t$.fx.speeds._default;\n\n\teffect.complete = callback || options.complete;\n\n\treturn effect;\n}\n\nfunction standardAnimationOption( option ) {\n\n\t// Valid standard speeds (nothing, number, named speed)\n\tif ( !option || typeof option === \"number\" || $.fx.speeds[ option ] ) {\n\t\treturn true;\n\t}\n\n\t// Invalid strings - treat as \"normal\" speed\n\tif ( typeof option === \"string\" && !$.effects.effect[ option ] ) {\n\t\treturn true;\n\t}\n\n\t// Complete callback\n\tif ( $.isFunction( option ) ) {\n\t\treturn true;\n\t}\n\n\t// Options hash (but not naming an effect)\n\tif ( typeof option === \"object\" && !option.effect ) {\n\t\treturn true;\n\t}\n\n\t// Didn't match any standard API\n\treturn false;\n}\n\n$.fn.extend( {\n\teffect: function( /* effect, options, speed, callback */ ) {\n\t\tvar args = _normalizeArguments.apply( this, arguments ),\n\t\t\teffectMethod = $.effects.effect[ args.effect ],\n\t\t\tdefaultMode = effectMethod.mode,\n\t\t\tqueue = args.queue,\n\t\t\tqueueName = queue || \"fx\",\n\t\t\tcomplete = args.complete,\n\t\t\tmode = args.mode,\n\t\t\tmodes = [],\n\t\t\tprefilter = function( next ) {\n\t\t\t\tvar el = $( this ),\n\t\t\t\t\tnormalizedMode = $.effects.mode( el, mode ) || defaultMode;\n\n\t\t\t\t// Sentinel for duck-punching the :animated psuedo-selector\n\t\t\t\tel.data( dataSpaceAnimated, true );\n\n\t\t\t\t// Save effect mode for later use,\n\t\t\t\t// we can't just call $.effects.mode again later,\n\t\t\t\t// as the .show() below destroys the initial state\n\t\t\t\tmodes.push( normalizedMode );\n\n\t\t\t\t// See $.uiBackCompat inside of run() for removal of defaultMode in 1.13\n\t\t\t\tif ( defaultMode && ( normalizedMode === \"show\" ||\n\t\t\t\t\t\t( normalizedMode === defaultMode && normalizedMode === \"hide\" ) ) ) {\n\t\t\t\t\tel.show();\n\t\t\t\t}\n\n\t\t\t\tif ( !defaultMode || normalizedMode !== \"none\" ) {\n\t\t\t\t\t$.effects.saveStyle( el );\n\t\t\t\t}\n\n\t\t\t\tif ( $.isFunction( next ) ) {\n\t\t\t\t\tnext();\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( $.fx.off || !effectMethod ) {\n\n\t\t\t// Delegate to the original method (e.g., .show()) if possible\n\t\t\tif ( mode ) {\n\t\t\t\treturn this[ mode ]( args.duration, complete );\n\t\t\t} else {\n\t\t\t\treturn this.each( function() {\n\t\t\t\t\tif ( complete ) {\n\t\t\t\t\t\tcomplete.call( this );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tfunction run( next ) {\n\t\t\tvar elem = $( this );\n\n\t\t\tfunction cleanup() {\n\t\t\t\telem.removeData( dataSpaceAnimated );\n\n\t\t\t\t$.effects.cleanUp( elem );\n\n\t\t\t\tif ( args.mode === \"hide\" ) {\n\t\t\t\t\telem.hide();\n\t\t\t\t}\n\n\t\t\t\tdone();\n\t\t\t}\n\n\t\t\tfunction done() {\n\t\t\t\tif ( $.isFunction( complete ) ) {\n\t\t\t\t\tcomplete.call( elem[ 0 ] );\n\t\t\t\t}\n\n\t\t\t\tif ( $.isFunction( next ) ) {\n\t\t\t\t\tnext();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override mode option on a per element basis,\n\t\t\t// as toggle can be either show or hide depending on element state\n\t\t\targs.mode = modes.shift();\n\n\t\t\tif ( $.uiBackCompat !== false && !defaultMode ) {\n\t\t\t\tif ( elem.is( \":hidden\" ) ? mode === \"hide\" : mode === \"show\" ) {\n\n\t\t\t\t\t// Call the core method to track \"olddisplay\" properly\n\t\t\t\t\telem[ mode ]();\n\t\t\t\t\tdone();\n\t\t\t\t} else {\n\t\t\t\t\teffectMethod.call( elem[ 0 ], args, done );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ( args.mode === \"none\" ) {\n\n\t\t\t\t\t// Call the core method to track \"olddisplay\" properly\n\t\t\t\t\telem[ mode ]();\n\t\t\t\t\tdone();\n\t\t\t\t} else {\n\t\t\t\t\teffectMethod.call( elem[ 0 ], args, cleanup );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Run prefilter on all elements first to ensure that\n\t\t// any showing or hiding happens before placeholder creation,\n\t\t// which ensures that any layout changes are correctly captured.\n\t\treturn queue === false ?\n\t\t\tthis.each( prefilter ).each( run ) :\n\t\t\tthis.queue( queueName, prefilter ).queue( queueName, run );\n\t},\n\n\tshow: ( function( orig ) {\n\t\treturn function( option ) {\n\t\t\tif ( standardAnimationOption( option ) ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t} else {\n\t\t\t\tvar args = _normalizeArguments.apply( this, arguments );\n\t\t\t\targs.mode = \"show\";\n\t\t\t\treturn this.effect.call( this, args );\n\t\t\t}\n\t\t};\n\t} )( $.fn.show ),\n\n\thide: ( function( orig ) {\n\t\treturn function( option ) {\n\t\t\tif ( standardAnimationOption( option ) ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t} else {\n\t\t\t\tvar args = _normalizeArguments.apply( this, arguments );\n\t\t\t\targs.mode = \"hide\";\n\t\t\t\treturn this.effect.call( this, args );\n\t\t\t}\n\t\t};\n\t} )( $.fn.hide ),\n\n\ttoggle: ( function( orig ) {\n\t\treturn function( option ) {\n\t\t\tif ( standardAnimationOption( option ) || typeof option === \"boolean\" ) {\n\t\t\t\treturn orig.apply( this, arguments );\n\t\t\t} else {\n\t\t\t\tvar args = _normalizeArguments.apply( this, arguments );\n\t\t\t\targs.mode = \"toggle\";\n\t\t\t\treturn this.effect.call( this, args );\n\t\t\t}\n\t\t};\n\t} )( $.fn.toggle ),\n\n\tcssUnit: function( key ) {\n\t\tvar style = this.css( key ),\n\t\t\tval = [];\n\n\t\t$.each( [ \"em\", \"px\", \"%\", \"pt\" ], function( i, unit ) {\n\t\t\tif ( style.indexOf( unit ) > 0 ) {\n\t\t\t\tval = [ parseFloat( style ), unit ];\n\t\t\t}\n\t\t} );\n\t\treturn val;\n\t},\n\n\tcssClip: function( clipObj ) {\n\t\tif ( clipObj ) {\n\t\t\treturn this.css( \"clip\", \"rect(\" + clipObj.top + \"px \" + clipObj.right + \"px \" +\n\t\t\t\tclipObj.bottom + \"px \" + clipObj.left + \"px)\" );\n\t\t}\n\t\treturn parseClip( this.css( \"clip\" ), this );\n\t},\n\n\ttransfer: function( options, done ) {\n\t\tvar element = $( this ),\n\t\t\ttarget = $( options.to ),\n\t\t\ttargetFixed = target.css( \"position\" ) === \"fixed\",\n\t\t\tbody = $( \"body\" ),\n\t\t\tfixTop = targetFixed ? body.scrollTop() : 0,\n\t\t\tfixLeft = targetFixed ? body.scrollLeft() : 0,\n\t\t\tendPosition = target.offset(),\n\t\t\tanimation = {\n\t\t\t\ttop: endPosition.top - fixTop,\n\t\t\t\tleft: endPosition.left - fixLeft,\n\t\t\t\theight: target.innerHeight(),\n\t\t\t\twidth: target.innerWidth()\n\t\t\t},\n\t\t\tstartPosition = element.offset(),\n\t\t\ttransfer = $( \"<div class='ui-effects-transfer'></div>\" )\n\t\t\t\t.appendTo( \"body\" )\n\t\t\t\t.addClass( options.className )\n\t\t\t\t.css( {\n\t\t\t\t\ttop: startPosition.top - fixTop,\n\t\t\t\t\tleft: startPosition.left - fixLeft,\n\t\t\t\t\theight: element.innerHeight(),\n\t\t\t\t\twidth: element.innerWidth(),\n\t\t\t\t\tposition: targetFixed ? \"fixed\" : \"absolute\"\n\t\t\t\t} )\n\t\t\t\t.animate( animation, options.duration, options.easing, function() {\n\t\t\t\t\ttransfer.remove();\n\t\t\t\t\tif ( $.isFunction( done ) ) {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t}\n} );\n\nfunction parseClip( str, element ) {\n\t\tvar outerWidth = element.outerWidth(),\n\t\t\touterHeight = element.outerHeight(),\n\t\t\tclipRegex = /^rect\\((-?\\d*\\.?\\d*px|-?\\d+%|auto),?\\s*(-?\\d*\\.?\\d*px|-?\\d+%|auto),?\\s*(-?\\d*\\.?\\d*px|-?\\d+%|auto),?\\s*(-?\\d*\\.?\\d*px|-?\\d+%|auto)\\)$/,\n\t\t\tvalues = clipRegex.exec( str ) || [ \"\", 0, outerWidth, outerHeight, 0 ];\n\n\t\treturn {\n\t\t\ttop: parseFloat( values[ 1 ] ) || 0,\n\t\t\tright: values[ 2 ] === \"auto\" ? outerWidth : parseFloat( values[ 2 ] ),\n\t\t\tbottom: values[ 3 ] === \"auto\" ? outerHeight : parseFloat( values[ 3 ] ),\n\t\t\tleft: parseFloat( values[ 4 ] ) || 0\n\t\t};\n}\n\n$.fx.step.clip = function( fx ) {\n\tif ( !fx.clipInit ) {\n\t\tfx.start = $( fx.elem ).cssClip();\n\t\tif ( typeof fx.end === \"string\" ) {\n\t\t\tfx.end = parseClip( fx.end, fx.elem );\n\t\t}\n\t\tfx.clipInit = true;\n\t}\n\n\t$( fx.elem ).cssClip( {\n\t\ttop: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top,\n\t\tright: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right,\n\t\tbottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom,\n\t\tleft: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left\n\t} );\n};\n\n} )();\n\n/******************************************************************************/\n/*********************************** EASING ***********************************/\n/******************************************************************************/\n\n( function() {\n\n// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing)\n\nvar baseEasings = {};\n\n$.each( [ \"Quad\", \"Cubic\", \"Quart\", \"Quint\", \"Expo\" ], function( i, name ) {\n\tbaseEasings[ name ] = function( p ) {\n\t\treturn Math.pow( p, i + 2 );\n\t};\n} );\n\n$.extend( baseEasings, {\n\tSine: function( p ) {\n\t\treturn 1 - Math.cos( p * Math.PI / 2 );\n\t},\n\tCirc: function( p ) {\n\t\treturn 1 - Math.sqrt( 1 - p * p );\n\t},\n\tElastic: function( p ) {\n\t\treturn p === 0 || p === 1 ? p :\n\t\t\t-Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 );\n\t},\n\tBack: function( p ) {\n\t\treturn p * p * ( 3 * p - 2 );\n\t},\n\tBounce: function( p ) {\n\t\tvar pow2,\n\t\t\tbounce = 4;\n\n\t\twhile ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}\n\t\treturn 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );\n\t}\n} );\n\n$.each( baseEasings, function( name, easeIn ) {\n\t$.easing[ \"easeIn\" + name ] = easeIn;\n\t$.easing[ \"easeOut\" + name ] = function( p ) {\n\t\treturn 1 - easeIn( 1 - p );\n\t};\n\t$.easing[ \"easeInOut\" + name ] = function( p ) {\n\t\treturn p < 0.5 ?\n\t\t\teaseIn( p * 2 ) / 2 :\n\t\t\t1 - easeIn( p * -2 + 2 ) / 2;\n\t};\n} );\n\n} )();\n\nvar effect = $.effects;\n\n\n/*!\n * jQuery UI Effects Blind 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Blind Effect\n//>>group: Effects\n//>>description: Blinds the element.\n//>>docs: http://api.jqueryui.com/blind-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectBlind = $.effects.define( \"blind\", \"hide\", function( options, done ) {\n\tvar map = {\n\t\t\tup: [ \"bottom\", \"top\" ],\n\t\t\tvertical: [ \"bottom\", \"top\" ],\n\t\t\tdown: [ \"top\", \"bottom\" ],\n\t\t\tleft: [ \"right\", \"left\" ],\n\t\t\thorizontal: [ \"right\", \"left\" ],\n\t\t\tright: [ \"left\", \"right\" ]\n\t\t},\n\t\telement = $( this ),\n\t\tdirection = options.direction || \"up\",\n\t\tstart = element.cssClip(),\n\t\tanimate = { clip: $.extend( {}, start ) },\n\t\tplaceholder = $.effects.createPlaceholder( element );\n\n\tanimate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ];\n\n\tif ( options.mode === \"show\" ) {\n\t\telement.cssClip( animate.clip );\n\t\tif ( placeholder ) {\n\t\t\tplaceholder.css( $.effects.clipToBox( animate ) );\n\t\t}\n\n\t\tanimate.clip = start;\n\t}\n\n\tif ( placeholder ) {\n\t\tplaceholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing );\n\t}\n\n\telement.animate( animate, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Bounce 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Bounce Effect\n//>>group: Effects\n//>>description: Bounces an element horizontally or vertically n times.\n//>>docs: http://api.jqueryui.com/bounce-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectBounce = $.effects.define( \"bounce\", function( options, done ) {\n\tvar upAnim, downAnim, refValue,\n\t\telement = $( this ),\n\n\t\t// Defaults:\n\t\tmode = options.mode,\n\t\thide = mode === \"hide\",\n\t\tshow = mode === \"show\",\n\t\tdirection = options.direction || \"up\",\n\t\tdistance = options.distance,\n\t\ttimes = options.times || 5,\n\n\t\t// Number of internal animations\n\t\tanims = times * 2 + ( show || hide ? 1 : 0 ),\n\t\tspeed = options.duration / anims,\n\t\teasing = options.easing,\n\n\t\t// Utility:\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tmotion = ( direction === \"up\" || direction === \"left\" ),\n\t\ti = 0,\n\n\t\tqueuelen = element.queue().length;\n\n\t$.effects.createPlaceholder( element );\n\n\trefValue = element.css( ref );\n\n\t// Default distance for the BIGGEST bounce is the outer Distance / 3\n\tif ( !distance ) {\n\t\tdistance = element[ ref === \"top\" ? \"outerHeight\" : \"outerWidth\" ]() / 3;\n\t}\n\n\tif ( show ) {\n\t\tdownAnim = { opacity: 1 };\n\t\tdownAnim[ ref ] = refValue;\n\n\t\t// If we are showing, force opacity 0 and set the initial position\n\t\t// then do the \"first\" animation\n\t\telement\n\t\t\t.css( \"opacity\", 0 )\n\t\t\t.css( ref, motion ? -distance * 2 : distance * 2 )\n\t\t\t.animate( downAnim, speed, easing );\n\t}\n\n\t// Start at the smallest distance if we are hiding\n\tif ( hide ) {\n\t\tdistance = distance / Math.pow( 2, times - 1 );\n\t}\n\n\tdownAnim = {};\n\tdownAnim[ ref ] = refValue;\n\n\t// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here\n\tfor ( ; i < times; i++ ) {\n\t\tupAnim = {};\n\t\tupAnim[ ref ] = ( motion ? \"-=\" : \"+=\" ) + distance;\n\n\t\telement\n\t\t\t.animate( upAnim, speed, easing )\n\t\t\t.animate( downAnim, speed, easing );\n\n\t\tdistance = hide ? distance * 2 : distance / 2;\n\t}\n\n\t// Last Bounce when Hiding\n\tif ( hide ) {\n\t\tupAnim = { opacity: 0 };\n\t\tupAnim[ ref ] = ( motion ? \"-=\" : \"+=\" ) + distance;\n\n\t\telement.animate( upAnim, speed, easing );\n\t}\n\n\telement.queue( done );\n\n\t$.effects.unshift( element, queuelen, anims + 1 );\n} );\n\n\n/*!\n * jQuery UI Effects Clip 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Clip Effect\n//>>group: Effects\n//>>description: Clips the element on and off like an old TV.\n//>>docs: http://api.jqueryui.com/clip-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectClip = $.effects.define( \"clip\", \"hide\", function( options, done ) {\n\tvar start,\n\t\tanimate = {},\n\t\telement = $( this ),\n\t\tdirection = options.direction || \"vertical\",\n\t\tboth = direction === \"both\",\n\t\thorizontal = both || direction === \"horizontal\",\n\t\tvertical = both || direction === \"vertical\";\n\n\tstart = element.cssClip();\n\tanimate.clip = {\n\t\ttop: vertical ? ( start.bottom - start.top ) / 2 : start.top,\n\t\tright: horizontal ? ( start.right - start.left ) / 2 : start.right,\n\t\tbottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom,\n\t\tleft: horizontal ? ( start.right - start.left ) / 2 : start.left\n\t};\n\n\t$.effects.createPlaceholder( element );\n\n\tif ( options.mode === \"show\" ) {\n\t\telement.cssClip( animate.clip );\n\t\tanimate.clip = start;\n\t}\n\n\telement.animate( animate, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n\n} );\n\n\n/*!\n * jQuery UI Effects Drop 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Drop Effect\n//>>group: Effects\n//>>description: Moves an element in one direction and hides it at the same time.\n//>>docs: http://api.jqueryui.com/drop-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectDrop = $.effects.define( \"drop\", \"hide\", function( options, done ) {\n\n\tvar distance,\n\t\telement = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\t\tdirection = options.direction || \"left\",\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tmotion = ( direction === \"up\" || direction === \"left\" ) ? \"-=\" : \"+=\",\n\t\toppositeMotion = ( motion === \"+=\" ) ? \"-=\" : \"+=\",\n\t\tanimation = {\n\t\t\topacity: 0\n\t\t};\n\n\t$.effects.createPlaceholder( element );\n\n\tdistance = options.distance ||\n\t\telement[ ref === \"top\" ? \"outerHeight\" : \"outerWidth\" ]( true ) / 2;\n\n\tanimation[ ref ] = motion + distance;\n\n\tif ( show ) {\n\t\telement.css( animation );\n\n\t\tanimation[ ref ] = oppositeMotion + distance;\n\t\tanimation.opacity = 1;\n\t}\n\n\t// Animate\n\telement.animate( animation, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Explode 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Explode Effect\n//>>group: Effects\n// jscs:disable maximumLineLength\n//>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness.\n// jscs:enable maximumLineLength\n//>>docs: http://api.jqueryui.com/explode-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectExplode = $.effects.define( \"explode\", \"hide\", function( options, done ) {\n\n\tvar i, j, left, top, mx, my,\n\t\trows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3,\n\t\tcells = rows,\n\t\telement = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\n\t\t// Show and then visibility:hidden the element before calculating offset\n\t\toffset = element.show().css( \"visibility\", \"hidden\" ).offset(),\n\n\t\t// Width and height of a piece\n\t\twidth = Math.ceil( element.outerWidth() / cells ),\n\t\theight = Math.ceil( element.outerHeight() / rows ),\n\t\tpieces = [];\n\n\t// Children animate complete:\n\tfunction childComplete() {\n\t\tpieces.push( this );\n\t\tif ( pieces.length === rows * cells ) {\n\t\t\tanimComplete();\n\t\t}\n\t}\n\n\t// Clone the element for each row and cell.\n\tfor ( i = 0; i < rows; i++ ) { // ===>\n\t\ttop = offset.top + i * height;\n\t\tmy = i - ( rows - 1 ) / 2;\n\n\t\tfor ( j = 0; j < cells; j++ ) { // |||\n\t\t\tleft = offset.left + j * width;\n\t\t\tmx = j - ( cells - 1 ) / 2;\n\n\t\t\t// Create a clone of the now hidden main element that will be absolute positioned\n\t\t\t// within a wrapper div off the -left and -top equal to size of our pieces\n\t\t\telement\n\t\t\t\t.clone()\n\t\t\t\t.appendTo( \"body\" )\n\t\t\t\t.wrap( \"<div></div>\" )\n\t\t\t\t.css( {\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tvisibility: \"visible\",\n\t\t\t\t\tleft: -j * width,\n\t\t\t\t\ttop: -i * height\n\t\t\t\t} )\n\n\t\t\t\t// Select the wrapper - make it overflow: hidden and absolute positioned based on\n\t\t\t\t// where the original was located +left and +top equal to the size of pieces\n\t\t\t\t.parent()\n\t\t\t\t\t.addClass( \"ui-effects-explode\" )\n\t\t\t\t\t.css( {\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\theight: height,\n\t\t\t\t\t\tleft: left + ( show ? mx * width : 0 ),\n\t\t\t\t\t\ttop: top + ( show ? my * height : 0 ),\n\t\t\t\t\t\topacity: show ? 0 : 1\n\t\t\t\t\t} )\n\t\t\t\t\t.animate( {\n\t\t\t\t\t\tleft: left + ( show ? 0 : mx * width ),\n\t\t\t\t\t\ttop: top + ( show ? 0 : my * height ),\n\t\t\t\t\t\topacity: show ? 1 : 0\n\t\t\t\t\t}, options.duration || 500, options.easing, childComplete );\n\t\t}\n\t}\n\n\tfunction animComplete() {\n\t\telement.css( {\n\t\t\tvisibility: \"visible\"\n\t\t} );\n\t\t$( pieces ).remove();\n\t\tdone();\n\t}\n} );\n\n\n/*!\n * jQuery UI Effects Fade 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Fade Effect\n//>>group: Effects\n//>>description: Fades the element.\n//>>docs: http://api.jqueryui.com/fade-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectFade = $.effects.define( \"fade\", \"toggle\", function( options, done ) {\n\tvar show = options.mode === \"show\";\n\n\t$( this )\n\t\t.css( \"opacity\", show ? 0 : 1 )\n\t\t.animate( {\n\t\t\topacity: show ? 1 : 0\n\t\t}, {\n\t\t\tqueue: false,\n\t\t\tduration: options.duration,\n\t\t\teasing: options.easing,\n\t\t\tcomplete: done\n\t\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Fold 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Fold Effect\n//>>group: Effects\n//>>description: Folds an element first horizontally and then vertically.\n//>>docs: http://api.jqueryui.com/fold-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectFold = $.effects.define( \"fold\", \"hide\", function( options, done ) {\n\n\t// Create element\n\tvar element = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\t\thide = mode === \"hide\",\n\t\tsize = options.size || 15,\n\t\tpercent = /([0-9]+)%/.exec( size ),\n\t\thorizFirst = !!options.horizFirst,\n\t\tref = horizFirst ? [ \"right\", \"bottom\" ] : [ \"bottom\", \"right\" ],\n\t\tduration = options.duration / 2,\n\n\t\tplaceholder = $.effects.createPlaceholder( element ),\n\n\t\tstart = element.cssClip(),\n\t\tanimation1 = { clip: $.extend( {}, start ) },\n\t\tanimation2 = { clip: $.extend( {}, start ) },\n\n\t\tdistance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ],\n\n\t\tqueuelen = element.queue().length;\n\n\tif ( percent ) {\n\t\tsize = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];\n\t}\n\tanimation1.clip[ ref[ 0 ] ] = size;\n\tanimation2.clip[ ref[ 0 ] ] = size;\n\tanimation2.clip[ ref[ 1 ] ] = 0;\n\n\tif ( show ) {\n\t\telement.cssClip( animation2.clip );\n\t\tif ( placeholder ) {\n\t\t\tplaceholder.css( $.effects.clipToBox( animation2 ) );\n\t\t}\n\n\t\tanimation2.clip = start;\n\t}\n\n\t// Animate\n\telement\n\t\t.queue( function( next ) {\n\t\t\tif ( placeholder ) {\n\t\t\t\tplaceholder\n\t\t\t\t\t.animate( $.effects.clipToBox( animation1 ), duration, options.easing )\n\t\t\t\t\t.animate( $.effects.clipToBox( animation2 ), duration, options.easing );\n\t\t\t}\n\n\t\t\tnext();\n\t\t} )\n\t\t.animate( animation1, duration, options.easing )\n\t\t.animate( animation2, duration, options.easing )\n\t\t.queue( done );\n\n\t$.effects.unshift( element, queuelen, 4 );\n} );\n\n\n/*!\n * jQuery UI Effects Highlight 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Highlight Effect\n//>>group: Effects\n//>>description: Highlights the background of an element in a defined color for a custom duration.\n//>>docs: http://api.jqueryui.com/highlight-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectHighlight = $.effects.define( \"highlight\", \"show\", function( options, done ) {\n\tvar element = $( this ),\n\t\tanimation = {\n\t\t\tbackgroundColor: element.css( \"backgroundColor\" )\n\t\t};\n\n\tif ( options.mode === \"hide\" ) {\n\t\tanimation.opacity = 0;\n\t}\n\n\t$.effects.saveStyle( element );\n\n\telement\n\t\t.css( {\n\t\t\tbackgroundImage: \"none\",\n\t\t\tbackgroundColor: options.color || \"#ffff99\"\n\t\t} )\n\t\t.animate( animation, {\n\t\t\tqueue: false,\n\t\t\tduration: options.duration,\n\t\t\teasing: options.easing,\n\t\t\tcomplete: done\n\t\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Size 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Size Effect\n//>>group: Effects\n//>>description: Resize an element to a specified width and height.\n//>>docs: http://api.jqueryui.com/size-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectSize = $.effects.define( \"size\", function( options, done ) {\n\n\t// Create element\n\tvar baseline, factor, temp,\n\t\telement = $( this ),\n\n\t\t// Copy for children\n\t\tcProps = [ \"fontSize\" ],\n\t\tvProps = [ \"borderTopWidth\", \"borderBottomWidth\", \"paddingTop\", \"paddingBottom\" ],\n\t\thProps = [ \"borderLeftWidth\", \"borderRightWidth\", \"paddingLeft\", \"paddingRight\" ],\n\n\t\t// Set options\n\t\tmode = options.mode,\n\t\trestore = mode !== \"effect\",\n\t\tscale = options.scale || \"both\",\n\t\torigin = options.origin || [ \"middle\", \"center\" ],\n\t\tposition = element.css( \"position\" ),\n\t\tpos = element.position(),\n\t\toriginal = $.effects.scaledDimensions( element ),\n\t\tfrom = options.from || original,\n\t\tto = options.to || $.effects.scaledDimensions( element, 0 );\n\n\t$.effects.createPlaceholder( element );\n\n\tif ( mode === \"show\" ) {\n\t\ttemp = from;\n\t\tfrom = to;\n\t\tto = temp;\n\t}\n\n\t// Set scaling factor\n\tfactor = {\n\t\tfrom: {\n\t\t\ty: from.height / original.height,\n\t\t\tx: from.width / original.width\n\t\t},\n\t\tto: {\n\t\t\ty: to.height / original.height,\n\t\t\tx: to.width / original.width\n\t\t}\n\t};\n\n\t// Scale the css box\n\tif ( scale === \"box\" || scale === \"both\" ) {\n\n\t\t// Vertical props scaling\n\t\tif ( factor.from.y !== factor.to.y ) {\n\t\t\tfrom = $.effects.setTransition( element, vProps, factor.from.y, from );\n\t\t\tto = $.effects.setTransition( element, vProps, factor.to.y, to );\n\t\t}\n\n\t\t// Horizontal props scaling\n\t\tif ( factor.from.x !== factor.to.x ) {\n\t\t\tfrom = $.effects.setTransition( element, hProps, factor.from.x, from );\n\t\t\tto = $.effects.setTransition( element, hProps, factor.to.x, to );\n\t\t}\n\t}\n\n\t// Scale the content\n\tif ( scale === \"content\" || scale === \"both\" ) {\n\n\t\t// Vertical props scaling\n\t\tif ( factor.from.y !== factor.to.y ) {\n\t\t\tfrom = $.effects.setTransition( element, cProps, factor.from.y, from );\n\t\t\tto = $.effects.setTransition( element, cProps, factor.to.y, to );\n\t\t}\n\t}\n\n\t// Adjust the position properties based on the provided origin points\n\tif ( origin ) {\n\t\tbaseline = $.effects.getBaseline( origin, original );\n\t\tfrom.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top;\n\t\tfrom.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left;\n\t\tto.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top;\n\t\tto.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left;\n\t}\n\telement.css( from );\n\n\t// Animate the children if desired\n\tif ( scale === \"content\" || scale === \"both\" ) {\n\n\t\tvProps = vProps.concat( [ \"marginTop\", \"marginBottom\" ] ).concat( cProps );\n\t\thProps = hProps.concat( [ \"marginLeft\", \"marginRight\" ] );\n\n\t\t// Only animate children with width attributes specified\n\t\t// TODO: is this right? should we include anything with css width specified as well\n\t\telement.find( \"*[width]\" ).each( function() {\n\t\t\tvar child = $( this ),\n\t\t\t\tchildOriginal = $.effects.scaledDimensions( child ),\n\t\t\t\tchildFrom = {\n\t\t\t\t\theight: childOriginal.height * factor.from.y,\n\t\t\t\t\twidth: childOriginal.width * factor.from.x,\n\t\t\t\t\touterHeight: childOriginal.outerHeight * factor.from.y,\n\t\t\t\t\touterWidth: childOriginal.outerWidth * factor.from.x\n\t\t\t\t},\n\t\t\t\tchildTo = {\n\t\t\t\t\theight: childOriginal.height * factor.to.y,\n\t\t\t\t\twidth: childOriginal.width * factor.to.x,\n\t\t\t\t\touterHeight: childOriginal.height * factor.to.y,\n\t\t\t\t\touterWidth: childOriginal.width * factor.to.x\n\t\t\t\t};\n\n\t\t\t// Vertical props scaling\n\t\t\tif ( factor.from.y !== factor.to.y ) {\n\t\t\t\tchildFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom );\n\t\t\t\tchildTo = $.effects.setTransition( child, vProps, factor.to.y, childTo );\n\t\t\t}\n\n\t\t\t// Horizontal props scaling\n\t\t\tif ( factor.from.x !== factor.to.x ) {\n\t\t\t\tchildFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom );\n\t\t\t\tchildTo = $.effects.setTransition( child, hProps, factor.to.x, childTo );\n\t\t\t}\n\n\t\t\tif ( restore ) {\n\t\t\t\t$.effects.saveStyle( child );\n\t\t\t}\n\n\t\t\t// Animate children\n\t\t\tchild.css( childFrom );\n\t\t\tchild.animate( childTo, options.duration, options.easing, function() {\n\n\t\t\t\t// Restore children\n\t\t\t\tif ( restore ) {\n\t\t\t\t\t$.effects.restoreStyle( child );\n\t\t\t\t}\n\t\t\t} );\n\t\t} );\n\t}\n\n\t// Animate\n\telement.animate( to, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: function() {\n\n\t\t\tvar offset = element.offset();\n\n\t\t\tif ( to.opacity === 0 ) {\n\t\t\t\telement.css( \"opacity\", from.opacity );\n\t\t\t}\n\n\t\t\tif ( !restore ) {\n\t\t\t\telement\n\t\t\t\t\t.css( \"position\", position === \"static\" ? \"relative\" : position )\n\t\t\t\t\t.offset( offset );\n\n\t\t\t\t// Need to save style here so that automatic style restoration\n\t\t\t\t// doesn't restore to the original styles from before the animation.\n\t\t\t\t$.effects.saveStyle( element );\n\t\t\t}\n\n\t\t\tdone();\n\t\t}\n\t} );\n\n} );\n\n\n/*!\n * jQuery UI Effects Scale 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Scale Effect\n//>>group: Effects\n//>>description: Grows or shrinks an element and its content.\n//>>docs: http://api.jqueryui.com/scale-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectScale = $.effects.define( \"scale\", function( options, done ) {\n\n\t// Create element\n\tvar el = $( this ),\n\t\tmode = options.mode,\n\t\tpercent = parseInt( options.percent, 10 ) ||\n\t\t\t( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== \"effect\" ? 0 : 100 ) ),\n\n\t\tnewOptions = $.extend( true, {\n\t\t\tfrom: $.effects.scaledDimensions( el ),\n\t\t\tto: $.effects.scaledDimensions( el, percent, options.direction || \"both\" ),\n\t\t\torigin: options.origin || [ \"middle\", \"center\" ]\n\t\t}, options );\n\n\t// Fade option to support puff\n\tif ( options.fade ) {\n\t\tnewOptions.from.opacity = 1;\n\t\tnewOptions.to.opacity = 0;\n\t}\n\n\t$.effects.effect.size.call( this, newOptions, done );\n} );\n\n\n/*!\n * jQuery UI Effects Puff 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Puff Effect\n//>>group: Effects\n//>>description: Creates a puff effect by scaling the element up and hiding it at the same time.\n//>>docs: http://api.jqueryui.com/puff-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectPuff = $.effects.define( \"puff\", \"hide\", function( options, done ) {\n\tvar newOptions = $.extend( true, {}, options, {\n\t\tfade: true,\n\t\tpercent: parseInt( options.percent, 10 ) || 150\n\t} );\n\n\t$.effects.effect.scale.call( this, newOptions, done );\n} );\n\n\n/*!\n * jQuery UI Effects Pulsate 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Pulsate Effect\n//>>group: Effects\n//>>description: Pulsates an element n times by changing the opacity to zero and back.\n//>>docs: http://api.jqueryui.com/pulsate-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectPulsate = $.effects.define( \"pulsate\", \"show\", function( options, done ) {\n\tvar element = $( this ),\n\t\tmode = options.mode,\n\t\tshow = mode === \"show\",\n\t\thide = mode === \"hide\",\n\t\tshowhide = show || hide,\n\n\t\t// Showing or hiding leaves off the \"last\" animation\n\t\tanims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),\n\t\tduration = options.duration / anims,\n\t\tanimateTo = 0,\n\t\ti = 1,\n\t\tqueuelen = element.queue().length;\n\n\tif ( show || !element.is( \":visible\" ) ) {\n\t\telement.css( \"opacity\", 0 ).show();\n\t\tanimateTo = 1;\n\t}\n\n\t// Anims - 1 opacity \"toggles\"\n\tfor ( ; i < anims; i++ ) {\n\t\telement.animate( { opacity: animateTo }, duration, options.easing );\n\t\tanimateTo = 1 - animateTo;\n\t}\n\n\telement.animate( { opacity: animateTo }, duration, options.easing );\n\n\telement.queue( done );\n\n\t$.effects.unshift( element, queuelen, anims + 1 );\n} );\n\n\n/*!\n * jQuery UI Effects Shake 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Shake Effect\n//>>group: Effects\n//>>description: Shakes an element horizontally or vertically n times.\n//>>docs: http://api.jqueryui.com/shake-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectShake = $.effects.define( \"shake\", function( options, done ) {\n\n\tvar i = 1,\n\t\telement = $( this ),\n\t\tdirection = options.direction || \"left\",\n\t\tdistance = options.distance || 20,\n\t\ttimes = options.times || 3,\n\t\tanims = times * 2 + 1,\n\t\tspeed = Math.round( options.duration / anims ),\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tpositiveMotion = ( direction === \"up\" || direction === \"left\" ),\n\t\tanimation = {},\n\t\tanimation1 = {},\n\t\tanimation2 = {},\n\n\t\tqueuelen = element.queue().length;\n\n\t$.effects.createPlaceholder( element );\n\n\t// Animation\n\tanimation[ ref ] = ( positiveMotion ? \"-=\" : \"+=\" ) + distance;\n\tanimation1[ ref ] = ( positiveMotion ? \"+=\" : \"-=\" ) + distance * 2;\n\tanimation2[ ref ] = ( positiveMotion ? \"-=\" : \"+=\" ) + distance * 2;\n\n\t// Animate\n\telement.animate( animation, speed, options.easing );\n\n\t// Shakes\n\tfor ( ; i < times; i++ ) {\n\t\telement\n\t\t\t.animate( animation1, speed, options.easing )\n\t\t\t.animate( animation2, speed, options.easing );\n\t}\n\n\telement\n\t\t.animate( animation1, speed, options.easing )\n\t\t.animate( animation, speed / 2, options.easing )\n\t\t.queue( done );\n\n\t$.effects.unshift( element, queuelen, anims + 1 );\n} );\n\n\n/*!\n * jQuery UI Effects Slide 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Slide Effect\n//>>group: Effects\n//>>description: Slides an element in and out of the viewport.\n//>>docs: http://api.jqueryui.com/slide-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effectsEffectSlide = $.effects.define( \"slide\", \"show\", function( options, done ) {\n\tvar startClip, startRef,\n\t\telement = $( this ),\n\t\tmap = {\n\t\t\tup: [ \"bottom\", \"top\" ],\n\t\t\tdown: [ \"top\", \"bottom\" ],\n\t\t\tleft: [ \"right\", \"left\" ],\n\t\t\tright: [ \"left\", \"right\" ]\n\t\t},\n\t\tmode = options.mode,\n\t\tdirection = options.direction || \"left\",\n\t\tref = ( direction === \"up\" || direction === \"down\" ) ? \"top\" : \"left\",\n\t\tpositiveMotion = ( direction === \"up\" || direction === \"left\" ),\n\t\tdistance = options.distance ||\n\t\t\telement[ ref === \"top\" ? \"outerHeight\" : \"outerWidth\" ]( true ),\n\t\tanimation = {};\n\n\t$.effects.createPlaceholder( element );\n\n\tstartClip = element.cssClip();\n\tstartRef = element.position()[ ref ];\n\n\t// Define hide animation\n\tanimation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef;\n\tanimation.clip = element.cssClip();\n\tanimation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ];\n\n\t// Reverse the animation if we're showing\n\tif ( mode === \"show\" ) {\n\t\telement.cssClip( animation.clip );\n\t\telement.css( ref, animation[ ref ] );\n\t\tanimation.clip = startClip;\n\t\tanimation[ ref ] = startRef;\n\t}\n\n\t// Actually animate\n\telement.animate( animation, {\n\t\tqueue: false,\n\t\tduration: options.duration,\n\t\teasing: options.easing,\n\t\tcomplete: done\n\t} );\n} );\n\n\n/*!\n * jQuery UI Effects Transfer 1.12.1\n * http://jqueryui.com\n *\n * Copyright jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n */\n\n//>>label: Transfer Effect\n//>>group: Effects\n//>>description: Displays a transfer effect from one element to another.\n//>>docs: http://api.jqueryui.com/transfer-effect/\n//>>demos: http://jqueryui.com/effect/\n\n\n\nvar effect;\nif ( $.uiBackCompat !== false ) {\n\teffect = $.effects.define( \"transfer\", function( options, done ) {\n\t\t$( this ).transfer( options, done );\n\t} );\n}\nvar effectsEffectTransfer = effect;\n\n\n\n\n}));"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/jquery-ui/package.json",
    "content": "{\n\t\"name\": \"jquery-ui\",\n\t\"title\": \"jQuery UI\",\n\t\"description\": \"A curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library.\",\n\t\"version\": \"1.12.1\",\n\t\"homepage\": \"http://jqueryui.com\",\n\t\"author\": {\n\t\t\"name\": \"jQuery Foundation and other contributors\",\n\t\t\"url\": \"https://github.com/jquery/jquery-ui/blob/1.12.1/AUTHORS.txt\"\n\t},\n\t\"main\": \"ui/widget.js\",\n\t\"maintainers\": [\n\t\t{\n\t\t\t\"name\": \"Scott González\",\n\t\t\t\"email\": \"scott.gonzalez@gmail.com\",\n\t\t\t\"url\": \"http://scottgonzalez.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Jörn Zaefferer\",\n\t\t\t\"email\": \"joern.zaefferer@gmail.com\",\n\t\t\t\"url\": \"http://bassistance.de\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Mike Sherov\",\n\t\t\t\"email\": \"mike.sherov@gmail.com\",\n\t\t\t\"url\": \"http://mike.sherov.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"TJ VanToll\",\n\t\t\t\"email\": \"tj.vantoll@gmail.com\",\n\t\t\t\"url\": \"http://tjvantoll.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Felix Nagel\",\n\t\t\t\"email\": \"info@felixnagel.com\",\n\t\t\t\"url\": \"http://www.felixnagel.com\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Alex Schmitz\",\n\t\t\t\"email\": \"arschmitz@gmail.com\",\n\t\t\t\"url\": \"https://github.com/arschmitz\"\n\t\t}\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git://github.com/jquery/jquery-ui.git\"\n\t},\n\t\"bugs\": \"https://bugs.jqueryui.com/\",\n\t\"license\": \"MIT\",\n\t\"scripts\": {\n\t\t\"test\": \"grunt\"\n\t},\n\t\"dependencies\": {},\n\t\"devDependencies\": {\n\t\t\"commitplease\": \"2.3.0\",\n\t\t\"grunt\": \"0.4.5\",\n\t\t\"grunt-bowercopy\": \"1.2.4\",\n\t\t\"grunt-cli\": \"0.1.13\",\n\t\t\"grunt-compare-size\": \"0.4.0\",\n\t\t\"grunt-contrib-concat\": \"0.5.1\",\n\t\t\"grunt-contrib-csslint\": \"0.5.0\",\n\t\t\"grunt-contrib-jshint\": \"0.12.0\",\n\t\t\"grunt-contrib-qunit\": \"1.0.1\",\n\t\t\"grunt-contrib-requirejs\": \"0.4.4\",\n\t\t\"grunt-contrib-uglify\": \"0.11.1\",\n\t\t\"grunt-git-authors\": \"3.1.0\",\n\t\t\"grunt-html\": \"6.0.0\",\n\t\t\"grunt-jscs\": \"2.1.0\",\n\t\t\"load-grunt-tasks\": \"3.4.0\",\n\t\t\"rimraf\": \"2.5.1\",\n\t\t\"testswarm\": \"1.1.0\"\n\t},\n\t\"keywords\": []\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/jquery.flot.axisvalues.js",
    "content": "/* Flot plugin for showing axis values when the mouse hovers over the plot.\nCopyright (c) 2019 Camille Lavayssière.\nLicensed under the MIT license.\nThe plugin supports these options:\n\taxisvalues: {\n\t\tmode: null or \"x\" or \"y\" or \"xy\"\n\t\tcolor: color\n        backgroundColor: \"#fee\",\n        opacity: 0.80\n\t}\nSet the mode to one of \"x\", \"y\" or \"xy\". The \"x\" mode enables a vertical\naxisvalues that lets you trace the values on the x axis, \"y\" enables a\nhorizontal axisvalues and \"xy\" enables them both. \"color\" is the color of the\naxisvalues (default is \"rgba(170, 0, 0, 0.80)\").\nThe plugin also adds four public methods:\n  - setAxisValues( pos )\n    Set the position of the axisvalues. Note that this is cleared if the user\n    moves the mouse. \"pos\" is in coordinates of the plot and should be on the\n    form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple\n    axes), which is coincidentally the same format as what you get from a\n    \"plothover\" event. If \"pos\" is null, the axisvalues is cleared.\n  - clearAxisValues()\n    Clear the axisvalues.\n  - lockAxisValues(pos)\n    Cause the axisvalues to lock to the current location, no longer updating if\n    the user moves the mouse. Optionally supply a position (passed on to\n    setAxisValues()) to move it to.\n    Example usage:\n\tvar myFlot = $.plot( $(\"#graph\"), ..., { axisvalues: { mode: \"x\" } } };\n\t$(\"#graph\").bind( \"plothover\", function ( evt, position, item ) {\n\t\tif ( item ) {\n\t\t\t// Lock the axisvalues to the data point being hovered\n\t\t\tmyFlot.lockAxisValues({\n\t\t\t\tx: item.datapoint[ 0 ],\n\t\t\t\ty: item.datapoint[ 1 ]\n\t\t\t});\n\t\t} else {\n\t\t\t// Return normal axisvalues operation\n\t\t\tmyFlot.unlockAxisValues();\n\t\t}\n\t});\n  - unlockAxisValues()\n    Free the axisvalues to move again after locking it.\n*/\n\n(function ($) {\n    var options = {\n        axisvalues: {\n            mode: null, // one of null, \"x\", \"y\" or \"xy\",\n            color: \"rgba(170, 0, 0, 0.80)\",\n            backgroundColor: \"#fee\",\n            opacity: 0.80\n        }\n    };\n\n    function init(plot) {\n        // position of axisvalues in pixels\n        var axisvalues = { x: -1, y: -1, locked: false };\n\n        plot.setAxisValues = function setAxisValues(pos) {\n            if (!pos) {\n                axisvalues.x = -1;\n            }else {\n                var o = plot.p2c(pos);\n                axisvalues.x = Math.max(0, Math.min(o.left, plot.width()));\n                axisvalues.y = Math.max(0, Math.min(o.top, plot.height()));\n            }\n\n            plot.triggerRedrawOverlay();\n        };\n\n        plot.clearAxisValues = plot.setAxisValues; // passes null for pos\n\n        plot.lockAxisValues = function lockAxisValues(pos) {\n            if (pos)\n                plot.setAxisValues(pos);\n            axisvalues.locked = true;\n        };\n\n        plot.unlockAxisValues = function unlockAxisValues() {\n            axisvalues.locked = false;\n        };\n\n        function onMouseOut(e) {\n            if (axisvalues.locked)\n                return;\n\n            $(\".axes-tooltips\").hide();\n\n            if (axisvalues.x !== -1) {\n                axisvalues.x = -1;\n                plot.triggerRedrawOverlay();\n            }\n        }\n\n        function onMouseMove(e) {\n            if (axisvalues.locked)\n                return;\n\n            if (plot.getSelection && plot.getSelection()) {\n                axisvalues.x = -1; // hide the axisvalues while selecting\n                return;\n            }\n\n            var offset = plot.offset();\n            if (0 <= e.pageX - offset.left && e.pageX - offset.left <= plot.width() && 0 <= e.pageY - offset.top && e.pageY - offset.top <= plot.height()) {\n                axisvalues.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));\n                axisvalues.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));\n                plot.triggerRedrawOverlay();\n            }else {\n                axisvalues.x = -1;\n            }\n        }\n\n        plot.hooks.bindEvents.push(function (plot, eventHolder) {\n            if (!plot.getOptions().axisvalues.mode)\n                return;\n\n            eventHolder.mouseout(onMouseOut);\n            eventHolder.mousemove(onMouseMove);\n        });\n\n        plot.hooks.drawOverlay.push(function (plot, ctx) {\n            var c = plot.getOptions().axisvalues;\n            if (!c.mode)\n                return;\n\n            var plotOffset = plot.getPlotOffset();\n            var xaxes = plot.getXAxes()\n            var yaxes = plot.getYAxes()\n            var offset = plot.offset();\n            tf = function (value, axis) {\n                return value.toFixed(axis.tickDecimals) + ((typeof axis.options.unit != \"undefined\") ? axis.options.unit : '');\n            };\n\n            if (axisvalues.x !== -1) {\n\n                if (c.mode.indexOf(\"x\") !== -1) {\n                    for (xaxis in xaxes) {\n                        var xaxisplusone = Number(xaxis) + 1;\n                        var fontSize = $(\"#y\" + xaxisplusone + \"-tooltip\").css(\"font-size\")\n                        fontSize = fontSize ? +fontSize.replace(\"px\", \"\") : 13;\n                        if (xaxes[xaxis].used && typeof xaxes[xaxis].box !== 'undefined' && typeof xaxes[xaxis].box.padding !== 'undefined' && typeof xaxes[xaxis].box.top !== 'undefined' && typeof xaxes[xaxis].box.height !== 'undefined') {\n                            var drawX = Math.floor(axisvalues.x);\n                            if (xaxes[xaxis].options.mode == \"time\") {\n                                dG = $.plot.dateGenerator(Number(xaxes[xaxis].c2p(drawX).toFixed(0)), xaxes[xaxis].options)\n                                dF = $.plot.formatDate(dG, xaxes[xaxis].options.timeformat, xaxes[xaxis].options.monthNames, xaxes[xaxis].options.dayNames);\n                                x_value = dF;\n                            }else {\n                                x_value = tf(xaxes[xaxis].c2p(drawX), xaxes[xaxis]);\n                            }\n                            x_length = x_value.includes(\":\") ? x_value.replace(\":\", \"\").length - 1 : x_value.length;\n                            if (xaxes[xaxis].position == \"top\") {\n                                $(\"#x\" + xaxisplusone + \"-tooltip\").html(x_value)\n                                    .css({top: offset.top - xaxes[xaxis].box.top - xaxes[xaxis].box.padding, left: offset.left + drawX - fontSize * x_length * 0.8 / 2})\n                                    .show();\n                            }else if (xaxes[xaxis].position == \"bottom\") {\n                                $(\"#x\" + xaxisplusone + \"-tooltip\").html(x_value)\n                                    .css({top: offset.top + xaxes[xaxis].box.top + xaxes[xaxis].box.padding - xaxes[xaxis].box.height + 2, left: offset.left + drawX - fontSize * x_length * 0.8 / 2})\n//                                    .css({top: offset.top + xaxes[xaxis].box.top - xaxes[xaxis].box.height, left: offset.left + drawX})\n                                    .show();\n                            }\n                        }\n                    }\n                }\n                if (c.mode.indexOf(\"y\") !== -1) {\n                    for (yaxis in yaxes) {\n                        var drawY = Math.floor(axisvalues.y),\n                        y_value = tf(yaxes[yaxis].c2p(drawY), yaxes[yaxis]),\n                        yaxisplusone = Number(yaxis) + 1;\n                        if (yaxes[yaxis].used && typeof yaxes[yaxis].box !== 'undefined' && typeof yaxes[yaxis].box.padding !== 'undefined' && typeof yaxes[yaxis].box.left !== 'undefined' && typeof yaxes[yaxis].box.width !== 'undefined' && $(\"#y\" + yaxisplusone + \"-tooltip\").length) {\n                            if (yaxes[yaxis].position == \"left\") {\n                                $(\"#y\" + yaxisplusone + \"-tooltip\").html(y_value)\n//                                    .css({top: offset.top + drawY - yaxes[yaxis].box.padding, left: offset.left - yaxes[yaxis].box.padding - window.getComputedStyle($(\"#y\" + yaxisplusone + \"-tooltip\")[0]).width.replace(\"px\", \"\")})\n                                    .css({top: offset.top + drawY - yaxes[yaxis].box.padding + yaxes[yaxis].labelHeight/2, left: offset.left + yaxes[yaxis].box.left - plotOffset.left + ((typeof yaxes[yaxis].options.axisLabel != \"undefined\") ? yaxes[yaxis].labelHeight : 0)})\n                                    .show();\n                            }else if (yaxes[yaxis].position == \"right\") {\n                                $(\"#y\" + yaxisplusone + \"-tooltip\").html(y_value)\n//                                    .css({top: offset.top + drawY - yaxes[yaxis].box.padding, left: offset.left + yaxes[yaxis].box.left + yaxes[yaxis].box.padding - yaxes[yaxis].box.width})\n                                    .css({top: offset.top + drawY - yaxes[yaxis].box.padding + yaxes[yaxis].labelHeight/2, left: offset.left + yaxes[yaxis].box.left + yaxes[yaxis].box.padding - plotOffset.left})\n                                    .show();\n                            }\n                        }\n                    }\n                }\n            }else {\n                //$(\".axes-tooltips\").hide();\n            }\n        });\n\n        plot.hooks.shutdown.push(function (plot, eventHolder) {\n            eventHolder.unbind(\"mouseout\", onMouseOut);\n            eventHolder.unbind(\"mousemove\", onMouseMove);\n        });\n    }\n\n    $.plot.plugins.push({\n        init: init,\n        options: options,\n        name: 'axisvalues',\n        version: '1.0'\n    });\n})(jQuery);"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/pyscada/TransformDataHmiPlugin.js",
    "content": "function PyScadaControlItemDisplayValueTransformDataMin(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataMin : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var result = null;\n  var data = sliceDATAusingTimestamps(variable_id);\n  for (d in data) {\n    if (result == null) {result = data[d][1]}\n    else{result = Math.min(result, data[d][1])}\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataMax(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataMax : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var result = null;\n  var data = sliceDATAusingTimestamps(variable_id);\n  for (d in data) {\n    if (result == null) {result = data[d][1]}\n    else{result = Math.max(result, data[d][1])}\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataTotal(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataTotal : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var result = 0;\n  var data = sliceDATAusingTimestamps(variable_id);\n  for (d in data) {\n    result += data[d][1]\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataDifference(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataDifference : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var result = null;\n  var data = sliceDATAusingTimestamps(variable_id);\n  if (data.length > 0) {\n    result = data[data.length - 1][1] - data[0][1]\n  }\n  return result;\n}\n\n\n// After PyScada Core JS loaded, add the variable needed by Difference and DifferencePercent to CHART_VARIABLE_KEYS\ndocument.addEventListener(\"PyScadaCoreJSLoaded\", (event) => {\n  document.querySelectorAll(\".transformdata-config2[data-inline-model-name='TransformDataDifference']\").forEach(e => {\n    tid=e.dataset[\"id\"];\n    document.querySelectorAll(\".displayvalueoption-config2[data-transform-data='\"+tid+\"']\").forEach(f => {\n      did=f.dataset[\"id\"];\n      document.querySelectorAll(\".controlitem-config2[data-display-value-options='\"+did+\"']\").forEach(g => {\n        if (g.dataset[\"variable\"]) {\n          CHART_VARIABLE_KEYS[g.dataset[\"variable\"]] = 0;\n        };\n      });\n    });\n  });\n  document.querySelectorAll(\".transformdata-config2[data-inline-model-name='TransformDataDifferencePercent']\").forEach(e => {\n    tid=e.dataset[\"id\"];\n    document.querySelectorAll(\".displayvalueoption-config2[data-transform-data='\"+tid+\"']\").forEach(f => {\n      did=f.dataset[\"id\"];\n      document.querySelectorAll(\".controlitem-config2[data-display-value-options='\"+did+\"']\").forEach(g => {\n        if (g.dataset[\"variable\"]) {\n          CHART_VARIABLE_KEYS[g.dataset[\"variable\"]] = 0;\n        };\n      });\n    });\n  });\n});\n\nfunction PyScadaControlItemDisplayValueTransformDataDifferencePercent(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataDifferencePercent : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var result = null;\n  var data = sliceDATAusingTimestamps(variable_id);\n  if (data.length > 0) {\n    result = data[data.length - 1][1] - data[0][1]\n    result = result / Math.abs(data[0][1] * 100)\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataDelta(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataDelta : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var result = 0;\n  var prev = null;\n  var data = sliceDATAusingTimestamps(variable_id);\n  for (d in data) {\n    if (prev != null && data[d][1] - prev > 0) {\n      result += data[d][1] - prev;\n    }\n    prev = data[d][1];\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataMean(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataMean : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var data = sliceDATAusingTimestamps(variable_id);\n  if (data.length == 0) {return null;}\n  var result = 0;\n  for (d in data) {\n    result += data[d][1]\n  }\n  return result / data.length;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataFirst(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataFirst : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var data = sliceDATAusingTimestamps(variable_id);\n  if (data.length == 0) {return null;}\n  return data[0][1];\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataCount(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataCount : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var result = 0;\n  var data = sliceDATAusingTimestamps(variable_id);\n  for (d in data) {\n    result += 1;\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataCountValue(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataCountValue : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var value = get_config_from_hidden_config('transformdatacountvalue', 'display-value-option', displayvalueoption_id, 'value');\n  var result = 0;\n  var data = sliceDATAusingTimestamps(variable_id);\n  for (d in data) {\n    if (data[d][1] == value) {\n      result += 1;\n    }\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataRange(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataRange : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var min = null;\n  var max = null;\n  var data = sliceDATAusingTimestamps(variable_id);\n  for (d in data) {\n    if (min == null) {\n      min = data[d][1];\n      max = data[d][1];\n    }\n    else{\n      min = Math.min(min, data[d][1]);\n      max = Math.max(max, data[d][1]);\n    }\n  }\n  return max - min;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataStep(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataStep : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var data = sliceDATAusingTimestamps(variable_id);\n  if (data.length == 0) {return null;}\n  var result = null;\n  var prev = null;\n  for (d in data) {\n    if (prev != null) {\n      if (result == null) {\n        result = Math.abs(data[d][1] - prev);\n      }else {\n        result = Math.min(result, Math.abs(data[d][1] - prev));\n      }\n    }\n    prev = data[d][1];\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataChangeCount(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataChangeCount : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var data = sliceDATAusingTimestamps(variable_id);\n  if (data.length == 0) {return null;}\n  var result = 0;\n  var prev = null;\n  for (d in data) {\n    if (prev != null && prev != data[d][1]) {\n      result += 1;\n    }\n    prev = data[d][1];\n  }\n  return result;\n}\n\nfunction PyScadaControlItemDisplayValueTransformDataDistinctCount(key, val, control_item_id, display_value_option_id, transform_data_id) {\n  var variable_id = get_config_from_hidden_config('controlitem', 'id', control_item_id, 'variable');\n  if (DATA[variable_id] == undefined) {\n    console.log(\"PyScada HMI : PyScadaControlItemDisplayValueTransformDataDistinctCount : \" + variable_id + \" not in DATA. \")\n    return val;\n  }\n  var data = sliceDATAusingTimestamps(variable_id);\n  if (data.length == 0) {return null;}\n  var result = 0;\n  var list = [];\n  for (d in data) {\n    if (!(data[d][1] in list)) {\n      result += 1;\n      list.push(data[d][1])\n    }\n  }\n  return result;\n}\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/pyscada/pyscada_tests.js",
    "content": "/*\nrun it it using :\nvar js = document.createElement(\"script\");\n\njs.type = \"text/javascript\";\njs.src = \"/static/pyscada/js/pyscada/pyscada_tests.js\";\n\ndocument.body.appendChild(js);\nfor (test in PYSCADA_TESTS) {PYSCADA_TESTS[test]();}\n*/\n\nfunction pyscada_test_add_data() {\n\n    // Warn if overriding existing method\n    if(Array.prototype.equals)\n        console.warn(\"Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.\");\n    // attach the .equals method to Array's prototype to call it on any array\n    Array.prototype.equals = function (array) {\n        // if the other array is a falsy value, return\n        if (!array)\n            return false;\n        // if the argument is the same array, we can be sure the contents are same as well\n        if(array === this)\n            return true;\n        // compare lengths - can save a lot of time\n        if (this.length != array.length)\n            return false;\n\n        for (var i = 0, l=this.length; i < l; i++) {\n            // Check if we have nested arrays\n            if (this[i] instanceof Array && array[i] instanceof Array) {\n                // recurse into the nested arrays\n                if (!this[i].equals(array[i]))\n                    return false;\n            }\n            else if (this[i] != array[i]) {\n                // Warning - two different object instances will never be equal: {x:20} != {x:20}\n                return false;\n            }\n        }\n        return true;\n    }\n    // Hide method from for-in loops\n    Object.defineProperty(Array.prototype, \"equals\", {enumerable: false});\n\n\n\n    key=\"test_add_data\";\n    CHART_VARIABLE_KEYS[key]=1;\n    DATA[key]=[[1,7], [2,8], [4,9], [11,15]];\n    console.log(DATA[key]);\n    ok=0;\n    fail=0;\n\n    function test_add_fetched_data(value, wanted) {\n        key=666;\n        CHART_VARIABLE_KEYS[key]=1;\n        DATA[key]=[[1,7], [2,8], [4,9], [11,15]];\n        add_fetched_data(key,value);\n        result = DATA[key].equals(wanted)\n        console.log(result);\n        if (!result) {console.log(DATA[key], value, wanted);};\n        return result;\n    }\n\n    value=[[0,1]];\n    wanted=[[0, 1], [1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[0,1],[1,1]];\n    wanted=[[0, 1], [1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[1,1]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[0,1],[2,1]];\n    wanted=[[0, 1], [1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[1,1],[2,1]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[2,1]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[2,1], [3,2]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[3,2]];\n    wanted=[[1, 7], [2, 8], [3, 2], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[5,2], [6,4]];\n    wanted=[[1, 7], [2, 8], [4, 9], [5, 2], [6, 4], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[3,2], [4,10]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[2,2], [4,10]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[2,2], [3, 2], [4,10]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[3,2], [6,10]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    value=[[11,2]];\n    wanted=[[1, 7], [2, 8], [4, 9], [11, 15]];\n    test_add_fetched_data(value, wanted) ? ok +=1 : fail += 1;\n\n    delete DATA[\"test_add_data\"]\n    console.log(\"pyscada_test_add_data : ok\", ok, \", fail\", fail);\n}\n\nPYSCADA_TESTS.push(pyscada_test_add_data)\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/pyscada/pyscada_v0-9-0.js",
    "content": "/* Javascript library for the PyScada web client based on jquery and flot,\n\nversion 0.9.0\n\nCopyright (c) 2013-2023 Martin Schröder, Camille Lavayssière\nLicensed under the AGPL.\n\n*/\n\n\n\n//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n//                                                      VARIABLES\n\n//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n/**\n * Script's version\n * @type {string}\n */\n var version = \"0.9.0\";\n\n /**\n  * Date format : day/month/year hours:minutes:seconds\n  * @type {string}\n  */\n var daterange_format = \"DD/MM/YYYY HH:mm:ss\";\n\n /**\n  * PyScada JavaScript tests\n  * Run using : for (test in PYSCADA_TESTS) {PYSCADA_TESTS[test]();}\n  * @type {Array<object>}\n  */\n var PYSCADA_TESTS = [];\n\n //                             -----------------------------------------------------------\n //                                             Client-Server's Variables\n //                             -----------------------------------------------------------\n /**\n  * Loading page state - 0 = not loaded / 1 = loaded\n  * @type {boolean}\n  */\n var LOADING_PAGE_DONE = 0;\n /**\n  * ???\n  */\n var debug = 0;\n\n\n // Loadings :\n /**\n  * Loading percentage\n  * @type {number}\n  */\n var loading_percent = 0;\n\n /**\n  * All recorded loading states\n  * @type {number}\n  */\n var loading_states = {};\n\n /**\n  * Current loading state\n  * @type {string}\n  */\n var loading_labels = {0:'CSS: ', 1:'Loading javascript: ', 4:'Loading static variables: ', 5:'Loading chart variables: ',};\n\n // Ajax :\n /**\n  * Count of all the JSON errors - used for errors notification.\n  * @type {number}\n  */\n var JSON_ERROR_COUNT = 0;\n\n /**\n  * Current server time\n  * @type {number}\n  */\n var SERVER_TIME = 0;\n\n /**\n  * Last recorded query time\n  * @type {number}\n  */\n var LAST_QUERY_TIME = 0;\n\n /**\n  * Token used for the ajax setup\n  * @type {string}\n  */\n var CSRFTOKEN = getCookie('csrftoken');\n\n /**\n  * Refresh rate in milliseconds - used to update data\n  * @type {number}\n  */\n var REFRESH_RATE = 2500;\n\n /**\n  * Cache timeout in milliseconds\n  * @type {number}\n  */\n var CACHE_TIMEOUT = 15000;\n\n /**\n  * Root's url - used to locate pyscada elements\n  * @type {string}\n  */\n var ROOT_URL = window.location.protocol+\"//\"+window.location.host + \"/\";\n\n // Log :\n /**\n  * Log's last timestamp - Collect the data last timestamp\n  * @type {number}\n  */\n var LOG_LAST_TIMESTAMP = 0;\n\n /**\n  * Count of fetching data pending\n  * @type {boolean}\n  */\n var LOG_FETCH_PENDING_COUNT = false;\n\n // Status :\n /**\n  * Chart initialization status count -\n  * @type {number}\n  */\n var INIT_STATUS_COUNT = 0;\n\n /**\n  * Chart update status count\n  * @type {number}\n  */\n var UPDATE_STATUS_COUNT = 0;\n\n /**\n  * Auto chart's data update button status\n  * @type {boolean}\n  */\n var AUTO_UPDATE_ACTIVE = true;\n\n /**\n  * Use synchronous data handling\n  * @type {boolean}\n  */\n var SYNC_HANDLING_DATA = true;\n\n /**\n  * Write task URL\n  * @type {string}\n  */\n var WRITE_TASK_URL = \"form/write_task/\";\n\n /**\n  * Read task URL\n  * @type {string}\n  */\n var READ_TASK_URL = \"form/read_task/\";\n\n /**\n  * Read all task URL\n  * @type {string}\n  */\n var READ_ALL_TASK_URL = \"form/read_all_task/\";\n\n /**\n  * Get cache data URL\n  * @type {string}\n  */\n var CACHE_DATA_URL = \"json/cache_data/\";\n\n /**\n  * Previous auto chart's data update button active\n  * @type {boolean}\n  */\n var PREVIOUS_AUTO_UPDATE_ACTIVE_STATE = false;\n\n /**\n  * Last end date\n  * @type {number}\n  */\n var PREVIOUS_END_DATE = 0;\n\n // Notifications :\n /**\n  * Count of all displayed notification\n  * @type {number}\n  */\n var NOTIFICATION_COUNT = 0;\n\n /**\n  * Id of the 'data out of date' error notification to display\n  * @type {number}\n  */\n var DATA_OUT_OF_DATE_ALERT_ID = '';\n\n\n  /**\n  * State of the daterangepicker\n  * @type {boolean}\n  */\n var DATERANGEPICKER_SET = false;\n\n  /**\n  * Default time delta value\n  * @type {number}\n  */\n var DEFAULT_TIME_DELTA = 0;\n\n/**\n* Ask before leaving the page\n*/\nvar ONBEFORERELOAD_ASK = true;\n\n  /**\n  * View id\n  * @type {number}\n  */\n var VIEW_ID = document.querySelector(\"body\").dataset[\"viewId\"];\n\n //                             -----------------------------------------------------------\n //                                                      Objects\n //                             -----------------------------------------------------------\n /**\n  * Chart's variables initialization count\n  * @type {number}\n  */\n var INIT_CHART_VARIABLES_COUNT = 0;\n\n /**\n  * Chart's variables initialization state\n  * @type {boolean}\n  */\n var INIT_CHART_VARIABLES_DONE = false;\n\n\n // List of Charts\n /**\n  * List of chart's variables keys\n  * @type {Array<number>}\n  */\n var CHART_VARIABLE_KEYS = {count:function(){var c = 0;for (var key in this){c++;} return c-2;},keys:function(){var k = [];for (var key in this){if (key !==\"keys\" && key !==\"count\"){k.push(key);}} return k;}};\n\n // Plot :\n\n /**\n  * List of all the chart to display\n  * @type {Array<object>}\n  */\n var PyScadaPlots = [];\n\n /**\n  * X axe's progress bar resize function state\n  * @type {boolean}\n  */\n var progressbar_resize_active = false;\n\n /**\n  * Crosshair status\n  * @type {boolean}\n  */\n var CROSSHAIR_LOCKED = false;\n\n\n //                             -----------------------------------------------------------\n //                                                  Data's Variables\n //                             -----------------------------------------------------------\n\n /**\n  * Holds the fetched data from the server\n  * @type {Array<object>}\n  */\n var DATA = {};\n /**\n  * Data initialization status\n  * @type {number}\n  */\n var DATA_INIT_STATUS = 0;\n\n /**\n  * Count of fetch data pending\n  * @type {number}\n  */\n var FETCH_DATA_PENDING = 0;\n\n /**\n  * Variables initialization done status\n  * @type {boolean}\n  */\n var INIT_STATUS_VARIABLES_DONE = false;\n\n /**\n  * Count of data fetching process\n  * @type {number}\n  */\n var DataFetchingProcessCount = 0;\n\n\n // Data's dates :\n /**\n  * Data out of date - would be used to display an error notification\n  * @type {boolean}\n  */\n var DATA_OUT_OF_DATE = false;\n\n /**\n  * Last data timestamp\n  * @type {number}\n  */\n var DATA_TO_TIMESTAMP = 0;\n\n /**\n  * First data timestamp\n  * @type {number}\n  */\n var DATA_FROM_TIMESTAMP = 0;\n\n /**\n  * First data timestamp to display\n  * @type {number}\n  */\n var DATA_DISPLAY_FROM_TIMESTAMP = -1;\n\n /**\n  * Last data timestamp to display\n  * @type {number}\n  */\n var DATA_DISPLAY_TO_TIMESTAMP = -1;\n\n /**\n  * Interval of time between the first data timestamp and the last one in milliseconds\n  * @type {number}\n  */\n var DATA_DISPLAY_WINDOW = 20*60*1000;\n\n // Data Time\n /**\n  * Size of the data buffer in milliseconds\n  * @type {number}\n  */\n var DATA_BUFFER_SIZE = 300*60*1000;\n\n /**\n  * Response timeout while retrieving data\n  * @type {number}\n  */\n var FETCH_DATA_TIMEOUT = 5000;\n\n // List of Datas\n /**\n  * List of all the variables keys\n  * @type {Array<number>}\n  */\n var VARIABLE_KEYS = [];\n\n /**\n  * List of variables property keys\n  * @type {Array<number>}\n  */\n var VARIABLE_PROPERTY_KEYS = [];\n\n /**\n  * List of variable status from keys\n  * @type {Array<string>}\n  */\n var STATUS_VARIABLE_KEYS = {count:function(){var c = 0;for (var key in this){c++;} return c-2;},keys:function(){var k = [];for (var key in this){if (key !==\"keys\" && key !==\"count\"){k.push(key);}} return k;}};\n\n /**\n  * List of variables properties\n  * @type {Array<object>}\n  */\n var VARIABLE_PROPERTIES = {};\n\n /**\n  * List of variable data\n  * @type {Array<object>}\n  */\n var VARIABLE_PROPERTIES_DATA = {};\n\n /**\n  * Historic of last modified variable properties\n  * @type {Array<object>}\n  */\n var VARIABLE_PROPERTIES_LAST_MODIFIED = {};\n\nvar store_temp_ajax_data = null;\n\n /**\n  * store all timeout ids for each functions\n  */\n var PYSCADA_TIMEOUTS = {};\n\n  /**\n   * store current ajax request\n   */\n  var PYSCADA_XHR = null;\n\n  var FETCH_CONTROLLERS = {}\n\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n //                                                      DATA\n\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n\n /**\n  * Adding fetched data\n  * @param {number} key Index used to fetch the data in 'DATA[]'\n  * @param {*} value The data\n  * @returns void\n  */\n function add_fetched_data(key,value){\n\n     // CHECK THE INPUT 'value' :\n\n     if (typeof(value)===\"object\"){\n         // check if there is values\n         if (value.length >0){\n             var event = new CustomEvent(\"pyscadaVariableDataChange-\" + key, {bubbles: false, cancelable: true, composed: false});\n             if (typeof(CHART_VARIABLE_KEYS[key]) === 'undefined'){\n                 // no history needed\n                 DATA[key] = [value.pop()];\n                 document.dispatchEvent(event);\n                 if (DATA[key][0] < DATA_FROM_TIMESTAMP){\n                     //DATA_FROM_TIMESTAMP = value[0][0];\n                 }\n             }else {\n                 // if the input data is not stored, we save it\n                 if (typeof(DATA[key]) == \"undefined\"){\n                     DATA[key] = value;\n                     document.dispatchEvent(event);\n                 } else {\n                     // Min and Max of 'value' and DATA\n                     var v_t_min = value[0][0];\n                     var v_t_max = value[value.length-1][0];\n                     var d_t_min = DATA[key][0][0];\n                     var d_t_max = DATA[key][DATA[key].length-1][0];\n\n\n                     // CHECKING 'value' and 'DATA' :\n\n                     if (v_t_min == d_t_min && v_t_max == d_t_max){\n                         // value and data seem equals, nothing to do\n                     } else if (v_t_min > d_t_max){\n                         // append, most likely\n                         DATA[key] = DATA[key].concat(value);\n                         document.dispatchEvent(event);\n                     } else if (v_t_min == d_t_max){\n                         if (value.length > 1){\n                            // append, drop first element of value\n                            DATA[key] = DATA[key].concat(value.slice(1));\n                            document.dispatchEvent(event);\n                         };\n                     } else if (v_t_max < d_t_min){\n                         // prepend,\n                         DATA[key] = value.concat(DATA[key]);\n                         document.dispatchEvent(event);\n                     } else if (v_t_max == d_t_min){\n                         // prepend, drop last element of value\n                         DATA[key] = value.slice(0,-1).concat(DATA[key]);\n                         document.dispatchEvent(event);\n                     } else if (v_t_max >= d_t_max && v_t_min <= d_t_min){\n                         // data and value overlapping, value has older and newer elements than data, prepend and append\n                         start_id = find_index_sub_lte(value,DATA[key][0][0],0);\n                         stop_id = find_index_sub_gte(value,DATA[key][DATA[key].length-1][0],0);\n                         if (typeof(stop_id) === \"number\" ){\n                             DATA[key] = DATA[key].concat(value.slice(stop_id));\n                             if (typeof(start_id) === \"number\" ){\n                                 DATA[key] = value.slice(0,start_id).concat(DATA[key]);\n                             }else{\n                                 console.log(\"PyScada HMI : var\" , key, \": dropped data, start_id not found.\", value, DATA[key][0][0]);\n                             }\n                             document.dispatchEvent(event);\n                         }else{\n                             console.log(\"PyScada HMI : var\" , key, \": dropped data, stop_id not found.\", value, DATA[key][DATA[key].length-1][0]);\n                         }\n                     }\n                     else if (v_t_max > d_t_min && v_t_min < d_t_min){\n                         // data and value overlapping, value has older elements than data, prepend\n                         stop_id = find_index_sub_lte(value,DATA[key][0][0],0);\n                         if (value[stop_id][0] != DATA[key][0][0]) {stop_id += 1;};\n                         if (typeof(stop_id) === \"number\" ){\n                             DATA[key] = value.slice(0,stop_id).concat(DATA[key]);\n                             document.dispatchEvent(event);\n                         }else{\n                             console.log(\"PyScada HMI : var\" , key, \": dropped data, stop_id not found.\", value, DATA[key][0][0]);\n                         }\n                     } else if (v_t_max > d_t_max && d_t_min < v_t_min){\n                         // data and value overlapping, data has older elements than value, append\n                         stop_id = find_index_sub_gte(value,DATA[key][DATA[key].length-1][0],0);\n                         if (typeof(stop_id) === \"number\" ){\n                             DATA[key] = DATA[key].concat(value.slice(stop_id));\n                             document.dispatchEvent(event);\n                         }else{\n                             console.log(\"PyScada HMI : var\" , key, \": dropped data, stop_id not found.\", value, DATA[key][DATA[key].length-1][0]);\n                         }\n                     } else{\n                         // data and value overlapping, data has older and newer elements than value, prepend and append only if value is not overlapping one element of data\n                         start_id = find_index_sub_lt(DATA[key],value[0][0],0);\n                         stop_id = find_index_sub_gt(DATA[key],value[value.length-1][0],0);\n                         if (stop_id == start_id +1) {\n                            if (value.length > 1) {\n                                if (DATA[key][start_id][0] == value[0][0]) {value=value.slice(1);}; // keep the element from DATA\n                                //start_id += 1;\n                            };\n                            start_id += 1;\n                            if (value.length > 0) {\n                                if (DATA[key][stop_id][0] == value[value.length-1][0]) {value=value.slice(0,-1);};\n                                if (value.length > 0) {\n                                    if (typeof(stop_id) === \"number\" && typeof(start_id) === \"number\" ){\n                                        DATA[key] = DATA[key].slice(0, start_id).concat(value).concat(DATA[key].slice(stop_id));\n                                        document.dispatchEvent(event);\n                                    }else{\n                                        console.log(\"PyScada HMI : var\" , key, \": dropped data, stop_id or start_id not found.\", start_id, stop_id, value[0][0], value[value.length-1][0], DATA[key][0][0], DATA[key][DATA[key].length-1][0]);\n                                    }\n                                }\n                            }\n                         }else{\n                             console.log(\"PyScada HMI : var\" , key, \": dropped data, value is not between 2 existing data.\");\n                         }\n                     }\n                 }\n             }\n         }else{\n             //console.log(key + ' : value.length==0')\n         }\n     }\n }\n\n\n\n //                             -----------------------------------------------------------\n //                                                  Data's Settings\n //                             -----------------------------------------------------------\n\n // TIME :\n\n /**\n  * Timestamp Conversion from data\n  * @param {number} id Data id\n  * @param {*} val\n  * @returns {string} Return a date\n  */\n function timestamp_conversion(id,val){\n     if (isNaN(val)) {\n         return val;\n     }else {\n         val = parseFloat(val);\n     }\n     if (id == 1){\n         // convert millisecond timestamp to local date\n         val = new Date(val).toDateString();\n     }else if (id == 2){\n         // convert millisecond timestamp to local time\n         val = new Date(val).toTimeString();\n     }else if (id == 3){\n         // convert millisecond timestamp to local date and time\n         val = new Date(val).toUTCString();\n     }else if (id == 4){\n         // convert second timestamp to local date\n         val = new Date(val * 1000).toDateString();\n     }else if (id == 5){\n         // convert second timestamp to local time\n         val = new Date(val * 1000).toTimeString();\n     }else if (id == 6){\n         // convert second timestamp to local date and time\n         val = new Date(val * 1000).toUTCString();\n     }\n     return val;\n }\n /**\n  * Convert milliseconds into time format\n  * @param {*} duration Time to convert\n  * @returns {string} Return a time string like\n  */\n function msToTime(duration) {\n     var milliseconds = parseInt(duration % 1000),\n       seconds = Math.floor((duration / 1000) % 60),\n       minutes = Math.floor((duration / (1000 * 60)) % 60),\n       hours = Math.floor((duration / (1000 * 60 * 60)) % 24);\n       days = Math.floor(duration / (1000 * 60 * 60 * 24));\n\n     //hours = (hours < 10) ? \"0\" + hours : hours;\n     //minutes = (minutes < 10) ? \"0\" + minutes : minutes;\n     //seconds = (seconds < 10) ? \"0\" + seconds : seconds;\n     if (days != 0) {\n       return days + \"d \" + hours + \"h \" + minutes + \"m \" + seconds + \"s\";\n     }else if (hours != 0) {\n       return hours + \"h \" + minutes + \"m \" + seconds + \"s\";\n     }else if (minutes != 0) {\n       return minutes + \"m \" + seconds + \"s\";\n     }else {\n       return seconds + \".\" + milliseconds + \"s\";\n     }\n }\n\n\n /**\n  * As data can take multiple format, we have to use a data dictionnary\n  * @param {number} id Data id\n  * @param {number|boolean} val Data format\n  * @returns {*} Returns data type of 'val' in the data dictionnary\n  */\n function dictionary(id, val, type){\n     var dict = get_config_from_hidden_config(type, 'id', id, 'dictionary');\n     if (typeof dict != 'undefined'){\n         l = get_config_from_hidden_configs(\"dictionaryitem\", 'id', 'dictionary')\n         for (item in l){\n             if (l[item] == dict && (get_config_from_hidden_config(\"dictionaryitem\", 'id', item, 'value') == val || get_config_from_hidden_config(\"dictionaryitem\", 'id', item, 'value') == parseFloat(val).toFixed(1))) { // last check : int stored as a float\n                 val = get_config_from_hidden_config(\"dictionaryitem\", 'id', item, 'label');\n             }\n         }\n     }\n     return val;\n }\n\nfunction colorToRgb(color_id) {\n    if (color_id == -1) {return [92, 200, 92];}\n    else if (color_id == 0) {return [210, 210, 210];}\n    var r = get_config_from_hidden_config(\"color\", 'id', color_id, 'r');\n    var g = get_config_from_hidden_config(\"color\", 'id', color_id, 'g');\n    var b = get_config_from_hidden_config(\"color\", 'id', color_id, 'b');\n    return [parseInt(r), parseInt(g), parseInt(b)]\n}\n\nfunction rgbToHex(rgb) {\n\n     function componentToHex(c) {\n         var hex = c.toString(16);\n         return hex.length == 1 ? \"0\" + hex : hex;\n     }\n\n    if (typeof rgb != \"object\" || rgb.constructor != Array || rgb.length != 3) {\n    console.log(\"PyScada HMI : cannot update data colors, rgb to hex error :\", typeof rgb, rgb.constructor, rgb.length, rgb);\n    return;\n    }\n    r = rgb[0]\n    g = rgb[1]\n    b = rgb[2]\n    return \"#\" + componentToHex(r) + componentToHex(g) + componentToHex(b);\n}\n\n /**\n  * Return control item color\n  * @param {number} id Control item id\n  * @param {boolean} val\n  * @returns {string} Return the hex color\n  */\n function update_data_colors(id,val){\n     if (typeof id == 'undefined' || id.split(\"-\").length < 2) {return;}\n     id = id.split(\"-\")[1]\n\n     var display_value_option_id = get_config_from_hidden_config(\"controlitem\", 'id', id, 'display-value-options');\n     // variable colors\n     var color_only = Number(get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'color-only'));\n     var gradient = get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'gradient');\n     if (gradient == \"True\") {gradient = 1;}else {gradient = 0;};\n     var gradient_higher_level = Number(get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'gradient-higher-level'));\n     var color_init = get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'color');\n     var colors = []\n     var color_options = get_config_from_hidden_configs(\"displayvaluecoloroption\", 'id', 'display-value-option');\n     for (dvco in color_options) {\n        if (display_value_option_id == color_options[dvco]) {\n            var level = Number(get_config_from_hidden_config(\"displayvaluecoloroption\", 'id', dvco, 'color-level'));\n            var level_type = Number(get_config_from_hidden_config(\"displayvaluecoloroption\", 'id', dvco, 'color-level-type'));\n            var color = get_config_from_hidden_config(\"displayvaluecoloroption\", 'id', dvco, 'color');\n            colors.push({'color': color, 'level': level, 'level_type':level_type})\n        }\n     }\n\n     var type = null;\n     var v_id = null;\n     if (get_config_from_hidden_config(\"controlitem\", 'id', id, 'variable') != 'None') {\n        type = \"variable\";\n        v_id = get_config_from_hidden_config(\"controlitem\", 'id', id, 'variable');\n     }else if (get_config_from_hidden_config(\"controlitem\", 'id', id, 'variable-property') != 'None') {\n        type = \"variableproperty\";\n        v_id = get_config_from_hidden_config(\"controlitem\", 'id', id, 'variable-property');\n     }\n\n     if (get_config_from_hidden_config(type, 'id', v_id, 'value-class') == 'BOOLEAN') {\n         if (val == false) { val = 0 ;} else if ( val == true ) { val = 1 ;}\n         if (color_init == null) {color_init = 0};\n         if (colors.length == 0) {\n            colors = [{'color': -1}];\n         }\n         colors[0]['level'] = 1;\n         colors[0]['level_type'] = 1;\n     }\n\n     if (typeof color_init == 'undefined') {return;}\n\n     var final_color = null;\n\n     // COLOR TYPE :\n     switch(gradient){\n         case 0:\n             var prev_color = color_init;\n             for (c in colors) {\n                if (colors[c]['level_type'] == 0) {\n                    if (val <= colors[c]['level']) {\n                        final_color = prev_color;\n                        break;\n                    }\n                }else {\n                    if (val < colors[c]['level']) {\n                        final_color = prev_color;\n                        break;\n                    }\n                }\n                prev_color = colors[c]['color'];\n             }\n             final_color = prev_color;\n         break;\n\n         case 1:\n             if (colors.length == 0) {return;}  // Need one display value color option\n             if (val <= colors[0]['level']) {\n                 final_color = color_init;\n             }else if (val >= gradient_higher_level) {\n                 final_color = colors[0]['color'];\n             }else {\n                 var fade = (val-colors[0]['level'])/(gradient_higher_level-colors[0]['level']);\n                 var color_1_new = new Color(Number(get_config_from_hidden_config(\"color\", 'id', color_init, 'r')),Number(get_config_from_hidden_config(\"color\", 'id', color_init, 'g')),Number(get_config_from_hidden_config(\"color\", 'id', color_init, 'b')));\n                 var color_2_new = new Color(Number(get_config_from_hidden_config(\"color\", 'id', colors[0]['color'], 'r')),Number(get_config_from_hidden_config(\"color\", 'id', colors[0]['color'], 'g')),Number(get_config_from_hidden_config(\"color\", 'id', colors[0]['color'], 'b')));\n                 final_color = colorGradient(fade, color_1_new, color_2_new);\n                 return rgbToHex(final_color);\n             }\n             break;\n     }\n\n     if (final_color == \"None\") {return null;}\n\n     return rgbToHex(colorToRgb(final_color));\n }\n\n\n //                             -----------------------------------------------------------\n //                                                  Data's Functions\n //                             -----------------------------------------------------------\n\n /**\n  * Transform data using control item display value option function\n  * @param {number} id Control item id\n  * @param {number} val\n  * @returns {number} Return the transformed value\n  */\nfunction transform_data(control_item_id, val, key) {\n  var display_value_option_id = get_config_from_hidden_config(\"controlitem\", 'id', control_item_id, 'display-value-options');\n  var transform_data_id = get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'transform-data');\n  var transform_data_function_name = get_config_from_hidden_config(\"transformdata\", 'id', transform_data_id, 'js-function-name');\n\n  if (transform_data_function_name != null && transform_data_function_name != \"\") {\n      if (typeof window[transform_data_function_name] === \"function\") {\n          var transform_data_function = eval(transform_data_function_name);\n          val = transform_data_function.call(null , key, val, control_item_id, display_value_option_id, transform_data_id);\n      }else {console.log(\"PyScada HMI : \" + transform_data_function_name + \" function not found.\")}\n  }\n  return val\n}\n\n\n /**\n  * Update variable data values and refresh logo\n  * @param {number} key Data id to update\n  * @param {object} data The data of the object\n  */\n function update_data_values(key,data){\n     // CHECKING 'key' TYPE :\n     if (key.split(\"-\")[0] == \"var\") {var type=\"variable\";} else {var type=\"variable-property\";}\n\n     // get the unit\n     var unit_id = get_config_from_hidden_config(type,'id',key.split(\"-\")[1],'unit')\n     var unit = get_config_from_hidden_config('unit','id',unit_id,'unit')\n     if (typeof unit == 'undefined') { unit = '' };\n\n     // get the device polling interval\n     if (type == \"variable\") {\n        var var_id = key.split(\"-\")[1];\n     }else {\n        var var_id = get_config_from_hidden_config(type, 'id', key.split(\"-\")[1], 'variable')\n     }\n     var device_id = get_config_from_hidden_config(\"variable\", 'id', var_id, 'device')\n     var device_polling_interval = get_config_from_hidden_config(\"device\", 'id', device_id, 'polling-interval')\n\n     // TYPE OF 'val' :\n     // NUMBER and BOOLEAN :\n    // timestamp, dictionary and color\n    document.querySelectorAll(\".control-item.type-numeric.\" + key + \", button.write-task-btn.\" + key).forEach(function(e) {\n        var control_item_id = e.id;\n        var ci_label = get_config_from_hidden_config(\"controlitem\", 'id', control_item_id.split('-')[1], 'label');\n        var display_value_option_id = get_config_from_hidden_config(\"controlitem\", 'id', control_item_id.split('-')[1], 'display-value-options');\n        var from_timestamp_offset = get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'from-timestamp-offset');\n        var var_id = get_config_from_hidden_config(\"controlitem\", 'id', control_item_id.split('-')[1], type);\n        var var_readable = get_config_from_hidden_config(\"variable\", 'id', var_id, \"readable\");\n        var var_class = get_config_from_hidden_config(\"variable\", 'id', var_id, \"valueClass\");\n        var glyphicon_alert = e.parentElement.querySelector('.glyphicon-alert')\n        var glyphicon_exclamation = e.parentElement.querySelector('.glyphicon-exclamation-sign')\n\n    // get time and value depending on date range picker, timeline and control item option\n    if (typeof(data) !==\"undefined\" && data !== null && data.length){\n        from_timestamp_offset = parseFloat(from_timestamp_offset);\n        if (isNaN(from_timestamp_offset)){\n            var data_temp = data;\n        }else {\n            var data_temp = sliceDATAusingTimestamps(data, display_from=DATA_DISPLAY_FROM_TIMESTAMP-from_timestamp_offset, display_to=DATA_DISPLAY_TO_TIMESTAMP, from=DATA_FROM_TIMESTAMP-from_timestamp_offset, to=DATA_TO_TIMESTAMP);\n        }\n        if (data_temp.length){\n            var val = data_temp[data_temp.length-1][1];\n            var time = data_temp[data_temp.length-1][0];\n        }else {\n            var val = \"No data\";\n            var time = null;\n        }\n    }else {\n        var val = \"No data\";\n        var time = null;\n    }\n\n    // TIME UPDATE :\n    if (time != null) {\n        var t_last_update = SERVER_TIME - time;\n        var t_next_update = 1000 * device_polling_interval - t_last_update;\n        var t_next_update_string = ((t_next_update < 1000) ? '< 1 sec' : msToTime(t_next_update));\n        var tooltip_text = 'Current value: ' + val + ' ' + unit + '<br>Last update: ' + msToTime(t_last_update) + ' ago';\n        if (var_readable === \"True\") {\n            tooltip_text += '<br>Next update: ' + t_next_update_string;\n\n            // Show and hide warning or alert icons left the the variable name when data is old in comparison of the device polling interval\n            if (time < SERVER_TIME - 10 * Math.max(1000 * device_polling_interval, REFRESH_RATE)) {\n                glyphicon_alert !== null ? glyphicon_alert.classList.remove(\"hidden\") : null;\n                glyphicon_exclamation !== null ? glyphicon_exclamation.classList.add(\"hidden\") : null;\n            }else if (time < SERVER_TIME - 3 * Math.max(1000 * device_polling_interval, REFRESH_RATE)) {\n                glyphicon_alert !== null ? glyphicon_alert.classList.add(\"hidden\") : null;\n                glyphicon_exclamation !== null ? glyphicon_exclamation.classList.remove(\"hidden\") : null;\n            }else {\n                glyphicon_alert !== null ? glyphicon_alert.classList.add(\"hidden\") : null;\n                glyphicon_exclamation !== null ? glyphicon_exclamation.classList.add(\"hidden\") : null;\n            }\n        }\n\n        e.setAttribute('data-original-title', tooltip_text);\n        set_config_from_hidden_config(type.replace('-', ''), 'id', key.split(\"-\")[1], 'value-timestamp', time)\n\n\n    }\n\n    // Quit if the value is not newer than the last read request\n    var refresh_requested_timestamp = parseFloat(get_config_from_hidden_config(type.replace('-', ''), 'id', key.split(\"-\")[1], 'refresh-requested-timestamp'));\n    if (refresh_requested_timestamp != null && time != null && time <= refresh_requested_timestamp) {\n        return;\n    }\n\n    var temp_val = transform_data(control_item_id.split(\"-\")[1], val, key);\n    var r_val = dictionary(var_id, temp_val, type.replace('-', ''));\n\n    if(typeof(r_val)===\"boolean\" || Math.abs(r_val) == 0) {\n        // don't change the rendering for boolean or 0\n        r_val = r_val;\n    }else if(Math.abs(r_val) < 1) {\n        r_val = r_val.toExponential(2);\n    }else if(r_val > 0) {\n        r_val = r_val.toPrecision(4);\n    };\n\n    if (typeof(val)===\"number\" || typeof(val)===\"boolean\" || typeof(val)===\"string\"){\n\n        // value update, dictionary, timestamp convertion\n        var color_only = get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'color-only');\n        var timestamp_conversion_value = get_config_from_hidden_config(\"displayvalueoption\", 'id', display_value_option_id, 'timestamp-conversion');\n\n        // if element is a button, update it according to the value\n        if (!Boolean(val) && e.classList.contains(\"btn-success\")) {e.classList.add(\"update-able\"); e.classList.add(\"btn-default\"); e.classList.remove(\"btn-success\")};\n        if (Boolean(val) && e.classList.contains(\"btn-default\")) {e.classList.add(\"update-able\"); e.classList.add(\"btn-success\"); e.classList.remove(\"btn-default\")};\n        // update input placeholder\n        document.querySelectorAll(\".form-control.\" + key).forEach(e => {e.placeholder = val + \" \" + unit});\n\n        if (typeof(color_only)==\"undefined\" || color_only != 'True') {\n                if (e.querySelector('.boolean-value') != null){\n                // Set the text value\n                e.querySelector('.boolean-value').innerHTML = ci_label + \" : \" + r_val + \" \" + unit;\n                }else {\n                if (timestamp_conversion_value != null && timestamp_conversion_value != 0 && typeof(timestamp_conversion_value) != \"undefined\"){\n                    // Transform timestamps\n                    r_val=timestamp_conversion(timestamp_conversion_value,r_val);\n                }\n                // Set the text value\n                e.innerHTML = r_val + \" \" + unit;\n            }\n        }\n        if (display_value_option_id != 'None'){\n            // Change background color\n            if (e.classList.contains(\"process-flow-diagram-item\")) {\n                var old_color = e.style.fill;\n            }else {\n                var old_color = e.style.backgroundColor;\n            }\n            if (old_color != \"\") {\n                if (old_color.startsWith(\"rgb(\") && old_color.endsWith(\")\")) {\n                    old_color_temp = []\n                    old_color = old_color.replace(\"rgb(\", \"\").replace(\")\", \"\").split(\",\");\n                    for (c in old_color) {old_color_temp.push(parseInt(old_color[c]))}\n                    old_color = rgbToHex(old_color_temp);\n                }else {\n                    old_color = \"\";\n                }\n            }\n            var color = update_data_colors(control_item_id,temp_val);\n            if (old_color != color) {\n                if (e.classList.contains(\"process-flow-diagram-item\")) {\n                    e.style.fill = color;\n                }else {\n                    e.style.backgroundColor = color;\n                }\n                // create event to announce color change for a control item\n                var event = new CustomEvent(\"changePyScadaControlItemColor_\" + control_item_id.split('-')[1], { detail: color });\n                window.dispatchEvent(event);\n            };\n        }\n    }else {\n        console.log(\"Invalid data format for \" + control_item_id + \" : \" + typeof(val) + \" : \" + val);\n    }\n    })\n\n    // update chart legend\n    if (typeof(data) !==\"undefined\" && data !== null && data.length){\n        var val = data[data.length-1][1];\n        var time = data[data.length-1][0];\n    }else {\n        var val = \"\";\n        var time = null;\n    }\n    if (typeof(val)===\"number\") {\n        var r_val = Number(val);\n\n        // adjusting r_val\n        if(Math.abs(r_val) == 0 ){\n            r_val = 0;\n        }else if(Math.abs(r_val) < 0.001) {\n            r_val = r_val.toExponential(2);\n        }else if (Math.abs(r_val) < 0.01) {\n            r_val = r_val.toExponential(2);\n        }else if(Math.abs(r_val) < 0.1) {\n            r_val = r_val.toExponential(2);\n        }else if(Math.abs(r_val) < 1) {\n            r_val = r_val.toExponential(2);\n        }else if(r_val > 100) {\n            r_val = r_val.toPrecision(4);\n        }else{\n            r_val = r_val.toPrecision(4);\n        }\n    }else if (typeof(val)===\"boolean\"){\n        var r_val = val;\n        if (r_val === 0 | r_val == false) {\n            r_val = 0;\n        } else {\n            r_val = 1;\n        }\n    }else {var r_val = val;}\n\n    if (DATA_DISPLAY_FROM_TIMESTAMP > 0 && time < DATA_DISPLAY_FROM_TIMESTAMP) {\n        r_val = \"\";\n    }else if (DATA_DISPLAY_TO_TIMESTAMP > 0 && time > DATA_DISPLAY_TO_TIMESTAMP) {\n        r_val = \"\";\n    }else if (DATA_FROM_TIMESTAMP > 0 && time < DATA_FROM_TIMESTAMP) {\n        r_val = \"\";\n    }else if (DATA_TO_TIMESTAMP > 0 && time > DATA_TO_TIMESTAMP) {\n        r_val = \"\";\n    }else {\n    }\n    document.querySelectorAll(\".legendValue.type-numeric.\" + key).forEach(e => e.innerHTML = r_val);\n\n     refresh_logo(key.split(\"-\")[1], type);\n }\n\n\n/**\n- Get a config from ALL hidden config div\n- @param {string} type Name of the model\n- @param {string} filter_data name of the data config to filter by\n- @param {string} get_data name of the data config wanted\n- @returns {Array<object>} dict of (id: value) of found values\n*/\nfunction get_config_from_hidden_configs(type,filter_data='id',get_data='id'){\n    var result = {};\n    if (typeof(type)!== 'string' || typeof(filter_data) !== 'string' || typeof(get_data) !== 'string' || filter_data === '' || get_data === '' || type === '') {\n      return result;\n    };\n    var query = document.querySelectorAll(\".\" + type + \"-config2\");\n    query.forEach(item => {\n        //var id = item.dataset.id;\n        var id = item.getAttribute(\"data-\" + filter_data);\n        var r = item.getAttribute(\"data-\" + get_data);\n        if (id in result === false && typeof(r) !== \"undefined\") {\n            result[id] = r;\n        };\n    });\n    return result;\n}\n\n\n/**\n- Get a config from ONE hidden config div\n- @param {string} type Name of the model\n- @param {string} filter_data name of the data config to filter by\n- @param {string} val value of the data config to filter by\n- @param {string} get_data name of the data config wanted\n- @returns {Array<object>} value found\n*/\nfunction get_config_from_hidden_config(type,filter_data,val,get_data){\n    var r = get_config_from_hidden_configs(type,filter_data,get_data);\n    if (val in r) {return r[val];}\n}\n\n/**\n- Set a config from ONE hidden config div\n- @param {string} type Name of the model\n- @param {string} filter_data name of the data config to filter by\n- @param {string} val value of the data config to filter by\n- @param {string} get_data name of the data config wanted\n- @param {string} value set get_data filed to this\n*/\nfunction set_config_from_hidden_config(type,filter_data,val,get_data,value){\n    document.querySelectorAll(\".\" + type + \"-config2[data-\" + filter_data + \"='\" + val + \"']\").forEach(function(e) {\n    e.setAttribute(\"data-\" + get_data, value);\n    })\n}\n\n\n /**\n  * Update 'DATA', by erasing datas which are out of date\n  * @param {number} key Data id to update\n  */\n function check_buffer(key){\n     if ((DATA[key][0][0] < DATA_FROM_TIMESTAMP)){\n         stop_id = find_index_sub_lte(DATA[key],DATA_FROM_TIMESTAMP,0);\n         DATA[key] = DATA[key].splice(stop_id);\n     }\n }\n\n function add_key_to_chart_vars(key, var_count, vars, device_pulling_interval_sum, timestamp, var_count_poll) {\n    if(key in CHART_VARIABLE_KEYS && CHART_VARIABLE_KEYS[key]<=DATA_INIT_STATUS){\n         CHART_VARIABLE_KEYS[key]++;\n         var_count++;\n         INIT_CHART_VARIABLES_COUNT++;\n         vars.push(key);\n         dpi = get_config_from_hidden_config('device','id',get_config_from_hidden_config('variable','id',key,'device') ,'polling-interval');\n         if (! isNaN(dpi)) {device_pulling_interval_sum += parseFloat(dpi);var_count_poll++;}else {console.log(\"PyScada HMI : ConfigV2 not found for var\", key);};\n         if (typeof(DATA[key]) == 'object'){\n             timestamp = Math.max(timestamp,DATA[key][0][0]);\n         }else{\n            // if a key doesn't exist in DATA timestamp to is set to now\n            timestamp = SERVER_TIME;\n         }\n    }\n    return [key, var_count, vars, device_pulling_interval_sum, timestamp, var_count_poll];\n }\n\n /**\n  * Update periodically the DATA, by requesting the server\n  */\n function data_handler(){\n     if(AUTO_UPDATE_ACTIVE || !INIT_STATUS_VARIABLES_DONE || !INIT_CHART_VARIABLES_DONE){\n         if(DATA_TO_TIMESTAMP==0 && FETCH_DATA_PENDING<=0){\n         // fetch the SERVER_TIME\n             data_handler_ajax(0,[],[],Date.now());\n         }else{\n             if(FETCH_DATA_PENDING<=0 && INIT_STATUS_VARIABLES_DONE && INIT_CHART_VARIABLES_DONE){\n             // fetch new data\n                 data_handler_ajax(0, VARIABLE_KEYS, VARIABLE_PROPERTY_KEYS, LAST_QUERY_TIME);\n             }\n             // fetch historic data\n             else if(FETCH_DATA_PENDING<=0){\n                 if(!INIT_STATUS_VARIABLES_DONE){\n                 loading_states[4] || set_loading_state(4, 0);\n                 // first load STATUS_VARIABLES\n                     var var_count = 0;\n                     var vars = [];\n                     var props = [];\n                     var timestamp = DATA_TO_TIMESTAMP;\n                     for (var key in STATUS_VARIABLE_KEYS){\n                         if (typeof(CHART_VARIABLE_KEYS[key]) === 'undefined'){\n                             if(STATUS_VARIABLE_KEYS[key]<1){\n                                 STATUS_VARIABLE_KEYS[key]++;\n                                 var_count++;\n                                 vars.push(key);\n                             }\n                         }\n                         if(var_count >= 5){break;}\n                     }\n                     if(var_count>0){\n                         data_handler_ajax(1,vars,props,timestamp);\n                         set_loading_state(4, (loading_states[4] || 0) + 100*var_count/STATUS_VARIABLE_KEYS.count());\n                     }else{\n                         INIT_STATUS_VARIABLES_DONE = true;\n                         set_loading_state(4, 100);\n                     }\n                 }else if (!INIT_CHART_VARIABLES_DONE){\n                     loading_states[5] || set_loading_state(5, 0);\n                     var var_count = 0;\n                     var var_count_poll = 0;\n                     var vars = [];\n                     var props = [];\n                     var device_pulling_interval_sum = 0.0\n                     if (DATA_FROM_TIMESTAMP == -1){\n                         var timestamp = SERVER_TIME;\n                     }else{\n                         var timestamp = DATA_FROM_TIMESTAMP;\n                     }\n\n                     var page = document.querySelector('#content .sub-page:not([style*=\"display:none\"]):not([style*=\"display: none\"])');\n                     var visible_hidden_config = page != null ? page.querySelectorAll('.hidden.variable-config') : [];\n                     var visible_vars = [];\n                     for (let vhc of visible_hidden_config.keys()) {\n                         visible_vars.push(visible_hidden_config[vhc].dataset['key']);\n                     }\n\n                     // First iterate on visible variables\n                     for (var key in visible_vars){\n                        key = visible_vars[key];\n                        if(var_count >= 10){break;}\n                        [key, var_count, vars, device_pulling_interval_sum, timestamp, var_count_poll] = add_key_to_chart_vars(key, var_count, vars, device_pulling_interval_sum, timestamp, var_count_poll);\n                     }\n                     if (var_count == 0) {\n                         for (var key in CHART_VARIABLE_KEYS){\n                            if(var_count >= 10){break;}\n                            [key, var_count, vars, device_pulling_interval_sum, timestamp, var_count_poll] = add_key_to_chart_vars(key, var_count, vars, device_pulling_interval_sum, timestamp, var_count_poll);\n                         }\n                     }\n                     if(var_count>0){\n                         //set_loading_state(5, (loading_states[5] || 0) + 100*var_count/CHART_VARIABLE_KEYS.count());\n                         if (timestamp === DATA_FROM_TIMESTAMP){\n                             timestamp = DATA_DISPLAY_TO_TIMESTAMP;\n                         }\n                         if (timestamp == -1){\n                             //var timestamp = SERVER_TIME;\n                             timestamp = DATA_TO_TIMESTAMP;\n                         }\n                         timestamp = Math.min(timestamp, DATA_TO_TIMESTAMP)  // prevent loading and showing data after DATA_TO_TIMESTAMP\n                         request_duration = timestamp - DATA_FROM_TIMESTAMP\n                         // Fetch 1 000 points by var\n                         point_quantity_to_fetch_by_var = 1000;\n                         t_start = DATA_FROM_TIMESTAMP;\n                         if (var_count_poll > 0) {\n                           duration_for_quantity = point_quantity_to_fetch_by_var * device_pulling_interval_sum / var_count_poll;\n                           duration_for_quantity = duration_for_quantity * 10 / var_count  //adjust for less than 10 vars\n                           duration_for_quantity = parseInt(duration_for_quantity);\n                           t = Math.max(timestamp - duration_for_quantity * 1000, t_start);\n                         }else {\n                           t = t_start;\n                           duration_for_quantity = 1;\n                         }\n                         FETCH_DATA_PENDING++;\n                         store_temp_ajax_data = [1,vars,props,t_start,t,timestamp,duration_for_quantity,timestamp]\n                         //data_handler_ajax(1,vars,props,DATA_FROM_TIMESTAMP,timestamp);\n                     }else{\n                         INIT_CHART_VARIABLES_DONE = true;\n                         set_loading_state(5, 100);\n                         $('.loadingAnimation').hide();\n                     }\n                 }\n             }else if (FETCH_DATA_PENDING<=1 && store_temp_ajax_data !== null) {\n               /*\n               */\n               vars = store_temp_ajax_data[1]\n               props = store_temp_ajax_data[2]\n               t_start = store_temp_ajax_data[3]\n               t = store_temp_ajax_data[4]\n               timestamp = store_temp_ajax_data[5]\n               duration_for_quantity = store_temp_ajax_data[6]\n               tmax = store_temp_ajax_data[7]\n               set_loading_state(5, (loading_states[5] || 0) + 100*(vars.length/CHART_VARIABLE_KEYS.count())*((timestamp-t)/(tmax-t_start)));\n               //data_handler_ajax(1,vars,props,t_start,t);\n               data_handler_ajax(1,vars,props,t,timestamp);\n               if (t_start < t) {\n                 timestamp = t;\n                 t = Math.max(t - duration_for_quantity * 1000, t_start);\n                 store_temp_ajax_data = [1,vars,props,t_start,t,timestamp,duration_for_quantity,tmax];\n               }else {\n                 FETCH_DATA_PENDING--;\n                 store_temp_ajax_data = null;\n               }\n             }\n         }\n     }\n\n     // call the data handler periodically\n     if(!INIT_STATUS_VARIABLES_DONE || !INIT_CHART_VARIABLES_DONE){\n         // initialisation is active\n         //setTimeout(function() {data_handler();}, REFRESH_RATE/2.0);\n         if (STATUS_VARIABLE_KEYS.count() + CHART_VARIABLE_KEYS.count() == 0 && LOADING_PAGE_DONE == 0) {LOADING_PAGE_DONE = 1;show_page();hide_loading_state();}\n         PYSCADA_TIMEOUTS[\"data_handler\"] = setTimeout(function() {data_handler();}, 100);\n     }else{\n         if (LOADING_PAGE_DONE == 0) {LOADING_PAGE_DONE = 1;show_page();hide_loading_state();loading_states={};}\n         PYSCADA_TIMEOUTS[\"data_handler\"] = setTimeout(function() {data_handler();}, REFRESH_RATE);\n     }\n }\n\n\n /**\n  * Send data to the Data handler\n  * @param {boolean} init Show update status or not\n  * @param {Array<number>} variable_keys List of variables to update\n  * @param {Array<*>} variable_property_keys List of variable properties to update\n  * @param {number} timestamp_from Update data from this timestamp\n  * @param {number} timestamp_to Update data to this timestamp\n  */\n function data_handler_ajax(init,variable_keys,variable_property_keys,timestamp_from,timestamp_to){\n     show_update_status();\n     FETCH_DATA_PENDING++;\n     if(init){show_init_status();}\n     request_data = {timestamp_from:timestamp_from, variables: variable_keys, init: init, variable_properties:variable_property_keys, view_id: VIEW_ID};\n     if (typeof(timestamp_to) !== 'undefined'){request_data['timestamp_to']=timestamp_to};\n     //if (!init){request_data['timestamp_from'] = request_data['timestamp_from'] - REFRESH_RATE;};\n     const formData = new FormData();\n     for ( var key in request_data ) {\n         formData.append(key, request_data[key]);\n     }\n     var controller = new AbortController();\n     var timeout = ((init == 1) ? FETCH_DATA_TIMEOUT*5: FETCH_DATA_TIMEOUT);\n     var timer = setTimeout(() => {add_notification(\"Timeout while fetching data\", 3, 0.9*5*REFRESH_RATE);controller.abort();}, timeout);\n     const signal = controller.signal;\n     FETCH_CONTROLLERS[\"data_handler_ajax\"] = controller\n     fetch(\n         ROOT_URL+CACHE_DATA_URL, {\n         method: \"POST\",\n         headers: {'X-CSRFToken': CSRFTOKEN},\n         body:formData,\n         signal: signal,\n         redirect: \"follow\",\n         })\n     .then((response) => {\n        clearTimeout(timer);\n        timer = null;\n        if (response.redirected) {\n            var next = \"?next=\";\n            var url_redirect = response.url;\n            if (response.url.includes(next)) {url_redirect = url_redirect.split(next)[0] + next + window.location.href;};\n            if (AUTO_UPDATE_ACTIVE){auto_update_click();}\n            add_notification(\"Authentication issue, please log-in <a href='\" + url_redirect + \"'>here</a>\", 3, 0, 0);\n            //window.location.href = response.url;\n        }else {\n            if (!response.ok || response.status != 200) {\n                data_handler_fail(response);\n            } else {\n                return response.json();\n            }\n        }\n     }).then((json) => {data_handler_done(json);})\n     .catch((error) => {\n        if (timer === null){add_notification(\"data_handler_ajax : \" + error, 3, 0.9*REFRESH_RATE);};\n        increase_error_count();\n     }).then(() => {\n        hide_update_status();\n        FETCH_DATA_PENDING--;\n     });\n}\n\nfunction pad(value) {\n    return value < 10 ? '0' + value : value;\n}\nfunction createOffset(date) {\n    var sign = (date.getTimezoneOffset() > 0) ? \"-\" : \"+\";\n    var offset = Math.abs(date.getTimezoneOffset());\n    var hours = pad(Math.floor(offset / 60));\n    var minutes = pad(offset % 60);\n    return sign + hours + \":\" + minutes;\n}\n\n /**\n  * Update DATA and Charts when initialization is done\n  * @param {Array<*>} fetched_data\n  */\n function data_handler_done(fetched_data){\n\n    if (typeof(fetched_data) === \"undefined\") {console.log(\"PyScada HMI : fetched data undefined\"); return;}\n\n     update_charts = true;\n\n     // checking 'fectched_data' type\n     if (typeof(fetched_data['timestamp'])===\"number\"){\n         timestamp = fetched_data['timestamp'];\n         delete fetched_data['timestamp'];\n     }else{\n         timestamp = 0;\n     }\n\n     if (typeof(fetched_data['server_time'])===\"number\"){\n         SERVER_TIME = fetched_data['server_time'];\n         delete fetched_data['server_time'];\n         var date = new Date(SERVER_TIME);\n         $(\".server_time\").html(date.toLocaleString() + \" (\" + createOffset(date) + \" GMT)\");\n     }else{\n         SERVER_TIME = 0;\n     }\n\n     if (typeof(fetched_data['date_saved_max'])===\"number\"){\n         LAST_QUERY_TIME = fetched_data['date_saved_max'];\n         delete fetched_data['date_saved_max'];\n     }else{\n         //LAST_QUERY_TIME = 0;\n     }\n\n     if (typeof(fetched_data['variable_properties'])===\"object\"){\n         for (vp_key in fetched_data['variable_properties']){\n            VARIABLE_PROPERTIES_DATA[vp_key] = fetched_data['variable_properties'][vp_key]\n         }\n         delete fetched_data['variable_properties'];\n         for (vp_key in fetched_data['variable_properties_last_modified']){\n            VARIABLE_PROPERTIES_LAST_MODIFIED[vp_key] = fetched_data['variable_properties_last_modified'][vp_key]\n         }\n         delete fetched_data['variable_properties_last_modified'];\n     }\n\n     // update data timestamp\n     if(DATA_TO_TIMESTAMP==0){\n         //DATA_TO_TIMESTAMP = DATA_FROM_TIMESTAMP = SERVER_TIME;\n         DEFAULT_TIME_DELTA = document.querySelector(\"body\").getAttribute(\"data-view-time-delta\") == null ? 7200 : parseFloat(document.querySelector(\"body\").getAttribute(\"data-view-time-delta\"));\n         DATA_TO_TIMESTAMP = SERVER_TIME;\n         DATA_FROM_TIMESTAMP = SERVER_TIME - DEFAULT_TIME_DELTA * 1000;\n     }else{\n         $.each(fetched_data, function(key, val) {\n             add_fetched_data(parseInt(key),val);\n         });\n         if (DATA_TO_TIMESTAMP < timestamp){\n             DATA_TO_TIMESTAMP = timestamp;\n             if ((DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP)> DATA_BUFFER_SIZE){\n                 DATA_BUFFER_SIZE = DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP\n                 //DATA_FROM_TIMESTAMP = DATA_TO_TIMESTAMP - DATA_BUFFER_SIZE;\n             }\n             if (DATA_DISPLAY_TO_TIMESTAMP < 0 && DATA_DISPLAY_FROM_TIMESTAMP < 0){\n                 // both fixed\n                 DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP;\n             }else if (DATA_DISPLAY_FROM_TIMESTAMP > 0 && DATA_DISPLAY_TO_TIMESTAMP < 0){\n                 // to time is fixed\n                 DATA_DISPLAY_FROM_TIMESTAMP = DATA_TO_TIMESTAMP - DATA_DISPLAY_WINDOW;\n             }else if (DATA_DISPLAY_FROM_TIMESTAMP < 0 && DATA_DISPLAY_TO_TIMESTAMP > 0 ){\n                 // from time fixed\n                 DATA_DISPLAY_TO_TIMESTAMP = DATA_FROM_TIMESTAMP + DATA_DISPLAY_WINDOW;\n             }\n         }\n         /*\n         DATA_OUT_OF_DATE = (SERVER_TIME - timestamp  > CACHE_TIMEOUT);\n         if (DATA_OUT_OF_DATE){\n             raise_data_out_of_date_error();\n         }else{\n             clear_data_out_of_date_error();\n          }\n          */\n         // todo\n     }\n\n     // update time line\n     update_timeline();\n\n     // update all legend tables\n     $('.legend table').trigger(\"update\");\n     if (JSON_ERROR_COUNT > 0) {\n         JSON_ERROR_COUNT = JSON_ERROR_COUNT - 1;\n     }\n     UPDATE_STATUS_COUNT = 0;\n     //hide_update_status();\n     if(typeof request_data != \"undefined\" && request_data.init===1){\n         hide_init_status();\n     }\n     //FETCH_DATA_PENDING--;\n\n }\n\n\n /**\n  * Will display an Ajax error notification\n  * @param {*} x\n  * @param {*} t\n  * @param {*} m\n  */\n function data_handler_fail(response) {\n     // error notifications\n     if(JSON_ERROR_COUNT % 5 == 0) {\n         add_notification(\"Fetching data failed (\" + response.status + \")\", 3, 0.95*5*REFRESH_RATE);\n     }\n     increase_error_count();\n}\n\n  function increase_error_count() {\n     JSON_ERROR_COUNT = JSON_ERROR_COUNT + 1;\n     if (JSON_ERROR_COUNT > 15) {\n         $(\".AutoUpdateStatus\").css(\"color\", \"red\");\n         auto_update_click();\n         add_notification(\"Fetching data failed limit reached, auto update deactivated.<br>Check your connectivity and active auto update in the top right corner.\", 2, 0);\n     } else if(JSON_ERROR_COUNT > 3){\n         $(\".AutoUpdateStatus\").css(\"color\", \"orange\");\n         for (var key in VARIABLE_KEYS) {\n             key = VARIABLE_KEYS[key];\n             //add_fetched_data(key, [[DATA_TO_TIMESTAMP,Number.NaN]]);\n         }\n     }\n     //hide_update_status();\n     if(typeof request_data != \"undefined\" && request_data.init===1){\n         for (key in request_data.variables){\n             key = request_data.variables[key];\n             if (typeof(CHART_VARIABLE_KEYS[key]) === 'number'){\n                 CHART_VARIABLE_KEYS[key]--;\n             }else if (typeof(STATUS_VARIABLE_KEYS[key]) == 'number'){\n                 STATUS_VARIABLE_KEYS[key]--;\n             }\n         }\n         hide_init_status();\n     }\n     //FETCH_DATA_PENDING--;\n\n }\n\n\n // FIND INDEX :\n\n /**\n  * Return index 'i' where a value in 'a' is lower or equal to value 't'\n  * @param {Array} a The array\n  * @param {number} t The value\n  * @returns {number} The index where a value lower than 't' where found\n  */\n function find_index(a,t){\n     var i = a.length; //or 10\n     while(i--){\n         if (a[i]<=t){\n             return i;\n         }\n     }\n }\n /**\n  * Return index 'i' where a value in 'd' is lower or equal to value 't'\n  * @param {Array} a The array\n  * @param {number} t The value\n  * @param {number} d Sub index\n  * @returns {number} The index where a value lower than 't' where found\n  */\n function find_index_sub_lte(a,t,d){\n     var i = a.length; //or 10\n     while(i--){\n         if (a[i][d]<=t){\n             return i;\n         }\n     }\n }\n /**\n  * Return index 'i' where a value in 'd' is superior or equal to value 't'\n  * @param {Array} a The array\n  * @param {number} t The value\n  * @param {number} d Sub index\n  * @returns {number} The index where a value superior than 't' where found\n  */\n function find_index_sub_gte(a,t,d){\n     var i = 0; //or 10\n     while(i < a.length){\n         if (a[i][d]>=t){\n             return i;\n         }\n         i++;\n     }\n }\n /**\n  * Return index 'i' where a value in 'd' is lower or equal to value 't'\n  * @param {Array} a The array\n  * @param {number} t The value\n  * @param {number} d Sub index\n  * @returns {number} The index where a value lower than 't' where found\n  */\n function find_index_sub_lt(a,t,d){\n     var i = a.length; //or 10\n     while(i--){\n         if (a[i][d]<t){\n             return i;\n         }\n     }\n }\n /**\n  * Return index 'i' where a value in 'd' is superior or equal to value 't'\n  * @param {Array} a The array\n  * @param {number} t The value\n  * @param {number} d Sub index\n  * @returns {number} The index where a value superior than 't' where found\n  */\n function find_index_sub_gt(a,t,d){\n     var i = 0; //or 10\n     while(i < a.length){\n         if (a[i][d]>t){\n             return i;\n         }\n         i++;\n     }\n }\n\n\n\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n //                                                   CHARTS & COLOR\n\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n function get_period_fields(key) {\n    calculatedvariableselectorID = Number(get_config_from_hidden_config('calculatedvariableselector','main-variable',key,'id'));\n    if (typeof(calculatedvariableselectorID) == 'undefined' || isNaN(calculatedvariableselectorID)) {return key;};\n    periodFields = get_config_from_hidden_config('calculatedvariableselector','id',calculatedvariableselectorID,'period-fields').split(',');\n    if (periodFields.length > 1) {periodFields.pop();};  // remove last empty item\n    return periodFields;\n }\n\n function filter_period_fields_by_type(perdioFields, type) {\n    validPeriodFields = [];\n    for (field in periodFields) {\n        if (get_config_from_hidden_config('periodicfield','id',periodFields[field],'type') == type) {\n            validPeriodFields.push(periodFields[field])\n        }\n    }\n    return validPeriodFields;\n }\n\n aggregation_types = [(0, 'min'),\n                    (1, 'max'),\n                    (2, 'total'),\n                    (3, 'difference'),\n                    (4, 'difference percent'),\n                    (5, 'delta'),\n                    (6, 'mean'),\n                    (7, 'first'),\n                    (8, 'last'),\n                    (9, 'count'),\n                    (10, 'count value'),\n                    (11, 'range'),\n                    (12, 'step'),\n                    (13, 'change count'),\n                    (14, 'distinct count'),]\n\n function filter_aggregation_type_for_period_list(period_list) {\n    result = []\n    for (p in period_list) {\n         i = get_config_from_hidden_config('periodicfield','id',period_list[p],'type')\n         if (typeof(i) !== 'undefined') {\n             result[i] = aggregation_types[i]\n         }\n    }\n    return result\n }\n\n function get_one_field_by_period_field(validPeriodFields) {\n    /*\n    period_choices = ((0, 'second'),\n                      (1, 'minute'),\n                      (2, 'hour'),\n                      (3, 'day'),\n                      (4, 'week'),\n                      (5, 'month'),\n                      (6, 'year'),\n                      )\n    */\n    // Store a field by period choice. Prefer the lowest period factor and if equals, the the lowest starting point.\n    periodCalcVar = {0:null,1:null,2:null,3:null,4:null,5:null,6:null,}\n    for (validPeriodField in validPeriodFields) {\n        period = get_config_from_hidden_config('periodicfield','id',validPeriodFields[validPeriodField],'period');\n        if (periodCalcVar[period] == null) {\n            periodCalcVar[period] = validPeriodFields[validPeriodField];\n        }else {\n            newPeriodFactor = get_config_from_hidden_config('periodicfield','id',validPeriodFields[validPeriodField],'period-factor');\n            newStartFrom = get_config_from_hidden_config('periodicfield','id',validPeriodFields[validPeriodField],'period-factor');\n            currentPeriodFactor = get_config_from_hidden_config('periodicfield','id',periodCalcVar[period],'period-factor');\n            currentStartFrom = get_config_from_hidden_config('periodicfield','id',periodCalcVar[period],'period-factor');\n            if (currentPeriodFactor > newPeriodFactor) {\n                periodCalcVar[period] = validPeriodFields[validPeriodField];\n            }else if (currentPeriodFactor = newPeriodFactor && currentStartFrom > newStartFrom) {\n                periodCalcVar[period] = validPeriodFields[validPeriodField];\n            }\n        }\n    }\n    return periodCalcVar;\n }\n\n function get_variable_keys_for_period_calculated_variables(periodCalcVar) {\n    keyCalcVar = {0:null,1:null,2:null,3:null,4:null,5:null,6:null,}\n    for (p in periodCalcVar) {\n        if (periodCalcVar[p] !== null) {\n            periods = get_config_from_hidden_configs('calculatedvariable','id','period');\n            for (idP in periods) {\n                if (periodCalcVar[p] == periods[idP] && get_config_from_hidden_config('calculatedvariable','id',idP,'variable-calculated-fields') == calculatedvariableselectorID) {\n                    keyCalcVar[p] = get_config_from_hidden_config('calculatedvariable','id',idP,'store-variable');\n                }\n            }\n        }\n    }\n    return keyCalcVar;\n }\n\n function get_one_variable_key_of_calculated_variable_for_duration(start, stop, min_aggregate, keyCalcVar) {\n    if (keyCalcVar[6] !== null && (stop - start) > (60 * 60 * 24 * 365 * min_aggregate)) {return keyCalcVar[6]}\n    else if (keyCalcVar[5] !== null && (stop - start) > (60 * 60 * 24 * 31 * min_aggregate)) {return keyCalcVar[5]}\n    else if (keyCalcVar[4] !== null && (stop - start) > (60 * 60 * 24 * 7 * min_aggregate)) {return keyCalcVar[4]}\n    else if (keyCalcVar[3] !== null && (stop - start) > (60 * 60 * 24 * min_aggregate)) {return keyCalcVar[3]}\n    else if (keyCalcVar[2] !== null && (stop - start) > (60 * 60 * min_aggregate)) {return keyCalcVar[2]}\n    else if (keyCalcVar[1] !== null && (stop - start) > (60 * min_aggregate)) {return keyCalcVar[1]}\n    else if (keyCalcVar[0] !== null && (stop - start) > min_aggregate) {return keyCalcVar[0]}\n    return null;\n }\n\n /*\n  * Get data from an agragated variable if it exist\n  * If stop - start > 1 year, get value by month if exist, or by week or by day ...\n  * @param {number} key key of the initial variable\n  * @param {number} start start timestamp in ms\n  * @param {number} stop stop timestamp in ms\n  * @param {number} type type could be (0, 'min'),\n                    (1, 'max'),\n                    (2, 'total'),\n                    (3, 'difference'),\n                    (4, 'difference percent'),\n                    (5, 'delta'),\n                    (6, 'mean'),\n                    (7, 'first'),\n                    (8, 'last'),\n                    (9, 'count'),\n                    (10, 'count value'),\n                    (11, 'range'),\n                    (12, 'step'),\n                    (13, 'change count'),\n                    (14, 'distinct count'),\n */\n function get_aggregated_data(key, start, stop, type=6, min_aggregate=3) {\n    if (!Number.isInteger(key) || Number.isNaN(start) || Number.isNaN(stop) || !Number.isInteger(type) || !Number.isInteger(min_aggregate)) {\n      console.log(\"PyScada HMI : get_aggregated_data : params types are not int, number, number, int : \", key, start, stop, type);\n      return key;\n    };\n    key = Number(key);\n    start = Number(start);\n    stop = Number(stop);\n    type = Number(type);\n    min_aggregate = parseInt(min_aggregate);\n    if (min_aggregate <= 0) {console.log(\"PyScada HMI : min_aggregate should be > 0, it is \", min_aggregate);return key;};\n    if (!key in DATA) {console.log(\"PyScada HMI :\", key, \"not in DATA\");return key;};\n    //if (start < 0 || stop < 0) {console.log(\"start or stop < 0 :\", start, stop);};\n    if (start < 0) {start = DATA_FROM_TIMESTAMP;};\n    if (stop < 0) {stop = DATA_TO_TIMESTAMP;};\n    start = start / 1000;\n    stop = stop / 1000;\n    if (start >= stop) {console.log(\"PyScada HMI : start is not < stop :\", start, stop);return key;};\n    if (type < 0 || type > 14) {console.log(\"PyScada HMI : type is not 0<= and >=14 :\", type)};\n\n    periodFields = get_period_fields(key);\n    validPeriodFields = filter_period_fields_by_type(periodFields, type);\n    periodCalcVar = get_one_field_by_period_field(validPeriodFields);\n    keyCalcVar = get_variable_keys_for_period_calculated_variables(periodCalcVar);\n    new_key = get_one_variable_key_of_calculated_variable_for_duration(start, stop, min_aggregate, keyCalcVar);\n    if (new_key !== null) {return new_key;}\n    return key;\n }\n\n // COLOR OBJECT :\n /**\n  * Color class\n  * @param {number} red red color between 0 and 255\n  * @param {number} green green color between 0 and 255\n  * @param {number} blue blue color between 0 and 255\n  */\n function Color(red,green,blue) {\n     this.red = red;\n     this.green = green;\n     this.blue = blue;\n }\n\n // CHARTS OBJETCS :\n\n /**\n  * A chart with x and y axes, with logarithmic mode\n  * @param {number} id The container id where to display the chart\n  * @param {boolean} xaxisVarId\n  * @param {boolean} xaxisLinLog Logarithmic mode\n  */\n function PyScadaPlot(id, xaxisVarId, xaxisLinLog){\n     var options = {\n         legend: {\n             show: false,\n         },\n         series: {\n             shadowSize: 0,\n             lines: {\n                show: true,\n                lineWidth: 3,\n             },\n             points: {\n                show: true,\n                radius: 4,\n                symbol: \"cross\",\n             },\n             bars: {\n                 show: false,\n                 barWidth: [0.5, false],\n                 align: \"center\",\n             },\n         },\n         xaxis: {\n             mode: (xaxisVarId == null ? \"time\" : (xaxisLinLog == true ? \"log\" : null)), // logarithmic mode\n             ticks: (xaxisVarId == null ? $('#chart-container-'+id).data('xaxisTicks') : null),\n             timeformat: \"%d/%m/%Y<br>%H:%M:%S\",\n             timezone: \"browser\",\n             timeBase: \"milliseconds\", // x axis is milliseconds\n             autoScale: (xaxisVarId == null ? \"none\" : \"exact\"),\n             showTickLabels: (xaxisVarId == null ? \"major\" : \"all\")\n         },\n         yaxis: {\n             position: \"left\",\n             autoScale: \"loose\",\n             autoScaleMargin: 0.1,\n             min: null,\n             max: null,\n         },\n         yaxes: [],\n         selection: {\n             mode: \"xy\",\n             visualization: \"focus\",\n             minSize: 0,\n         },\n         grid: {\n             labelMargin: 10,\n             margin: {\n                 top: 20,\n                 bottom: 8,\n                 left: 20\n             },\n             borderWidth: 0,\n             hoverable: true,\n             clickable: true\n         },\n         zoom: {\n             active: true,\n         },\n         pan: {\n             interactive: false,\n         },\n         axisvalues: {\n             mode: \"xy\",\n         },\n         crosshair: {\n             mode: \"xy\"\n         },\n     },\n     series = [],\t\t// just the active data series\n     keys   = [],\t\t// list of variable keys (ids)\n     variable_names = [], // list of all variable names\n     variables = {},     // list of all variables\n     flotPlot,\t\t\t// handle to plot\n     prepared = false,\t// is the chart prepared\n\n     // areas in the container\n     legend_id = '#chart-legend-' + id,\n     legend_table_id = '#chart-legend-table-' + id,\n     chart_container_id = '#chart-container-'+id,\n     legend_checkbox_id = '#chart-legend-checkbox-' + id + '-',\n     legend_checkbox_status_id = '#chart-legend-checkbox-status-' + id + '-',\n     legend_value_id = '#chart-legend-value-' + id + '-',\n\n     axes = {},\n     raxes = {},\n\n     // the object\n     plot = this;\n\n\n     // public functions\n     plot.update \t\t\t= update;\n     plot.prepare \t\t\t= prepare;\n     plot.resize \t\t\t= resize;\n     plot.updateLegend \t\t= updateLegend;\n\n     //getter\n     plot.getSeries \t\t\t= function () { return series };\n     plot.getFlotObject\t\t= function () { return flotPlot};\n     plot.getKeys\t\t\t= function (){ return keys};\n     plot.getVariableNames\t= function (){ return variable_names};\n\n     plot.getInitStatus\t\t= function () { if(InitDone){return InitRetry}else{return false}};\n     plot.getId\t\t\t\t= function () {return id};\n     plot.getChartContainerId= function () {return chart_container_id};\n\n     // init data\n     tf = function (value, axis) {\n         return value.toFixed(axis.tickDecimals) + (((typeof options.yaxes[axis.n-1].unit != \"undefined\") && options.yaxes[axis.n-1].unit != null) ? options.yaxes[axis.n-1].unit : '');\n     };\n     options.yaxis.tickFormatter = tf;\n\n     k=0\n     $.each($(legend_id + ' .axis-config'),function(key,val){\n         axis_inst = $(val);\n         axis_id = axis_inst.data('key');\n\n         axis_label = axis_inst.data('label');\n         axis_position = axis_inst.data('position') == 0 ? \"left\" : \"right\";\n         axis_min = axis_inst.data('min') == \"None\" ? null : axis_inst.data('min');\n         axis_max = axis_inst.data('max') == \"None\" ? null : axis_inst.data('max');\n         axis_points = axis_inst.data('show-plot-points') == \"True\";\n         axis_bars = axis_inst.data('show-plot-bars') == \"True\";\n         axis_lines = axis_inst.data('show-plot-lines') >= 1;\n         axis_steps = axis_inst.data('show-plot-lines') >= 2;\n         axis_stack = axis_inst.data('stack') == \"True\";\n         axis_fill = axis_inst.data('fill') == \"True\";\n         raxes[axis_id] = {'list_id':k,}\n         axes[k] = {'list_id':axis_id, 'label':axis_label, 'position': axis_position, 'min': axis_min, 'max': axis_max, 'points': axis_points, 'lines': axis_lines, 'bars': axis_bars, 'steps': axis_steps, 'stack': axis_stack, 'fill': axis_fill, 'unit': null};\n         options.yaxes[k] = {};\n         options.yaxes[k].list_id = axis_id;\n         options.yaxes[k].label = axis_label;\n         options.yaxes[k].position = axis_position;\n         //options.yaxes[k].labelWidth = null;\n         //options.yaxes[k].reserveSpace = false;\n         options.yaxes[k].min = axis_min;\n         options.yaxes[k].max = axis_max;\n         k++;\n     });\n\n     $.each($(legend_table_id + ' .variable-config'),function(key,val){\n         val_inst = $(val);\n         axis_id = val_inst.data('axis-id')\n         raxis_id = raxes[axis_id].list_id\n         variable_name = val_inst.data('name');\n         variable_key = val_inst.data('key');\n         variables[variable_key] = {'color':val_inst.data('color'),'yaxis': raxis_id, 'axis_id': axis_id}\n         keys.push(variable_key);\n         variable_names.push(variable_name);\n         variables[variable_key].label = $(\".legendLabel[data-key=\" + variable_key + \"] .legendLabel-text\")[0].textContent.replace(/\\s/g, '');\n         variables[variable_key].unit = $(\".legendUnit[data-key=\" + variable_key + \"]\")[0].textContent.replace(/\\s/g, '');\n         if (axes[raxis_id].unit == null) {\n             axes[raxis_id].unit = variables[variable_key].unit;\n         }else if (axes[raxis_id].unit !== variables[variable_key].unit) {\n             axes[raxis_id].unit = \"\";\n         }\n         options.yaxes[raxis_id].unit = axes[raxes[axis_id].list_id].unit\n         options.yaxes[raxis_id].axisLabel = options.yaxes[raxis_id].label.replace(/\\s/g, '') + (((typeof options.yaxes[raxis_id].unit != \"undefined\") && options.yaxes[raxis_id].unit != \"\" && options.yaxes[raxis_id].unit !=  null) ? \" (\" + options.yaxes[raxis_id].unit + \")\" : '');\n     });\n\n\n     function linearInterpolation (x, x0, y0, x1, y1) {\n       var a = (y1 - y0) / (x1 - x0)\n       var b = -a * x0 + y0\n       return a * x + b\n     }\n\n     //Show interpolated value in legend\n     function updateLegend() {\n         var pos = flotPlot.c2p({left:flotPlot.crosshair_x, top:flotPlot.crosshair_y});\n         var axes = flotPlot.getAxes();\n\n         if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max ||\n             pos.y < axes.yaxis.min || pos.y > axes.yaxis.max) {\n             return;\n         }\n\n         var i, j, dataset = flotPlot.getData();\n\n         for (i = 0; i < dataset.length; ++i) {\n             var series = dataset[i];\n             var key = series.key\n             // Find the nearest points, x-wise\n             for (j = 0; j < series.data.length; ++j) {\n                 if (series.data[j][0] > pos.x) {\n                     break;\n                 }\n             }\n             // Now Interpolate\n             var y,\n                 p1 = series.data[j - 1],\n                 p2 = series.data[j];\n             if (p1 == null && typeof(p2) != \"undefined\") {\n                 y = p2[1];\n             } else if (p2 == null && typeof(p1) != \"undefined\") {\n                 y = p1[1];\n             } else if (typeof(12) != \"undefined\" && typeof(p2) != \"undefined\") {\n                 y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);\n             }\n             if (typeof(y) === \"number\") {\n                 $(legend_value_id+key).text(y.toFixed(2));\n             }\n         }\n     }\n\n     // prepare the chart and display it even without data\n     function prepare(){\n         // prepare legend table sorter\n         if (keys.length > 0) {\n             $(legend_table_id).tablesorter({sortList: [[2,0]]});\n         };\n\n         // CHECKBOX EVENTS :\n\n         // add onchange function to every checkbox in legend / shows or hides the variable linked to the checked/unchecked checkbox\n         $.each(variables,function(key,val){\n             $(legend_checkbox_id+key).change(function() {\n                 plot.update(true);\n                 if ($(legend_checkbox_id+key).is(':checked')){\n                     $(legend_checkbox_status_id+key).html(1);\n                 }else{\n                     $(legend_checkbox_status_id+key).html(0);\n                 }\n             });\n         });\n         // add onchange function to 'make_all_none' checkbox in legend / shows or hides all the variable in the chart\n         $(legend_checkbox_id+'make_all_none').change(function() {\n             if ($(legend_checkbox_id+'make_all_none').is(':checked')){\n                 $.each(variables,function(key,val){\n                     $(legend_checkbox_status_id+key).html(1);\n                     if ($(legend_checkbox_id+key).length > 0) {\n                         $(legend_checkbox_id+key)[0].checked = true;\n                     }\n                 });\n             }else{\n                 $.each(variables,function(key,val){\n                     $(legend_checkbox_status_id+key).html(0);\n                     if ($(legend_checkbox_id+key).length > 0) {\n                         $(legend_checkbox_id+key)[0].checked = false;\n                     }\n                  });\n             }\n             plot.update(true);\n          });\n\n\n         // CORRECTING THE CHART SIZE AND IT'S CONTENTS :\n\n         // expand the chart to the maximum width\n         main_chart_area  = $(chart_container_id).closest('.main-chart-area');\n\n\n         contentAreaHeight = main_chart_area.parent().height();\n         mainChartAreaHeight = main_chart_area.height();\n         // resize the main chart area if the content height exceed the main chart's\n         if (contentAreaHeight>mainChartAreaHeight){\n             main_chart_area.height(contentAreaHeight);\n         }\n\n         flotPlot = $.plot($(chart_container_id + ' .chart-placeholder'), series, options);\n         set_chart_selection_mode();\n         // update the plot\n         update(true);\n\n         //add info on mouse over a point and position of the mouse\n         $(chart_container_id + ' .chart-placeholder').on(\"plothover\", function (event, pos, item) {\n             if(!pos) {\n                 //$(\".axes-tooltips\").hide();\n             }\n             for (axis in pos) {\n                 if (!$(\"#\" + axis + \"-tooltip\").length) {\n                     $(\"<div id='\" + axis + \"-tooltip' class='axes-tooltips'></div>\").css({\n                         position: \"absolute\",\n                         display: \"none\",\n                         border: \"1px solid #fdd\",\n                         padding: \"2px\",\n                         \"background-color\": \"#fee\",\n                         opacity: 0.90,\n                         \"z-index\": 90,\n                         \"font-size\": \"14px\"\n                     }).appendTo(\"body\");\n                 }\n             }\n             if (item && typeof item.datapoint != 'undefined' && item.datapoint.length > 1) {\n                 opts = item.series.xaxis.options;\n                 if (opts.mode == \"time\") {\n                     dG = $.plot.dateGenerator(Number(item.datapoint[0].toFixed(0)), opts);\n                     dF = $.plot.formatDate(dG, opts.timeformat, opts.monthNames, opts.dayNames);\n                     var x = dF,\n                         y = item.datapoint[1].toFixed(2);\n                 }else {\n                     var x = item.datapoint[0].toFixed(2),\n                         y = item.datapoint[1].toFixed(2);\n                 }\n                 y_label = (typeof item.series.label !== 'undefined') ? item.series.label : \"T\";\n                 y_unit = (typeof item.series.unit !== 'undefined') ? item.series.unit : \"\";\n                 $(\"#tooltip\").html(y_label + \" (\" + x + \") = \" + y + \" \" + y_unit)\n                     .css({top: item.pageY+5, left: item.pageX+5, \"z-index\": 91})\n                     .show();\n                     //.fadeIn(200);\n             } else {\n                 $(\"#tooltip\").hide();\n             }\n             // set Crosshairs\n             var offset = flotPlot.getPlaceholder().offset();\n             var plotOffset = flotPlot.getPlotOffset();\n             flotPlot.crosshair_x = Math.max(0, Math.min(pos.pageX - offset.left - plotOffset.left, flotPlot.width()));\n             flotPlot.crosshair_y = Math.max(0, Math.min(pos.pageY - offset.top - plotOffset.top, flotPlot.height()));\n             setCrosshairs(flotPlot, id);\n\n         // mouse leave\n         }).on(\"mouseleave\", function (event, pos, item) {\n             if(! CROSSHAIR_LOCKED) {\n                 delCrosshairs(flotPlot);\n             }\n         // mouse down\n         }).on(\"mousedown\", function (e) {\n             var offset = flotPlot.getPlaceholder().offset();\n             var plotOffset = flotPlot.getPlotOffset();\n             var pos={};\n             pos.x = Math.max(0, Math.min(e.pageX - offset.left - plotOffset.left, flotPlot.width()));\n             pos.y = Math.max(0, Math.min(e.pageY - offset.top - plotOffset.top, flotPlot.height()));\n             //pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, flotPlot.width());\n             //pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, flotPlot.height());\n             flotPlot.crosshair_lastPositionMouseDown = pos;\n         // mouse up\n         }).on(\"mouseup\", function (e) {\n             var offset = flotPlot.getPlaceholder().offset();\n             var plotOffset = flotPlot.getPlotOffset();\n             var pos={};\n             pos.x = Math.max(0, Math.min(e.pageX - offset.left - plotOffset.left, flotPlot.width()));\n             pos.y = Math.max(0, Math.min(e.pageY - offset.top - plotOffset.top, flotPlot.height()));\n             //pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, flotPlot.width());\n             //pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, flotPlot.height());\n             var old_pos = flotPlot.crosshair_lastPositionMouseDown;\n             if (CROSSHAIR_LOCKED) {\n                 CROSSHAIR_LOCKED = false;\n                 flotPlot.crosshair_x = pos.x;\n                 flotPlot.crosshair_y = pos.y;\n                 unlockCrosshairs(flotPlot);\n                 setCrosshairs(flotPlot, id);\n             } else if (pos.x == old_pos.x && pos.y == old_pos.y) {\n                 CROSSHAIR_LOCKED = true;\n                 flotPlot.crosshair_x = pos.x;\n                 flotPlot.crosshair_y = pos.y;\n                 setCrosshairs(flotPlot, id);\n                 var x = e.pageX\n                 var y = e.pageY\n                 lockCrosshairs(x, y);\n             }\n         // plot selected\n         }).on(\"plotselected\", function(event, ranges) {\n             pOpt = flotPlot.getOptions();\n             // activate zoom y\n             if ($(chart_container_id + \" .activate_zoom_y\").is(':checked')) {\n                 for (range in ranges) {\n                     if (~range.indexOf('y')) {\n                         if (range.match(/\\d+/) != null) {\n                             y_number = range.match(/\\d+/)[0];\n                             pOpt.yaxes[y_number-1].min = ranges[range].from;\n                             pOpt.yaxes[y_number-1].max = ranges[range].to;\n                             pOpt.yaxes[y_number-1].autoScale = \"none\";\n                         }else {\n                             pOpt.yaxes[0].min = ranges[range].from;\n                             pOpt.yaxes[0].max = ranges[range].to;\n                             pOpt.yaxes[0].autoScale = \"none\";\n                         }\n                     }\n                 }\n                 flotPlot.setupGrid(true);\n                 flotPlot.draw();\n             }\n\n             flotPlot.clearSelection();\n             // activate zoom x\n             if ($(chart_container_id + \" .activate_zoom_x\").is(':checked') && ranges.xaxis != null) {\n                 if (xaxisVarId == null) {\n                     DATA_DISPLAY_TO_TIMESTAMP = ((DATA_TO_TIMESTAMP == ranges.xaxis.to) ? DATA_DISPLAY_TO_TIMESTAMP : ranges.xaxis.to);\n                     DATA_DISPLAY_FROM_TIMESTAMP = ((DATA_FROM_TIMESTAMP == ranges.xaxis.from) ? DATA_DISPLAY_FROM_TIMESTAMP : ranges.xaxis.from);\n                     if (DATA_DISPLAY_TO_TIMESTAMP < 0 && DATA_DISPLAY_FROM_TIMESTAMP < 0) {DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP;}\n                     else if (DATA_DISPLAY_TO_TIMESTAMP < 0) {DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_DISPLAY_FROM_TIMESTAMP;}\n                     else if (DATA_DISPLAY_FROM_TIMESTAMP < 0) {DATA_DISPLAY_WINDOW = DATA_DISPLAY_TO_TIMESTAMP - DATA_FROM_TIMESTAMP;}\n                     else {DATA_DISPLAY_WINDOW = DATA_DISPLAY_TO_TIMESTAMP-DATA_DISPLAY_FROM_TIMESTAMP;}\n                     set_x_axes();\n                 }else {\n                   pOpt.xaxes[0].min = ranges.xaxis.from;\n                   pOpt.xaxes[0].max = ranges.xaxis.to;\n                   pOpt.xaxes[0].autoScale = \"none\";\n                   update(true);\n                 }\n             }\n         });\n\n         // Since CSS transforms use the top-left corner of the label as the transform origin,\n         // we need to center the y-axis label by shifting it down by half its width.\n         // Subtract 20 to factor the chart's bottom margin into the centering.\n         var chartTitle = $(chart_container_id + ' .chartTitle');\n         chartTitle.css(\"margin-left\", -chartTitle.width() / 2);\n         var xaxisLabel = $(chart_container_id + ' .axisLabel.xaxisLabel');\n         xaxisLabel.css(\"margin-left\", -xaxisLabel.width() / 2);\n         var yaxisLabel = $(chart_container_id + ' .axisLabel.yaxisLabel');\n         yaxisLabel.css(\"margin-top\", yaxisLabel.width() / 2 - 20);\n\n         // The download function takes a CSV string, the filename and mimeType as parameters\n         // Scroll/look down at the bottom of this snippet to see how download is called\n         var download = function(content, fileName, mimeType) {\n             var a = document.createElement('a');\n             mimeType = mimeType || 'application/octet-stream';\n\n             if (mimeType == 'image/png' && 'download' in a) {\n                 a.href = content;\n                 a.setAttribute('download', fileName);\n                 document.body.appendChild(a);\n                 a.click();\n                 document.body.removeChild(a);\n             } else if (navigator.msSaveBlob) { // IE10\n                 navigator.msSaveBlob(new Blob([content], {\n                 type: mimeType\n                 }), fileName);\n             } else if (URL && 'download' in a) { //html5 A[download]\n                 a.href = URL.createObjectURL(new Blob([content], {\n                   type: mimeType\n                 }));\n                 a.setAttribute('download', fileName);\n                 document.body.appendChild(a);\n                 a.click();\n                 document.body.removeChild(a);\n             } else {\n                 location.href = 'data:application/octet-stream,' + encodeURIComponent(content); // only this mime type is supported\n             }\n         };\n         // chrt save csv button\n         $(chart_container_id + \" .btn.btn-default.chart-save-csv\").click(function() {\n             // Example data given in question text\n             var data = [['Label'], ['Unité'], ['Couleur'], ['Données']];\n             mode = flotPlot.getXAxes()[0].options.mode;\n             for (s=0; s<series.length; s++){\n                 data[0][(s+1)*2-1] = \"x\";\n                 data[1][(s+1)*2-1] = (mode = \"time\") ? \"ms\" : \"\";\n                 data[2][(s+1)*2-1] = \"\";\n                 data[0][(s+1)*2] = series[s].label;\n                 data[1][(s+1)*2] = series[s].unit;\n                 data[2][(s+1)*2] = series[s].color;\n                 for (l=0; l<series[s].data.length; l++) {\n                     data.push([]);\n                     data[3+l][(s+1)*2-1] = series[s].data[l][0]\n                     data[3+l][(s+1)*2] = series[s].data[l][1]\n                 }\n             }\n\n             // Building the CSV from the Data two-dimensional array\n             // Each column is separated by \";\" and new line \"\\n\" for next row\n             var csvContent = '';\n             data.forEach(function(infoArray, index) {\n               dataString = infoArray.join(';');\n               csvContent += index < data.length ? dataString + '\\n' : dataString;\n             });\n\n             download(csvContent, 'download.csv', 'text/csv;encoding:utf-8');\n         });\n         // chart save picture button\n         $(chart_container_id + \" .btn.btn-default.chart-save-picture\").click(function() {\n             var originalCanvas1 = $(chart_container_id + ' .flot-base')[0]\n             var originalCanvas2 = $(chart_container_id + ' .flot-overlay')[0]\n             var originalCanvas3 = $(chart_container_id + ' .flot-svg')[0].children[0]\n             var ctx = originalCanvas2.getContext(\"2d\");\n             ctx.fillStyle = \"#FFFFFF\";\n             ctx.fillRect(0, 0, originalCanvas2.width, originalCanvas2.height);\n             var sources = [originalCanvas2, originalCanvas1, originalCanvas3]\n             var destinationCanvas = document.getElementById(\"myCanvas\");\n             $.plot.composeImages(sources, destinationCanvas)\n             //setTimeout(function() {window.open($('#myCanvas')[0].toDataURL('image/png'));}, 500);\n             setTimeout(function() {download($('#myCanvas')[0].toDataURL('image/png'), 'image.png', 'image/png');}, 500);\n             ctx.fillRect(0, 0, 0, 0);\n         });\n         // chart reset selection button\n         $(chart_container_id + \" .btn.btn-default.chart-ResetSelection\").click(function() {\n             e = jQuery.Event( \"click\" );\n             jQuery(chart_container_id + \" .btn.btn-default.chart-ZoomYToFit\").trigger(e);\n             jQuery(chart_container_id + \" .btn.btn-default.chart-ZoomXToFit\").trigger(e);\n         });\n         // chart zoom y to fit button\n         $(chart_container_id + \" .btn.btn-default.chart-ZoomYToFit\").click(function() {\n             pOpt = flotPlot.getOptions();\n             for (y in pOpt.yaxes){\n                 pOpt.yaxes[y].autoScale = \"loose\";\n             }\n             update(true);\n         });\n         // chart zoom x to fit button\n         $(chart_container_id + \" .btn.btn-default.chart-ZoomXToFit\").click(function() {\n             if (xaxisVarId == null) {\n                 DATA_DISPLAY_FROM_TIMESTAMP = -1;\n                 DATA_DISPLAY_TO_TIMESTAMP = -1;\n                 DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP;\n                 set_x_axes();\n             }else {\n               pOpt = flotPlot.getOptions();\n                 pOpt.xaxes[0].autoScale = \"exact\";\n                 update(true);\n             }\n         });\n     }\n\n     // update the chart\n     function update(force){\n         // PREPARE THE CHART :\n         if(!prepared ){\n             if($(chart_container_id).is(\":visible\")){\n                 prepared = true;\n                 prepare();\n             }else{\n                 return;\n             }\n         }\n         // UPDATE DATA :\n         if($(chart_container_id).is(\":visible\") || force){\n             // only update if plot is visible\n             // add the selected data series to the \"series\" variable\n             old_series = series;\n             new_data_bool = false;\n             series = [];\n             start_id = 0;\n             j=0;\n             jk=1;\n             // for each variable\n             for (var key in keys){\n                 original_key = keys[key];\n                 aggregatedtType = document.querySelector(legend_table_id + ' .aggregation-type-option[data-id=\"' + original_key + '\"]');\n                 if (aggregatedtType !== null && ! isNaN(parseInt(aggregatedtType.value))) {\n                     key = Number(get_aggregated_data(keys[key], DATA_DISPLAY_FROM_TIMESTAMP, DATA_DISPLAY_TO_TIMESTAMP, parseInt(aggregatedtType.value)));\n                 }else {\n                    key = original_key;\n                 }\n                 if (!variables.hasOwnProperty(key) && original_key in variables) {\n                     variables[key] = variables[original_key]\n                 }\n                 if (key == original_key) {\n                     variables[key].label = $(\".legendLabel[data-key=\" + original_key + \"] .legendLabel-text\")[0].textContent.replace(/\\s/g, '')\n                 }else {\n                     variables[key].label = $(\".legendLabel[data-key=\" + original_key + \"] .legendLabel-text\")[0].textContent.replace(/\\s/g, '') + \" \" + $(\".legendLabel[data-key=\" + original_key + \"] span\")[1].textContent.replace(/\\s/g, '');\n                 }\n\n                 //key = keys[key];\n                 xkey = xaxisVarId;\n                 // if the variable checkbox is check, update data\n                 if($(legend_checkbox_id+original_key).is(':checked') && typeof(DATA[key]) === 'object'){\n                     if (DATA_DISPLAY_TO_TIMESTAMP > 0 && DATA_DISPLAY_FROM_TIMESTAMP > 0){\n                         start_id = find_index_sub_gte(DATA[key],DATA_DISPLAY_FROM_TIMESTAMP,0);\n                         stop_id = find_index_sub_lte(DATA[key],DATA_DISPLAY_TO_TIMESTAMP,0);\n                     }else if (DATA_DISPLAY_FROM_TIMESTAMP > 0 && DATA_DISPLAY_TO_TIMESTAMP < 0){\n                         start_id = find_index_sub_gte(DATA[key],DATA_DISPLAY_FROM_TIMESTAMP,0);\n                         stop_id = find_index_sub_lte(DATA[key],DATA_TO_TIMESTAMP,0);\n                     }else if (DATA_DISPLAY_FROM_TIMESTAMP < 0 && DATA_DISPLAY_TO_TIMESTAMP > 0){\n                         if (DATA_DISPLAY_TO_TIMESTAMP < DATA[key][0][0]){continue;}\n                         start_id = find_index_sub_gte(DATA[key],DATA_FROM_TIMESTAMP,0);\n                         stop_id = find_index_sub_lte(DATA[key],DATA_DISPLAY_TO_TIMESTAMP,0);\n                     }else {\n                         start_id = find_index_sub_gte(DATA[key],DATA_FROM_TIMESTAMP,0);\n                         stop_id = find_index_sub_lte(DATA[key],DATA_TO_TIMESTAMP,0);\n                     }\n                     if (typeof(start_id) == \"undefined\") {\n                        //console.log('start_id for var id ', key, 'is undefined');\n                         continue;\n                     }else {\n                         chart_data = DATA[key].slice(start_id,stop_id+1);\n                     }\n                     if (xkey == null) {\n                         for (serie in old_series) {\n                           if (new_data_bool === false && chart_data.length > 0 && key === old_series[serie]['key'] && chart_data.length !== old_series[serie]['data'].length && (old_series[serie]['data'].length == 0 || chart_data[0][0] !== old_series[serie]['data'][0][0] || chart_data[0][1] !== old_series[serie]['data'][0][1] || chart_data[chart_data.length-1][0] !== old_series[serie]['data'][old_series[serie]['data'].length-1][0] && chart_data[chart_data.length-1][1] !== old_series[serie]['data'][old_series[serie]['data'].length-1][-1])) {\n                             new_data_bool = true;\n                           }\n                         }\n                         series.push({\"data\":chart_data,\"color\":variables[key].color,\"yaxis\":variables[key].yaxis+1,\"label\":variables[key].label,\"unit\":variables[key].unit, \"key\":key, \"points\": {\"show\": axes[variables[key].yaxis].points,}, \"stack\": axes[variables[key].yaxis].stack, \"bars\": {\"show\": axes[variables[key].yaxis].bars}, \"lines\": {\"show\": axes[variables[key].yaxis].lines, \"steps\": axes[variables[key].yaxis].steps, \"fill\": axes[variables[key].yaxis].fill,},});\n                     }else if (xkey !== null && typeof(DATA[xkey]) === 'object'){\n                         if (DATA_DISPLAY_TO_TIMESTAMP > 0 && DATA_DISPLAY_FROM_TIMESTAMP > 0){\n                             start_xid = find_index_sub_gte(DATA[xkey],DATA_DISPLAY_FROM_TIMESTAMP,0);\n                             stop_xid = find_index_sub_lte(DATA[xkey],DATA_DISPLAY_TO_TIMESTAMP,0);\n                         }else if (DATA_DISPLAY_FROM_TIMESTAMP > 0 && DATA_DISPLAY_TO_TIMESTAMP < 0){\n                             start_xid = find_index_sub_gte(DATA[xkey],DATA_DISPLAY_FROM_TIMESTAMP,0);\n                             stop_xid = find_index_sub_lte(DATA[xkey],DATA_TO_TIMESTAMP,0);\n                         }else if (DATA_DISPLAY_FROM_TIMESTAMP < 0 && DATA_DISPLAY_TO_TIMESTAMP > 0){\n                             if (DATA_DISPLAY_TO_TIMESTAMP < DATA[key][0][0]){continue;}\n                             start_xid = find_index_sub_gte(DATA[xkey],DATA_FROM_TIMESTAMP,0);\n                             stop_xid = find_index_sub_lte(DATA[xkey],DATA_DISPLAY_TO_TIMESTAMP,0);\n                         }else {\n                             start_xid = find_index_sub_gte(DATA[xkey],DATA_FROM_TIMESTAMP,0);\n                             stop_xid = find_index_sub_lte(DATA[xkey],DATA_TO_TIMESTAMP,0);\n                         }\n                         if (typeof(start_xid) == \"undefined\") {\n                             continue;\n                         }else {\n                             chart_x_data = DATA[xkey].slice(start_xid,stop_xid+1);\n                         };\n                         new_data=[];\n                         if (chart_data.length > 0 && chart_x_data.length > 0){\n                             chart_data_min = chart_data[0][1]\n                             chart_data_max = chart_data[0][1]\n                             x_data_min = chart_x_data[0][1]\n                             x_data_max = chart_x_data[0][1]\n                             ix=0;\n                             for (iy=0; iy < chart_data.length; iy++) {\n                                 xf=0;\n                                 if (chart_x_data.length > 1){\n                                     while (ix < chart_x_data.length && xf == 0) {\n                                         if (chart_x_data[ix][0] >= chart_data[iy][0]) {\n                                             if (ix == 0) {\n                                                 fx = linearInterpolation(chart_data[iy][0], chart_x_data[ix][0], chart_x_data[ix][1], chart_x_data[ix+1][0], chart_x_data[ix+1][1]);\n                                             }else {\n                                                 fx = linearInterpolation(chart_data[iy][0], chart_x_data[ix-1][0], chart_x_data[ix-1][1], chart_x_data[ix][0], chart_x_data[ix][1]);\n                                             }\n                                             new_data.push([fx,chart_data[iy][1]]);\n                                             chart_data_min = Math.min(chart_data_min, chart_data[iy][1])\n                                             chart_data_max = Math.max(chart_data_max, chart_data[iy][1])\n                                             x_data_min = Math.min(x_data_min, fx)\n                                             x_data_max = Math.max(x_data_max, fx)\n                                             xf=1;\n                                             ix-=1;\n                                         }\n                                         ix+=1;\n                                     }\n                                     if (xf == 0) {\n                                         fx = linearInterpolation(chart_data[iy][0], chart_x_data[chart_x_data.length-2][0], chart_x_data[chart_x_data.length-2][1], chart_x_data[chart_x_data.length-1][0], chart_x_data[chart_x_data.length-1][1]);\n                                         new_data.push([fx,chart_data[iy][1]]);\n                                         chart_data_min = Math.min(chart_data_min, chart_data[iy][1])\n                                         chart_data_max = Math.max(chart_data_max, chart_data[iy][1])\n                                         x_data_min = Math.min(x_data_min, fx)\n                                         x_data_max = Math.max(x_data_max, fx)\n                                         xf=1;\n                                     }\n                                 }else if (chart_x_data.length > 0){\n                                     new_data.push([chart_x_data[0][1],chart_data[iy][1]]);\n                                     chart_data_min = Math.min(chart_data_min, chart_data[iy][1])\n                                     chart_data_max = Math.max(chart_data_max, chart_data[iy][1])\n                                     iy = chart_data.length;\n                                 }\n                             }\n                         }else {\n                             chart_data_min = null;\n                             chart_data_max = null;\n                         }\n                         if (new_data.length > 0){\n                             j += 1;\n                             //plot Y with different axis\n                             for (serie in old_series) {\n                               if (new_data_bool === false && new_data.length > 0 && key === old_series[serie]['key'] && new_data.length !== old_series[serie]['data'].length && (old_series[serie]['data'].length == 0 || new_data[0][0] !== old_series[serie]['data'][0][0] || new_data[0][1] !== old_series[serie]['data'][0][1] || new_data[new_data.length-1][0] !== old_series[serie]['data'][old_series[serie]['data'].length-1][0] && new_data[new_data.length-1][1] !== old_series[serie]['data'][old_series[serie]['data'].length-1][-1] || chart_x_data[0][0] !== old_series[serie]['xdata'][0][0] || chart_x_data[0][1] !== old_series[serie]['xdata'][0][1] || chart_x_data[chart_x_data.length-1][0] !== old_series[serie]['xdata'][old_series[serie]['xdata'].length-1][0] && chart_x_data[chart_x_data.length-1][1] !== old_series[serie]['xdata'][old_series[serie]['xdata'].length-1][-1])) {\n                                 new_data_bool = true;\n                               }\n                             }\n                             series.push({\"data\":new_data, \"xdata\":chart_x_data,\"color\":variables[key].color,\"yaxis\":variables[key].yaxis+1,\"label\":variables[key].label,\"unit\":variables[key].unit,\"chart_data_min\":chart_data_min,\"chart_data_max\":chart_data_max,\"x_data_min\":x_data_min,\"x_data_max\":x_data_max, \"key\":key, \"points\": {\"show\": axes[variables[key].yaxis].points,}, \"stack\": axes[variables[key].yaxis].stack, \"bars\": {\"show\": axes[variables[key].yaxis].bars}, \"lines\": {\"show\": axes[variables[key].yaxis].lines, \"steps\": axes[variables[key].yaxis].steps, \"fill\": axes[variables[key].yaxis].fill,},});\n                         }\n                     }\n                 }\n                 jk += 1;\n             }\n             if (new_data_bool || old_series.length == 0 || series.length == 0 || old_series.length != series.length || force) {\n\n               //update y window\n               pOpt = flotPlot.getOptions();\n               if (xaxisVarId == null) {\n                 if (DATA_DISPLAY_TO_TIMESTAMP > 0 && DATA_DISPLAY_FROM_TIMESTAMP > 0){\n                       pOpt.xaxes[0].min = DATA_DISPLAY_FROM_TIMESTAMP;\n                       pOpt.xaxes[0].max = DATA_DISPLAY_TO_TIMESTAMP;\n\n                   }else if (DATA_DISPLAY_FROM_TIMESTAMP > 0 && DATA_DISPLAY_TO_TIMESTAMP < 0){\n                       pOpt.xaxes[0].min = DATA_DISPLAY_FROM_TIMESTAMP;\n                       pOpt.xaxes[0].max = DATA_TO_TIMESTAMP;\n                   }else if (DATA_DISPLAY_FROM_TIMESTAMP < 0 && DATA_DISPLAY_TO_TIMESTAMP > 0){\n                       pOpt.xaxes[0].min = DATA_FROM_TIMESTAMP;\n                       pOpt.xaxes[0].max = DATA_DISPLAY_TO_TIMESTAMP;\n                   }else{\n                       pOpt.xaxes[0].min = DATA_FROM_TIMESTAMP;\n                       pOpt.xaxes[0].max = DATA_TO_TIMESTAMP;\n                   }\n                   pOpt.xaxes[0].key=0;\n               }else {\n\n\n                   // Reset min and max for xaxis and yaxes when no data\n                   allYAxesEmpty = true;\n                   for (y = 0;y < pOpt.yaxes.length;y++){\n                       if (j != 0){\n                           yAxesEmpty = true;\n                           for (k = 1;k <= j;k++){\n                               S = series[k-1];\n                               if (S['yaxis']-1 == y) {yAxesEmpty = false;}\n                           }\n                           if (yAxesEmpty == true) {\n                               pOpt.yaxes[y].min = null;\n                               pOpt.yaxes[y].max = null;\n                           }else {allYAxesEmpty = false;}\n                       }else {\n                           pOpt.yaxes[y].min = null;\n                           pOpt.yaxes[y].max = null;\n                           pOpt.xaxes[0].min = null;\n                           pOpt.xaxes[0].max = null;\n                       }\n                   }\n                   if (allYAxesEmpty == true) {\n                       pOpt.xaxes[0].min = null;\n                       pOpt.xaxes[0].max = null;\n                   }\n\n                   pOpt.xaxes[0].key=xkey\n               }\n\n               // update flot plot\n               flotPlot.setData(series);\n               flotPlot.setupGrid(true);\n               flotPlot.draw();\n\n               // Change the color of the axis\n               if (xaxisVarId !== null && jk != 1){\n                   for (k = 1;k <= jk;k++){\n                       S = series[k-1];\n                       if (typeof S !== 'undefined') {\n                           $(chart_container_id + ' .axisLabels.y' + S['yaxis'] + 'Label').css('fill',S['color'])\n                           $(chart_container_id + ' .flot-y' + S['yaxis'] + '-axis text').css('fill',S['color'])\n                       }\n                   }\n               }\n             }\n         }\n     }\n\n     // resize the chart when the navigator window dimensiosn changed\n     function resize() {\n         if (typeof(flotPlot) !== 'undefined') {\n             flotPlot.resize();\n             flotPlot.setupGrid(true);\n             flotPlot.draw();\n         }\n     }\n }\n\n // Gauge\n /**\n  * A 240° circular chart with a data range\n  * @param {number} id The container id where to display the chart\n  * @param {numer} min_value Range minimum value\n  * @param {number} max_value Range maximum value\n  * @param {Array<object>} threshold_values Datas\n  */\n function Gauge(id, min_value, max_value, threshold_values){\n     var options = {\n         series: {\n             gauges: {\n                 debug:{log:false},\n                 show: true,\n                 frame: {\n                     show: false\n                 },\n                 gauge: {\n                     min: min_value,\n                     max: max_value,\n                 },\n                 cell: {\n                     border: {\n                         show: false,\n                     },\n                 },\n                 label: {\n                     show: false,\n                 },\n                 threshold: {\n                     values: threshold_values,\n                 },\n                 value: {\n                     formatter: function(label, value) {\n                         if (value == null) {return \"No data\"}else{return value;}\n                     },\n                 }\n             },\n         },\n     },\n     series = [],\t\t// just the active data series\n     keys   = [],\t\t// list of variable keys (ids)\n     variable_names = [], // list of all variable names\n     variables = {},     // list of all variable\n     flotPlot,\t\t\t// handle to plot\n     prepared = false,\t// is the chart prepared\n\n     // chart container areas\n     chart_container_id = '#chart-container-'+id, // the HTML element where the chart is displayed\n     legend_table_id = '#chart-legend-table-' + id, // table of legend\n     legend_checkbox_id = '#chart-legend-checkbox-' + id + '-', // legend of checkbox, when checked will display the linked variable in the chart\n     legend_checkbox_status_id = '#chart-legend-checkbox-status-' + id + '-', // legend of checkbox status\n\n     // the object\n     plot = this;\n\n     // public functions\n     plot.update \t\t\t= update;\n     plot.prepare \t\t\t= prepare;\n     plot.resize \t\t\t= resize;\n\n     //getter\n     plot.getSeries \t\t\t= function () { return series ;};\n     plot.getFlotObject\t\t= function () { return flotPlot;};\n     plot.getKeys\t\t\t= function (){ return keys;};\n     plot.getVariableNames\t= function (){ return variable_names;};\n\n     plot.getInitStatus\t\t= function () { if(InitDone){return InitRetry}else{return false;}};\n     plot.getId\t\t\t\t= function () {return id;};\n     plot.getChartContainerId= function () {return chart_container_id;};\n\n     // init data\n     val_id=$(chart_container_id).data('id');\n     val_inst=$(\".variable-config[data-id=\" + val_id + \"]\")\n     variable_name = $(val_inst).data('name');\n     variable_key = $(val_inst).data('key');\n     variables[variable_key] = {'color':$(val_inst).data('color'),'yaxis':1}\n     keys.push(variable_key);\n     variable_names.push(variable_name);\n     variables[variable_key].label = variable_name\n     variables[variable_key].unit = $(val_inst).data('unit');\n\n     //options[\"series\"][\"gauges\"][\"gauge\"][\"background\"] = {\"color\": $(val_inst).data('color')};\n\n     // prepare the chart and display it even without data\n     function prepare(){\n     }\n\n     // update the chart\n     function update(force){\n         // prepare the chart\n         prepared = true;\n         if(prepared && ($(chart_container_id).is(\":visible\") || force)){\n             // only update if plot is visible\n             // add the selected data series to the \"series\" variable\n             series = [];\n             for (var key in keys){\n                 key = keys[key];\n                 if (key in DATA) {\n                     // get the last value using the daterangepicker and the timeline slider values\n\n                     var value = sliceDATAusingTimestamps(DATA[key])\n                     if (value.length) {\n                        value = value[value.length - 1][1];\n                        value = transform_data(id.split(\"-\")[1], value, \"var-\" + key);\n                        data=[[min_value, value]];\n                     }else {\n                        data=[[min_value, null]];\n                     }\n                     series.push({\"data\":data, \"label\":variables[key].label});\n                 }\n             }\n             // draw the chart if we have data\n             if (series.length > 0) {\n                 var plotCanvas = $('<div></div>');\n                 elem = $(chart_container_id + ' .chart-placeholder');\n\n                 //mhw = Math.min(elem.parent().height() * 1.3, elem.parent().width());\n                 mhw = elem.parent().width();\n                 elem.parent().parent().css('height', mhw/1.3);\n                 elem.parent().parent().find('.loading-gauge').text(\"\");\n                 elem.parent().parent().find('.gauge-title').css(\"display\", \"inherit\");\n\n                 fontScale = parseInt(30, 10) / 100;\n                 fontSize = Math.min(mhw / 5, 100) * fontScale;\n\n\n                 options[\"series\"][\"gauges\"][\"value\"][\"font\"] = {\"size\": fontSize};\n\n                 var plotCss = {\n                     top: '0px',\n                     margin: 'auto',\n                     position: 'relative',\n                     height: (elem.parent().height() * 0.9) + 'px',\n                     width: mhw + 'px'\n                 };\n\n                 elem.css(plotCss)\n                 //elem.append(plotCanvas);\n                 try {\n                    flotPlot = $.plot(elem, series, options);\n                 }catch(err) {\n                    console.log(\"Gauge : \" + err);\n                 }\n             }\n         }\n     }\n\n     // resize the chart when the navigator window dimensiosn changed\n     function resize() {\n         if (typeof(flotPlot) !== 'undefined') {\n             flotPlot.resize();\n             update();\n         }\n     }\n }\n\n // Pie\n function labelFormatter(label, series) {\n     return \"<div style='font-size:8pt; text-align:center; padding:2px; color:\" + series.color + \";'>\" + label + \"<br/>\" + Math.round(series.percent) + \"%</div>\";\n     //return \"<div style='font-size:8pt; text-align:center; padding:2px; color:\" + series.color + \";'>\" + label + \"<br/>\" + Math.round(series.percent) + \"%<br/>\" + series.data[0][1] + \" \" + series.unit + \"</div>\";\n }\n /**\n  * A chart with radius and innierRadius options\n  * @param {number} id The container id where to display the chart\n  * @param {number} radius The pie radius\n  * @param {number} innerRadius The pie inner radius\n  */\n function Pie(id, radius, innerRadius){\n     var options = {\n         series: {\n             pie: {\n                 show: true,\n                 innerRadius: innerRadius,\n                 label: {\n                     show: true,\n                     radius: radius,\n                     formatter: labelFormatter,\n                     background:{\n                       opacity: 0.5,\n                       color: \"#FFF\"\n                     }\n                     //threshold: 0.05\n                 }\n             }\n         },\n         legend: {\n             show: false\n         },\n         grid: {\n             hoverable: true,\n             clickable: true\n         },\n     },\n     series = [],\t\t// just the active data series\n     keys   = [],\t\t// list of variable keys (ids)\n     variable_names = [], // list of all variable names\n     variables = {},     // list of all variable\n     flotPlot,\t\t\t// handle to plot\n     prepared = false,\t// is the chart prepared\n     chart_container_id = '#chart-container-'+id, // the HTML element where the chart is displayed\n     legend_table_id = '#chart-legend-table-' + id, // table of legend\n     legend_checkbox_id = '#chart-legend-checkbox-' + id + '-', // legend of checkbox, when checked will display the linked variable in the chart\n     legend_checkbox_status_id = '#chart-legend-checkbox-status-' + id + '-', // legend of checkbox status\n     plot = this; // the object\n\n     // public functions\n     plot.update \t\t\t= update;\n     plot.prepare \t\t\t= prepare;\n     plot.resize \t\t\t= resize;\n     plot.updateLegend \t\t= updateLegend;\n\n     // getter\n     plot.getSeries \t\t\t= function () { return series ;};\n     plot.getFlotObject\t\t= function () { return flotPlot;};\n     plot.getKeys\t\t\t= function (){ return keys;};\n     plot.getVariableNames\t= function (){ return variable_names;};\n\n     plot.getInitStatus\t\t= function () { if(InitDone){return InitRetry;}else{return false;}};\n     plot.getId\t\t\t\t= function () {return id;};\n     plot.getChartContainerId= function () {return chart_container_id;};\n\n     // init data\n     $.each($(legend_table_id + ' .variable-config'),function(key,val){\n         val_inst = $(val);\n         variable_name = val_inst.data('name');\n         variable_key = val_inst.data('key');\n         variables[variable_key] = {'color':val_inst.data('color'),'yaxis':1};\n         keys.push(variable_key);\n         variable_names.push(variable_name);\n         unit = \"\";\n         label = \"\";\n         $.each($(legend_table_id + ' .legendSeries'),function(kkey,val){\n             val_inst = $(val);\n             if (variable_key == val_inst.find(\".variable-config\").data('key')){\n                 variables[variable_key].label = val_inst.find(\".legendLabel\").text().replace(/\\s/g, '');\n                 variables[variable_key].unit = val_inst.find(\".legendUnit\").text().replace(/\\s/g, '');\n             }\n         });\n     });\n\n     //Show interpolated value in legend\n     function updateLegend() {\n         var pos = flotPlot.c2p({left:flotPlot.crosshair_x, top:flotPlot.crosshair_y});\n         var axes = flotPlot.getAxes();\n\n         if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max ||\n             pos.y < axes.yaxis.min || pos.y > axes.yaxis.max) {\n             return;\n         }\n\n         var i, j, dataset = flotPlot.getData();\n\n         for (i = 0; i < dataset.length; ++i) {\n             var series = dataset[i];\n             var key = series.key\n             // Find the nearest points, x-wise\n             for (j = 0; j < series.data.length; ++j) {\n                 if (series.data[j][0] > pos.x) {\n                     break;\n                 }\n             }\n             // Now Interpolate\n             var y,\n                 p1 = series.data[j - 1],\n                 p2 = series.data[j];\n             if (p1 == null && typeof(p2) != \"undefined\") {\n                 y = p2[1];\n             } else if (p2 == null && typeof(p1) != \"undefined\") {\n                 y = p1[1];\n             } else if (typeof(12) != \"undefined\" && typeof(p2) != \"undefined\") {\n                 y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);\n             }\n             if (typeof(y) === \"number\") {\n                 $(legend_value_id+key).text(y.toFixed(2));\n             }\n         }\n     }\n\n     // prepare the chart and display it even without data\n     function prepare(){\n         // prepare legend table sorter\n         if (keys.length > 0) {\n             $(legend_table_id).tablesorter({sortList: [[2,0]]});\n         }\n\n         // add onchange function to every checkbox in legend / shows or hides the variable linked to the checked/unchecked checkbox\n         $.each(variables,function(key,val){\n             $(legend_checkbox_id+key).change(function() {\n                 plot.update(true);\n                 if ($(legend_checkbox_id+key).is(':checked')){\n                     $(legend_checkbox_status_id+key).html(1);\n                 }else{\n                     $(legend_checkbox_status_id+key).html(0);\n                 }\n             });\n         });\n         // add onchange function to make_all_none checkbox in legend / shows or hides the chart's variables\n         $(legend_checkbox_id+'make_all_none').change(function() {\n             if ($(legend_checkbox_id+'make_all_none').is(':checked')){\n                 $.each(variables,function(key,val){\n                     $(legend_checkbox_status_id+key).html(1);\n                     $(legend_checkbox_id+key)[0].checked = true;\n                 });\n             }else{\n                 $.each(variables,function(key,val){\n                     $(legend_checkbox_status_id+key).html(0);\n                     $(legend_checkbox_id+key)[0].checked = false;\n                  });\n             }\n             plot.update(true);\n         });\n         // expand the pie to the maximum width\n         main_chart_area = $(chart_container_id).closest('.main-chart-area');\n\n\n         contentAreaHeight = main_chart_area.parent().height();\n         mainChartAreaHeight = main_chart_area.height();\n\n         // resize the main chart area if the content height exceed the main chart's\n         if (contentAreaHeight>mainChartAreaHeight){\n             main_chart_area.height(contentAreaHeight);\n         }\n\n         //add info on mouse over a slice\n         $(chart_container_id + ' .chart-placeholder').on(\"plothover\", function (event, pos, item) {\n             var eventDoc, doc, body;\n\n            event = window.event; // IE-ism\n\n            // If pageX/Y aren't available and clientX/Y are,\n            // calculate pageX/Y - logic taken from jQuery.\n            // (This is to support old IE)\n            if (event.pageX == null && event.clientX != null) {\n                eventDoc = (event.target && event.target.ownerDocument) || document;\n                doc = eventDoc.documentElement;\n                body = eventDoc.body;\n\n                event.pageX = event.clientX +\n                    (doc && doc.scrollLeft || body && body.scrollLeft || 0) -\n                    (doc && doc.clientLeft || body && body.clientLeft || 0);\n                event.pageY = event.clientY +\n                    (doc && doc.scrollTop  || body && body.scrollTop  || 0) -\n                    (doc && doc.clientTop  || body && body.clientTop  || 0 );\n            }\n\n             if (item && typeof item.datapoint != 'undefined' && item.datapoint.length > 1) {\n                 var x = item.datapoint[0].toFixed(2),\n                     y = item.datapoint[1][0][1].toFixed(2);\n                 y_label = (typeof item.series.label !== 'undefined') ? item.series.label : \"T\";\n                 y_unit = (typeof item.series.unit !== 'undefined') ? item.series.unit : \"\";\n                 $(\"#tooltip\").html(y_label + \" (\" + x + \" %) = \" + y + \" \" + y_unit)\n                     .css({top: event.pageY+5, left: event.pageX+5, \"z-index\": 91})\n                     .show();\n                     //.fadeIn(200);\n             } else {\n                 $(\"#tooltip\").hide();\n             }\n         })\n\n         // Since CSS transforms use the top-left corner of the label as the transform origin,\n         // we need to center the y-axis label by shifting it down by half its width.\n         // Subtract 20 to factor the chart's bottom margin into the centering.\n         var chartTitle = $(chart_container_id + ' .chartTitle');\n         chartTitle.css(\"margin-left\", -chartTitle.width() / 2);\n         var xaxisLabel = $(chart_container_id + ' .axisLabel.xaxisLabel');\n         xaxisLabel.css(\"margin-left\", -xaxisLabel.width() / 2);\n         var yaxisLabel = $(chart_container_id + ' .axisLabel.yaxisLabel');\n         yaxisLabel.css(\"margin-top\", yaxisLabel.width() / 2 - 20);\n\n         // draw the chart if we have data\n         if (series.length > 0) {\n             flotPlot = $.plot($(chart_container_id + ' .chart-placeholder'), series, options);\n             // update the plot\n             update(false);\n         }else {\n             //prepared = false;\n         }\n     }\n\n     // update the chart\n     function update(force){\n         // prepare the chart\n         if(!prepared ){\n             if($(chart_container_id).is(\":visible\")){\n                 prepared = true;\n                 prepare();\n             }else{\n                 return;\n             }\n         }\n         // update the chart\n         if(prepared && ($(chart_container_id).is(\":visible\") || force)){\n             // only update if plot is visible\n             // add the selected data series to the \"series\" variable\n             series = [];\n             for (var key in keys){\n                 key = keys[key];\n                 if (key in DATA) {\n                    var d = sliceDATAusingTimestamps(DATA[key]);\n                    if (d.length) {\n                        d = d[d.length - 1];\n                    }else {\n                        d=null;\n                    }\n                 }else {\n                    d=null;\n                 }\n                 if($(legend_checkbox_id+key).is(':checked') && typeof(d) === 'object'){\n                     series.push({\"data\":d, \"label\":variables[key].label,\"unit\":variables[key].unit, \"color\":variables[key].color});\n                 }\n             }\n             if (series.length > 0 || force) {\n                 if (typeof flotPlot !== 'undefined') {\n                     // update flot plot\n                     flotPlot.setData(series);\n                     flotPlot.setupGrid(true);\n                     // remove old errors\n                     document.querySelectorAll(chart_container_id + ' .chart-placeholder .error').forEach(e => e.remove());\n                     flotPlot.draw();\n                 }else {\n                     flotPlot = $.plot($(chart_container_id + ' .chart-placeholder'), series, options)\n                 }\n             }\n         }\n     }\n\n     // resize the chart when the navigator window dimensiosn changed\n     function resize() {\n         if (typeof(flotPlot) !== 'undefined') {\n             flotPlot.resize();\n             flotPlot.setupGrid(true);\n             flotPlot.draw();\n         }\n     }\n }\n\n\n //                             -----------------------------------------------------------\n //                                                    Charts Settings\n //                             -----------------------------------------------------------\n\n // CHART SELECTION :\n /**\n  * Depending on the checked check box, zoom x or y activated for each x y axes chart\n  * @returns void\n  */\n function set_chart_selection_mode(){\n\n     // ZOOM MODE :\n\n     var mode = \"\";\n     $.each($('.xy-chart-container'),function(key,val){\n         // get identifier of the chart\n         id = val.id.substring(19);\n\n         // active the zoom mode according to the checked checkbox\n         if ($('#xy-chart-container-' + id +' .activate_zoom_x').is(':checked') && $('#xy-chart-container-' + id +' .activate_zoom_y').is(':checked')){\n             mode = \"xy\";\n         }else if($('#xy-chart-container-' + id +' .activate_zoom_y').is(':checked')){\n             mode = \"y\";\n         }else if($('#xy-chart-container-' + id +' .activate_zoom_x').is(':checked')){\n             mode = \"x\";\n         }\n         $.each(PyScadaPlots,function(plot_id){\n             if(typeof(PyScadaPlots[plot_id].getFlotObject()) !== 'undefined' && PyScadaPlots[plot_id].getId() === id){\n                 PyScadaPlots[plot_id].getFlotObject().getOptions().selection.mode = mode;\n             }\n         });\n     });\n     $.each($('.chart-container'),function(key,val){\n         // get identifier of the chart\n         id = val.id.substring(16);\n\n         // active the zoom mode according to the checked checkbox\n         if ($('#chart-container-' + id +' .activate_zoom_x').is(':checked') && $('#chart-container-' + id +' .activate_zoom_y').is(':checked')){\n             mode = \"xy\";\n         }else if($('#chart-container-' + id +' .activate_zoom_y').is(':checked')){\n             mode = \"y\";\n         }else if($('#chart-container-' + id +' .activate_zoom_x').is(':checked')){\n             mode = \"x\";\n         }\n         $.each(PyScadaPlots,function(plot_id){\n             if(typeof(PyScadaPlots[plot_id].getFlotObject()) !== 'undefined' && PyScadaPlots[plot_id].getId() === id){\n                 PyScadaPlots[plot_id].getFlotObject().getOptions().selection.mode = mode;\n             }\n         });\n     });\n }\n\n\n // X AXES\n /**\n  * For each chart container, update charts then update timeline\n  * @returns void\n  */\n function set_x_axes(){\n     if(!progressbar_resize_active){\n         updatePyScadaPlots(true);\n         // update the progressbar\n         update_timeline();\n     }\n }\n\n\n // UPDATE TIMELINE\n /**\n  * Update the timeline axe depending on data timestamp variables\n  * @returns void\n  */\n function update_timeline(){\n     if (DATA_DISPLAY_TO_TIMESTAMP < 0){\n         $('#timeline-time-to-label').html(\"\");\n         min_to = 0;\n     }else{\n         //var min_to = ((DATA_TO_TIMESTAMP - DATA_DISPLAY_TO_TIMESTAMP)/60/1000);\n         //$('#timeline-time-to-label').html(\"-\" + min_to.toPrecision(3) + \"min\");\n         var date = new Date(DATA_DISPLAY_TO_TIMESTAMP);\n         $(\"#timeline-time-to-label\").html(date.toLocaleString());\n     }\n     var min_full = ((DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP)/60/1000);\n     if (DATA_DISPLAY_FROM_TIMESTAMP < 0 ){\n         var min_from = Math.min(min_full,((DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP)/60/1000));\n         $('#timeline-time-from-label').html(\"\");\n     }else{\n         var min_from = Math.min(min_full,((DATA_TO_TIMESTAMP - DATA_DISPLAY_FROM_TIMESTAMP)/60/1000));\n         //$('#timeline-time-from-label').html(\"-\" + min_from.toPrecision(3) + \"min\");\n         var date = new Date(DATA_DISPLAY_FROM_TIMESTAMP);\n         $(\"#timeline-time-from-label\").html(date.toLocaleString());\n     }\n     if (DATA_DISPLAY_FROM_TIMESTAMP < 0 && DATA_DISPLAY_TO_TIMESTAMP < 0){\n         $('#timeline').css(\"width\", \"100%\");\n         $('#timeline').css(\"left\", \"0px\");\n     }else{\n         $('#timeline').css(\"width\", (Math.min(100,(DATA_DISPLAY_WINDOW/60/1000/min_full * 100)).toString()) + \"%\");\n         $('#timeline').css(\"left\",Math.max(0,Math.min((100-(min_from/min_full * 100)),100)).toString() + \"%\");\n     }\n     //$('#timeline-time-left-label').html(\"-\" + min_full.toPrecision(3) + \"min\");\n     //var date = new Date(DATA_FROM_TIMESTAMP);\n     //$(\"#timeline-time-left-label\").html(date.toLocaleString());\n\n     // Update DateTime pickers\n     if (typeof moment !== 'undefined' && typeof moment === 'function') {\n         daterange_cb(moment(DATA_FROM_TIMESTAMP), moment(DATA_TO_TIMESTAMP));\n         if (DATERANGEPICKER_SET == false) {\n             set_datetimepicker();\n         }\n     };\n\n    updatePyScadaPlots(force=false, update=true, resize=false);\n    for (var key in VARIABLE_KEYS) {\n        key = VARIABLE_KEYS[key];\n        update_data_values('var-' + key,DATA[key]);\n    }\n    for (var key in VARIABLE_PROPERTIES_DATA) {\n        value = VARIABLE_PROPERTIES_DATA[key];\n        if (key in VARIABLE_PROPERTIES_LAST_MODIFIED) {\n            time = VARIABLE_PROPERTIES_LAST_MODIFIED[key];\n        }else {time = null};\n        update_data_values('prop-' + key,[[time, value]]);\n    }\n }\n\n\n// Date range picker\n /**\n  * Set the date time range picker\n  * @returns void\n  */\n function set_datetimepicker() {\n     if ($(\".show_daterangepicker\").length) {\n         $('#daterange').daterangepicker({\n             \"showDropdowns\": true,\n             \"timePicker\": true,\n             \"timePicker24Hour\": true,\n             \"timePickerSeconds\": true,\n             ranges: {\n                 'Last 10 Minutes': [moment().subtract(10, 'minutes'), moment()],\n                 'Last 30 Minutes': [moment().subtract(30, 'minutes'), moment()],\n                 'Last Hour': [moment().subtract(1, 'hours'), moment()],\n                 'Last 2 Hour': [moment().subtract(2, 'hours'), moment()],\n                 'Last 6 Hour': [moment().subtract(6, 'hours'), moment()],\n                 'Last 12 Hour': [moment().subtract(12, 'hours'), moment()],\n                 'Today': [moment().startOf('day'), moment()],\n                 'Yesterday': [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')],\n                 'Last 7 Days': [moment().subtract(6, 'days'), moment()],\n                 'Last 30 Days': [moment().subtract(29, 'days'), moment()],\n                 'This Month': [moment().startOf('month'), moment()],\n                 'Previous Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],\n                 'Last Month': [moment().subtract(1, 'month'), moment()],\n                 'Last 2 Month': [moment().subtract(2, 'month'), moment()],\n                 'Last 6 Month': [moment().subtract(6, 'month'), moment()],\n                 'This Year': [moment().startOf('year'), moment()],\n                 'Previous Year': [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')],\n                 'Last Year': [moment().subtract(1, 'year'), moment()],\n             },\n             \"locale\": {\n                 \"format\": daterange_format,\n                 \"separator\": \" - \",\n                 \"applyLabel\": \"Apply\",\n                 \"cancelLabel\": \"Cancel\",\n                 \"fromLabel\": \"From\",\n                 \"toLabel\": \"To\",\n                 \"customRangeLabel\": \"Custom\",\n                 \"weekLabel\": \"W\",\n                 \"daysOfWeek\": [\n                     \"Su\",\n                     \"Mo\",\n                     \"Tu\",\n                     \"We\",\n                     \"Th\",\n                     \"Fr\",\n                     \"Sa\",\n                 ],\n                 \"monthNames\": [\n                     \"January\",\n                     \"February\",\n                     \"March\",\n                     \"April\",\n                     \"May\",\n                     \"June\",\n                     \"July\",\n                     \"August\",\n                     \"September\",\n                     \"October\",\n                     \"November\",\n                     \"December\"\n                 ],\n                 \"firstDay\": 1\n             },\n             \"alwaysShowCalendars\": true,\n             \"linkedCalendars\": false,\n             \"startDate\": moment(),\n             \"endDate\": moment().subtract(2, 'hours'),\n             \"opens\": \"left\"\n         }, function(start, end, label) {\n             LOADING_PAGE_DONE = 0;\n             set_loading_state(5, 0);\n             daterange_cb(start, end);\n             DATA_INIT_STATUS++;\n             DATA_FROM_TIMESTAMP = start.unix() * 1000;\n             if (label.indexOf('Last') !== -1 || label.indexOf('Today') !== -1 || label.indexOf('This Month') !== -1 || label.indexOf('This Year') !== -1) {\n                 PREVIOUS_AUTO_UPDATE_ACTIVE_STATE = true;\n             }else {\n                 PREVIOUS_AUTO_UPDATE_ACTIVE_STATE = false;\n             }\n             DATA_TO_TIMESTAMP = Math.min(end.unix() * 1000, SERVER_TIME);\n             DATA_BUFFER_SIZE = DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP;\n             INIT_CHART_VARIABLES_DONE = false;\n\n             DATA_DISPLAY_FROM_TIMESTAMP = -1;\n             DATA_DISPLAY_TO_TIMESTAMP = -1;\n             DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP;\n             set_x_axes();\n\n             var event = new CustomEvent(\"pyscadaDateTimeChange\", { detail: {'picker': this}, bubbles: false, cancelable: true, composed: false});\n             document.querySelectorAll(\".pyscadaDateTimeChange\").forEach(el=>el.dispatchEvent(event));\n\n             $('.loadingAnimation').show()\n         });\n         $('#daterange').on('show.daterangepicker', function(ev, picker) {\n             PREVIOUS_AUTO_UPDATE_ACTIVE_STATE = AUTO_UPDATE_ACTIVE\n             PREVIOUS_END_DATE = moment.min(picker.endDate, moment()).unix();\n             if($('.AutoUpdateButton').bootstrapSwitch('state') && AUTO_UPDATE_ACTIVE){\n                 auto_update_click();\n             };\n         });\n         $('#daterange').on('hide.daterangepicker', function(ev, picker) {\n             if(!$('.AutoUpdateButton').bootstrapSwitch('state') && PREVIOUS_AUTO_UPDATE_ACTIVE_STATE){\n                 auto_update_click();\n             };\n         });\n     }\n\n     var event = new CustomEvent(\"pyscadaDateTimeChange\", { detail: {'picker': $('#daterange').data('daterangepicker')}, bubbles: false, cancelable: true, composed: false});\n     document.querySelectorAll(\".pyscadaDateTimeChange\").forEach(el=>el.dispatchEvent(event));\n     DATERANGEPICKER_SET = true;\n}\n\n\n\n //                             -----------------------------------------------------------\n //                                           Charts and Color's Functions\n //                             -----------------------------------------------------------\n\n // UPDATE :\n /**\n  * Draw as many charts as requested\n  * @param {boolean} force\n  * @returns void\n  */\n function updatePyScadaPlots(force=false, update=true, resize=true) {\n      $.each(PyScadaPlots,function(plot_id){\n          var self = this, doBind = function() {\n            if (update) {PyScadaPlots[plot_id].update(force);}\n            if (resize) {PyScadaPlots[plot_id].resize();}\n         };\n         $.browserQueue.add(doBind, this);\n     });\n }\n\n // CROSSHAIRS :\n\n // Set Crosshairs\n /**\n  * Set crosshairs on 'flotPlot'\n  * @param {object} flotPlot The chart\n  * @param {*} id\n  * @returns void\n  */\n function setCrosshairs(flotPlot, id) {\n     //test if function setCrosshairs exist in hooks.drawOverlay before add it\n     $('.chart-legend-value-' + id).removeClass('type-numeric');\n     pOpt=flotPlot.getOptions();\n     $.each(PyScadaPlots,function(plot_id){\n         if(flotPlot.crosshair_x !== -1  && flotPlot.crosshair_x !== 0 && !CROSSHAIR_LOCKED) {\n             if(typeof(PyScadaPlots[plot_id].getFlotObject()) !== 'undefined' && typeof(PyScadaPlots[plot_id].getFlotObject().getOptions) !== 'undefined' && PyScadaPlots[plot_id].getFlotObject().getOptions().xaxes.length === pOpt.xaxes.length){\n                 if (PyScadaPlots[plot_id].getFlotObject().getOptions().xaxes.length === 1 && pOpt.xaxes.length === 1 && PyScadaPlots[plot_id].getFlotObject().getOptions().xaxes[0].key === pOpt.xaxes[0].key) {\n                     $('.chart-legend-value-' + PyScadaPlots[plot_id].getId()).removeClass('type-numeric');\n                     setTimeout(PyScadaPlots[plot_id].updateLegend(), 50);\n                     if (PyScadaPlots[plot_id].getId() == id) {\n                         PyScadaPlots[plot_id].getFlotObject().getOptions().crosshair.mode = 'xy';\n                         //PyScadaPlots[plot_id].getFlotObject().setCrosshair(flotPlot.c2p({left:flotPlot.crosshair_x, top:flotPlot.crosshair_y}))\n                     }else {\n                         PyScadaPlots[plot_id].getFlotObject().getOptions().crosshair.mode = 'x';\n                         var x = flotPlot.crosshair_x + flotPlot.getPlaceholder().offset().left + flotPlot.getPlotOffset().left - PyScadaPlots[plot_id].getFlotObject().getPlaceholder().offset().left - PyScadaPlots[plot_id].getFlotObject().getPlotOffset().left;\n                         x = Math.max(0, Math.min(x, PyScadaPlots[plot_id].getFlotObject().width()));\n                         PyScadaPlots[plot_id].getFlotObject().setCrosshair(PyScadaPlots[plot_id].getFlotObject().c2p({left:x}))\n                     }\n                 }else {\n                     PyScadaPlots[plot_id].getFlotObject().getOptions().crosshair.mode = 'xy';\n                     PyScadaPlots[plot_id].getFlotObject().setCrosshair();\n                     $('.chart-legend-value-' + PyScadaPlots[plot_id].getId()).addClass('type-numeric');\n                 }\n             }\n         }\n     });\n }\n // Delete Crosshairs\n /**\n  * Delete every crosshairs on 'flotPlot'\n  * @param {object} flotPlot The chart\n  * @returns void\n  */\n function delCrosshairs(flotPlot) {\n     $.each(PyScadaPlots,function(plot_id){\n         if (typeof PyScadaPlots[plot_id].getFlotObject() !== 'undefined' && typeof(PyScadaPlots[plot_id].getFlotObject().getOptions) !== 'undefined' && typeof(PyScadaPlots[plot_id].getFlotObject().setCrosshair) !== 'undefined') {\n             PyScadaPlots[plot_id].getFlotObject().getOptions().crosshair.mode = 'xy';\n             PyScadaPlots[plot_id].getFlotObject().setCrosshair();\n         }\n         $('.chart-legend-value-' + PyScadaPlots[plot_id].getId()).addClass('type-numeric');\n     });\n }\n // Unlock Crosshairs\n /**\n  * Unlock each crosshairs on 'flotPlot'\n  * @param {object} flotPlot The chart\n  * @returns void\n  */\n function unlockCrosshairs(flotPlot) {\n     $.each(PyScadaPlots,function(plot_id){\n         if (typeof PyScadaPlots[plot_id].getFlotObject() !== 'undefined' && typeof(PyScadaPlots[plot_id].getFlotObject().unlockCrosshair) !== 'undefined') {\n             PyScadaPlots[plot_id].getFlotObject().unlockCrosshair();\n         }\n     });\n }\n // Lock Crosshairs\n /**\n  * Lock each crosshairs on each chart\n  */\n function lockCrosshairs(x, y) {\n     $.each(PyScadaPlots,function(plot_id){\n         if (typeof PyScadaPlots[plot_id].getFlotObject() !== 'undefined' && PyScadaPlots[plot_id].getFlotObject().getData().length) {\n             flotPlot=PyScadaPlots[plot_id].getFlotObject();\n             x_tmp = x - flotPlot.getPlaceholder().offset().left - flotPlot.getPlotOffset().left;\n             x_tmp = Math.max(0, Math.min(x_tmp, flotPlot.width()));\n             y_tmp = y - flotPlot.getPlaceholder().offset().top - flotPlot.getPlotOffset().top;\n             y_tmp = Math.max(0, Math.min(y_tmp, flotPlot.height()));\n             flotPlot.lockCrosshair(flotPlot.c2p({left:x_tmp, top:y_tmp}));\n         }\n     });\n }\n\n\n // COLOR GRADIENT :\n /**\n  *  Color gradient\n  * @param {number} fadeFraction\n  * @param {Color} rgbColor1\n  * @param {Color} rgbColor2\n  * @param {Color} rgbColor3\n  * @returns {string} return a color gradient\n  */\n function colorGradient(fadeFraction, rgbColor1, rgbColor2, rgbColor3) {\n     var color1 = rgbColor1;\n     var color2 = rgbColor2;\n     var fade = fadeFraction;\n\n     // Do we have 3 colors for the gradient? Need to adjust the params.\n     if (rgbColor3) {\n       fade = fade * 2;\n\n       // Find which interval to use and adjust the fade percentage\n       if (fade >= 1) {\n         fade -= 1;\n         color1 = rgbColor2;\n         color2 = rgbColor3;\n       }\n     }\n\n     var diffRed = color2.red - color1.red;\n     var diffGreen = color2.green - color1.green;\n     var diffBlue = color2.blue - color1.blue;\n\n     var gradient = {\n       red: parseInt(Math.floor(parseInt(color1.red) + (diffRed * fade)), 10),\n       green: parseInt(Math.floor(parseInt(color1.green) + (diffGreen * fade)), 10),\n       blue: parseInt(Math.floor(parseInt(color1.blue) + (diffBlue * fade)), 10),\n     };\n\n     return [gradient.red, gradient.green, gradient.blue];\n     //return 'rgb(' + gradient.red + ',' + gradient.green + ',' + gradient.blue + ')';\n }\n\n /**\n  *  Fill aggregated type and period for each variable and for all selectors\n  */\nfunction setAggregatedLists() {\n    c=document.querySelectorAll('.aggregation-type-option');\n    for (cc in c) {\n      if (typeof(c[cc]) == 'object') {\n        var_id = c[cc].getAttribute('data-id');\n        widget_id = c[cc].getAttribute('data-widget-id');\n        a=get_period_fields(var_id);\n        b=filter_aggregation_type_for_period_list(a);\n        for (v in b) {\n          c[cc].add(new Option(b[v], v));\n          if (document.querySelector('#aggregation-type-all-select-' + widget_id) != null && !document.querySelectorAll('#aggregation-type-all-select-' + widget_id + ' option[value=\"' + v + '\"]').length) {\n            document.querySelector('#aggregation-type-all-select-' + widget_id).add(new Option(b[v], v));\n          }\n        }\n        c[cc].onchange = function(){\n            updatePyScadaPlots(true);\n            setAggregatedPeriodList(widget_id, var_id);\n            widget_id = this.dataset['widgetId'];\n            var_id = this.dataset['id'];\n            if (this.value == \"null\") {\n                document.querySelector('#chart-legend-options-span-' + widget_id + '-' + var_id).innerHTML = \"\";\n            }else {\n                document.querySelector('#chart-legend-options-span-' + widget_id + '-' + var_id).innerHTML = \"(\" + this.selectedOptions[0].text + \")\";\n            }\n        };\n        document.querySelector('#aggregation-type-all-select-' + widget_id).onchange = function(){\n            widget_id = this.dataset['widgetId'];\n            c = document.querySelectorAll('.aggregation-type-option[data-widget-id=\"' + widget_id + '\"]')\n            for (cc in c) {\n                for (o in c[cc].options) {\n                    if (this.value == c[cc][o].value){\n                        c[cc].value = this.value;\n                        var_id = c[cc].dataset['id'];\n                        if (this.value == \"null\") {\n                            document.querySelector('#chart-legend-options-span-' + widget_id + '-' + var_id).innerHTML = \"\";\n                        }else {\n                            document.querySelector('#chart-legend-options-span-' + widget_id + '-' + var_id).innerHTML = \"(\" + this.selectedOptions[0].text + \")\";\n                        }\n                    };\n                }\n            }\n            if (this.value == \"null\") {\n                document.querySelector('#chart-legend-options-span-' + widget_id).innerHTML = \"\";\n            }else {\n                document.querySelector('#chart-legend-options-span-' + widget_id).innerHTML = \"(\" + this.selectedOptions[0].text + \")\";\n            }\n            updatePyScadaPlots(true);\n        };\n      }\n    }\n}\n\nfunction setAggregatedPeriodList(widget_id, var_id) {\n    a=get_period_fields(var_id);\n    b=filter_period_fields_by_type(a, );\n    document.querySelector(\"li-aggregation-all-period-select-\" + widget_id + \"-\" + var_id);\n}\n\n/**\n *  select data in DATA for key using the daterangepicker and timeline slider values\n */\nfunction sliceDATAusingTimestamps(data, display_from=DATA_DISPLAY_FROM_TIMESTAMP, display_to=DATA_DISPLAY_TO_TIMESTAMP, from=DATA_FROM_TIMESTAMP, to=DATA_TO_TIMESTAMP) {\n  if (typeof(data) === \"undefined\") {return [];}\n  if (display_to > 0 && display_from > 0){\n      start_id = find_index_sub_gte(data,display_from,0);\n      stop_id = find_index_sub_lte(data,display_to,0) + 1;\n  }else if (display_from > 0 && display_to < 0){\n      start_id = find_index_sub_gte(data,display_from,0);\n      stop_id = find_index_sub_lte(data,to,0) + 1;\n  }else if (display_from < 0 && display_to > 0){\n      if (display_to < data[0][0]){\n        start_id = stop_id = null;\n      }else {\n        start_id = find_index_sub_gte(data,from,0);\n        stop_id = find_index_sub_lte(data,display_to,0) + 1;\n      }\n  }else {\n      start_id = find_index_sub_gte(data,from,0);\n      stop_id = find_index_sub_lte(data,to,0) + 1;\n  }\n  if (stop_id >= 0 && start_id >= 0 ) {\n    return data.slice(start_id, stop_id);\n  }else {\n    return []\n  }\n}\n\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n //                                                     CLIENT-SERVER\n\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n\n //                             -----------------------------------------------------------\n //                                                    Page's Settings\n //                             -----------------------------------------------------------\n\n\n // PAGES\n /**\n  * Show page\n  * @returns void\n  */\n function show_page() {\n     // hide all pages\n     $(\".sub-page\").hide();\n     // show page\n     if (window.location.hash.length > 0) {\n         $(window.location.hash).show();\n     }else{\n         window.location.hash = $('ul.navbar-nav li a').first().attr(\"href\");\n     }\n }\n\n/**\n * fix the anchor point for page links\n * @returns void\n */\nfunction fix_page_anchor() {\n  // fix the page anchor position\n  var navbar_hight = document.querySelector(\"#navbar-top\").offsetHeight + 20;\n  document.querySelectorAll('.sub-page').forEach(function(e){\n    e.style.paddingTop = navbar_hight + \"px\";\n    e.style.marginTop = -navbar_hight + \"px\";\n  });\n}\n\n\n // PROGRESS BAR :\n /**\n  * Set the window progress bar\n  * @returns void\n  */\n function progressbarSetWindow( event, ui ) {\n     updatePyScadaPlots(true);\n\n     progressbar_resize_active = false;\n }\n\n\n // TIMELINE :\n\n // Timeline Resize\n /**\n  * Resize the timeline if window size changed\n  * @returns void\n  */\n function timeline_resize( event, ui ) {\n     var window_width = ui.size.width/($('#timeline-border').width()-10);\n     var window_left = ui.position.left/($('#timeline-border').width()-10);\n     var min_full = (DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP);\n\n     if (window_left < 0.02){\n         if ((window_width+window_left) < 0.98){\n             DATA_DISPLAY_TO_TIMESTAMP = DATA_FROM_TIMESTAMP + min_full * (window_width+window_left);\n             DATA_DISPLAY_WINDOW = DATA_DISPLAY_TO_TIMESTAMP - DATA_FROM_TIMESTAMP\n         }else{\n             DATA_DISPLAY_TO_TIMESTAMP = -1;\n             DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP\n         }\n\n         DATA_DISPLAY_FROM_TIMESTAMP = -1;\n     }else{\n         DATA_DISPLAY_FROM_TIMESTAMP = DATA_FROM_TIMESTAMP + min_full * window_left;\n         if ((window_width+window_left) < 0.98){\n             DATA_DISPLAY_TO_TIMESTAMP = DATA_FROM_TIMESTAMP + min_full * (window_width+window_left);\n             DATA_DISPLAY_WINDOW = DATA_DISPLAY_TO_TIMESTAMP - DATA_DISPLAY_FROM_TIMESTAMP\n         }else{\n             DATA_DISPLAY_TO_TIMESTAMP = -1;\n             DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_DISPLAY_FROM_TIMESTAMP\n         }\n     }\n     update_timeline();\n }\n // Timeline Drag\n /**\n  * ?\n  * @returns void\n  */\n function timeline_drag( event, ui ) {\n     var window_left = ui.position.left/($('#timeline-border').width()-10);\n     var min_full = (DATA_TO_TIMESTAMP - DATA_FROM_TIMESTAMP);\n\n     if (window_left < 0.02){\n         DATA_DISPLAY_FROM_TIMESTAMP = -1\n         DATA_DISPLAY_TO_TIMESTAMP = DATA_FROM_TIMESTAMP + DATA_DISPLAY_WINDOW\n     }else{\n         DATA_DISPLAY_FROM_TIMESTAMP = DATA_FROM_TIMESTAMP + min_full * window_left;\n         DATA_DISPLAY_TO_TIMESTAMP = DATA_DISPLAY_FROM_TIMESTAMP + DATA_DISPLAY_WINDOW;\n         if (DATA_DISPLAY_TO_TIMESTAMP >= DATA_TO_TIMESTAMP){\n             DATA_DISPLAY_TO_TIMESTAMP = -1;\n             DATA_DISPLAY_WINDOW = DATA_TO_TIMESTAMP - DATA_DISPLAY_FROM_TIMESTAMP;\n         }\n     }\n     update_timeline();\n }\n\n // toggle daterangepicker :\n /**\n  * Shows or hides date range picker\n  * @returns void\n  */\n function toggle_daterangepicker(){\n     // Show/hide daterangepicker\n     if (window.location.hash.substr(1) !== '') {\n         if ($(\"#\" + window.location.hash.substr(1) + \".show_daterangepicker\").length) {\n             $(\".daterangepicker_parent\").removeClass(\"hidden\");\n         }else {\n             $(\".daterangepicker_parent\").addClass(\"hidden\");\n         }\n     }\n }\n\n // Shows or hides timeline :\n /**\n  * Toggle timeline\n  * @returns void\n  */\n function toggle_timeline(){\n     // Show/hide timeline\n     if (window.location.hash.substr(1) !== '') {\n         if ($(\"#\" + window.location.hash.substr(1) + \".show_timeline\").length) {\n             $(\".timeline\").removeClass(\"hidden\");\n         }else {\n             $(\".timeline\").addClass(\"hidden\");\n         }\n     }\n }\n\n //                             -----------------------------------------------------------\n //                                                   Page's Functions\n //                             -----------------------------------------------------------\n\n // FORM :\n /**\n  * Check form value\n  * @param {number} id_form Form to check\n  * @returns {boolean} Return an error status\n  */\n function check_form(id_form) {\n\n     err = false;\n     tabinputs = $.merge($('#'+id_form+ ' :text:visible'),$('#'+id_form+ ' :input:not(:text):hidden'));\n\n     for (i=0;i<tabinputs.length;i++){ //test if there is an empty or non numeric value\n\n         value = $(tabinputs[i]).val();\n         id = $(tabinputs[i]).attr('id');\n\n         var_name = $(tabinputs[i]).attr(\"name\");\n         val=$('.variable-config[data-id='+id.replace('-value', '')+']');\n         key = parseInt($(val).data('key'));\n\n         item_type = $(val).data('type');\n         value_class = $(val).data('value-class');\n\n         min = $(val).data('min');\n         max = $(val).data('max');\n         min_type = $(val).data('min-type');\n         max_type = $(val).data('max-type');\n\n\n         if (min_type == 'lte') {min_type_char = \">=\";} else {min_type_char = \">\";}\n         if (max_type == 'gte') {max_type_char = \"<=\";} else {max_type_char = \"<\";}\n\n         if (value == \"\" || value == null){\n             $(tabinputs[i]).parents(\".input-group\").addClass(\"has-error\");\n             $(tabinputs[i]).parents(\".input-group\").find('.help-block').remove();\n             $(tabinputs[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Please provide a value !</span>');\n             err = true;\n         }else {\n             $(tabinputs[i]).parents(\".input-group\").find('.help-block').remove();\n             check_mm = check_min_max(parseFloat(value), parseFloat(min), parseFloat(max), min_type, max_type);\n             if (check_mm == -1) {\n                 $(tabinputs[i]).parents(\".input-group\").addClass(\"has-error\");\n                 $(tabinputs[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Enter a value ' + min_type_char + ' ' + min + '</span>');\n                 err = true;\n             }else if (check_mm == 1) {\n                 $(tabinputs[i]).parents(\".input-group\").addClass(\"has-error\");\n                 $(tabinputs[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Enter a value ' + max_type_char + ' ' + max + '</span>');\n                 err = true;\n             }else if (check_mm == 0) {\n                 $(tabinputs[i]).parents(\".input-group\").removeClass(\"has-error\");\n                 if (isNaN(value)) {\n                     if (item_type == \"variable_property\" && value_class == 'STRING') {\n                     }else {\n                         $(tabinputs[i]).parents(\".input-group\").addClass(\"has-error\");\n                         $(tabinputs[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">The value must be a number ! Use dot not coma.</span>');\n                         err = true;\n                     }\n                 }\n             }\n         }\n     }\n\n     tabselects = $('#'+id_form+ ' .select');\n\n     for (i=0;i<tabselects.length;i++){ //test if there is an empty value\n         value = $(tabselects[i]).val();\n         id = $(tabselects[i]).attr('id');\n         var_name = $(tabselects[i]).data(\"name\");\n         val=$('.variable-config[data-id='+id.replace('-value', '')+']');\n         key = parseInt($(val).data('key'));\n         item_type = $(val).data('type');\n         value_class = $(val).data('value-class');\n         min = $(val).data('min');\n         max = $(val).data('max');\n         min_type = $(val).data('min-type');\n         max_type = $(val).data('max-type');\n         if (min_type == 'lte') {min_type_char = \">=\";} else {min_type_char = \">\";}\n         if (max_type == 'gte') {max_type_char = \"<=\";} else {max_type_char = \"<\";}\n\n         if (value == \"\" || value == null){\n             $(tabselects[i]).parents(\".input-group\").addClass(\"has-error\");\n             $(tabselects[i]).parents(\".input-group\").find('.help-block').remove();\n             $(tabselects[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Please provide a value !</span>');\n             err = true;\n         }else {\n             $(tabselects[i]).parents(\".input-group\").find('.help-block').remove();\n             check_mm = check_min_max(parseFloat(value), parseFloat(min), parseFloat(max), min_type, max_type);\n             if (check_mm == -1) {\n                 $(tabselects[i]).parents(\".input-group\").addClass(\"has-error\");\n                 $(tabselects[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Enter a value ' + min_type_char + ' ' + min + '</span>');\n                 err = true;\n             }else if (check_mm == 1) {\n                 $(tabselects[i]).parents(\".input-group\").addClass(\"has-error\");\n                 $(tabselects[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Enter a value ' + max_type_char + ' ' + max + '</span>');\n                 err = true;\n             }else if (check_mm == 0) {\n                 $(tabselects[i]).parents(\".input-group\").removeClass(\"has-error\");\n                 if (isNaN(value)) {\n                     if (item_type == \"variable_property\" && value_class == 'STRING') {\n                     }else {\n                         $(tabselects[i]).parents(\".input-group\").addClass(\"has-error\");\n                         $(tabselects[i]).parents(\".input-group\").append('<span id=\"helpBlock-' + id + '\" class=\"help-block\">The value must be a number ! Use dot not coma.</span>');\n                         err = true;\n                     }\n                 }\n             }\n         }\n     }\n     return err;\n }\n\n\n // INITS :\n\n // Show Init\n /**\n  * Show initialization status\n  * @returns void\n  */\n function show_init_status(){\n     //$(\".loadingAnimation\").show();\n     INIT_STATUS_COUNT = INIT_STATUS_COUNT + 1;\n }\n\n // Hide Init\n /**\n  * Hide initialization status\n  * @returns void\n  */\n function hide_init_status(){\n     INIT_STATUS_COUNT = INIT_STATUS_COUNT -1;\n     if (INIT_STATUS_COUNT <= 0){\n         //$(\".loadingAnimation\").hide();\n     }\n }\n\n\n //LOADINGS :\n\n // Set and Show Loading\n /**\n  * Set and show loading state\n  * @param {number} key Element id where to show the loading state\n  * @param {number} value Value to display\n  * @returns void\n  */\n function set_loading_state(key, value) {\n     loading_states[key] = value;\n     if (value < 100) {\n         $('#page-load-label').show();\n         $('#page-load-state').show();\n     }else {\n         hide_loading_state();\n     };\n     $('#page-load-label').text(loading_labels[key]);\n     if ($('#page-load-state').length > 0) {\n         $('#page-load-state')[0].setAttribute('value', (Number.parseFloat(loading_states[key]).toFixed(2)));\n     }\n }\n\n // Hide Loading\n /**\n  * Hide loading state\n  * @returns void\n  */\n function hide_loading_state() {\n     $('#page-load-label').hide();\n     $('#page-load-state').hide();\n }\n\n // UPDATES :\n\n // Show Status\n /**\n  * Show 'AutoUpdateStatus'\n  * @returns void\n  */\n function show_update_status(){\n     $(\".AutoUpdateStatus\").css(\"color\", \"\");\n     $(\".AutoUpdateStatus\").show();\n     UPDATE_STATUS_COUNT++;\n }\n\n // Hide Status\n /**\n  * Hide 'AutoUpdateStatus'\n  * @returns void\n  */\n function hide_update_status(){\n     UPDATE_STATUS_COUNT--;\n     if (UPDATE_STATUS_COUNT <= 0){\n         $(\".AutoUpdateStatus\").hide();\n         UPDATE_STATUS_COUNT = 0;\n     }\n }\n\n // Auto Update\n /**\n  * 'AutoUpdateButton' event\n  * @param {boolean} toggleState\n  */\n function auto_update_click(toggleState=true){\n     if( toggleState) {\n         $('.AutoUpdateButton').bootstrapSwitch('toggleState');\n     }\n     AUTO_UPDATE_ACTIVE = $('.AutoUpdateButton').bootstrapSwitch('state');\n     if (AUTO_UPDATE_ACTIVE) {\n         // deactivate auto update\n     } else {\n         // activate auto update\n         JSON_ERROR_COUNT = 0;\n         //data_handler();\n     }\n }\n\n\n // NOTIFICATIONS :\n\n // Add Notification\n /**\n  * Display an error/information message\n  * @param {string} message Message to display\n  * @param {number} level Level of notification\n  * @param {number} timeout Notification timeout\n  * @param {boolean} clearable Is clearable ?\n  * @returns void\n  */\n function add_notification(message, level,timeout,clearable) {\n     timeout = typeof timeout !== 'undefined' ? timeout : 7000;\n     clearable = typeof clearable !== 'undefined' ? clearable : true;\n\n     var right = 4;\n     var top = 55;\n     if ($('#notification_area').children().hasClass('notification')) {\n         top = Number($('#notification_area .notification').last().css('top').replace(/[^\\d\\.]/g, '')) + 56;\n         right = Number($('#notification_area .notification').last().css('right').replace(/[^\\d\\.]/g, ''));\n     }\n     if (top > 400) {\n         right = right + 50;\n         top = 55;\n     }\n     if (right > 150) {\n         $('#notification_area').empty();\n         right = 4;\n         top = 55;\n     }\n\n     //<0 - Debug\n     //1 - Emergency\n     //2 - Critical\n     //3 - Errors\n     //4 - Alerts\n     //5 - Warnings\n     //6 - Notification (webnotice)\n     //7 - Information (webinfo)\n     //8 - Notification (notice)\n     //9 - Information (info)\n     if (level === 1) {\n         level = 'danger';\n         message_pre = 'Emergency! ';\n     } else if (level === 2) {\n         level = 'danger';\n         message_pre = 'Critical! ';\n     } else if (level === 3) {\n         level = 'danger';\n         message_pre = 'Error! ';\n     } else if (level === 4) {\n         level = 'danger';\n         message_pre = 'Alert! ';\n     } else if (level === 5) {\n         level = 'warning';\n         message_pre = 'Warning! ';\n     }else if (level === 6) {\n         level = 'success';\n         message_pre = 'Notice ';\n     }else if (level === 7) {\n         level = 'info';\n         message_pre = 'Info ';\n     }else if (level === 8) {\n         level = 'success';\n         message_pre = 'Notice ';\n     }else if (level === 9) {\n         level = 'info';\n         message_pre = 'Info ';\n     }\n     if(clearable){\n         $('#notification_area').append('<div id=\"notification_Nb' + NOTIFICATION_COUNT + '\" class=\"notification alert alert-' + level + ' alert-dismissable\" style=\"position: fixed; top: ' + top + 'px; right: ' + right + 'px; z-index: 2000\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-hidden=\"true\">&times;</button><strong>' + message_pre + '</strong>' + new Date().toLocaleTimeString() + ': ' + message + '</div>');\n     }else{\n         $('#notification_area_2').append('<div id=\"notification_Nb' + NOTIFICATION_COUNT + '\" class=\"notification alert alert-' + level + '\" ><strong>'+ message_pre + '</strong>' + new Date().toLocaleTimeString() + ': ' + message + '</div>');\n     }\n     if (timeout){\n         setTimeout('$(\"#notification_Nb' + NOTIFICATION_COUNT + '\").alert(\"close\");', timeout);\n     }\n     NOTIFICATION_COUNT = NOTIFICATION_COUNT + 1;\n     console.log(message_pre + new Date().toLocaleTimeString() + ': ' + message);\n }\n\n // Raise Data\n /**\n  * Display a 'date out of date' error notification\n  * @returns void\n  */\n function raise_data_out_of_date_error(){\n     if (!DATA_OUT_OF_DATE){\n         DATA_OUT_OF_DATE = true;\n         DATA_OUT_OF_DATE_ALERT_ID = add_notification('displayed data is out of date!',4,false,false);\n     }\n }\n\n // CLear Date\n /**\n  * Close the 'data out of date' error notification\n  * @returns void\n  */\n function clear_data_out_of_date_error(){\n     if (DATA_OUT_OF_DATE){\n         DATA_OUT_OF_DATE = false;\n         $('#'+DATA_OUT_OF_DATE_ALERT_ID).alert(\"close\");\n     }\n }\n\n\n // UPDATE LOG :\n /**\n  * Update logs\n  * @returns {boolean}\n  */\n function update_log() {\n     if (LOG_FETCH_PENDING_COUNT){return false;}\n     LOG_FETCH_PENDING_COUNT = true;\n     if(LOG_LAST_TIMESTAMP === 0){\n         if(SERVER_TIME > 0){\n                 LOG_LAST_TIMESTAMP = SERVER_TIME;\n         }else{\n             LOG_FETCH_PENDING_COUNT = false;\n             return false;\n         }\n     }\n\n     show_update_status();\n\n     PYSCADA_XHR = $.ajax({\n         url: ROOT_URL+'json/log_data/',\n         type: 'post',\n         dataType: \"json\",\n         timeout: 29000,\n         data: {timestamp: LOG_LAST_TIMESTAMP},\n         methode: 'post',\n         success: function(data) {\n             $.each(data,function(key,val){\n                     if(\"timestamp\" in data[key]){\n                         if (LOG_LAST_TIMESTAMP<data[key].timestamp){\n                             LOG_LAST_TIMESTAMP = data[key].timestamp;\n                         }\n                         add_notification(data[key].message,+data[key].level);\n                     }\n                 });\n             hide_update_status();\n             LOG_FETCH_PENDING_COUNT = false;\n         },\n         error: function(x, t, m) {\n             hide_update_status();\n             LOG_FETCH_PENDING_COUNT = false;\n         }\n     });\n }\n\n\n // REFRESH LOGO :\n /**\n  * Refresh logo\n  * @param {number} key Element id where to refresh the logo\n  * @param {object} type\n  * @returns void\n  */\n function refresh_logo(key, type){\n     last_time = 0;\n     if (type == \"variable\") {\n         type_short=\"var\";\n         if (key in DATA && DATA[key].length) {\n             last_time = DATA[key][DATA[key].length - 1][0];\n         }\n     } else {\n         type_short = \"prop\";\n         if (key in VARIABLE_PROPERTIES_LAST_MODIFIED) {last_time = VARIABLE_PROPERTIES_LAST_MODIFIED[key]};\n     }\n     type = type.replace(\"_\", \"\").replace(\"-\", \"\")\n     document.querySelectorAll(\".control-item.type-numeric.\" + type_short + \"-\" + key + \" img, button.write-task-btn.\" + type_short + \"-\" + key + \" img, button.write-task-set.\" + type_short + \"-\" + key + \" img\").forEach(item => {\n         item.remove();\n     });\n     key = key.toString();\n     if (get_config_from_hidden_config(type, 'id', key, 'refresh-requested-timestamp')>last_time && (get_config_from_hidden_config(\"variable\", 'id', key, \"readable\") === \"True\" || type !== \"variable\")) {\n         document.querySelectorAll(\".control-item.type-numeric.\" + type_short + \"-\" + key + \", button.write-task-btn.\" + type_short + \"-\" + key + \", button.write-task-set.\" + type_short + \"-\" + key).forEach(e => {\n            var url = '/static/pyscada/img/load.gif';\n            var image = new Image();\n            image.src = url;\n            image.style = \"height:14px;\";\n            image.alt = \"refreshing\";\n            e.prepend(image);\n         });\n     }else {\n         document.querySelectorAll(\".control-item.type-numeric.\" + type_short + \"-\" + key + \" img, button.write-task-btn.\" + type_short + \"-\" + key + \" img, button.write-task-set.\" + type_short + \"-\" + key + \" img\").forEach(item => {\n             item.remove();\n         });\n     }\n }\n\n\n // CHECK MIN MAX :\n /**\n  * Check if 'value' is between min max\n  * @param {number} value Value to check\n  * @param {number} min\n  * @param {number} max\n  * @param {number} min_strict\n  * @param {number} max_strict\n  * @returns {boolean}\n  */\n function check_min_max(value, min, max, min_strict, max_strict) {\n\n     min_strict = typeof min_strict !== 'undefined' ? min_strict : \"lte\";\n     max_strict = typeof max_strict !== 'undefined' ? max_strict : \"gte\";\n     min = typeof min !== 'undefined' ? min : false;\n     max = typeof max !== 'undefined' ? max : false;\n\n     if (min_strict == \"lt\" && parseFloat(value) <= parseFloat(min) && min !== false) {\n         return -1;\n     }\n     if (min_strict == \"lte\" && parseFloat(value) < parseFloat(min) && min !== false) {\n         return -1;\n     }\n     if (max_strict == \"gt\" && parseFloat(value) >= parseFloat(max) && max !== false) {\n         return 1;\n     }\n     if (max_strict == \"gte\" && parseFloat(value) > parseFloat(max) && max !== false) {\n         return 1;\n     }\n     return 0;\n }\n\n\n //                             -----------------------------------------------------------\n //                                                     Client-Server\n //                             -----------------------------------------------------------\n\n // from http://debuggable.com/posts/run-intense-js-without-freezing-the-browser:480f4dd6-f864-4f72-ae16-41cccbdd56cb\n // on 11.04.2014\n $.browserQueue = {\n     _timer: null,\n     _queue: [],\n     add: function(fn, context, time) {\n         var setTimer = function(time) {\n             $.browserQueue._timer = setTimeout(function() {\n                 time = $.browserQueue.add();\n                 if ($.browserQueue._queue.length) {\n                     setTimer(time);\n                 }\n             }, time || 2);\n         }\n\n         if (fn) {\n             $.browserQueue._queue.push([fn, context, time]);\n             if ($.browserQueue._queue.length == 1) {\n                 setTimer(time);\n             }\n             return;\n         }\n\n         var next = $.browserQueue._queue.shift();\n         if (!next) {\n             return 0;\n         }\n         next[0].call(next[1] || window);\n         return next[2];\n     },\n     clear: function() {\n         clearTimeout($.browserQueue._timer);\n         $.browserQueue._queue = [];\n     }\n };\n\n $.ajaxSetup({\n     crossDomain: false, // obviates need for sameOrigin test\n     beforeSend: function(xhr, settings) {\n         if (!csrfSafeMethod(settings.type)) {\n             xhr.setRequestHeader(\"X-CSRFToken\", CSRFTOKEN);\n         }\n     }\n });\n\nfunction getCookie(name) {\n    let cookieValue = null;\n    if (document.cookie && document.cookie !== '') {\n        const cookies = document.cookie.split(';');\n        for (let i = 0; i < cookies.length; i++) {\n            const cookie = cookies[i].trim();\n            // Does this cookie string begin with the name we want?\n            if (cookie.substring(0, name.length + 1) === (name + '=')) {\n                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\n                break;\n            }\n        }\n    }\n    return cookieValue;\n}\n\n\n //                             -----------------------------------------------------------\n //                                             Client-Server's Functions\n //                             -----------------------------------------------------------\n\n // HTTP SAFE METHOD :\n /**\n  * Return a HTTP methods\n  * @param {object} method\n  * @returns {object}\n  */\n function csrfSafeMethod(method) {\n     // these HTTP methods do not require CSRF protection\n     return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));\n }\n\n // DATE :\n\n // Date Range Cb\n /**\n  * Set 'daterange span' and adapt content padding top on navbar size\n  * @param {number} start Date range start\n  * @param {number} end Date range stop\n  */\n function daterange_cb(start, end) {\n     $('#daterange span').html(start.format(daterange_format) + ' - ' + end.format(daterange_format));\n }\n\n\n //                             -----------------------------------------------------------\n //                                                    Click Events\n //                             -----------------------------------------------------------\n\n // FORM :\n\n //form/read-task\n $('button.read-task-set').click(function(){\n     t = SERVER_TIME;\n     key = $(this).data('key');\n     type = $(this).data('type');\n     type = type.replace(\"_\", \"\");\n     //$(\".variable-config[data-key=\" + key + \"][data-type=\" + type + \"]\").attr('data-refresh-requested-timestamp',t)\n     set_config_from_hidden_config(type.replace(\"_\",\"\"),\"id\",key,\"refresh-requested-timestamp\",t);\n     refresh_logo(key, type);\n     data_type = $(this).data('type');\n     $(this)[0].disabled = true;\n     PYSCADA_XHR = $.ajax({\n         type: 'post',\n         url: ROOT_URL+READ_TASK_URL,\n         data: {key:key, type:data_type},\n         success: function (data) {\n\n         },\n         error: function(data) {\n             add_notification('read task failed',3);\n         }\n     });\n     $(this)[0].disabled = false;\n });\n\n //form/write_task/\n $('button.write-task-set').click(function(){\n     key = $(this).data('key');\n     id = $(this).attr('id');\n     value = $(\"#\"+id+\"-value\").val();\n     item_type = $(this).data('type');\n     val=$('.variable-config[data-id='+id.replace('-value', '')+']');\n     min = $(val).data('min');\n     max = $(val).data('max');\n     value_class = $(val).data('value-class');\n     min_type = $(val).data('min-type');\n     max_type = $(val).data('max-type');\n     if (min_type == 'lte') {min_type_char = \">=\";} else {min_type_char = \">\";}\n     if (max_type == 'gte') {max_type_char = \"<=\";} else {max_type_char = \"<\";}\n     if (value == \"\" || value == null) {\n         $(this).parents(\".input-group\").addClass(\"has-error\");\n         $(this).parents(\".input-group\").find('.help-block').remove()\n         $(this).parents(\".input-group-btn\").after('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Please provide a value !</span>');\n     }else {\n         $(this).parents(\".input-group\").find('.help-block').remove();\n         check_mm = check_min_max(parseFloat(value), parseFloat(min), parseFloat(max), min_type, max_type);\n         if (check_mm == -1) {\n             $(this).parents(\".input-group\").addClass(\"has-error\");\n             $(this).parents(\".input-group-btn\").after('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Enter a value ' + min_type_char + ' ' + min + '</span>');\n         }else if (check_mm == 1) {\n             $(this).parents(\".input-group\").addClass(\"has-error\");\n             $(this).parents(\".input-group-btn\").after('<span id=\"helpBlock-' + id + '\" class=\"help-block\">Enter a value ' + max_type_char + ' ' + max + '</span>');\n         }else if (check_mm == 0) {\n             $(this).parents(\".input-group\").removeClass(\"has-error\")\n             if (isNaN(value)) {\n                 if (item_type == \"variable_property\" && value_class == 'STRING'){\n                     set_config_from_hidden_config(item_type.replace(\"_\",\"\"),\"id\",key,\"refresh-requested-timestamp\",SERVER_TIME);\n                     refresh_logo(key, item_type);\n                     PYSCADA_XHR = $.ajax({\n                         type: 'post',\n                         url: ROOT_URL+WRITE_TASK_URL,\n                         data: {key:key, value:value, item_type:item_type, view_id: VIEW_ID},\n                         success: function (data) {\n\n                         },\n                         error: function(data) {\n                             add_notification('Operation not permitted (prop ' + key + \")\",3);\n                         }\n                     });\n                 }else {\n                     $(this).parents(\".input-group\").addClass(\"has-error\");\n                     $(this).parents(\".input-group-btn\").after('<span id=\"helpBlock-' + id + '\" class=\"help-block\">The value must be a number ! Use dot not coma.</span>');\n                 };\n             }else {\n                 set_config_from_hidden_config(item_type.replace(\"_\",\"\"),\"id\",key,\"refresh-requested-timestamp\",SERVER_TIME);\n                 refresh_logo(key, item_type);\n                 PYSCADA_XHR = $.ajax({\n                     type: 'post',\n                     url: ROOT_URL+WRITE_TASK_URL,\n                     data: {key:key, value:value, item_type:item_type, view_id: VIEW_ID},\n                     success: function (data) {\n\n                     },\n                     error: function(data) {\n                         add_notification('Operation not permitted (var ' + key + \")\",3);\n                     }\n                 });\n             };\n         };\n     };\n });\n // set\n $('button.write-task-form-set').click(function(){\n     id_form = $(this.form).attr('id');\n     if (check_form(id_form)) {return;}\n\n     tabinputs = $.merge(tabinputs,$('#'+id_form+ ' :input:button.type-bool'));\n     for (i=0;i<tabinputs.length;i++){\n         value = $(tabinputs[i]).val();\n         id = $(tabinputs[i]).attr('id');\n         val=$('.variable-config[data-id='+id.replace('-value', '')+']')\n         var_name = $(val).data(\"name\");\n         key = parseInt($(val).data('key'));\n         item_type = $(val).data('type');\n         set_config_from_hidden_config(item_type.replace(\"_\",\"\"),\"id\",key,\"refresh-requested-timestamp\",SERVER_TIME);\n         refresh_logo(key, item_type);\n\n         if ($(tabinputs[i]).hasClass('btn-success')){\n             id = $(tabinputs[i]).attr('id');\n             //$('#'+id).removeClass('update-able');\n             PYSCADA_XHR = $.ajax({\n                 type: 'post',\n                 url: ROOT_URL+WRITE_TASK_URL,\n                 data: {key:key,value:1,item_type:item_type, view_id: VIEW_ID},\n                 success: function (data) {\n                 },\n                 error: function(data) {\n                     add_notification('Operation not permitted (bool ' + key + \")\",3);\n                 }\n             });\n         }else if ($(tabinputs[i]).hasClass('btn-default')){\n             id = $(tabinputs[i]).attr('id');\n             //$('#'+id).removeClass('update-able');\n             PYSCADA_XHR = $.ajax({\n                 type: 'post',\n                 url: ROOT_URL+WRITE_TASK_URL,\n                 data: {key:key,value:0,item_type:item_type, view_id: VIEW_ID},\n                 success: function (data) {\n                 },\n                 error: function(data) {\n                     add_notification('Operation not permitted (bool ' + key + \")\",3);\n                 }\n             });\n         }else{\n             PYSCADA_XHR = $.ajax({\n                 type: 'post',\n                 url: ROOT_URL+WRITE_TASK_URL,\n                 data: {key:key, value:value, item_type:item_type, view_id: VIEW_ID},\n                 success: function (data) {\n\n                 },\n                 error: function(data) {\n                     add_notification('Operation not permitted (var ' + key + \")\",3);\n                     alert(\"Form Set NOK inputs \"+data+\" - key \"+key+\" - value \"+value+\" - item_type \"+item_type + \" - name \"+var_name)\n                 }\n             });\n         };\n     };\n     for (i=0;i<tabselects.length;i++){ //test if there is an empty value\n         value = $(tabselects[i]).val();\n         var_name = $(tabselects[i]).data(\"name\");\n         key = $(tabselects[i]).data('key');\n         item_type = $(tabselects[i]).data('type');\n         set_config_from_hidden_config(item_type.replace(\"_\",\"\"),\"id\",key,\"refresh-requested-timestamp\",SERVER_TIME);\n         refresh_logo(key, item_type);\n         if (isNaN(value)){\n             if (item_type == \"variable_property\"){\n                 PYSCADA_XHR = $.ajax({\n                     type: 'post',\n                     url: ROOT_URL+WRITE_TASK_URL,\n                     data: {key:key, value:value, item_type:item_type, view_id: VIEW_ID},\n                     success: function (data) {\n\n                     },\n                     error: function(data) {\n                         add_notification('Operation not permitted (dropdown ' + key + \")\",3);\n                     }\n                 });\n             }else {\n                 add_notification(\"select is \" + item_type + \" and not a number\",3);\n             };\n         }else {\n             PYSCADA_XHR = $.ajax({\n                 type: 'post',\n                 url: ROOT_URL+WRITE_TASK_URL,\n                 data: {key:key, value:value, item_type:item_type, view_id: VIEW_ID},\n                 success: function (data) {\n\n                 },\n                 error: function(data) {\n                     add_notification('Operation not permitted (dropdown ' + key + \")\",3);\n                     alert(\"Form Set NOK selects \"+data+\" - key \"+key+\" - value \"+value+\" - item_type \"+item_type + \" - name \"+var_name)\n                 }\n             });\n         };\n     };\n });\n // button\n $('input.write-task-btn').click(function(){\n     key = $(this).data('key');\n     id = $(this).attr('id');\n     item_type = $(this).data('type');\n     set_config_from_hidden_config(item_type.replace(\"_\",\"\"),\"id\",key,\"refresh-requested-timestamp\",SERVER_TIME);\n     refresh_logo(key, item_type);\n     $('#'+id).removeClass('update-able');\n     $(\".variable-config[data-refresh-requested-timestamp][data-key=\" + key + \"][data-type=\" + item_type + \"]\").attr('data-refresh-requested-timestamp', SERVER_TIME)\n     $(\".variable-config2[data-refresh-requested-timestamp][data-id=\" + key + \"]\").attr('data-refresh-requested-timestamp', SERVER_TIME)\n     if($(this).hasClass('btn-default')){\n         $('#'+id).removeClass('btn-default')\n         $('#'+id).addClass('btn-success');\n     }else if ($(this).hasClass('btn-success')){\n         $('#'+id).addClass('btn-default')\n         $('#'+id).removeClass('btn-success');\n     }\n });\n\n $('button.write-task-btn').click(function(){\n     key = $(this).data('key');\n     id = $(this).attr('id');\n     item_type = $(this).data('type');\n     set_config_from_hidden_config(item_type.replace(\"_\",\"\"),\"id\",key,\"refresh-requested-timestamp\",SERVER_TIME);\n     refresh_logo(key, item_type);\n     $('#'+id).removeClass('update-able');\n     $(\".variable-config[data-refresh-requested-timestamp][data-key=\" + key + \"][data-type=\" + item_type + \"]\").attr('data-refresh-requested-timestamp', SERVER_TIME)\n     $(\".variable-config2[data-refresh-requested-timestamp][data-id=\" + key + \"]\").attr('data-refresh-requested-timestamp', SERVER_TIME)\n     if($(this).hasClass('btn-default')){\n         PYSCADA_XHR = $.ajax({\n             type: 'post',\n             url: ROOT_URL+WRITE_TASK_URL,\n             data: {key:key,value:1,item_type:item_type, view_id: VIEW_ID},\n             success: function (data) {\n                 $('#'+id).removeClass('btn-default')\n                 $('#'+id).addClass('btn-success');\n             },\n             error: function(data) {\n                 add_notification('Operation not permitted (bool ' + key + \")\",3);\n             }\n         });\n     }else if ($(this).hasClass('btn-success')){\n         PYSCADA_XHR = $.ajax({\n             type: 'post',\n             url: ROOT_URL+WRITE_TASK_URL,\n             data: {key:key,value:0,item_type:item_type, view_id: VIEW_ID},\n             success: function (data) {\n                 $('#'+id).addClass('btn-default')\n                 $('#'+id).removeClass('btn-success');\n             },\n             error: function(data) {\n                 add_notification('Operation not permitted (bool ' + key + \")\",3);\n             }\n         });\n     }\n });\n\n set_loading_state(1, 10);\n\ndocument.body.onload = () => init_pyscada_content();\n\nfunction init_pyscada_content() {\n  console.log(\"PyScada HMI : fetching hidden config2\")\n  fetch('/getHiddenConfig2/' + document.querySelector(\"body\").dataset[\"viewTitle\"] + \"/\", {\n   method: \"POST\",\n   headers: {\n     \"X-CSRFToken\": CSRFTOKEN,\n     \"Content-Type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n   },\n   body: \"\",\n }).then((response) => {\n   if (!response.ok) {\n     throw new Error(`HTTP error, status = ${response.status}`);\n   }\n   set_loading_state(1, 20);\n   return response.text();\n }).then((text) => {\n   document.querySelector(\"body #wrap #content .hidden.globalConfig2\").innerHTML = text;\n   console.log(\"PyScada HMI : hidden config2 loaded\");\n }).then(() => {\n     // show buttons\n     $(\".loadingAnimation\").parent().show();\n     $(\".AutoUpdateStatus\").parent().parent().show();\n     $(\".ReadAllTask\").parent().parent().show();\n     $(\".AutoUpdateButtonParent\").show();\n\n     // init loading states\n     set_loading_state(1, 40);\n\n     // padding top content\n\n     // Show current page or first\n     show_page();\n\n     // move overlapping side menus\n     // left\n     var menu_pos = $('footer')[0].clientHeight + 6;\n     $.each($('.side-menu.left'),function(key,val){\n         $(val).attr(\"style\",\"bottom: \" + menu_pos + \"px;\");\n         menu_pos = menu_pos + val.clientHeight + 10;\n     });\n     // right\n     menu_pos = $('footer')[0].clientHeight + 6;\n     $.each($('.side-menu.right'),function(key,val){\n         $(val).attr(\"style\",\"bottom: \" + menu_pos + \"px;\");\n         menu_pos = menu_pos + val.clientHeight + 10;\n     });\n\n     // sidemenus\n     //left\n     $('.side-menu.left').mouseenter(function(){\n         $(this).stop().animate({\"left\":0},500);\n     }).mouseleave(function(){\n         ow = $(this).outerWidth();\n         $(this).stop().animate({\"left\":-(ow - 11)},500);\n     });\n     // right\n     $('.side-menu.right').mouseenter(function(){\n         $(this).stop().animate({\"right\":0},500);\n     }).mouseleave(function(){\n         ow = $(this).outerWidth();\n         $(this).stop().animate({\"right\":-(ow - 11)},500);\n     });\n     // bottom\n     $('.side-menu.bottom').css('margin-left',- $('.side-menu.bottom').outerWidth(true)/2);\n     $('.side-menu.bottom').stop().animate({\"bottom\":-($('.side-menu.bottom').outerHeight(true) - 31)},500);\n     $('.side-menu.bottom').mouseenter(function(){\n         $(this).stop().animate({\"bottom\":0},500);\n     }).mouseleave(function(){\n         outerHeight = $(this).outerHeight(true);\n         $(this).stop().animate({\"bottom\":-(outerHeight - 31)},500);\n     });\n\n     set_loading_state(1, loading_states[1] + 10);\n\n\n     // prevent reloading by existent\n     window.onbeforeunload = function() {\n         if (ONBEFORERELOAD_ASK) {\n             return \"you realy wan't to reload/leave the page?\";\n         }else {\n             return null;\n         };\n     };\n     // stop all setTimeout and Ajax requests when leaving\n     window.onunload = function() {\n         for (t in PYSCADA_TIMEOUTS) {clearTimeout(PYSCADA_TIMEOUTS[t]);console.log(\"PyScada HMI : clearing timeout\", t);}\n         if(PYSCADA_XHR != null && PYSCADA_XHR.readyState != 4){\n             PYSCADA_XHR.abort();\n             for (c in FETCH_CONTROLLERS) {FETCH_CONTROLLERS[c].abort();}\n             console.log(\"PyScada HMI : aborting xhr and fetch requests\")\n         }\n     };\n     $(window).on('hashchange', function() {\n         // nav menu click event\n         if (window.location.hash.length > 0) {\n             $('ul.navbar-nav li.active').removeClass('active');\n             $('a[href$=\"' + window.location.hash + '\"]').parent('li').addClass('active');\n             show_page();\n         }\n         toggle_daterangepicker();\n         toggle_timeline();\n         updatePyScadaPlots(false);\n     });\n\n     set_loading_state(1, loading_states[1] + 10);\n\n\tfix_page_anchor();\n\n     // Activate tooltips\n     $('[data-toggle*=\"tooltip\"]').tooltip();\n\n     // Setup drop down menu\n     $('.dropdown-toggle').dropdown();\n\n     // Setup auto-update switch button\n     $('.AutoUpdateButton').removeClass('hidden');\n     $('.AutoUpdateButton').bootstrapSwitch({\n         onInit: function(event) {\n             $('.bootstrap-switch-id-AutoUpdateButton').tooltip({title:\"Auto update data\", placement:\"bottom\"});\n         }\n       });\n\n     // Fix input element click problem\n     $('.dropdown input, .dropdown label, .dropdown button').click(function(e) {\n         e.stopPropagation();\n     });\n     set_loading_state(1, loading_states[1] + 10);\n\n     // init\n     $.each($('.bar-container'),function(key,val){\n         // get identifier of the chart\n         id = val.id.substring(16);\n         min = $(val).data('min');\n         max = $(val).data('max');\n         if ( min === null ) {min = 0;}\n         if ( max === null ) {max = 100;}\n\n         // add a new Plot\n         PyScadaPlots.push(new Bar(id, min, max));\n     });\n     $.each($('.chart-container'),function(key,val){\n         // get identifier of the chart\n         id = val.id.substring(16);\n         if ($(val).data('xaxis').id == 'False') {xaxisVarId = null;} else {xaxisVarId = $(val).data('xaxis').id;}\n         if ($(val).data('xaxis').linlog == 'True') {xaxisLinLog = true;} else {xaxisLinLog = false;}\n         // add a new Plot\n         PyScadaPlots.push(new PyScadaPlot(id, xaxisVarId, xaxisLinLog));\n     });\n     $.each($('.pie-container'),function(key,val){\n         // get identifier of the chart\n         id = val.id.substring(16);\n         radius = $(val).data('radius').radius / 100;\n         innerRadius = $(val).data('radius').innerRadius / 100;\n         // add a new Plot\n         PyScadaPlots.push(new Pie(id, radius, innerRadius));\n     });\n     document.querySelectorAll('.gauge-container').forEach(e => {\n         // get identifier of the chart\n         id = e.id.substring(16);\n         min = JSON.parse(e.dataset[\"params\"]).min;\n         max = JSON.parse(e.dataset[\"params\"]).max;\n         if ( min === null ) {min = 0;}\n         if ( max === null ) {max = 100;}\n\n         thresholdValues = JSON.parse(JSON.parse(e.dataset[\"params\"]).threshold_values);\n\n         max2 = max;\n         for (v in thresholdValues) {\n             if (v != \"max\") {max2 = Math.max(max2, v);}\n         }\n         threshold_values = [];\n         for (var v in thresholdValues) {\n             if (v == \"max\") {\n                v = max2;\n                thresholdValues[v]=thresholdValues['max'];\n             }\n             threshold_values.push({value:Number(v), color:thresholdValues[v]})\n         }\n         // fix threshold values: items must be minimum 3, has the gauge threshold default\n         while (threshold_values.length < 3 && Object.keys(thresholdValues).length) {\n             threshold_values.push({value:Number(Object.keys(thresholdValues)[0]), color:thresholdValues[Object.keys(thresholdValues)[0]]})\n         }\n         if ( threshold_values === \"\" ) {threshold_values = [];}\n         // add a new Plot\n         PyScadaPlots.push(new Gauge(id, min, max, threshold_values));\n     });\n\n     $.each($('.variable-config'),function(key,val){\n         key = parseInt($(val).data('key'));\n         init_type = parseInt($(val).data('init-type'));\n         item_type = $(val).data('type');\n         if(item_type == '' || typeof(item_type) == 'undefined'){\n             item_type = \"variable\";\n         }\n\n         if( VARIABLE_PROPERTY_KEYS.indexOf(key)==-1 && item_type === \"variable_property\"){\n             VARIABLE_PROPERTY_KEYS.push(key);\n         }else if (VARIABLE_KEYS.indexOf(key)==-1 && item_type === \"variable\"){\n             VARIABLE_KEYS.push(key);\n         }\n         if (typeof(STATUS_VARIABLE_KEYS[key]) == 'undefined' && init_type==0 && item_type === \"variable\"){\n             STATUS_VARIABLE_KEYS[key] = 0;\n         }\n         if (typeof(CHART_VARIABLE_KEYS[key]) == 'undefined' && init_type==1 && item_type === \"variable\"){\n             CHART_VARIABLE_KEYS[key] = 0;\n         }\n         if (typeof(VARIABLE_PROPERTIES[key]) == 'undefined' && item_type === \"variable_property\"){\n             VARIABLE_PROPERTIES[key] = 0;\n         }\n     });\n\n     // Add calculated aggregated variable to CHART_VARIABLE_KEYS\n     $.each($('.calculatedvariable-config2'),function(key,val){\n         id = parseInt($(val).data('store-variable'));\n         CHART_VARIABLE_KEYS[id] = 0;\n     });\n\n     // Add control item variables with transform data (display value option) needing the whole data\n     document.querySelectorAll(\".transformdata-config2\").forEach(e => {\n       if (e.dataset[\"needHistoricalData\"] == \"True\") {\n         displayvalueoption_id = get_config_from_hidden_config('displayvalueoption', 'transform-data', e.dataset[\"id\"], 'id')\n         variable_id = get_config_from_hidden_config('controlitem', 'display-value-options', displayvalueoption_id, 'variable')\n         CHART_VARIABLE_KEYS[variable_id] = 0;\n       }\n     });\n\n     set_loading_state(1, loading_states[1] + 10);\n\n\n     // zoom selection mode\n     $('.activate_zoom_x').change(function() {\n         set_chart_selection_mode();\n     });\n     $('.activate_zoom_y').change(function() {\n         set_chart_selection_mode();\n     });\n\n\n     if (SYNC_HANDLING_DATA) {\n        PYSCADA_TIMEOUTS[\"data_handler\"] = setTimeout(function() {data_handler();}, 5000);\n     }\n     set_chart_selection_mode();\n\n\n     // timeline setup\n     $( \"#timeline\" ).resizable({\n         handles: \"e, w\",\n         containment: \"#timeline-border\",\n         stop: progressbarSetWindow,\n         start: function( event, ui ) {progressbar_resize_active = true;},\n         resize: timeline_resize,\n         maxWidth: $('#timeline-border').width()-10\n     });\n     $('#timeline-border').on('resize', function(){\n         $( \"#timeline\" ).resizable(\"option\", \"maxWidth\",$('#timeline-border').width()-10);\n     });\n     $('#timeline').draggable({\n         axis: \"x\",\n         containment: \"#timeline-border\",\n         drag: timeline_drag,\n         start: function( event, ui ) {progressbar_resize_active = true;},\n         stop: progressbarSetWindow,\n     });\n\n     // Send request data to all devices\n     $('.ReadAllTask').click(function(e) {\n       PYSCADA_XHR = $.ajax({\n           url: ROOT_URL+READ_ALL_TASK_URL,\n           type: \"POST\",\n           data:{},\n           success: function (data) {\n             document.querySelectorAll('.hidden.variable-config2').forEach(function(e) {\n                e.setAttribute('data-refresh-requested-timestamp',SERVER_TIME);\n             });\n             document.querySelectorAll('.hidden.variable-config2').forEach(function(e) {\n                 var type = \"variable\";\n                 var key = e.getAttribute('data-id');\n                 refresh_logo(key, type);\n             })\n\n             document.querySelectorAll('.hidden.variableproperty-config2').forEach(function(e) {\n                e.setAttribute('data-refresh-requested-timestamp',SERVER_TIME);\n             });\n             document.querySelectorAll('.hidden.variableproperty-config2').forEach(function(e) {\n                 var type = \"variableproperty\";\n                 var key = e.getAttribute('data-id');\n                 refresh_logo(key, type);\n             })\n           },\n           error: function(x, t, m) {\n               add_notification('Request all data failed', 1);\n           },\n         });\n     });\n\n     // auto update function\n     $(\".AutoUpdateButton\").on('switchChange.bootstrapSwitch', function(e, d) {\n         auto_update_click(false);\n     });\n\n     // show timeline and daterangepicker\n     toggle_daterangepicker();\n     toggle_timeline();\n\n     set_loading_state(1, loading_states[1] + 10);\n\n\n     // Resize charts on windows resize\n     $(window).resize(function() {\n       updatePyScadaPlots(force=false, update=false, resize=true);\n       fix_page_anchor(); // also adjust the anchor points for page refs if nessesary\n     });\n     set_loading_state(1, loading_states[1] + 10);\n\n     // Prevent closing dropdown on click\n     $('.dropdown-menu').click(function(e) {\n         e.stopPropagation();\n     });\n\n     set_loading_state(1, 100);\n     hide_loading_state();\n\n     // Set and show refresh rate input\n     document.querySelectorAll('.refresh-rate-input').forEach(item => {item.oninput = function () {\n         document.querySelectorAll('.refresh-rate-output').forEach(item => {item.innerHTML = this.value});\n         document.querySelectorAll('.refresh-rate-input').forEach(item => {item.value = this.value});\n         REFRESH_RATE = this.value;\n     }});\n     document.querySelectorAll('.refresh-rate-output').forEach(item => {item.innerHTML= document.querySelector('.refresh-rate-input').value});\n     document.querySelectorAll('.refresh-rate-li').forEach(item => {item.classList.remove('hidden')});\n     document.querySelectorAll('.refresh-rate-divider').forEach(item => {item.classList.remove('hidden')});\n\n     // Fill aggregated lists\n     setAggregatedLists();\n }).then(() => {\n   // PyScada Core JS loaded successfully => send the event for plugins\n   var event = new CustomEvent(\"PyScadaCoreJSLoaded\");\n   document.dispatchEvent(event);\n   console.log(\"PyScada HMI : dispatch PyScadaCoreJSLoaded event\");\n }).catch((err) => {\n   console.log(\"PyScada HMI : \", err);\n   console.log(\"Retrying to init PyScada Core JS in 1 sec...\");\n   setTimeout(init_pyscada_content, 1000);\n });\n};\n"
  },
  {
    "path": "pyscada/hmi/static/pyscada/js/tempusdominus-bootstrap-3.js",
    "content": "/*@preserve\n * Tempus Dominus Bootstrap3 v5.0.0-alpha10 (https://tempusdominus.github.io/bootstrap-3/)\n * Copyright 2016-2018 Jonathan Peterson\n * Licensed under MIT (https://github.com/tempusdominus/bootstrap-3/blob/master/LICENSE)\n */\n\nif (typeof jQuery === 'undefined') {\n  throw new Error('Tempus Dominus Bootstrap3\\'s requires jQuery. jQuery must be included before Tempus Dominus Bootstrap3\\'s JavaScript.');\n}\n\n+function ($) {\n  var version = $.fn.jquery.split(' ')[0].split('.');\n  if ((version[0] < 2 && version[1] < 9) || (version[0] === 1 && version[1] === 9 && version[2] < 1) || (version[0] >= 4)) {\n    throw new Error('Tempus Dominus Bootstrap3\\'s requires at least jQuery v3.0.0 but less than v4.0.0');\n  }\n}(jQuery);\n\n\nif (typeof moment === 'undefined') {\n  throw new Error('Tempus Dominus Bootstrap3\\'s requires moment.js. Moment.js must be included before Tempus Dominus Bootstrap3\\'s JavaScript.');\n}\n\nvar version = moment.version.split('.')\nif ((version[0] <= 2 && version[1] < 17) || (version[0] >= 3)) {\n  throw new Error('Tempus Dominus Bootstrap3\\'s requires at least moment.js v2.17.0 but less than v3.0.0');\n}\n\n+function () {\n\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n// ReSharper disable once InconsistentNaming\nvar DateTimePicker = function ($, moment) {\n    // ReSharper disable InconsistentNaming\n    var NAME = 'datetimepicker',\n        DATA_KEY = '' + NAME,\n        EVENT_KEY = '.' + DATA_KEY,\n        DATA_API_KEY = '.data-api',\n        Selector = {\n        DATA_TOGGLE: '[data-toggle=\"' + DATA_KEY + '\"]'\n    },\n        ClassName = {\n        INPUT: NAME + '-input'\n    },\n        Event = {\n        CHANGE: 'change' + EVENT_KEY,\n        BLUR: 'blur' + EVENT_KEY,\n        KEYUP: 'keyup' + EVENT_KEY,\n        KEYDOWN: 'keydown' + EVENT_KEY,\n        FOCUS: 'focus' + EVENT_KEY,\n        CLICK_DATA_API: 'click' + EVENT_KEY + DATA_API_KEY,\n        //emitted\n        UPDATE: 'update' + EVENT_KEY,\n        ERROR: 'error' + EVENT_KEY,\n        HIDE: 'hide' + EVENT_KEY,\n        SHOW: 'show' + EVENT_KEY\n    },\n        DatePickerModes = [{\n        CLASS_NAME: 'days',\n        NAV_FUNCTION: 'M',\n        NAV_STEP: 1\n    }, {\n        CLASS_NAME: 'months',\n        NAV_FUNCTION: 'y',\n        NAV_STEP: 1\n    }, {\n        CLASS_NAME: 'years',\n        NAV_FUNCTION: 'y',\n        NAV_STEP: 10\n    }, {\n        CLASS_NAME: 'decades',\n        NAV_FUNCTION: 'y',\n        NAV_STEP: 100\n    }],\n        KeyMap = {\n        'up': 38,\n        38: 'up',\n        'down': 40,\n        40: 'down',\n        'left': 37,\n        37: 'left',\n        'right': 39,\n        39: 'right',\n        'tab': 9,\n        9: 'tab',\n        'escape': 27,\n        27: 'escape',\n        'enter': 13,\n        13: 'enter',\n        'pageUp': 33,\n        33: 'pageUp',\n        'pageDown': 34,\n        34: 'pageDown',\n        'shift': 16,\n        16: 'shift',\n        'control': 17,\n        17: 'control',\n        'space': 32,\n        32: 'space',\n        't': 84,\n        84: 't',\n        'delete': 46,\n        46: 'delete'\n    },\n        ViewModes = ['times', 'days', 'months', 'years', 'decades'],\n        keyState = {},\n        keyPressHandled = {};\n\n    var MinViewModeNumber = 0,\n        Default = {\n        timeZone: '',\n        format: false,\n        dayViewHeaderFormat: 'MMMM YYYY',\n        extraFormats: false,\n        stepping: 1,\n        minDate: false,\n        maxDate: false,\n        useCurrent: true,\n        collapse: true,\n        locale: moment.locale(),\n        defaultDate: false,\n        disabledDates: false,\n        enabledDates: false,\n        icons: {\n            time: 'fa fa-clock-o',\n            date: 'fa fa-calendar',\n            up: 'fa fa-arrow-up',\n            down: 'fa fa-arrow-down',\n            previous: 'fa fa-chevron-left',\n            next: 'fa fa-chevron-right',\n            today: 'fa fa-calendar-check-o',\n            clear: 'fa fa-delete',\n            close: 'fa fa-times'\n        },\n        tooltips: {\n            today: 'Go to today',\n            clear: 'Clear selection',\n            close: 'Close the picker',\n            selectMonth: 'Select Month',\n            prevMonth: 'Previous Month',\n            nextMonth: 'Next Month',\n            selectYear: 'Select Year',\n            prevYear: 'Previous Year',\n            nextYear: 'Next Year',\n            selectDecade: 'Select Decade',\n            prevDecade: 'Previous Decade',\n            nextDecade: 'Next Decade',\n            prevCentury: 'Previous Century',\n            nextCentury: 'Next Century',\n            pickHour: 'Pick Hour',\n            incrementHour: 'Increment Hour',\n            decrementHour: 'Decrement Hour',\n            pickMinute: 'Pick Minute',\n            incrementMinute: 'Increment Minute',\n            decrementMinute: 'Decrement Minute',\n            pickSecond: 'Pick Second',\n            incrementSecond: 'Increment Second',\n            decrementSecond: 'Decrement Second',\n            togglePeriod: 'Toggle Period',\n            selectTime: 'Select Time',\n            selectDate: 'Select Date'\n        },\n        useStrict: false,\n        sideBySide: false,\n        daysOfWeekDisabled: false,\n        calendarWeeks: false,\n        viewMode: 'days',\n        toolbarPlacement: 'default',\n        buttons: {\n            showToday: false,\n            showClear: false,\n            showClose: false\n        },\n        widgetPositioning: {\n            horizontal: 'auto',\n            vertical: 'auto'\n        },\n        widgetParent: null,\n        ignoreReadonly: false,\n        keepOpen: false,\n        focusOnShow: true,\n        inline: false,\n        keepInvalid: false,\n        keyBinds: {\n            up: function up() {\n                if (!this.widget) {\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().subtract(7, 'd'));\n                } else {\n                    this.date(d.clone().add(this.stepping(), 'm'));\n                }\n                return true;\n            },\n            down: function down() {\n                if (!this.widget) {\n                    this.show();\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().add(7, 'd'));\n                } else {\n                    this.date(d.clone().subtract(this.stepping(), 'm'));\n                }\n                return true;\n            },\n            'control up': function controlUp() {\n                if (!this.widget) {\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().subtract(1, 'y'));\n                } else {\n                    this.date(d.clone().add(1, 'h'));\n                }\n                return true;\n            },\n            'control down': function controlDown() {\n                if (!this.widget) {\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().add(1, 'y'));\n                } else {\n                    this.date(d.clone().subtract(1, 'h'));\n                }\n                return true;\n            },\n            left: function left() {\n                if (!this.widget) {\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().subtract(1, 'd'));\n                }\n                return true;\n            },\n            right: function right() {\n                if (!this.widget) {\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().add(1, 'd'));\n                }\n                return true;\n            },\n            pageUp: function pageUp() {\n                if (!this.widget) {\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().subtract(1, 'M'));\n                }\n                return true;\n            },\n            pageDown: function pageDown() {\n                if (!this.widget) {\n                    return false;\n                }\n                var d = this._dates[0] || this.getMoment();\n                if (this.widget.find('.datepicker').is(':visible')) {\n                    this.date(d.clone().add(1, 'M'));\n                }\n                return true;\n            },\n            enter: function enter() {\n                if (!this.widget) {\n                    return false;\n                }\n                this.hide();\n                return true;\n            },\n            escape: function escape() {\n                if (!this.widget) {\n                    return false;\n                }\n                this.hide();\n                return true;\n            },\n            'control space': function controlSpace() {\n                if (!this.widget) {\n                    return false;\n                }\n                if (this.widget.find('.timepicker').is(':visible')) {\n                    this.widget.find('.btn[data-action=\"togglePeriod\"]').click();\n                }\n                return true;\n            },\n            t: function t() {\n                if (!this.widget) {\n                    return false;\n                }\n                this.date(this.getMoment());\n                return true;\n            },\n            'delete': function _delete() {\n                if (!this.widget) {\n                    return false;\n                }\n                this.clear();\n                return true;\n            }\n        },\n        debug: false,\n        allowInputToggle: false,\n        disabledTimeIntervals: false,\n        disabledHours: false,\n        enabledHours: false,\n        viewDate: false,\n        allowMultidate: false,\n        multidateSeparator: ','\n    };\n\n    // ReSharper restore InconsistentNaming\n\n    // ReSharper disable once DeclarationHides\n    // ReSharper disable once InconsistentNaming\n\n    var DateTimePicker = function () {\n        /** @namespace eData.dateOptions */\n        /** @namespace moment.tz */\n\n        function DateTimePicker(element, options) {\n            _classCallCheck(this, DateTimePicker);\n\n            this._options = this._getOptions(options);\n            this._element = element;\n            this._dates = [];\n            this._datesFormatted = [];\n            this._viewDate = null;\n            this.unset = true;\n            this.component = false;\n            this.widget = false;\n            this.use24Hours = null;\n            this.actualFormat = null;\n            this.parseFormats = null;\n            this.currentViewMode = null;\n\n            this._int();\n        }\n\n        /**\n         * @return {string}\n         */\n\n\n        //private\n\n        DateTimePicker.prototype._int = function _int() {\n            var targetInput = this._element.data('target-input');\n            if (this._element.is('input')) {\n                this.input = this._element;\n            } else if (targetInput !== undefined) {\n                if (targetInput === 'nearest') {\n                    this.input = this._element.find('input');\n                } else {\n                    this.input = $(targetInput);\n                }\n            }\n\n            this._dates = [];\n            this._dates[0] = this.getMoment();\n            this._viewDate = this.getMoment().clone();\n\n            $.extend(true, this._options, this._dataToOptions());\n\n            this.options(this._options);\n\n            this._initFormatting();\n\n            if (this.input !== undefined && this.input.is('input') && this.input.val().trim().length !== 0) {\n                this._setValue(this._parseInputDate(this.input.val().trim()), 0);\n            } else if (this._options.defaultDate && this.input !== undefined && this.input.attr('placeholder') === undefined) {\n                this._setValue(this._options.defaultDate, 0);\n            }\n            if (this._options.inline) {\n                this.show();\n            }\n        };\n\n        DateTimePicker.prototype._update = function _update() {\n            if (!this.widget) {\n                return;\n            }\n            this._fillDate();\n            this._fillTime();\n        };\n\n        DateTimePicker.prototype._setValue = function _setValue(targetMoment, index) {\n            var oldDate = this.unset ? null : this._dates[index];\n            var outpValue = '';\n            // case of calling setValue(null or false)\n            if (!targetMoment) {\n                if (!this._options.allowMultidate || this._dates.length === 1) {\n                    this.unset = true;\n                    this._dates = [];\n                    this._datesFormatted = [];\n                } else {\n                    outpValue = this._element.data('date') + ',';\n                    outpValue = outpValue.replace(oldDate.format(this.actualFormat) + ',', '').replace(',,', '').replace(/,\\s*$/, '');\n                    this._dates.splice(index, 1);\n                    this._datesFormatted.splice(index, 1);\n                }\n                if (this.input !== undefined) {\n                    this.input.val(outpValue);\n                    this.input.trigger('input');\n                }\n                this._element.data('date', outpValue);\n                this._notifyEvent({\n                    type: DateTimePicker.Event.CHANGE,\n                    date: false,\n                    oldDate: oldDate\n                });\n                this._update();\n                return;\n            }\n\n            targetMoment = targetMoment.clone().locale(this._options.locale);\n\n            if (this._hasTimeZone()) {\n                targetMoment.tz(this._options.timeZone);\n            }\n\n            if (this._options.stepping !== 1) {\n                targetMoment.minutes(Math.round(targetMoment.minutes() / this._options.stepping) * this._options.stepping).seconds(0);\n            }\n\n            if (this._isValid(targetMoment)) {\n                this._dates[index] = targetMoment;\n                this._datesFormatted[index] = targetMoment.format('YYYY-MM-DD');\n                this._viewDate = targetMoment.clone();\n                if (this._options.allowMultidate && this._dates.length > 1) {\n                    for (var i = 0; i < this._dates.length; i++) {\n                        outpValue += '' + this._dates[i].format(this.actualFormat) + this._options.multidateSeparator;\n                    }\n                    outpValue = outpValue.replace(/,\\s*$/, '');\n                } else {\n                    outpValue = this._dates[index].format(this.actualFormat);\n                }\n                if (this.input !== undefined) {\n                    this.input.val(outpValue);\n                    this.input.trigger('input');\n                }\n                this._element.data('date', outpValue);\n\n                this.unset = false;\n                this._update();\n                this._notifyEvent({\n                    type: DateTimePicker.Event.CHANGE,\n                    date: this._dates[index].clone(),\n                    oldDate: oldDate\n                });\n            } else {\n                if (!this._options.keepInvalid) {\n                    if (this.input !== undefined) {\n                        this.input.val('' + (this.unset ? '' : this._dates[index].format(this.actualFormat)));\n                        this.input.trigger('input');\n                    }\n                } else {\n                    this._notifyEvent({\n                        type: DateTimePicker.Event.CHANGE,\n                        date: targetMoment,\n                        oldDate: oldDate\n                    });\n                }\n                this._notifyEvent({\n                    type: DateTimePicker.Event.ERROR,\n                    date: targetMoment,\n                    oldDate: oldDate\n                });\n            }\n        };\n\n        DateTimePicker.prototype._change = function _change(e) {\n            var val = $(e.target).val().trim(),\n                parsedDate = val ? this._parseInputDate(val) : null;\n            this._setValue(parsedDate);\n            e.stopImmediatePropagation();\n            return false;\n        };\n\n        //noinspection JSMethodCanBeStatic\n\n\n        DateTimePicker.prototype._getOptions = function _getOptions(options) {\n            options = $.extend(true, {}, Default, options);\n            return options;\n        };\n\n        DateTimePicker.prototype._hasTimeZone = function _hasTimeZone() {\n            return moment.tz !== undefined && this._options.timeZone !== undefined && this._options.timeZone !== null && this._options.timeZone !== '';\n        };\n\n        DateTimePicker.prototype._isEnabled = function _isEnabled(granularity) {\n            if (typeof granularity !== 'string' || granularity.length > 1) {\n                throw new TypeError('isEnabled expects a single character string parameter');\n            }\n            switch (granularity) {\n                case 'y':\n                    return this.actualFormat.indexOf('Y') !== -1;\n                case 'M':\n                    return this.actualFormat.indexOf('M') !== -1;\n                case 'd':\n                    return this.actualFormat.toLowerCase().indexOf('d') !== -1;\n                case 'h':\n                case 'H':\n                    return this.actualFormat.toLowerCase().indexOf('h') !== -1;\n                case 'm':\n                    return this.actualFormat.indexOf('m') !== -1;\n                case 's':\n                    return this.actualFormat.indexOf('s') !== -1;\n                case 'a':\n                case 'A':\n                    return this.actualFormat.toLowerCase().indexOf('a') !== -1;\n                default:\n                    return false;\n            }\n        };\n\n        DateTimePicker.prototype._hasTime = function _hasTime() {\n            return this._isEnabled('h') || this._isEnabled('m') || this._isEnabled('s');\n        };\n\n        DateTimePicker.prototype._hasDate = function _hasDate() {\n            return this._isEnabled('y') || this._isEnabled('M') || this._isEnabled('d');\n        };\n\n        DateTimePicker.prototype._dataToOptions = function _dataToOptions() {\n            var eData = this._element.data();\n            var dataOptions = {};\n\n            if (eData.dateOptions && eData.dateOptions instanceof Object) {\n                dataOptions = $.extend(true, dataOptions, eData.dateOptions);\n            }\n\n            $.each(this._options, function (key) {\n                var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); //todo data api key\n                if (eData[attributeName] !== undefined) {\n                    dataOptions[key] = eData[attributeName];\n                } else {\n                    delete dataOptions[key];\n                }\n            });\n            return dataOptions;\n        };\n\n        DateTimePicker.prototype._notifyEvent = function _notifyEvent(e) {\n            if (e.type === DateTimePicker.Event.CHANGE && e.date && e.date.isSame(e.oldDate) || !e.date && !e.oldDate) {\n                return;\n            }\n            this._element.trigger(e);\n        };\n\n        DateTimePicker.prototype._viewUpdate = function _viewUpdate(e) {\n            if (e === 'y') {\n                e = 'YYYY';\n            }\n            this._notifyEvent({\n                type: DateTimePicker.Event.UPDATE,\n                change: e,\n                viewDate: this._viewDate.clone()\n            });\n        };\n\n        DateTimePicker.prototype._showMode = function _showMode(dir) {\n            if (!this.widget) {\n                return;\n            }\n            if (dir) {\n                this.currentViewMode = Math.max(MinViewModeNumber, Math.min(3, this.currentViewMode + dir));\n            }\n            this.widget.find('.datepicker > div').hide().filter('.datepicker-' + DatePickerModes[this.currentViewMode].CLASS_NAME).show();\n        };\n\n        DateTimePicker.prototype._isInDisabledDates = function _isInDisabledDates(testDate) {\n            return this._options.disabledDates[testDate.format('YYYY-MM-DD')] === true;\n        };\n\n        DateTimePicker.prototype._isInEnabledDates = function _isInEnabledDates(testDate) {\n            return this._options.enabledDates[testDate.format('YYYY-MM-DD')] === true;\n        };\n\n        DateTimePicker.prototype._isInDisabledHours = function _isInDisabledHours(testDate) {\n            return this._options.disabledHours[testDate.format('H')] === true;\n        };\n\n        DateTimePicker.prototype._isInEnabledHours = function _isInEnabledHours(testDate) {\n            return this._options.enabledHours[testDate.format('H')] === true;\n        };\n\n        DateTimePicker.prototype._isValid = function _isValid(targetMoment, granularity) {\n            if (!targetMoment.isValid()) {\n                return false;\n            }\n            if (this._options.disabledDates && granularity === 'd' && this._isInDisabledDates(targetMoment)) {\n                return false;\n            }\n            if (this._options.enabledDates && granularity === 'd' && !this._isInEnabledDates(targetMoment)) {\n                return false;\n            }\n            if (this._options.minDate && targetMoment.isBefore(this._options.minDate, granularity)) {\n                return false;\n            }\n            if (this._options.maxDate && targetMoment.isAfter(this._options.maxDate, granularity)) {\n                return false;\n            }\n            if (this._options.daysOfWeekDisabled && granularity === 'd' && this._options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {\n                return false;\n            }\n            if (this._options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && this._isInDisabledHours(targetMoment)) {\n                return false;\n            }\n            if (this._options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !this._isInEnabledHours(targetMoment)) {\n                return false;\n            }\n            if (this._options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) {\n                var found = false;\n                $.each(this._options.disabledTimeIntervals, function () {\n                    if (targetMoment.isBetween(this[0], this[1])) {\n                        found = true;\n                        return false;\n                    }\n                });\n                if (found) {\n                    return false;\n                }\n            }\n            return true;\n        };\n\n        DateTimePicker.prototype._parseInputDate = function _parseInputDate(inputDate) {\n            if (this._options.parseInputDate === undefined) {\n                if (!moment.isMoment(inputDate)) {\n                    inputDate = this.getMoment(inputDate);\n                }\n            } else {\n                inputDate = this._options.parseInputDate(inputDate);\n            }\n            //inputDate.locale(this.options.locale);\n            return inputDate;\n        };\n\n        DateTimePicker.prototype._keydown = function _keydown(e) {\n            var handler = null,\n                index = void 0,\n                index2 = void 0,\n                keyBindKeys = void 0,\n                allModifiersPressed = void 0;\n            var pressedKeys = [],\n                pressedModifiers = {},\n                currentKey = e.which,\n                pressed = 'p';\n\n            keyState[currentKey] = pressed;\n\n            for (index in keyState) {\n                if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {\n                    pressedKeys.push(index);\n                    if (parseInt(index, 10) !== currentKey) {\n                        pressedModifiers[index] = true;\n                    }\n                }\n            }\n\n            for (index in this._options.keyBinds) {\n                if (this._options.keyBinds.hasOwnProperty(index) && typeof this._options.keyBinds[index] === 'function') {\n                    keyBindKeys = index.split(' ');\n                    if (keyBindKeys.length === pressedKeys.length && KeyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {\n                        allModifiersPressed = true;\n                        for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {\n                            if (!(KeyMap[keyBindKeys[index2]] in pressedModifiers)) {\n                                allModifiersPressed = false;\n                                break;\n                            }\n                        }\n                        if (allModifiersPressed) {\n                            handler = this._options.keyBinds[index];\n                            break;\n                        }\n                    }\n                }\n            }\n\n            if (handler) {\n                if (handler.call(this)) {\n                    e.stopPropagation();\n                    e.preventDefault();\n                }\n            }\n        };\n\n        //noinspection JSMethodCanBeStatic,SpellCheckingInspection\n\n\n        DateTimePicker.prototype._keyup = function _keyup(e) {\n            keyState[e.which] = 'r';\n            if (keyPressHandled[e.which]) {\n                keyPressHandled[e.which] = false;\n                e.stopPropagation();\n                e.preventDefault();\n            }\n        };\n\n        DateTimePicker.prototype._indexGivenDates = function _indexGivenDates(givenDatesArray) {\n            // Store given enabledDates and disabledDates as keys.\n            // This way we can check their existence in O(1) time instead of looping through whole array.\n            // (for example: options.enabledDates['2014-02-27'] === true)\n            var givenDatesIndexed = {},\n                self = this;\n            $.each(givenDatesArray, function () {\n                var dDate = self._parseInputDate(this);\n                if (dDate.isValid()) {\n                    givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;\n                }\n            });\n            return Object.keys(givenDatesIndexed).length ? givenDatesIndexed : false;\n        };\n\n        DateTimePicker.prototype._indexGivenHours = function _indexGivenHours(givenHoursArray) {\n            // Store given enabledHours and disabledHours as keys.\n            // This way we can check their existence in O(1) time instead of looping through whole array.\n            // (for example: options.enabledHours['2014-02-27'] === true)\n            var givenHoursIndexed = {};\n            $.each(givenHoursArray, function () {\n                givenHoursIndexed[this] = true;\n            });\n            return Object.keys(givenHoursIndexed).length ? givenHoursIndexed : false;\n        };\n\n        DateTimePicker.prototype._initFormatting = function _initFormatting() {\n            var format = this._options.format || 'L LT',\n                self = this;\n\n            this.actualFormat = format.replace(/(\\[[^\\[]*])|(\\\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) {\n                return self._dates[0].localeData().longDateFormat(formatInput) || formatInput; //todo taking the first date should be ok\n            });\n\n            this.parseFormats = this._options.extraFormats ? this._options.extraFormats.slice() : [];\n            if (this.parseFormats.indexOf(format) < 0 && this.parseFormats.indexOf(this.actualFormat) < 0) {\n                this.parseFormats.push(this.actualFormat);\n            }\n\n            this.use24Hours = this.actualFormat.toLowerCase().indexOf('a') < 1 && this.actualFormat.replace(/\\[.*?]/g, '').indexOf('h') < 1;\n\n            if (this._isEnabled('y')) {\n                MinViewModeNumber = 2;\n            }\n            if (this._isEnabled('M')) {\n                MinViewModeNumber = 1;\n            }\n            if (this._isEnabled('d')) {\n                MinViewModeNumber = 0;\n            }\n\n            this.currentViewMode = Math.max(MinViewModeNumber, this.currentViewMode);\n\n            if (!this.unset) {\n                this._setValue(this._dates[0], 0);\n            }\n        };\n\n        DateTimePicker.prototype._getLastPickedDate = function _getLastPickedDate() {\n            return this._dates[this._getLastPickedDateIndex()];\n        };\n\n        DateTimePicker.prototype._getLastPickedDateIndex = function _getLastPickedDateIndex() {\n            return this._dates.length - 1;\n        };\n\n        //public\n\n\n        DateTimePicker.prototype.getMoment = function getMoment(d) {\n            var returnMoment = void 0;\n\n            if (d === undefined || d === null) {\n                returnMoment = moment(); //TODO should this use format? and locale?\n            } else if (this._hasTimeZone()) {\n                // There is a string to parse and a default time zone\n                // parse with the tz function which takes a default time zone if it is not in the format string\n                returnMoment = moment.tz(d, this.parseFormats, this._options.useStrict, this._options.timeZone);\n            } else {\n                returnMoment = moment(d, this.parseFormats, this._options.useStrict);\n            }\n\n            if (this._hasTimeZone()) {\n                returnMoment.tz(this._options.timeZone);\n            }\n\n            return returnMoment;\n        };\n\n        DateTimePicker.prototype.toggle = function toggle() {\n            return this.widget ? this.hide() : this.show();\n        };\n\n        DateTimePicker.prototype.ignoreReadonly = function ignoreReadonly(_ignoreReadonly) {\n            if (arguments.length === 0) {\n                return this._options.ignoreReadonly;\n            }\n            if (typeof _ignoreReadonly !== 'boolean') {\n                throw new TypeError('ignoreReadonly () expects a boolean parameter');\n            }\n            this._options.ignoreReadonly = _ignoreReadonly;\n        };\n\n        DateTimePicker.prototype.options = function options(newOptions) {\n            if (arguments.length === 0) {\n                return $.extend(true, {}, this._options);\n            }\n\n            if (!(newOptions instanceof Object)) {\n                throw new TypeError('options() this.options parameter should be an object');\n            }\n            $.extend(true, this._options, newOptions);\n            var self = this;\n            $.each(this._options, function (key, value) {\n                if (self[key] !== undefined) {\n                    self[key](value);\n                }\n            });\n        };\n\n        DateTimePicker.prototype.date = function date(newDate, index) {\n            index = index || 0;\n            if (arguments.length === 0) {\n                if (this.unset) {\n                    return null;\n                }\n                if (this._options.allowMultidate) {\n                    return this._dates.join(this._options.multidateSeparator);\n                } else {\n                    return this._dates[index].clone();\n                }\n            }\n\n            if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {\n                throw new TypeError('date() parameter must be one of [null, string, moment or Date]');\n            }\n\n            this._setValue(newDate === null ? null : this._parseInputDate(newDate), index);\n        };\n\n        DateTimePicker.prototype.format = function format(newFormat) {\n            if (arguments.length === 0) {\n                return this._options.format;\n            }\n\n            if (typeof newFormat !== 'string' && (typeof newFormat !== 'boolean' || newFormat !== false)) {\n                throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat);\n            }\n\n            this._options.format = newFormat;\n            if (this.actualFormat) {\n                this._initFormatting(); // reinitialize formatting\n            }\n        };\n\n        DateTimePicker.prototype.timeZone = function timeZone(newZone) {\n            if (arguments.length === 0) {\n                return this._options.timeZone;\n            }\n\n            if (typeof newZone !== 'string') {\n                throw new TypeError('newZone() expects a string parameter');\n            }\n\n            this._options.timeZone = newZone;\n        };\n\n        DateTimePicker.prototype.dayViewHeaderFormat = function dayViewHeaderFormat(newFormat) {\n            if (arguments.length === 0) {\n                return this._options.dayViewHeaderFormat;\n            }\n\n            if (typeof newFormat !== 'string') {\n                throw new TypeError('dayViewHeaderFormat() expects a string parameter');\n            }\n\n            this._options.dayViewHeaderFormat = newFormat;\n        };\n\n        DateTimePicker.prototype.extraFormats = function extraFormats(formats) {\n            if (arguments.length === 0) {\n                return this._options.extraFormats;\n            }\n\n            if (formats !== false && !(formats instanceof Array)) {\n                throw new TypeError('extraFormats() expects an array or false parameter');\n            }\n\n            this._options.extraFormats = formats;\n            if (this.parseFormats) {\n                this._initFormatting(); // reinit formatting\n            }\n        };\n\n        DateTimePicker.prototype.disabledDates = function disabledDates(dates) {\n            if (arguments.length === 0) {\n                return this._options.disabledDates ? $.extend({}, this._options.disabledDates) : this._options.disabledDates;\n            }\n\n            if (!dates) {\n                this._options.disabledDates = false;\n                this._update();\n                return true;\n            }\n            if (!(dates instanceof Array)) {\n                throw new TypeError('disabledDates() expects an array parameter');\n            }\n            this._options.disabledDates = this._indexGivenDates(dates);\n            this._options.enabledDates = false;\n            this._update();\n        };\n\n        DateTimePicker.prototype.enabledDates = function enabledDates(dates) {\n            if (arguments.length === 0) {\n                return this._options.enabledDates ? $.extend({}, this._options.enabledDates) : this._options.enabledDates;\n            }\n\n            if (!dates) {\n                this._options.enabledDates = false;\n                this._update();\n                return true;\n            }\n            if (!(dates instanceof Array)) {\n                throw new TypeError('enabledDates() expects an array parameter');\n            }\n            this._options.enabledDates = this._indexGivenDates(dates);\n            this._options.disabledDates = false;\n            this._update();\n        };\n\n        DateTimePicker.prototype.daysOfWeekDisabled = function daysOfWeekDisabled(_daysOfWeekDisabled) {\n            if (arguments.length === 0) {\n                return this._options.daysOfWeekDisabled.splice(0);\n            }\n\n            if (typeof _daysOfWeekDisabled === 'boolean' && !_daysOfWeekDisabled) {\n                this._options.daysOfWeekDisabled = false;\n                this._update();\n                return true;\n            }\n\n            if (!(_daysOfWeekDisabled instanceof Array)) {\n                throw new TypeError('daysOfWeekDisabled() expects an array parameter');\n            }\n            this._options.daysOfWeekDisabled = _daysOfWeekDisabled.reduce(function (previousValue, currentValue) {\n                currentValue = parseInt(currentValue, 10);\n                if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {\n                    return previousValue;\n                }\n                if (previousValue.indexOf(currentValue) === -1) {\n                    previousValue.push(currentValue);\n                }\n                return previousValue;\n            }, []).sort();\n            if (this._options.useCurrent && !this._options.keepInvalid) {\n                for (var i = 0; i < this._dates.length; i++) {\n                    var tries = 0;\n                    while (!this._isValid(this._dates[i], 'd')) {\n                        this._dates[i].add(1, 'd');\n                        if (tries === 31) {\n                            throw 'Tried 31 times to find a valid date';\n                        }\n                        tries++;\n                    }\n                    this._setValue(this._dates[i], i);\n                }\n            }\n            this._update();\n        };\n\n        DateTimePicker.prototype.maxDate = function maxDate(_maxDate) {\n            if (arguments.length === 0) {\n                return this._options.maxDate ? this._options.maxDate.clone() : this._options.maxDate;\n            }\n\n            if (typeof _maxDate === 'boolean' && _maxDate === false) {\n                this._options.maxDate = false;\n                this._update();\n                return true;\n            }\n\n            if (typeof _maxDate === 'string') {\n                if (_maxDate === 'now' || _maxDate === 'moment') {\n                    _maxDate = this.getMoment();\n                }\n            }\n\n            var parsedDate = this._parseInputDate(_maxDate);\n\n            if (!parsedDate.isValid()) {\n                throw new TypeError('maxDate() Could not parse date parameter: ' + _maxDate);\n            }\n            if (this._options.minDate && parsedDate.isBefore(this._options.minDate)) {\n                throw new TypeError('maxDate() date parameter is before this.options.minDate: ' + parsedDate.format(this.actualFormat));\n            }\n            this._options.maxDate = parsedDate;\n            for (var i = 0; i < this._dates.length; i++) {\n                if (this._options.useCurrent && !this._options.keepInvalid && this._dates[i].isAfter(_maxDate)) {\n                    this._setValue(this._options.maxDate, i);\n                }\n            }\n            if (this._viewDate.isAfter(parsedDate)) {\n                this._viewDate = parsedDate.clone().subtract(this._options.stepping, 'm');\n            }\n            this._update();\n        };\n\n        DateTimePicker.prototype.minDate = function minDate(_minDate) {\n            if (arguments.length === 0) {\n                return this._options.minDate ? this._options.minDate.clone() : this._options.minDate;\n            }\n\n            if (typeof _minDate === 'boolean' && _minDate === false) {\n                this._options.minDate = false;\n                this._update();\n                return true;\n            }\n\n            if (typeof _minDate === 'string') {\n                if (_minDate === 'now' || _minDate === 'moment') {\n                    _minDate = this.getMoment();\n                }\n            }\n\n            var parsedDate = this._parseInputDate(_minDate);\n\n            if (!parsedDate.isValid()) {\n                throw new TypeError('minDate() Could not parse date parameter: ' + _minDate);\n            }\n            if (this._options.maxDate && parsedDate.isAfter(this._options.maxDate)) {\n                throw new TypeError('minDate() date parameter is after this.options.maxDate: ' + parsedDate.format(this.actualFormat));\n            }\n            this._options.minDate = parsedDate;\n            for (var i = 0; i < this._dates.length; i++) {\n                if (this._options.useCurrent && !this._options.keepInvalid && this._dates[i].isBefore(_minDate)) {\n                    this._setValue(this._options.minDate, i);\n                }\n            }\n            if (this._viewDate.isBefore(parsedDate)) {\n                this._viewDate = parsedDate.clone().add(this._options.stepping, 'm');\n            }\n            this._update();\n        };\n\n        DateTimePicker.prototype.defaultDate = function defaultDate(_defaultDate) {\n            if (arguments.length === 0) {\n                return this._options.defaultDate ? this._options.defaultDate.clone() : this._options.defaultDate;\n            }\n            if (!_defaultDate) {\n                this._options.defaultDate = false;\n                return true;\n            }\n\n            if (typeof _defaultDate === 'string') {\n                if (_defaultDate === 'now' || _defaultDate === 'moment') {\n                    _defaultDate = this.getMoment();\n                } else {\n                    _defaultDate = this.getMoment(_defaultDate);\n                }\n            }\n\n            var parsedDate = this._parseInputDate(_defaultDate);\n            if (!parsedDate.isValid()) {\n                throw new TypeError('defaultDate() Could not parse date parameter: ' + _defaultDate);\n            }\n            if (!this._isValid(parsedDate)) {\n                throw new TypeError('defaultDate() date passed is invalid according to component setup validations');\n            }\n\n            this._options.defaultDate = parsedDate;\n\n            if (this._options.defaultDate && this._options.inline || this.input !== undefined && this.input.val().trim() === '') {\n                this._setValue(this._options.defaultDate, 0);\n            }\n        };\n\n        DateTimePicker.prototype.locale = function locale(_locale) {\n            if (arguments.length === 0) {\n                return this._options.locale;\n            }\n\n            if (!moment.localeData(_locale)) {\n                throw new TypeError('locale() locale ' + _locale + ' is not loaded from moment locales!');\n            }\n\n            this._options.locale = _locale;\n\n            for (var i = 0; i < this._dates.length; i++) {\n                this._dates[i].locale(this._options.locale);\n            }\n            this._viewDate.locale(this._options.locale);\n\n            if (this.actualFormat) {\n                this._initFormatting(); // reinitialize formatting\n            }\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        DateTimePicker.prototype.stepping = function stepping(_stepping) {\n            if (arguments.length === 0) {\n                return this._options.stepping;\n            }\n\n            _stepping = parseInt(_stepping, 10);\n            if (isNaN(_stepping) || _stepping < 1) {\n                _stepping = 1;\n            }\n            this._options.stepping = _stepping;\n        };\n\n        DateTimePicker.prototype.useCurrent = function useCurrent(_useCurrent) {\n            var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];\n            if (arguments.length === 0) {\n                return this._options.useCurrent;\n            }\n\n            if (typeof _useCurrent !== 'boolean' && typeof _useCurrent !== 'string') {\n                throw new TypeError('useCurrent() expects a boolean or string parameter');\n            }\n            if (typeof _useCurrent === 'string' && useCurrentOptions.indexOf(_useCurrent.toLowerCase()) === -1) {\n                throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));\n            }\n            this._options.useCurrent = _useCurrent;\n        };\n\n        DateTimePicker.prototype.collapse = function collapse(_collapse) {\n            if (arguments.length === 0) {\n                return this._options.collapse;\n            }\n\n            if (typeof _collapse !== 'boolean') {\n                throw new TypeError('collapse() expects a boolean parameter');\n            }\n            if (this._options.collapse === _collapse) {\n                return true;\n            }\n            this._options.collapse = _collapse;\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        DateTimePicker.prototype.icons = function icons(_icons) {\n            if (arguments.length === 0) {\n                return $.extend({}, this._options.icons);\n            }\n\n            if (!(_icons instanceof Object)) {\n                throw new TypeError('icons() expects parameter to be an Object');\n            }\n\n            $.extend(this._options.icons, _icons);\n\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        DateTimePicker.prototype.tooltips = function tooltips(_tooltips) {\n            if (arguments.length === 0) {\n                return $.extend({}, this._options.tooltips);\n            }\n\n            if (!(_tooltips instanceof Object)) {\n                throw new TypeError('tooltips() expects parameter to be an Object');\n            }\n            $.extend(this._options.tooltips, _tooltips);\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        DateTimePicker.prototype.useStrict = function useStrict(_useStrict) {\n            if (arguments.length === 0) {\n                return this._options.useStrict;\n            }\n\n            if (typeof _useStrict !== 'boolean') {\n                throw new TypeError('useStrict() expects a boolean parameter');\n            }\n            this._options.useStrict = _useStrict;\n        };\n\n        DateTimePicker.prototype.sideBySide = function sideBySide(_sideBySide) {\n            if (arguments.length === 0) {\n                return this._options.sideBySide;\n            }\n\n            if (typeof _sideBySide !== 'boolean') {\n                throw new TypeError('sideBySide() expects a boolean parameter');\n            }\n            this._options.sideBySide = _sideBySide;\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        DateTimePicker.prototype.viewMode = function viewMode(_viewMode) {\n            if (arguments.length === 0) {\n                return this._options.viewMode;\n            }\n\n            if (typeof _viewMode !== 'string') {\n                throw new TypeError('viewMode() expects a string parameter');\n            }\n\n            if (DateTimePicker.ViewModes.indexOf(_viewMode) === -1) {\n                throw new TypeError('viewMode() parameter must be one of (' + DateTimePicker.ViewModes.join(', ') + ') value');\n            }\n\n            this._options.viewMode = _viewMode;\n            this.currentViewMode = Math.max(DateTimePicker.ViewModes.indexOf(_viewMode) - 1, DateTimePicker.MinViewModeNumber);\n\n            this._showMode();\n        };\n\n        DateTimePicker.prototype.calendarWeeks = function calendarWeeks(_calendarWeeks) {\n            if (arguments.length === 0) {\n                return this._options.calendarWeeks;\n            }\n\n            if (typeof _calendarWeeks !== 'boolean') {\n                throw new TypeError('calendarWeeks() expects parameter to be a boolean value');\n            }\n\n            this._options.calendarWeeks = _calendarWeeks;\n            this._update();\n        };\n\n        DateTimePicker.prototype.buttons = function buttons(_buttons) {\n            if (arguments.length === 0) {\n                return $.extend({}, this._options.buttons);\n            }\n\n            if (!(_buttons instanceof Object)) {\n                throw new TypeError('buttons() expects parameter to be an Object');\n            }\n\n            $.extend(this._options.buttons, _buttons);\n\n            if (typeof this._options.buttons.showToday !== 'boolean') {\n                throw new TypeError('buttons.showToday expects a boolean parameter');\n            }\n            if (typeof this._options.buttons.showClear !== 'boolean') {\n                throw new TypeError('buttons.showClear expects a boolean parameter');\n            }\n            if (typeof this._options.buttons.showClose !== 'boolean') {\n                throw new TypeError('buttons.showClose expects a boolean parameter');\n            }\n\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        DateTimePicker.prototype.keepOpen = function keepOpen(_keepOpen) {\n            if (arguments.length === 0) {\n                return this._options.keepOpen;\n            }\n\n            if (typeof _keepOpen !== 'boolean') {\n                throw new TypeError('keepOpen() expects a boolean parameter');\n            }\n\n            this._options.keepOpen = _keepOpen;\n        };\n\n        DateTimePicker.prototype.focusOnShow = function focusOnShow(_focusOnShow) {\n            if (arguments.length === 0) {\n                return this._options.focusOnShow;\n            }\n\n            if (typeof _focusOnShow !== 'boolean') {\n                throw new TypeError('focusOnShow() expects a boolean parameter');\n            }\n\n            this._options.focusOnShow = _focusOnShow;\n        };\n\n        DateTimePicker.prototype.inline = function inline(_inline) {\n            if (arguments.length === 0) {\n                return this._options.inline;\n            }\n\n            if (typeof _inline !== 'boolean') {\n                throw new TypeError('inline() expects a boolean parameter');\n            }\n\n            this._options.inline = _inline;\n        };\n\n        DateTimePicker.prototype.clear = function clear() {\n            this._setValue(null); //todo\n        };\n\n        DateTimePicker.prototype.keyBinds = function keyBinds(_keyBinds) {\n            if (arguments.length === 0) {\n                return this._options.keyBinds;\n            }\n\n            this._options.keyBinds = _keyBinds;\n        };\n\n        DateTimePicker.prototype.debug = function debug(_debug) {\n            if (typeof _debug !== 'boolean') {\n                throw new TypeError('debug() expects a boolean parameter');\n            }\n\n            this._options.debug = _debug;\n        };\n\n        DateTimePicker.prototype.allowInputToggle = function allowInputToggle(_allowInputToggle) {\n            if (arguments.length === 0) {\n                return this._options.allowInputToggle;\n            }\n\n            if (typeof _allowInputToggle !== 'boolean') {\n                throw new TypeError('allowInputToggle() expects a boolean parameter');\n            }\n\n            this._options.allowInputToggle = _allowInputToggle;\n        };\n\n        DateTimePicker.prototype.keepInvalid = function keepInvalid(_keepInvalid) {\n            if (arguments.length === 0) {\n                return this._options.keepInvalid;\n            }\n\n            if (typeof _keepInvalid !== 'boolean') {\n                throw new TypeError('keepInvalid() expects a boolean parameter');\n            }\n            this._options.keepInvalid = _keepInvalid;\n        };\n\n        DateTimePicker.prototype.datepickerInput = function datepickerInput(_datepickerInput) {\n            if (arguments.length === 0) {\n                return this._options.datepickerInput;\n            }\n\n            if (typeof _datepickerInput !== 'string') {\n                throw new TypeError('datepickerInput() expects a string parameter');\n            }\n\n            this._options.datepickerInput = _datepickerInput;\n        };\n\n        DateTimePicker.prototype.parseInputDate = function parseInputDate(_parseInputDate2) {\n            if (arguments.length === 0) {\n                return this._options.parseInputDate;\n            }\n\n            if (typeof _parseInputDate2 !== 'function') {\n                throw new TypeError('parseInputDate() should be as function');\n            }\n\n            this._options.parseInputDate = _parseInputDate2;\n        };\n\n        DateTimePicker.prototype.disabledTimeIntervals = function disabledTimeIntervals(_disabledTimeIntervals) {\n            if (arguments.length === 0) {\n                return this._options.disabledTimeIntervals ? $.extend({}, this._options.disabledTimeIntervals) : this._options.disabledTimeIntervals;\n            }\n\n            if (!_disabledTimeIntervals) {\n                this._options.disabledTimeIntervals = false;\n                this._update();\n                return true;\n            }\n            if (!(_disabledTimeIntervals instanceof Array)) {\n                throw new TypeError('disabledTimeIntervals() expects an array parameter');\n            }\n            this._options.disabledTimeIntervals = _disabledTimeIntervals;\n            this._update();\n        };\n\n        DateTimePicker.prototype.disabledHours = function disabledHours(hours) {\n            if (arguments.length === 0) {\n                return this._options.disabledHours ? $.extend({}, this._options.disabledHours) : this._options.disabledHours;\n            }\n\n            if (!hours) {\n                this._options.disabledHours = false;\n                this._update();\n                return true;\n            }\n            if (!(hours instanceof Array)) {\n                throw new TypeError('disabledHours() expects an array parameter');\n            }\n            this._options.disabledHours = this._indexGivenHours(hours);\n            this._options.enabledHours = false;\n            if (this._options.useCurrent && !this._options.keepInvalid) {\n                for (var i = 0; i < this._dates.length; i++) {\n                    var tries = 0;\n                    while (!this._isValid(this._dates[i], 'h')) {\n                        this._dates[i].add(1, 'h');\n                        if (tries === 24) {\n                            throw 'Tried 24 times to find a valid date';\n                        }\n                        tries++;\n                    }\n                    this._setValue(this._dates[i], i);\n                }\n            }\n            this._update();\n        };\n\n        DateTimePicker.prototype.enabledHours = function enabledHours(hours) {\n            if (arguments.length === 0) {\n                return this._options.enabledHours ? $.extend({}, this._options.enabledHours) : this._options.enabledHours;\n            }\n\n            if (!hours) {\n                this._options.enabledHours = false;\n                this._update();\n                return true;\n            }\n            if (!(hours instanceof Array)) {\n                throw new TypeError('enabledHours() expects an array parameter');\n            }\n            this._options.enabledHours = this._indexGivenHours(hours);\n            this._options.disabledHours = false;\n            if (this._options.useCurrent && !this._options.keepInvalid) {\n                for (var i = 0; i < this._dates.length; i++) {\n                    var tries = 0;\n                    while (!this._isValid(this._dates[i], 'h')) {\n                        this._dates[i].add(1, 'h');\n                        if (tries === 24) {\n                            throw 'Tried 24 times to find a valid date';\n                        }\n                        tries++;\n                    }\n                    this._setValue(this._dates[i], i);\n                }\n            }\n            this._update();\n        };\n\n        DateTimePicker.prototype.viewDate = function viewDate(newDate) {\n            if (arguments.length === 0) {\n                return this._viewDate.clone();\n            }\n\n            if (!newDate) {\n                this._viewDate = (this._dates[0] || this.getMoment()).clone();\n                return true;\n            }\n\n            if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {\n                throw new TypeError('viewDate() parameter must be one of [string, moment or Date]');\n            }\n\n            this._viewDate = this._parseInputDate(newDate);\n            this._viewUpdate();\n        };\n\n        DateTimePicker.prototype.allowMultidate = function allowMultidate(_allowMultidate) {\n            if (typeof _allowMultidate !== 'boolean') {\n                throw new TypeError('allowMultidate() expects a boolean parameter');\n            }\n\n            this._options.allowMultidate = _allowMultidate;\n        };\n\n        DateTimePicker.prototype.multidateSeparator = function multidateSeparator(_multidateSeparator) {\n            if (arguments.length === 0) {\n                return this._options.multidateSeparator;\n            }\n\n            if (typeof _multidateSeparator !== 'string' || _multidateSeparator.length > 1) {\n                throw new TypeError('multidateSeparator expects a single character string parameter');\n            }\n\n            this._options.multidateSeparator = _multidateSeparator;\n        };\n\n        _createClass(DateTimePicker, null, [{\n            key: 'NAME',\n            get: function get() {\n                return NAME;\n            }\n\n            /**\n             * @return {string}\n             */\n\n        }, {\n            key: 'DATA_KEY',\n            get: function get() {\n                return DATA_KEY;\n            }\n\n            /**\n             * @return {string}\n             */\n\n        }, {\n            key: 'EVENT_KEY',\n            get: function get() {\n                return EVENT_KEY;\n            }\n\n            /**\n             * @return {string}\n             */\n\n        }, {\n            key: 'DATA_API_KEY',\n            get: function get() {\n                return DATA_API_KEY;\n            }\n        }, {\n            key: 'DatePickerModes',\n            get: function get() {\n                return DatePickerModes;\n            }\n        }, {\n            key: 'ViewModes',\n            get: function get() {\n                return ViewModes;\n            }\n\n            /**\n             * @return {number}\n             */\n\n        }, {\n            key: 'MinViewModeNumber',\n            get: function get() {\n                return MinViewModeNumber;\n            }\n        }, {\n            key: 'Event',\n            get: function get() {\n                return Event;\n            }\n        }, {\n            key: 'Selector',\n            get: function get() {\n                return Selector;\n            }\n        }, {\n            key: 'Default',\n            get: function get() {\n                return Default;\n            },\n            set: function set(value) {\n                Default = value;\n            }\n        }, {\n            key: 'ClassName',\n            get: function get() {\n                return ClassName;\n            }\n        }]);\n\n        return DateTimePicker;\n    }();\n\n    return DateTimePicker;\n}(jQuery, moment);\n\n//noinspection JSUnusedGlobalSymbols\n/* global DateTimePicker */\nvar TempusDominusBootstrap3 = function ($) {\n    // eslint-disable-line no-unused-vars\n    // ReSharper disable once InconsistentNaming\n    var JQUERY_NO_CONFLICT = $.fn[DateTimePicker.NAME],\n        verticalModes = ['top', 'bottom', 'auto'],\n        horizontalModes = ['left', 'right', 'auto'],\n        toolbarPlacements = ['default', 'top', 'bottom'],\n        getSelectorFromElement = function getSelectorFromElement($element) {\n        var selector = $element.data('target'),\n            $selector = void 0;\n\n        if (!selector) {\n            selector = $element.attr('href') || '';\n            selector = /^#[a-z]/i.test(selector) ? selector : null;\n        }\n        $selector = $(selector);\n        if ($selector.length === 0) {\n            return $selector;\n        }\n\n        if (!$selector.data(DateTimePicker.DATA_KEY)) {\n            $.extend({}, $selector.data(), $(this).data());\n        }\n\n        return $selector;\n    };\n\n    // ReSharper disable once InconsistentNaming\n\n    var TempusDominusBootstrap3 = function (_DateTimePicker) {\n        _inherits(TempusDominusBootstrap3, _DateTimePicker);\n\n        function TempusDominusBootstrap3(element, options) {\n            _classCallCheck(this, TempusDominusBootstrap3);\n\n            $.extend(true, DateTimePicker.Default, {\n                icons: {\n                    time: 'glyphicon glyphicon-time',\n                    date: 'glyphicon glyphicon-calendar',\n                    up: 'glyphicon glyphicon-chevron-up',\n                    down: 'glyphicon glyphicon-chevron-down',\n                    previous: 'glyphicon glyphicon-chevron-left',\n                    next: 'glyphicon glyphicon-chevron-right',\n                    today: 'glyphicon glyphicon-screenshot',\n                    clear: 'glyphicon glyphicon-trash',\n                    close: 'glyphicon glyphicon-remove'\n                }\n            });\n\n            var _this = _possibleConstructorReturn(this, _DateTimePicker.call(this, element, options));\n\n            _this._init();\n            return _this;\n        }\n\n        TempusDominusBootstrap3.prototype._init = function _init() {\n            if (this._element.hasClass('input-group')) {\n                // in case there is more then one 'input-group-addon' Issue #48\n                var datepickerButton = this._element.find('.datepickerbutton');\n                if (datepickerButton.length === 0) {\n                    this.component = this._element.find('.input-group-addon');\n                } else {\n                    this.component = datepickerButton;\n                }\n            }\n        };\n\n        TempusDominusBootstrap3.prototype._getDatePickerTemplate = function _getDatePickerTemplate() {\n            var headTemplate = $('<thead>').append($('<tr>').append($('<th>').addClass('prev').attr('data-action', 'previous').append($('<span>').addClass(this._options.icons.previous))).append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', '' + (this._options.calendarWeeks ? '6' : '5'))).append($('<th>').addClass('next').attr('data-action', 'next').append($('<span>').addClass(this._options.icons.next)))),\n                contTemplate = $('<tbody>').append($('<tr>').append($('<td>').attr('colspan', '' + (this._options.calendarWeeks ? '8' : '7'))));\n\n            return [$('<div>').addClass('datepicker-days').append($('<table>').addClass('table-condensed').append(headTemplate).append($('<tbody>'))), $('<div>').addClass('datepicker-months').append($('<table>').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone())), $('<div>').addClass('datepicker-years').append($('<table>').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone())), $('<div>').addClass('datepicker-decades').append($('<table>').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone()))];\n        };\n\n        TempusDominusBootstrap3.prototype._getTimePickerMainTemplate = function _getTimePickerMainTemplate() {\n            var topRow = $('<tr>'),\n                middleRow = $('<tr>'),\n                bottomRow = $('<tr>');\n\n            if (this._isEnabled('h')) {\n                topRow.append($('<td>').append($('<a>').attr({\n                    href: '#',\n                    tabindex: '-1',\n                    'title': this._options.tooltips.incrementHour\n                }).addClass('btn').attr('data-action', 'incrementHours').append($('<span>').addClass(this._options.icons.up))));\n                middleRow.append($('<td>').append($('<span>').addClass('timepicker-hour').attr({\n                    'data-time-component': 'hours',\n                    'title': this._options.tooltips.pickHour\n                }).attr('data-action', 'showHours')));\n                bottomRow.append($('<td>').append($('<a>').attr({\n                    href: '#',\n                    tabindex: '-1',\n                    'title': this._options.tooltips.decrementHour\n                }).addClass('btn').attr('data-action', 'decrementHours').append($('<span>').addClass(this._options.icons.down))));\n            }\n            if (this._isEnabled('m')) {\n                if (this._isEnabled('h')) {\n                    topRow.append($('<td>').addClass('separator'));\n                    middleRow.append($('<td>').addClass('separator').html(':'));\n                    bottomRow.append($('<td>').addClass('separator'));\n                }\n                topRow.append($('<td>').append($('<a>').attr({\n                    href: '#',\n                    tabindex: '-1',\n                    'title': this._options.tooltips.incrementMinute\n                }).addClass('btn').attr('data-action', 'incrementMinutes').append($('<span>').addClass(this._options.icons.up))));\n                middleRow.append($('<td>').append($('<span>').addClass('timepicker-minute').attr({\n                    'data-time-component': 'minutes',\n                    'title': this._options.tooltips.pickMinute\n                }).attr('data-action', 'showMinutes')));\n                bottomRow.append($('<td>').append($('<a>').attr({\n                    href: '#',\n                    tabindex: '-1',\n                    'title': this._options.tooltips.decrementMinute\n                }).addClass('btn').attr('data-action', 'decrementMinutes').append($('<span>').addClass(this._options.icons.down))));\n            }\n            if (this._isEnabled('s')) {\n                if (this._isEnabled('m')) {\n                    topRow.append($('<td>').addClass('separator'));\n                    middleRow.append($('<td>').addClass('separator').html(':'));\n                    bottomRow.append($('<td>').addClass('separator'));\n                }\n                topRow.append($('<td>').append($('<a>').attr({\n                    href: '#',\n                    tabindex: '-1',\n                    'title': this._options.tooltips.incrementSecond\n                }).addClass('btn').attr('data-action', 'incrementSeconds').append($('<span>').addClass(this._options.icons.up))));\n                middleRow.append($('<td>').append($('<span>').addClass('timepicker-second').attr({\n                    'data-time-component': 'seconds',\n                    'title': this._options.tooltips.pickSecond\n                }).attr('data-action', 'showSeconds')));\n                bottomRow.append($('<td>').append($('<a>').attr({\n                    href: '#',\n                    tabindex: '-1',\n                    'title': this._options.tooltips.decrementSecond\n                }).addClass('btn').attr('data-action', 'decrementSeconds').append($('<span>').addClass(this._options.icons.down))));\n            }\n\n            if (!this.use24Hours) {\n                topRow.append($('<td>').addClass('separator'));\n                middleRow.append($('<td>').append($('<button>').addClass('btn btn-primary').attr({\n                    'data-action': 'togglePeriod',\n                    tabindex: '-1',\n                    'title': this._options.tooltips.togglePeriod\n                })));\n                bottomRow.append($('<td>').addClass('separator'));\n            }\n\n            return $('<div>').addClass('timepicker-picker').append($('<table>').addClass('table-condensed').append([topRow, middleRow, bottomRow]));\n        };\n\n        TempusDominusBootstrap3.prototype._getTimePickerTemplate = function _getTimePickerTemplate() {\n            var hoursView = $('<div>').addClass('timepicker-hours').append($('<table>').addClass('table-condensed')),\n                minutesView = $('<div>').addClass('timepicker-minutes').append($('<table>').addClass('table-condensed')),\n                secondsView = $('<div>').addClass('timepicker-seconds').append($('<table>').addClass('table-condensed')),\n                ret = [this._getTimePickerMainTemplate()];\n\n            if (this._isEnabled('h')) {\n                ret.push(hoursView);\n            }\n            if (this._isEnabled('m')) {\n                ret.push(minutesView);\n            }\n            if (this._isEnabled('s')) {\n                ret.push(secondsView);\n            }\n\n            return ret;\n        };\n\n        TempusDominusBootstrap3.prototype._getToolbar = function _getToolbar() {\n            var row = [];\n            if (this._options.buttons.showToday) {\n                row.push($('<td>').append($('<a>').attr({\n                    'data-action': 'today',\n                    'title': this._options.tooltips.today\n                }).append($('<span>').addClass(this._options.icons.today))));\n            }\n            if (!this._options.sideBySide && this._hasDate() && this._hasTime()) {\n                row.push($('<td>').append($('<a>').attr({\n                    'data-action': 'togglePicker',\n                    'title': this._options.tooltips.selectTime\n                }).append($('<span>').addClass(this._options.icons.time))));\n            }\n            if (this._options.buttons.showClear) {\n                row.push($('<td>').append($('<a>').attr({\n                    'data-action': 'clear',\n                    'title': this._options.tooltips.clear\n                }).append($('<span>').addClass(this._options.icons.clear))));\n            }\n            if (this._options.buttons.showClose) {\n                row.push($('<td>').append($('<a>').attr({\n                    'data-action': 'close',\n                    'title': this._options.tooltips.close\n                }).append($('<span>').addClass(this._options.icons.close))));\n            }\n            return row.length === 0 ? '' : $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));\n        };\n\n        TempusDominusBootstrap3.prototype._getTemplate = function _getTemplate() {\n            var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),\n                dateView = $('<div>').addClass('datepicker').append(this._getDatePickerTemplate()),\n                timeView = $('<div>').addClass('timepicker').append(this._getTimePickerTemplate()),\n                content = $('<ul>').addClass('list-unstyled'),\n                toolbar = $('<li>').addClass('picker-switch' + (this._options.collapse ? ' accordion-toggle' : '')).append(this._getToolbar());\n\n            if (this._options.inline) {\n                template.removeClass('dropdown-menu');\n            }\n\n            if (this.use24Hours) {\n                template.addClass('usetwentyfour');\n            }\n            if (this._isEnabled('s') && !this.use24Hours) {\n                template.addClass('wider');\n            }\n\n            if (this._options.sideBySide && this._hasDate() && this._hasTime()) {\n                template.addClass('timepicker-sbs');\n                if (this._options.toolbarPlacement === 'top') {\n                    template.append(toolbar);\n                }\n                template.append($('<div>').addClass('row').append(dateView.addClass('col-md-6')).append(timeView.addClass('col-md-6')));\n                if (this._options.toolbarPlacement === 'bottom' || this._options.toolbarPlacement === 'default') {\n                    template.append(toolbar);\n                }\n                return template;\n            }\n\n            if (this._options.toolbarPlacement === 'top') {\n                content.append(toolbar);\n            }\n            if (this._hasDate()) {\n                content.append($('<li>').addClass(this._options.collapse && this._hasTime() ? 'collapse' : '').addClass(this._options.collapse && this._hasTime() && this._options.viewMode === 'days' ? 'in' : '').append(dateView));\n            }\n            if (this._options.toolbarPlacement === 'default') {\n                content.append(toolbar);\n            }\n            if (this._hasTime()) {\n                content.append($('<li>').addClass(this._options.collapse && this._hasDate() ? 'collapse' : '').addClass(this._options.collapse && this._hasDate() && this._options.viewMode === 'times' ? 'in' : '').append(timeView));\n            }\n            if (this._options.toolbarPlacement === 'bottom') {\n                content.append(toolbar);\n            }\n            return template.append(content);\n        };\n\n        TempusDominusBootstrap3.prototype._place = function _place(e) {\n            var self = e && e.data && e.data.picker || this,\n                vertical = self._options.widgetPositioning.vertical,\n                horizontal = self._options.widgetPositioning.horizontal,\n                parent = void 0;\n            var position = (self.component || self._element).position(),\n                offset = (self.component || self._element).offset();\n\n            if (self._options.widgetParent) {\n                parent = self._options.widgetParent.append(self.widget);\n            } else if (self._element.is('input')) {\n                parent = self._element.after(self.widget).parent();\n            } else if (self._options.inline) {\n                parent = self._element.append(self.widget);\n                return;\n            } else {\n                parent = self._element;\n                self._element.children().first().after(self.widget);\n            }\n\n            // Top and bottom logic\n            if (vertical === 'auto') {\n                //noinspection JSValidateTypes\n                if (offset.top + self.widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() && self.widget.height() + self._element.outerHeight() < offset.top) {\n                    vertical = 'top';\n                } else {\n                    vertical = 'bottom';\n                }\n            }\n\n            // Left and right logic\n            if (horizontal === 'auto') {\n                if (parent.width() < offset.left + self.widget.outerWidth() / 2 && offset.left + self.widget.outerWidth() > $(window).width()) {\n                    horizontal = 'right';\n                } else {\n                    horizontal = 'left';\n                }\n            }\n\n            if (vertical === 'top') {\n                self.widget.addClass('top').removeClass('bottom');\n            } else {\n                self.widget.addClass('bottom').removeClass('top');\n            }\n\n            if (horizontal === 'right') {\n                self.widget.addClass('pull-right');\n            } else {\n                self.widget.removeClass('pull-right');\n            }\n\n            // find the first parent element that has a static css positioning\n            if (parent.css('position') !== 'static') {\n                parent = parent.parents().filter(function () {\n                    return $(this).css('position') === 'static';\n                }).first();\n            }\n\n            if (parent.length === 0) {\n                throw new Error('datetimepicker component should be placed within a static positioned container');\n            }\n\n            self.widget.css({\n                top: vertical === 'top' ? 'auto' : position.top + self._element.outerHeight() + 'px',\n                bottom: vertical === 'top' ? parent.outerHeight() - (parent === self._element ? 0 : position.top) + 'px' : 'auto',\n                left: horizontal === 'left' ? (parent === self._element ? 0 : position.left) + 'px' : 'auto',\n                right: horizontal === 'left' ? 'auto' : parent.outerWidth() - self._element.outerWidth() - (parent === self._element ? 0 : position.left) + 'px'\n            });\n        };\n\n        TempusDominusBootstrap3.prototype._fillDow = function _fillDow() {\n            var row = $('<tr>'),\n                currentDate = this._viewDate.clone().startOf('w').startOf('d');\n\n            if (this._options.calendarWeeks === true) {\n                row.append($('<th>').addClass('cw').text('#'));\n            }\n\n            while (currentDate.isBefore(this._viewDate.clone().endOf('w'))) {\n                row.append($('<th>').addClass('dow').text(currentDate.format('dd')));\n                currentDate.add(1, 'd');\n            }\n            this.widget.find('.datepicker-days thead').append(row);\n        };\n\n        TempusDominusBootstrap3.prototype._fillMonths = function _fillMonths() {\n            var spans = [],\n                monthsShort = this._viewDate.clone().startOf('y').startOf('d');\n            while (monthsShort.isSame(this._viewDate, 'y')) {\n                spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));\n                monthsShort.add(1, 'M');\n            }\n            this.widget.find('.datepicker-months td').empty().append(spans);\n        };\n\n        TempusDominusBootstrap3.prototype._updateMonths = function _updateMonths() {\n            var self = this,\n                monthsView = this.widget.find('.datepicker-months'),\n                monthsViewHeader = monthsView.find('th'),\n                months = monthsView.find('tbody').find('span');\n\n            monthsViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevYear);\n            monthsViewHeader.eq(1).attr('title', this._options.tooltips.selectYear);\n            monthsViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextYear);\n\n            monthsView.find('.disabled').removeClass('disabled');\n\n            if (!this._isValid(this._viewDate.clone().subtract(1, 'y'), 'y')) {\n                monthsViewHeader.eq(0).addClass('disabled');\n            }\n\n            monthsViewHeader.eq(1).text(this._viewDate.year());\n\n            if (!this._isValid(this._viewDate.clone().add(1, 'y'), 'y')) {\n                monthsViewHeader.eq(2).addClass('disabled');\n            }\n\n            months.removeClass('active');\n            if (this._getLastPickedDate().isSame(this._viewDate, 'y') && !this.unset) {\n                months.eq(this._getLastPickedDate().month()).addClass('active');\n            }\n            months.each(function (index) {\n                if (!self._isValid(self._viewDate.clone().month(index), 'M')) {\n                    $(this).addClass('disabled');\n                }\n            });\n        };\n\n        TempusDominusBootstrap3.prototype._getStartEndYear = function _getStartEndYear(factor, year) {\n            var step = factor / 10,\n                startYear = Math.floor(year / factor) * factor,\n                endYear = startYear + step * 9,\n                focusValue = Math.floor(year / step) * step;\n            return [startYear, endYear, focusValue];\n        };\n\n        TempusDominusBootstrap3.prototype._updateYears = function _updateYears() {\n            var yearsView = this.widget.find('.datepicker-years'),\n                yearsViewHeader = yearsView.find('th'),\n                yearCaps = this._getStartEndYear(10, this._viewDate.year()),\n                startYear = this._viewDate.clone().year(yearCaps[0]),\n                endYear = this._viewDate.clone().year(yearCaps[1]);\n            var html = '';\n\n            yearsViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevDecade);\n            yearsViewHeader.eq(1).attr('title', this._options.tooltips.selectDecade);\n            yearsViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextDecade);\n\n            yearsView.find('.disabled').removeClass('disabled');\n\n            if (this._options.minDate && this._options.minDate.isAfter(startYear, 'y')) {\n                yearsViewHeader.eq(0).addClass('disabled');\n            }\n\n            yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());\n\n            if (this._options.maxDate && this._options.maxDate.isBefore(endYear, 'y')) {\n                yearsViewHeader.eq(2).addClass('disabled');\n            }\n            html += '<span data-action=\"selectYear\" class=\"year old\">' + (startYear.year() - 1) + '</span>';\n            while (!startYear.isAfter(endYear, 'y')) {\n                html += '<span data-action=\"selectYear\" class=\"year' + (startYear.isSame(this._getLastPickedDate(), 'y') && !this.unset ? ' active' : '') + (!this._isValid(startYear, 'y') ? ' disabled' : '') + '\">' + startYear.year() + '</span>';\n                startYear.add(1, 'y');\n            }\n            html += '<span data-action=\"selectYear\" class=\"year old\">' + startYear.year() + '</span>';\n\n            yearsView.find('td').html(html);\n        };\n\n        TempusDominusBootstrap3.prototype._updateDecades = function _updateDecades() {\n            var decadesView = this.widget.find('.datepicker-decades'),\n                decadesViewHeader = decadesView.find('th'),\n                yearCaps = this._getStartEndYear(100, this._viewDate.year()),\n                startDecade = this._viewDate.clone().year(yearCaps[0]),\n                endDecade = this._viewDate.clone().year(yearCaps[1]);\n            var minDateDecade = false,\n                maxDateDecade = false,\n                endDecadeYear = void 0,\n                html = '';\n\n            decadesViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevCentury);\n            decadesViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextCentury);\n\n            decadesView.find('.disabled').removeClass('disabled');\n\n            if (startDecade.year() === 0 || this._options.minDate && this._options.minDate.isAfter(startDecade, 'y')) {\n                decadesViewHeader.eq(0).addClass('disabled');\n            }\n\n            decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());\n\n            if (this._options.maxDate && this._options.maxDate.isBefore(endDecade, 'y')) {\n                decadesViewHeader.eq(2).addClass('disabled');\n            }\n\n            if (startDecade.year() - 10 < 0) {\n                html += '<span>&nbsp;</span>';\n            } else {\n                html += '<span data-action=\"selectDecade\" class=\"decade old\" data-selection=\"' + (startDecade.year() + 6) + '\">' + (startDecade.year() - 10) + '</span>';\n            }\n\n            while (!startDecade.isAfter(endDecade, 'y')) {\n                endDecadeYear = startDecade.year() + 11;\n                minDateDecade = this._options.minDate && this._options.minDate.isAfter(startDecade, 'y') && this._options.minDate.year() <= endDecadeYear;\n                maxDateDecade = this._options.maxDate && this._options.maxDate.isAfter(startDecade, 'y') && this._options.maxDate.year() <= endDecadeYear;\n                html += '<span data-action=\"selectDecade\" class=\"decade' + (this._getLastPickedDate().isAfter(startDecade) && this._getLastPickedDate().year() <= endDecadeYear ? ' active' : '') + (!this._isValid(startDecade, 'y') && !minDateDecade && !maxDateDecade ? ' disabled' : '') + '\" data-selection=\"' + (startDecade.year() + 6) + '\">' + startDecade.year() + '</span>';\n                startDecade.add(10, 'y');\n            }\n            html += '<span data-action=\"selectDecade\" class=\"decade old\" data-selection=\"' + (startDecade.year() + 6) + '\">' + startDecade.year() + '</span>';\n\n            decadesView.find('td').html(html);\n        };\n\n        TempusDominusBootstrap3.prototype._fillDate = function _fillDate() {\n            var daysView = this.widget.find('.datepicker-days'),\n                daysViewHeader = daysView.find('th'),\n                html = [];\n            var currentDate = void 0,\n                row = void 0,\n                clsName = void 0,\n                i = void 0;\n\n            if (!this._hasDate()) {\n                return;\n            }\n\n            daysViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevMonth);\n            daysViewHeader.eq(1).attr('title', this._options.tooltips.selectMonth);\n            daysViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextMonth);\n\n            daysView.find('.disabled').removeClass('disabled');\n            daysViewHeader.eq(1).text(this._viewDate.format(this._options.dayViewHeaderFormat));\n\n            if (!this._isValid(this._viewDate.clone().subtract(1, 'M'), 'M')) {\n                daysViewHeader.eq(0).addClass('disabled');\n            }\n            if (!this._isValid(this._viewDate.clone().add(1, 'M'), 'M')) {\n                daysViewHeader.eq(2).addClass('disabled');\n            }\n\n            currentDate = this._viewDate.clone().startOf('M').startOf('w').startOf('d');\n\n            for (i = 0; i < 42; i++) {\n                //always display 42 days (should show 6 weeks)\n                if (currentDate.weekday() === 0) {\n                    row = $('<tr>');\n                    if (this._options.calendarWeeks) {\n                        row.append('<td class=\"cw\">' + currentDate.week() + '</td>');\n                    }\n                    html.push(row);\n                }\n                clsName = '';\n                if (currentDate.isBefore(this._viewDate, 'M')) {\n                    clsName += ' old';\n                }\n                if (currentDate.isAfter(this._viewDate, 'M')) {\n                    clsName += ' new';\n                }\n                if (this._options.allowMultidate) {\n                    var index = this._datesFormatted.indexOf(currentDate.format('YYYY-MM-DD'));\n                    if (index !== -1) {\n                        if (currentDate.isSame(this._datesFormatted[index], 'd') && !this.unset) {\n                            clsName += ' active';\n                        }\n                    }\n                } else {\n                    if (currentDate.isSame(this._getLastPickedDate(), 'd') && !this.unset) {\n                        clsName += ' active';\n                    }\n                }\n                if (!this._isValid(currentDate, 'd')) {\n                    clsName += ' disabled';\n                }\n                if (currentDate.isSame(this.getMoment(), 'd')) {\n                    clsName += ' today';\n                }\n                if (currentDate.day() === 0 || currentDate.day() === 6) {\n                    clsName += ' weekend';\n                }\n                row.append('<td data-action=\"selectDay\" data-day=\"' + currentDate.format('L') + '\" class=\"day' + clsName + '\">' + currentDate.date() + '</td>');\n                currentDate.add(1, 'd');\n            }\n\n            daysView.find('tbody').empty().append(html);\n\n            this._updateMonths();\n\n            this._updateYears();\n\n            this._updateDecades();\n        };\n\n        TempusDominusBootstrap3.prototype._fillHours = function _fillHours() {\n            var table = this.widget.find('.timepicker-hours table'),\n                currentHour = this._viewDate.clone().startOf('d'),\n                html = [];\n            var row = $('<tr>');\n\n            if (this._viewDate.hour() > 11 && !this.use24Hours) {\n                currentHour.hour(12);\n            }\n            while (currentHour.isSame(this._viewDate, 'd') && (this.use24Hours || this._viewDate.hour() < 12 && currentHour.hour() < 12 || this._viewDate.hour() > 11)) {\n                if (currentHour.hour() % 4 === 0) {\n                    row = $('<tr>');\n                    html.push(row);\n                }\n                row.append('<td data-action=\"selectHour\" class=\"hour' + (!this._isValid(currentHour, 'h') ? ' disabled' : '') + '\">' + currentHour.format(this.use24Hours ? 'HH' : 'hh') + '</td>');\n                currentHour.add(1, 'h');\n            }\n            table.empty().append(html);\n        };\n\n        TempusDominusBootstrap3.prototype._fillMinutes = function _fillMinutes() {\n            var table = this.widget.find('.timepicker-minutes table'),\n                currentMinute = this._viewDate.clone().startOf('h'),\n                html = [],\n                step = this._options.stepping === 1 ? 5 : this._options.stepping;\n            var row = $('<tr>');\n\n            while (this._viewDate.isSame(currentMinute, 'h')) {\n                if (currentMinute.minute() % (step * 4) === 0) {\n                    row = $('<tr>');\n                    html.push(row);\n                }\n                row.append('<td data-action=\"selectMinute\" class=\"minute' + (!this._isValid(currentMinute, 'm') ? ' disabled' : '') + '\">' + currentMinute.format('mm') + '</td>');\n                currentMinute.add(step, 'm');\n            }\n            table.empty().append(html);\n        };\n\n        TempusDominusBootstrap3.prototype._fillSeconds = function _fillSeconds() {\n            var table = this.widget.find('.timepicker-seconds table'),\n                currentSecond = this._viewDate.clone().startOf('m'),\n                html = [];\n            var row = $('<tr>');\n\n            while (this._viewDate.isSame(currentSecond, 'm')) {\n                if (currentSecond.second() % 20 === 0) {\n                    row = $('<tr>');\n                    html.push(row);\n                }\n                row.append('<td data-action=\"selectSecond\" class=\"second' + (!this._isValid(currentSecond, 's') ? ' disabled' : '') + '\">' + currentSecond.format('ss') + '</td>');\n                currentSecond.add(5, 's');\n            }\n\n            table.empty().append(html);\n        };\n\n        TempusDominusBootstrap3.prototype._fillTime = function _fillTime() {\n            var toggle = void 0,\n                newDate = void 0;\n            var timeComponents = this.widget.find('.timepicker span[data-time-component]');\n\n            if (!this.use24Hours) {\n                toggle = this.widget.find('.timepicker [data-action=togglePeriod]');\n                newDate = this._getLastPickedDate().clone().add(this._getLastPickedDate().hours() >= 12 ? -12 : 12, 'h');\n\n                toggle.text(this._getLastPickedDate().format('A'));\n\n                if (this._isValid(newDate, 'h')) {\n                    toggle.removeClass('disabled');\n                } else {\n                    toggle.addClass('disabled');\n                }\n            }\n            timeComponents.filter('[data-time-component=hours]').text(this._getLastPickedDate().format('' + (this.use24Hours ? 'HH' : 'hh')));\n            timeComponents.filter('[data-time-component=minutes]').text(this._getLastPickedDate().format('mm'));\n            timeComponents.filter('[data-time-component=seconds]').text(this._getLastPickedDate().format('ss'));\n\n            this._fillHours();\n            this._fillMinutes();\n            this._fillSeconds();\n        };\n\n        TempusDominusBootstrap3.prototype._doAction = function _doAction(e, action) {\n            var lastPicked = this._getLastPickedDate();\n            if ($(e.currentTarget).is('.disabled')) {\n                return false;\n            }\n            action = action || $(e.currentTarget).data('action');\n            switch (action) {\n                case 'next':\n                    {\n                        var navFnc = DateTimePicker.DatePickerModes[this.currentViewMode].NAV_FUNCTION;\n                        this._viewDate.add(DateTimePicker.DatePickerModes[this.currentViewMode].NAV_STEP, navFnc);\n                        this._fillDate();\n                        this._viewUpdate(navFnc);\n                        break;\n                    }\n                case 'previous':\n                    {\n                        var _navFnc = DateTimePicker.DatePickerModes[this.currentViewMode].NAV_FUNCTION;\n                        this._viewDate.subtract(DateTimePicker.DatePickerModes[this.currentViewMode].NAV_STEP, _navFnc);\n                        this._fillDate();\n                        this._viewUpdate(_navFnc);\n                        break;\n                    }\n                case 'pickerSwitch':\n                    this._showMode(1);\n                    break;\n                case 'selectMonth':\n                    var month = $(e.target).closest('tbody').find('span').index($(e.target));\n                    this._viewDate.month(month);\n                    if (this.currentViewMode === DateTimePicker.MinViewModeNumber) {\n                        this._setValue(lastPicked.clone().year(this._viewDate.year()).month(this._viewDate.month()), this._getLastPickedDateIndex());\n                        if (!this._options.inline) {\n                            this.hide();\n                        }\n                    } else {\n                        this._showMode(-1);\n                        this._fillDate();\n                    }\n                    this._viewUpdate('M');\n                    break;\n                case 'selectYear':\n                    {\n                        var year = parseInt($(e.target).text(), 10) || 0;\n                        this._viewDate.year(year);\n                        if (this.currentViewMode === DateTimePicker.MinViewModeNumber) {\n                            this._setValue(lastPicked.clone().year(this._viewDate.year()), this._getLastPickedDateIndex());\n                            if (!this._options.inline) {\n                                this.hide();\n                            }\n                        } else {\n                            this._showMode(-1);\n                            this._fillDate();\n                        }\n                        this._viewUpdate('YYYY');\n                        break;\n                    }\n                case 'selectDecade':\n                    {\n                        var _year = parseInt($(e.target).data('selection'), 10) || 0;\n                        this._viewDate.year(_year);\n                        if (this.currentViewMode === DateTimePicker.MinViewModeNumber) {\n                            this._setValue(lastPicked.clone().year(this._viewDate.year()), this._getLastPickedDateIndex());\n                            if (!this._options.inline) {\n                                this.hide();\n                            }\n                        } else {\n                            this._showMode(-1);\n                            this._fillDate();\n                        }\n                        this._viewUpdate('YYYY');\n                        break;\n                    }\n                case 'selectDay':\n                    {\n                        var day = this._viewDate.clone();\n                        if ($(e.target).is('.old')) {\n                            day.subtract(1, 'M');\n                        }\n                        if ($(e.target).is('.new')) {\n                            day.add(1, 'M');\n                        }\n                        var selectDate = day.date(parseInt($(e.target).text(), 10)),\n                            index = -1;\n                        if (this._options.allowMultidate) {\n                            index = this._datesFormatted.indexOf(selectDate.format('YYYY-MM-DD'));\n                            if (index !== -1) {\n                                this._setValue(null, index); //deselect multidate\n                            } else {\n                                this._setValue(selectDate, this._getLastPickedDateIndex() + 1);\n                            }\n                        } else {\n                            this._setValue(selectDate, 0);\n                        }\n                        if (!this._hasTime() && !this._options.keepOpen && !this._options.inline && !this._options.allowMultidate) {\n                            this.hide();\n                        }\n                        break;\n                    }\n                case 'incrementHours':\n                    {\n                        var newDate = lastPicked.clone().add(1, 'h');\n                        if (this._isValid(newDate, 'h')) {\n                            this._setValue(newDate, this._getLastPickedDateIndex());\n                        }\n                        break;\n                    }\n                case 'incrementMinutes':\n                    {\n                        var _newDate = this._getLastPickedDate().clone().add(this._options.stepping, 'm');\n                        if (this._isValid(_newDate, 'm')) {\n                            this._setValue(_newDate, this._getLastPickedDateIndex());\n                        }\n                        break;\n                    }\n                case 'incrementSeconds':\n                    {\n                        var _newDate2 = this._getLastPickedDate().clone().add(1, 's');\n                        if (this._isValid(_newDate2, 's')) {\n                            this._setValue(_newDate2, this._getLastPickedDateIndex());\n                        }\n                        break;\n                    }\n                case 'decrementHours':\n                    {\n                        var _newDate3 = lastPicked.clone().subtract(1, 'h');\n                        if (this._isValid(_newDate3, 'h')) {\n                            this._setValue(_newDate3, this._getLastPickedDateIndex());\n                        }\n                        break;\n                    }\n                case 'decrementMinutes':\n                    {\n                        var _newDate4 = lastPicked.clone().subtract(this._options.stepping, 'm');\n                        if (this._isValid(_newDate4, 'm')) {\n                            this._setValue(_newDate4, this._getLastPickedDateIndex());\n                        }\n                        break;\n                    }\n                case 'decrementSeconds':\n                    {\n                        var _newDate5 = lastPicked.clone().subtract(1, 's');\n                        if (this._isValid(_newDate5, 's')) {\n                            this._setValue(_newDate5, this._getLastPickedDateIndex());\n                        }\n                        break;\n                    }\n                case 'togglePeriod':\n                    {\n                        this._setValue(lastPicked.clone().add(lastPicked.hours() >= 12 ? -12 : 12, 'h'), this._getLastPickedDateIndex());\n                        break;\n                    }\n                case 'togglePicker':\n                    {\n                        var $this = $(e.target),\n                            $link = $this.closest('a'),\n                            $parent = $this.closest('ul'),\n                            expanded = $parent.find('.in'),\n                            closed = $parent.find('.collapse:not(.in)'),\n                            $span = $this.is('span') ? $this : $this.find('span');\n                        var collapseData = void 0;\n\n                        if (expanded && expanded.length) {\n                            collapseData = expanded.data('collapse');\n                            if (collapseData && collapseData.transitioning) {\n                                return false;\n                            }\n                            if (expanded.collapse) {\n                                // if collapse plugin is available through bootstrap.js then use it\n                                expanded.collapse('hide');\n                                closed.collapse('show');\n                            } else {\n                                // otherwise just toggle in class on the two views\n                                expanded.removeClass('in');\n                                closed.addClass('in');\n                            }\n                            $span.toggleClass(this._options.icons.time + ' ' + this._options.icons.date);\n\n                            if ($span.hasClass(this._options.icons.date)) {\n                                $link.attr('title', this._options.tooltips.selectDate);\n                            } else {\n                                $link.attr('title', this._options.tooltips.selectTime);\n                            }\n                        }\n                    }\n                    break;\n                case 'showPicker':\n                    this.widget.find('.timepicker > div:not(.timepicker-picker)').hide();\n                    this.widget.find('.timepicker .timepicker-picker').show();\n                    break;\n                case 'showHours':\n                    this.widget.find('.timepicker .timepicker-picker').hide();\n                    this.widget.find('.timepicker .timepicker-hours').show();\n                    break;\n                case 'showMinutes':\n                    this.widget.find('.timepicker .timepicker-picker').hide();\n                    this.widget.find('.timepicker .timepicker-minutes').show();\n                    break;\n                case 'showSeconds':\n                    this.widget.find('.timepicker .timepicker-picker').hide();\n                    this.widget.find('.timepicker .timepicker-seconds').show();\n                    break;\n                case 'selectHour':\n                    {\n                        var hour = parseInt($(e.target).text(), 10);\n\n                        if (!this.use24Hours) {\n                            if (lastPicked.hours() >= 12) {\n                                if (hour !== 12) {\n                                    hour += 12;\n                                }\n                            } else {\n                                if (hour === 12) {\n                                    hour = 0;\n                                }\n                            }\n                        }\n                        this._setValue(lastPicked.clone().hours(hour), this._getLastPickedDateIndex());\n                        this._doAction(e, 'showPicker');\n                        break;\n                    }\n                case 'selectMinute':\n                    this._setValue(lastPicked.clone().minutes(parseInt($(e.target).text(), 10)), lastPicked);\n                    this._doAction(e, 'showPicker');\n                    break;\n                case 'selectSecond':\n                    this._setValue(lastPicked.clone().seconds(parseInt($(e.target).text(), 10)), this._getLastPickedDateIndex());\n                    this._doAction(e, 'showPicker');\n                    break;\n                case 'clear':\n                    this.clear();\n                    break;\n                case 'today':\n                    {\n                        var todaysDate = this.getMoment();\n                        if (this._isValid(todaysDate, 'd')) {\n                            this._setValue(todaysDate, lastPicked);\n                        }\n                        break;\n                    }\n            }\n            return false;\n        };\n\n        //public\n\n\n        TempusDominusBootstrap3.prototype.hide = function hide() {\n            var transitioning = false;\n            if (!this.widget) {\n                return;\n            }\n            // Ignore event if in the middle of a picker transition\n            this.widget.find('.collapse').each(function () {\n                var collapseData = $(this).data('collapse');\n                if (collapseData && collapseData.transitioning) {\n                    transitioning = true;\n                    return false;\n                }\n                return true;\n            });\n            if (transitioning) {\n                return;\n            }\n            if (this.component && this.component.hasClass('btn')) {\n                this.component.toggleClass('active');\n            }\n            this.widget.hide();\n\n            $(window).off('resize', this._place);\n            this.widget.off('click', '[data-action]');\n            this.widget.off('mousedown', false);\n\n            this.widget.remove();\n            this.widget = false;\n\n            this._notifyEvent({\n                type: DateTimePicker.Event.HIDE,\n                date: this._getLastPickedDate().clone()\n            });\n\n            if (this.input !== undefined) {\n                this.input.blur();\n            }\n\n            this._viewDate = this._getLastPickedDate().clone();\n        };\n\n        TempusDominusBootstrap3.prototype.show = function show() {\n            var currentMoment = void 0;\n            var useCurrentGranularity = {\n                'year': function year(m) {\n                    return m.month(0).date(1).hours(0).seconds(0).minutes(0);\n                },\n                'month': function month(m) {\n                    return m.date(1).hours(0).seconds(0).minutes(0);\n                },\n                'day': function day(m) {\n                    return m.hours(0).seconds(0).minutes(0);\n                },\n                'hour': function hour(m) {\n                    return m.seconds(0).minutes(0);\n                },\n                'minute': function minute(m) {\n                    return m.seconds(0);\n                }\n            };\n\n            if (this.input !== undefined) {\n                if (this.input.prop('disabled') || !this._options.ignoreReadonly && this.input.prop('readonly') || this.widget) {\n                    return;\n                }\n                if (this.input.val() !== undefined && this.input.val().trim().length !== 0) {\n                    this._setValue(this._parseInputDate(this.input.val().trim()), 0);\n                } else if (this.unset && this._options.useCurrent) {\n                    currentMoment = this.getMoment();\n                    if (typeof this._options.useCurrent === 'string') {\n                        currentMoment = useCurrentGranularity[this._options.useCurrent](currentMoment);\n                    }\n                    this._setValue(currentMoment, 0);\n                }\n            } else if (this.unset && this._options.useCurrent) {\n                currentMoment = this.getMoment();\n                if (typeof this._options.useCurrent === 'string') {\n                    currentMoment = useCurrentGranularity[this._options.useCurrent](currentMoment);\n                }\n                this._setValue(currentMoment, 0);\n            }\n\n            this.widget = this._getTemplate();\n\n            this._fillDow();\n            this._fillMonths();\n\n            this.widget.find('.timepicker-hours').hide();\n            this.widget.find('.timepicker-minutes').hide();\n            this.widget.find('.timepicker-seconds').hide();\n\n            this._update();\n            this._showMode();\n\n            $(window).on('resize', { picker: this }, this._place);\n            this.widget.on('click', '[data-action]', $.proxy(this._doAction, this)); // this handles clicks on the widget\n            this.widget.on('mousedown', false);\n\n            if (this.component && this.component.hasClass('btn')) {\n                this.component.toggleClass('active');\n            }\n            this._place();\n            this.widget.show();\n            if (this.input !== undefined && this._options.focusOnShow && !this.input.is(':focus')) {\n                this.input.focus();\n            }\n\n            this._notifyEvent({\n                type: DateTimePicker.Event.SHOW\n            });\n        };\n\n        TempusDominusBootstrap3.prototype.destroy = function destroy() {\n            this.hide();\n            //todo doc off?\n            this._element.removeData(DateTimePicker.DATA_KEY);\n            this._element.removeData('date');\n        };\n\n        TempusDominusBootstrap3.prototype.disable = function disable() {\n            this.hide();\n            if (this.component && this.component.hasClass('btn')) {\n                this.component.addClass('disabled');\n            }\n            if (this.input !== undefined) {\n                this.input.prop('disabled', true); //todo disable this/comp if input is null\n            }\n        };\n\n        TempusDominusBootstrap3.prototype.enable = function enable() {\n            if (this.component && this.component.hasClass('btn')) {\n                this.component.removeClass('disabled');\n            }\n            if (this.input !== undefined) {\n                this.input.prop('disabled', false); //todo enable comp/this if input is null\n            }\n        };\n\n        TempusDominusBootstrap3.prototype.toolbarPlacement = function toolbarPlacement(_toolbarPlacement) {\n            if (arguments.length === 0) {\n                return this._options.toolbarPlacement;\n            }\n\n            if (typeof _toolbarPlacement !== 'string') {\n                throw new TypeError('toolbarPlacement() expects a string parameter');\n            }\n            if (toolbarPlacements.indexOf(_toolbarPlacement) === -1) {\n                throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');\n            }\n            this._options.toolbarPlacement = _toolbarPlacement;\n\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        TempusDominusBootstrap3.prototype.widgetPositioning = function widgetPositioning(_widgetPositioning) {\n            if (arguments.length === 0) {\n                return $.extend({}, this._options.widgetPositioning);\n            }\n\n            if ({}.toString.call(_widgetPositioning) !== '[object Object]') {\n                throw new TypeError('widgetPositioning() expects an object variable');\n            }\n            if (_widgetPositioning.horizontal) {\n                if (typeof _widgetPositioning.horizontal !== 'string') {\n                    throw new TypeError('widgetPositioning() horizontal variable must be a string');\n                }\n                _widgetPositioning.horizontal = _widgetPositioning.horizontal.toLowerCase();\n                if (horizontalModes.indexOf(_widgetPositioning.horizontal) === -1) {\n                    throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');\n                }\n                this._options.widgetPositioning.horizontal = _widgetPositioning.horizontal;\n            }\n            if (_widgetPositioning.vertical) {\n                if (typeof _widgetPositioning.vertical !== 'string') {\n                    throw new TypeError('widgetPositioning() vertical variable must be a string');\n                }\n                _widgetPositioning.vertical = _widgetPositioning.vertical.toLowerCase();\n                if (verticalModes.indexOf(_widgetPositioning.vertical) === -1) {\n                    throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');\n                }\n                this._options.widgetPositioning.vertical = _widgetPositioning.vertical;\n            }\n            this._update();\n        };\n\n        TempusDominusBootstrap3.prototype.widgetParent = function widgetParent(_widgetParent) {\n            if (arguments.length === 0) {\n                return this._options.widgetParent;\n            }\n\n            if (typeof _widgetParent === 'string') {\n                _widgetParent = $(_widgetParent);\n            }\n\n            if (_widgetParent !== null && typeof _widgetParent !== 'string' && !(_widgetParent instanceof $)) {\n                throw new TypeError('widgetParent() expects a string or a jQuery object parameter');\n            }\n\n            this._options.widgetParent = _widgetParent;\n            if (this.widget) {\n                this.hide();\n                this.show();\n            }\n        };\n\n        //static\n\n\n        TempusDominusBootstrap3._jQueryHandleThis = function _jQueryHandleThis(me, option, argument) {\n            var data = $(me).data(DateTimePicker.DATA_KEY);\n            if ((typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object') {\n                $.extend({}, DateTimePicker.Default, option);\n            }\n\n            if (!data) {\n                data = new TempusDominusBootstrap3($(me), option);\n                $(me).data(DateTimePicker.DATA_KEY, data);\n            }\n\n            if (typeof option === 'string') {\n                if (data[option] === undefined) {\n                    throw new Error('No method named \"' + option + '\"');\n                }\n                if (argument === undefined) {\n                    return data[option]();\n                } else {\n                    return data[option](argument);\n                }\n            }\n        };\n\n        TempusDominusBootstrap3._jQueryInterface = function _jQueryInterface(option, argument) {\n            if (this.length === 1) {\n                return TempusDominusBootstrap3._jQueryHandleThis(this[0], option, argument);\n            }\n            return this.each(function () {\n                TempusDominusBootstrap3._jQueryHandleThis(this, option, argument);\n            });\n        };\n\n        return TempusDominusBootstrap3;\n    }(DateTimePicker);\n\n    /**\n    * ------------------------------------------------------------------------\n    * jQuery\n    * ------------------------------------------------------------------------\n    */\n\n\n    $(document).on(DateTimePicker.Event.CLICK_DATA_API, DateTimePicker.Selector.DATA_TOGGLE, function () {\n        var $target = getSelectorFromElement($(this));\n        if ($target.length === 0) {\n            return;\n        }\n        TempusDominusBootstrap3._jQueryInterface.call($target, 'toggle');\n    }).on(DateTimePicker.Event.CHANGE, '.' + DateTimePicker.ClassName.INPUT, function (event) {\n        var $target = getSelectorFromElement($(this));\n        if ($target.length === 0) {\n            return;\n        }\n        TempusDominusBootstrap3._jQueryInterface.call($target, '_change', event); //todo multidate probably broke this\n    }).on(DateTimePicker.Event.BLUR, '.' + DateTimePicker.ClassName.INPUT, function (event) {\n        var $target = getSelectorFromElement($(this)),\n            config = $target.data(DateTimePicker.DATA_KEY);\n        if ($target.length === 0) {\n            return;\n        }\n        if (config._options.debug || window.debug) {\n            return;\n        }\n        TempusDominusBootstrap3._jQueryInterface.call($target, 'hide', event);\n    }).on(DateTimePicker.Event.KEYDOWN, '.' + DateTimePicker.ClassName.INPUT, function (event) {\n        var $target = getSelectorFromElement($(this));\n        if ($target.length === 0) {\n            return;\n        }\n        TempusDominusBootstrap3._jQueryInterface.call($target, '_keydown', event);\n    }).on(DateTimePicker.Event.KEYUP, '.' + DateTimePicker.ClassName.INPUT, function (event) {\n        var $target = getSelectorFromElement($(this));\n        if ($target.length === 0) {\n            return;\n        }\n        TempusDominusBootstrap3._jQueryInterface.call($target, '_keyup', event);\n    }).on(DateTimePicker.Event.FOCUS, '.' + DateTimePicker.ClassName.INPUT, function (event) {\n        var $target = getSelectorFromElement($(this)),\n            config = $target.data(DateTimePicker.DATA_KEY);\n        if ($target.length === 0) {\n            return;\n        }\n        if (!config._options.allowInputToggle) {\n            return;\n        }\n        TempusDominusBootstrap3._jQueryInterface.call($target, config, event);\n    });\n\n    $.fn[DateTimePicker.NAME] = TempusDominusBootstrap3._jQueryInterface;\n    $.fn[DateTimePicker.NAME].Constructor = TempusDominusBootstrap3;\n    $.fn[DateTimePicker.NAME].noConflict = function () {\n        $.fn[DateTimePicker.NAME] = JQUERY_NO_CONFLICT;\n        return TempusDominusBootstrap3._jQueryInterface;\n    };\n\n    return TempusDominusBootstrap3;\n}(jQuery);\n\n}();"
  },
  {
    "path": "pyscada/hmi/templates/403.html",
    "content": "{% if exception %}\n  <h1>{{ exception }}</h1>\n{% else %}\n  <h1>403 Forbidden</h1>\n{% endif %}\n"
  },
  {
    "path": "pyscada/hmi/templates/_chart_legend.html",
    "content": " <div class=\"hidden axis-config\" data-label=\"{{axe.label}}\" data-key=\"{{axe.pk}}\" data-position=\"{{ axe.position }}\" data-min=\"{{ axe.min }}\" data-max=\"{{ axe.max }}\" data-show-plot-points=\"{{ axe.show_plot_points }}\" data-show-plot-bars=\"{{ axe.show_bars }}\" data-show-plot-lines=\"{{ axe.show_plot_lines }}\" data-stack=\"{{ axe.stack }}\" data-fill=\"{{ axe.fill }}\"></div>\n  {% for var in axe.variables.all %}\n      {% if var.active and var.device.active %}\n      <tr class=\"legendSeries\" title=\"{{var.description}}\">\n          <td>\n              <span class=\"hidden\" id=\"chart-legend-checkbox-status-{{widget_pk}}-{{chart.pk}}-{{var.pk}}\" >1</span>\n              <input type=\"checkbox\" checked=\"checked\" id=\"chart-legend-checkbox-{{widget_pk}}-{{ chart.pk }}-{{ var.pk }}\">\n              <div class=\"hidden variable-config\" data-name=\"{{var.name}}\" data-device-id={{var.device_id}} data-key=\"{{var.pk}}\" data-color=\"{{ var.chart_line_color_code }}\" data-init-type=\"1\" data-type=\"variable\" data-axis-id=\"{{ axe.pk }}\" data-device-polling_interval=\"{{ var.device.polling_interval }}\"></div>\n          </td>\n          <td class=\"legendColorBox\">\n              <div style=\"border:1px solid #ccc;padding:1px; width: 14px;\">\n                  <div style=\"width:4px;height:0;border:5px solid {{ var.chart_line_color_code }};overflow:hidden\">\n                  </div>\n              </div>\n          </td>\n          <td class=\"legendLabel\" data-key=\"{{var.pk}}\" style=\"word-wrap: break-word; position: inherit;\">\n              <span class=\"legendLabel-text\">{{ var.hmi_name }}</span>\n              <span id=\"chart-legend-options-span-{{widget_pk}}-{{chart.pk}}-{{ var.pk }}\"></span>\n          </td>\n          <td class=\"legendValue type-numeric chart-legend-value-{{widget_pk}}-{{chart.pk}} var-{{ var.pk }}\" id=\"chart-legend-value-{{widget_pk}}-{{chart.pk}}-{{var.pk}}\" style=\"text-align:right; word-wrap: break-word;\"></td>\n          <td class=\"legendUnit\" data-key=\"{{var.pk}}\">{{ var.unit.unit }}</td>\n          <td>\n              <div class=\"dropdown\">\n                  <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" id=\"chart-legend-options-{{widget_pk}}-{{chart.pk}}-{{ var.pk }}\">\n                      <span class=\"chart-legend-options-text\"></span>\n                      <span class=\"glyphicon glyphicon-cog\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Chart options\" style=\"font-size:8px;vertical-align:text-top;\"></span>\n                  </a>\n                  <ul class=\"dropdown-menu\" style=\"min-width: 0px;font-size:11px;\">\n                      <li>Aggregation</li>\n                      <li class=\"divider\"></li>\n                      <li>\n                          Type\n                          <select class=\"aggregation-type-option\" id=\"aggregation-type-select-{{widget_pk}}-{{chart.pk}}-{{ var.pk }}\" data-id=\"{{ var.id }}\" data-widget-id=\"{{widget_pk}}-{{chart.pk}}\" style=\"width:66px;\">\n                              <option value=null>None</option>\n                          </select>\n                      </li>\n                      <li class=\"hidden\" id=\"li-aggregation-all-period-select-{{widget_pk}}-{{chart.pk}}-{{ var.pk }}\">\n                          Period\n                          <select class=\"aggregation-period-option hidden\" id=\"aggregation-period-select-{{widget_pk}}-{{chart.pk}}-{{ var.pk }}\" data-id=\"{{ var.id }}\" data-widget-id=\"{{widget_pk}}-{{chart.pk}}\" style=\"width:66px;\">\n                            <option value=null>None</option>\n                          </select>\n                      </li>\n                  </ul>\n              </div>\n          </td>\n      </tr>\n      {% endif %}\n  {% endfor %}\n"
  },
  {
    "path": "pyscada/hmi/templates/base.html",
    "content": "{% load i18n static %}\n{% csrf_token %}\n<!DOCTYPE html>\n<html xmlns=\"html://www.w3.org/1999/xhtml\" lang=\"en\" dir=\"ltr\" xml:lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\"/>\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n        <meta name=\"description\" content=\"PyScada html5 client application\"/>\n        <meta name=\"author\" content=\"Martin Schroeder\"/>\n\n        <title>{% block title %}{% endblock %}</title>\n        <!-- Bootstrap core CSS -->\n        <link href='{% static \"pyscada/css/bootstrap/bootstrap.min.css\" %}' rel=\"stylesheet\"/>\n        <!-- Bootstrap theme -->\n        <link href='{% static \"pyscada/css/bootstrap/bootstrap-theme.min.css\" %}' rel=\"stylesheet\"/>\n        <!-- Bootstrap switch -->\n        <link href='{% static \"pyscada/css/bootstrap/bootstrap-switch.min.css\" %}' rel=\"stylesheet\">\n        <!-- Bootstrap theme -->\n        <link href='{% static \"pyscada/css/jquery-ui/jquery-ui.min.css\" %}' rel=\"stylesheet\"/>\n        <!-- Bootstrap theme -->\n        <link href='{% static \"pyscada/css/jquery-ui/jquery-ui.theme.min.css\" %}' rel=\"stylesheet\"/>\n        <!-- Custom styles for this template -->\n        <link href='{% static \"pyscada/css/pyscada/pyscada-theme.css\" %}' rel=\"stylesheet\"/>\n        <!-- Tablesorter theme -->\n        <link href='{% static \"pyscada/css/theme.default.min.css\" %}' rel=\"stylesheet\"/>\n        {% block head_css %}{% endblock %}\n\n        <link rel=\"icon\" href='{% static \"pyscada/img/favicon.ico\" %}' type=\"image/x-icon\">\n    </head>\n    <body {% block body_confic_data %} {% endblock %}>\n        <div id=\"wrap\" {% block wrap_config %} {% endblock %}>\n            {% block navbar %}\n            <!-- Fixed navbar -->\n            <div id=\"navbar-top\" class=\"navbar navbar-inverse navbar-fixed-top\" role=\"navigation\">\n                <div class=\"container\">\n                    <div class=\"navbar-header\">\n                        {% block navbar_logo %}{% endblock %}\n                        <button type=\"button\" class=\"navbar-toggle\" data-toggle=\"collapse\" data-target=\".navbar-collapse\">\n                            <span class=\"icon-bar\"></span>\n                            <span class=\"icon-bar\"></span>\n                            <span class=\"icon-bar\"></span>\n                        </button>\n                        <a  class=\"navbar-brand\" target=\"{{ link_target }}\" href=\"{% url 'view-overview'%}\">{% if site_name %}{{ site_name }}{% else %}PyScada{% endif %}</a>\n                    </div>\n                    <div class=\"navbar-collapse collapse\">\n                        <ul class=\"nav navbar-left navbar-nav\">\n                            {% block top_menu_left %}{% endblock %}\n                            {% block top_menu_left_collapsed %}{% endblock %}\n                        </ul>\n                        <ul class=\"nav navbar-right navbar-nav\">\n                            {% block top_menu_right %}{% endblock %}\n                            {% block user_dropdown %}\n                                {% include \"user_dropdown.html\" %}\n                            {% endblock %}\n                            {% if not user.is_authenticated %}\n                                <li>\n                                    <a href=\"/accounts/login\" class=\"sign_in_link\" target=\"_self\">Sign in</a>\n                                </li>\n                            {% endif %}\n                            <!--<li><a href=\"#page-help\">Help</a></li>-->\n                            <li>\n                              <a href=\"javascript:;\" style=\"padding-left:0px; padding-right:0px;width:40px;height:30px;display: none;\"><img class=\"loadingAnimation\" style=\"display: inline-block; height: 14px;\" src='{% static \"pyscada/img/load.gif\" %}' alt=\"loading\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"\" data-original-title=\"Initialisation\"></a>\n                            </li>\n                            <li class=\"AutoUpdateButtonParent\" style=\"width:100px;height:30px;padding-top: 15px;margin-right:-25px;display: none;\">\n                                <input class=\"AutoUpdateButton hidden\" type=\"checkbox\" data-inverse=\"true\" data-size=\"mini\" data-on-color=\"success\" data-off-color=\"default\" checked>\n                            </li>\n                            <li style=\"width:40px;height:30px;display: none;\">\n                                <a href=\"javascript:;\" ><span class=\"ReadAllTask glyphicon glyphicon-refresh\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"\" data-original-title=\"Send request data to all devices\"></span></a>\n                            </li>\n                            <li style=\"width:40px;height:40px;display: none;\">\n                                <a href=\"javascript:;\" ><span class=\"AutoUpdateStatus glyphicon glyphicon-transfer\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"\" data-original-title=\"Data transfer\"></span></a>\n                            </li>\n                        </ul>\n                    </div><!--/.nav-collapse -->\n                </div>\n            </div>\n            {% endblock %}\n            <div class=\"container\" id=\"content\">\n                {% block top_area %}{% endblock %}\n                <!-- loading animation -->\n                <div id=\"notification_area\"></div>\n                <div id=\"notification_area_2\"></div>\n                {% block content %}{% endblock %}\n            </div> <!-- /container -->\n        </div>\n        <footer class=\"footer\">\n        <span class=\"pull-left\">{% if version_string %}PyScada Version: {{version_string}}{% endif %}</span>\n        {% block footer_left %}{% endblock %}\n        <span class=\"pull-right server_time\"></span>\n        </footer>\n        <canvas id=\"myCanvas\" width=\"30\" height=\"15\" style=\"display:none\"></canvas>\n        <!-- Bootstrap core JavaScript\n        ================================================== -->\n        <!-- Placed at the end of the document so the pages load faster -->\n        <script type=\"text/javascript\" src='{% static \"pyscada/js/jquery/jquery.min.js\" %}'></script>\n        <script type=\"text/javascript\" src='{% static \"pyscada/js/jquery-ui/jquery-ui.js\" %}'></script>\n        <script type=\"text/javascript\" src='{% static \"pyscada/js/jquery/jquery.cookie.js\" %}'></script>\n        <script type=\"text/javascript\" src='{% static \"pyscada/js/bootstrap/bootstrap.min.js\" %}'></script>\n        <script type=\"text/javascript\" src='{% static \"pyscada/js/bootstrap/bootstrap-switch.min.js\" %}'></script>\n        {% block include_bottom %}{% endblock %}\n\n        <script>\n        {% block script_bottom %}{% endblock %}\n        </script>\n        <div id='tooltip'></div>\n    </body>\n</html>\n"
  },
  {
    "path": "pyscada/hmi/templates/button.html",
    "content": "{% if item.type == 0 %}<!-- input value -->\n    {% if form %}\n        <div class=\"input-group set_value\">\n            <input type=\"button\" class=\"btn btn-default update-able write-task-btn type-bool {{ item.web_class_str }}\" id=\"{{ form.web_id }}-{{ item.web_id }}-{{ uuid }}\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type}}\" placeholder=\"{% if item.control_element_options is not None %}{{ item.control_element_options.placeholder }}{% endif %}\" value=\"{{ item.label }}\">\n        </div>\n    {% else %}\n        <div class=\"input-group control_button\">\n            <button type=\"button\" class=\"btn btn-default update-able write-task-btn type-bool {{ item.web_class_str }}\" id=\"{{ item.web_id }}-{{ uuid }}\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type}}\">\n                <span class=\"boolean-value\">\n                    {{ item.label }}\n                </span>\n            </button>\n        </div>\n    {% endif %}\n{% elif item.type == 1 %}<!-- display value -->\n    <div class=\"input-group set_value\">\n        <span class=\"input-group-addon control-item type-numeric {{ item.web_class_str }}\" id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type}}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"\" data-html=\"true\">\n            {% if item.readable %}\n                <button class=\"btn btn-default read-task-set {{ item.web_class_str }}\" type=\"button\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type }}\" style=\"width:16px;height:16px;padding:0;margin:0;\">\n                    <span class=\"glyphicon glyphicon-refresh\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Refresh data\" style=\"font-size:10px;vertical-align:text-top;\"></span>\n                </button>\n            {% endif %}\n            <span style=\"color:red;height:12px;\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Very old value (>10 polling interval)\" class=\"glyphicon glyphicon-alert hidden\"></span>\n            <span style=\"color:orange;height:12px;\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Old value (between 3 and 10 polling interval)\" class=\"glyphicon glyphicon-exclamation-sign hidden\"></span>\n            <span class=\"boolean-value\">\n              {{ item.label }}\n            </span>\n        </span>\n        <!--<span class=\"input-group-addon input-group-addon-label control-item type-numeric {{ item.web_class_str }}\" id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"\" data-html=\"true\">Loading...</span>-->\n    </div>\n{% endif %}\n"
  },
  {
    "path": "pyscada/hmi/templates/chart.html",
    "content": "<div class=\"widget-body chart-body {{ widget_extra_css_class }}\" id=\"widget-body-{{ widget_pk }}-ch-{{chart.pk}}\">\n    {% if chart.x_axis_var is not None %}<div class=\"hidden variable-config\" data-name=\"{{ chart.x_axis_var.name }}\" data-key=\"{{ chart.x_axis_var.id }}\" data-init-type=\"1\" data-type=\"variable\"></div>{% endif %}\n    <div class=\"main-chart-area\">\n        <div id=\"chart-container-{{ widget_pk }}-{{chart.pk}}\" class=\"chart-container\" style=\"width: 100%; height:100%;\"\n             data-xaxis='{\"id\":\"{% if chart.x_axis_var is not None %}{{chart.x_axis_var.id}}{% else %}False{% endif %}\",\"linlog\":\"{% if chart.x_axis_linlog %}True{% else %}False{% endif %}\"}'\n             data-xaxis-ticks='{{chart.x_axis_ticks}}'\n             data-axes0-yaxis='{\"min\":{{chart.y_axis_min}},\"max\":{{chart.y_axis_max}},\"label\":\"{{chart.y_axis_label}}\"}'\n             data-yaxis='{\"plotpoints\":\"{% if chart.show_plot_points %}True{% else %}False{% endif %}\",\"plotlines\":\"{% if chart.show_plot_lines > 0 %}True{% else %}False{% endif %}\",\n             \"steplines\":\"{% if chart.show_plot_lines == 2 %}True{% else %}False{% endif %}\",\"uniquescale\":\"{% if chart.y_axis_uniquescale %}True{% else %}False{% endif %}\"}'>\n            <div class=\"chart-placeholder\"></div>\n            <div class=\"chartTitle\">{{chart.title}}</div>\n            <div class=\"chart-zoom-bar\" >\n                <label>\n                    Zoom on axes :\n                    <input class=\"activate_zoom_x\" type=\"checkbox\" checked=\"checked\">X\n                    <input class=\"activate_zoom_y\" type=\"checkbox\" checked=\"checked\">Y\n                </label>\n            </div>\n            <div class=\"chart-btn-bar\" >\n                <div class=\"btn-group\">\n                    <button type=\"button\" class=\"btn btn-default chart-save-csv\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Export data (csv)\">\n                        <span class=\"glyphicon glyphicon-list-alt\"></span>\n                    </button>\n                    <button type=\"button\" class=\"btn btn-default chart-save-picture\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Save picture\">\n                        <span class=\"glyphicon glyphicon-picture\"></span>\n                    </button>\n                    <button type=\"button\" class=\"btn btn-default chart-ResetSelection\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Reset X and Y zooms\">\n                        <span class=\"glyphicon glyphicon-fullscreen\"></span>\n                    </button>\n                    <button type=\"button\" class=\"btn btn-default chart-ZoomYToFit\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Reset Y zoom\">\n                        <span class=\"glyphicon glyphicon-resize-vertical\"></span>\n                    </button>\n                    <button type=\"button\" class=\"btn btn-default chart-ZoomXToFit\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Reset X zoom\">\n                        <span class=\"glyphicon glyphicon-resize-horizontal\"></span>\n                    </button>\n                </div>\n            </div>\n            <div class=\"axisLabel xaxisLabel\">{{chart.x_axis_label}}</div>\n            <!--<div class=\"axisLabel yaxisLabel\">{{chart.y_axis_label}}</div>-->\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/chart_legend.html",
    "content": "<div id=\"chart-legend-{{widget_pk}}-{{chart.pk}}\" style=\"padding: 0px; position: relative; table-layout: fixed;\">\n    <div class=\"legendTitle\">{{ chart.title }}</div>\n    <div class=\"legend\">\n        <table id=\"chart-legend-table-{{widget_pk}}-{{chart.pk}}\" class=\"tablesorter\" style=\"font-size:smaller;color:#545454; width: 100%;table-layout: fixed;\">\n            <thead>\n                <tr class=\"legendSeries\">\n                    <th style=\"width:14px;\">\n                        <input type=\"checkbox\" checked=\"checked\" id=\"chart-legend-checkbox-{{widget_pk}}-{{ chart.pk }}-make_all_none\">\n                    </th>\n                    <th style=\"width:20px;\" class=\"sorter-false\"></th>\n                    <th style=\"position: inherit;\">\n                        Mark All/None\n                        <span id=\"chart-legend-options-span-{{widget_pk}}-{{chart.pk}}\"></span>\n                    </th>\n                    <th style=\"width:45px; text-align:right\"></th>\n                    <th style=\"width:35px;\"></th>\n                    <th class=\"sorter-false\" style=\"width:14px;\">\n                      <div class=\"dropdown\" style=\"display: none;\">\n                        <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" id=\"chart-legend-options-{{widget_pk}}-{{chart.pk}}\">\n                            <span class=\"glyphicon glyphicon-cog\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Chart options\" style=\"font-size:8px;vertical-align:text-top;\"></span>\n                        </a>\n                        <ul class=\"dropdown-menu\" style=\"min-width: 0px;font-size:11px;\">\n                            <li>Aggregation</li>\n                            <li class=\"divider\"></li>\n                            <li>Type <select class=\"aggregation-all-option\" id=\"aggregation-type-all-select-{{widget_pk}}-{{chart.pk}}\" data-id=\"{{ var.id }}\" data-widget-id=\"{{widget_pk}}-{{chart.pk}}\" style=\"width:66px;\">\n                                <option value=null>None</option>\n                            </select></li>\n                            <li class=\"hidden\" id=\"li-aggregation-all-period-select-{{widget_pk}}-{{chart.pk}}\">Period <select class=\"aggregation-all-option\" id=\"aggregation-all-period-select-{{widget_pk}}-{{chart.pk}}\" data-id=\"{{ var.id }}\" data-widget-id=\"{{widget_pk}}-{{chart.pk}}\" style=\"width:66px;\">\n                                <option value=null>None</option>\n                            </select></li>\n                        </ul>\n                      </div>\n                    </th>\n                </tr>\n            </thead>\n            <tbody>\n\n            {% for axe in chart.chartaxis_set.all|dictsort:\"id\" %}\n                {% include \"_chart_legend.html\" with axe=axe %}\n            {% endfor %}\n            {% if pie %}\n                {% include \"_chart_legend.html\" with axe=chart %}\n            {% endif %}\n            </tbody>\n        </table>\n    </div>\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/choose_login.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n\n\n\n{% if form.errors %}\n<p>Your username and password didn't match. Please try again.</p>\n{% endif %}\n\n{% csrf_token %}\n<p style=\"text-align: center;\">\n    <button class=\"btn btn-lg btn-primary\" type=\"button\" onclick=\"window.location.href='/accounts/login/?next=/'\">Se connecter avec Django</button>\n    <button class=\"btn btn-lg btn-primary\" type=\"button\" onclick=\"window.location.href='/accounts/CASlogin/?next=/&server_url=https://sso.univ-pau.fr/cas/&version=2'\">Se connecter avec UPPA</button>\n</p>\n</div>\n</div>\n{% endblock %}\n{% block script_bottom %}\n        $( document ).ready(function() {\n            // Handler for .ready() called.\n            $(\".loadingAnimation\").parent().hide();\n            $(\".AutoUpdateStatus\").parent().parent().hide();\n            $(\".ReadAllTask\").parent().parent().hide();\n            $(\".AutoUpdateButtonParent\").hide();\n        });\n{% endblock %}\n"
  },
  {
    "path": "pyscada/hmi/templates/circular_gauge.html",
    "content": "<div class=\"widget-body gauge-body {{ widget_extra_css_class }}\" id=\"widget-body-{{ widget_pk }}-gauge-{{ item.pk }}\">\n    <div class=\"main-chart-area\">\n        <div id=\"chart-container-{{ widget_pk }}-{{ item.pk }}\" class=\"gauge-container\" style=\"width: 100%; height:100%;\" data-id=\"{{ item.web_id }}-{{ uuid }}\" data-params='{{ item.gauge_params }}' >\n            <div class=\"chart-placeholder\"><div class=\"loading-gauge\" style=\"vertical-align: middle;\">Loading gauge...</div></div>\n            <div class=\"gauge-title\" style=\"display: none; text-align: center; font-size: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; position: relative; width: 100%; top: -14px; cursor: default;\">{{ item.label }} ({{ item.unit }})</div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/content_page.html",
    "content": "<div id=\"{{page.link_title}}\" class=\"sub-page {% if show_daterangepicker %}show_daterangepicker{% endif %} {% if show_timeline %}show_timeline{% endif %}\" style=\"display: none;\">\n    {{ widget_rows_html|safe}}\n</div>"
  },
  {
    "path": "pyscada/hmi/templates/control_element.html",
    "content": "{% if item.active == 1 %}\n        <div class=\"hidden variable-config\"\n             data-id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}\"\n             data-name=\"{{ item.name }}\"\n             data-key=\"{{ item.key }}\"\n             data-device-id={{var.device_id}}\n             data-color=\"{{ item.variable.chart_line_color.color_code }}\"\n             data-init-type=\"0\"\n             data-type=\"{{ item.item_type}}\"\n             data-min=\"{{ item.min }}\"\n             data-max=\"{{ item.max }}\"\n             data-value-class=\"{{ item.value_class }}\"\n             data-min-type=\"{{ item.min_type }}\"\n             data-max-type=\"{{ item.max_type }}\"\n             data-unit=\"{% if item.unit %}{{ item.unit }}{% endif %}\"\n             data-device-polling_interval=\"{{ item.device.polling_interval }}\"\n             data-value-timestamp=\"\"\n             data-refresh-requested-timestamp=\"\"\n             data-dictionary=\"{{ item.dictionary.dict_as_json }}\"\n        {% if item.display_value_options %}\n             data-color-type=\"{{ item.display_value_options.color_type }}\"\n             data-color-mode=\"{{ item.display_value_options.mode }}\"\n             data-level-1-type=\"{{ item.display_value_options.color_level_1_type }}\"\n             data-level-2-type=\"{{ item.display_value_options.color_level_2_type }}\"\n             data-level-1=\"{{ item.display_value_options.color_level_1 }}\"\n             data-level-2=\"{{ item.display_value_options.color_level_2 }}\"\n             data-color-1=\"{{ item.display_value_options.color_1 }}\"\n             data-color-2=\"{{ item.display_value_options.color_2 }}\"\n             data-color-3=\"{{ item.display_value_options.color_3 }}\"\n             data-timestamp-conversion=\"{{ item.display_value_options.timestamp_conversion }}\"\n         {% endif %}\n        ></div>\n    <!-- {{ item.display_value_options }} - {{ item.display_value_options.template }} - {{ item.display_value_options.template.template_name }} -->\n    {% if item.display_value_options and item.display_value_options.template and item.display_value_options.template.template_name %}<!-- custom template {{ item.display_value_options.template.template }} -->\n        {% include item.display_value_options.template.get_template_name %}\n    {% elif item.type == 0 %}<!-- control element -->\n        {% if item.value_class == 'BOOL' or item.value_class == 'BOOLEAN' %}\n            <!-- input button -->\n            {% include \"button.html\" %}\n        {% elif item.control_element_options.dropdown and item.dictionary %}<!-- dropdown -->\n            {% include \"dropdown.html\" with last_value=item.value %}\n        {% else %}\n            <!-- input field -->\n            {% include \"value_field.html\" %}\n        {% endif %}\n    {% elif item.type == 1 %}<!-- display value -->\n        {% if not item.display_value_options %}\n            <!-- No display options -->\n            <!-- Value only -->\n            {% if item.value_class == 'BOOL' or item.value_class == 'BOOLEAN' %}\n                <!-- boolean display -->\n                {% include \"button.html\" %}\n            {% else %}\n                {% include \"value_field.html\" %}\n            {% endif %}\n        {% else %}\n            <!-- Display options -->\n            {% if item.value_class == 'BOOL' or item.value_class == 'BOOLEAN' or item.display_value_options.color_only == 1 %}\n                <!-- boolean display -->\n                {% include \"button.html\" %}\n            {% else %}\n                {% include \"value_field.html\" %}\n            {% endif %}\n        {% endif %}\n    {% else %}\n        <!-- else 3 -->\n        Control item type is wrong : {{ item.type }} !\n    {% endif %}\n{% endif %}\n"
  },
  {
    "path": "pyscada/hmi/templates/control_panel.html",
    "content": "<div class=\"panel-body\" >\n    <div class=\"control-panel\">\n        <div class=\"panel-title\">{{ control_panel.title|safe }}</div>\n        {% for item in control_panel.items.all %}\n            {% if item.pk in visible_control_element_list %}\n                {% include \"control_element.html\" %}\n            {% endif %}\n        {% endfor %}\n        {% for form in control_panel.forms.all %}\n            {% if form.pk in visible_form_list %}\n                {% include \"form.html\" %}\n            {% endif %}\n        {% endfor %}\n    </div>\n</div>"
  },
  {
    "path": "pyscada/hmi/templates/custom_html_panel.html",
    "content": "<div class=\"widget-body panel-body {{ widget_extra_css_class }}\" >\n<!--  -->\n{% for var in custom_html_panel.variables.all %}\n    {% if var.active and var.device.active %}\n        <div class=\"hidden variable-config\" data-name=\"{{ var.name }}\" data-key=\"{{ var.id }}\" data-color=\"{{ var.chart_line_color_code }}\" data-init-type=\"0\" data-type=\"{{ var.item_type }}\" data-min=\"{{ var.value_min }}\" data-max=\"{{ var.value_max }}\" data-value-class=\"{{ var.value_class }}\"  data-min-type=\"{{ var.min_type }}\" data-max-type=\"{{ var.max_type }}\"></div>\n    {% endif %}\n{% endfor %}\n{% for var in custom_html_panel.variable_properties.all %}\n    {% if var.variable.active and var.variable.device.active %}\n        <div class=\"hidden variable-config\" data-name=\"{{ var.name }}\" data-key=\"{{ var.id }}\" data-unit=\"{% if var.unit %}{{ var.unit }}{% endif %}\" data-color=\"{{ var.variable.chart_line_color_code }}\" data-init-type=\"0\" data-type=\"{{ var.item_type }}\" data-min=\"{{ var.value_min }}\" data-max=\"{{ var.value_max }}\" data-value-class=\"{{ var.value_class }}\"  data-min-type=\"{{ var.min_type }}\" data-max-type=\"{{ var.max_type }}\"></div>\n    {% endif %}\n{% endfor %}\n{{ custom_html_panel.html|safe }}\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/dropdown.html",
    "content": "<!-- Dropdown {{ dropdown.name }} -->\n{% if item.active == 1 %}\n    <div class=\"hidden variable-config\" data-id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}\" data-name=\"{{ item.name }}\" data-key=\"{{ item.key }}\" data-init-type=\"0\" data-type=\"{{ item.item_type}}\" data-min=\"{{ item.min }}\" data-max=\"{{ item.max }}\" data-value-class=\"{{ item.value_class }}\"  data-min-type=\"{{ item.min_type }}\" data-max-type=\"{{ item.max_type }}\"></div>\n    <div class=\"input-group set_value\">\n        <span class=\"input-group-addon input-group-addon-label\" style=\"width:40%;height:34px;\">{{ item.label }}</span>\n        <span class=\"input-group-addon input-group-addon-label\" style=\"{% if item.unit and not form %}width:40%;{% elif item.unit or not form %}width: 50%;{% else %}width:60%;{% endif %}padding:0;border:0;\">\n            <select class=\"form-control select {{ item.web_class_str }}\" id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}-value\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type }}\" data-name=\"{{ item.name }}\">\n                {% if item.control_element_options.empty_dropdown_value == 1 %}\n                    <option disabled selected value> {{ item.control_element_options.placeholder }} </option>\n                {% endif %}\n                {% for dict_item in item.dictionary.dictionaryitem_set.all %}\n                    <option {% if item.control_element_options.empty_dropdown_value == 0 and last_value|floatformat:5 == dict_item.value|floatformat:5 %} selected {% endif %} class=\"select-item {{ item.web_class_str }}\" id=\"{{ dict_item.pk }}\" data-value=\"{{ dict_item.value }}\" value=\"{{ dict_item.value }}\">{{ dict_item.label }}</option>\n                {% endfor %}\n                </select>\n            </span>\n        {% if item.unit %}\n            <span class=\"input-group-addon\" style=\"width: 10%;display: inline-block;height: 34px;\">{{ item.unit }}</span>\n        {% endif %}\n        {% if not form %}\n            <span class=\"input-group-btn\" style=\"display:inline-block;width:10%;\">\n                <button class=\"btn btn-default write-task-set {{ item.web_class_str }}\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type}}\" id=\"{{ item.web_id }}-{{ uuid }}\" type=\"button\" style=\"width:100%;height:34px;padding-top:0;padding-bottom:0;\">set</button>\n            </span>\n        {% endif %}\n    </div>\n{% endif %}"
  },
  {
    "path": "pyscada/hmi/templates/form.html",
    "content": "<form name=\"{{ form.title }}\" class=\"form\" id=\"{{ form.web_id }}-{{ uuid }}\" style=\"width:100%;\">\n    {% for item in form.hidden_control_items_to_true.all %}\n        {% if item.active == 1 %}\n            {% if item.type == 0 %}<!-- hidden form input {{ item.key }} -->\n                <div class=\"hidden variable-config\" data-id=\"{{ form.web_id }}-{{ item.web_id }}-{{ uuid }}\" data-name=\"{{ item.name }}\" data-key=\"{{ item.key }}\" data-init-type=\"0\" data-type=\"{{ item.item_type}}\" data-min=\"{{ item.min }}\" data-max=\"{{ item.max }}\" data-min-type=\"{{ item.min_type }}\" data-max-type=\"{{ item.max_type }}\"></div>\n                <input type=\"hidden\" class=\"form-control {{ item.web_class_str }}\" name=\"{{ item.name }}\" id=\"{{ form.web_id }}-{{ item.web_id }}-{{ uuid }}-value\" placeholder=\"\" value=\"1\">\n            {% endif %}\n        {% endif %}\n    {% endfor %}\n    {% for item in form.control_items.all %}\n        {% if item.active == 1 and item.pk in visible_control_element_list %}\n            {% if item.type == 0 %}<!-- form input {{ item.key }} -->\n                {% include \"control_element.html\" with form=form %}\n            {% endif %}\n        {% endif %}\n    {% endfor %}\n\n    <div class=\"input-group set_value\">\n        <span class=\"input-group-btn\">\n            <button class=\"btn btn-primary write-task-form-set set-{{ form.pk }}\" type=\"button\" id=\"{{ form.web_id }}-{{ uuid }}-button\">{{ form.button }}</button>\n        </span>\n    </div>\n</form>"
  },
  {
    "path": "pyscada/hmi/templates/login.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n\n{{ request.GET.message }}\n\n{% if form.errors %}\n<p>Your username and password didn't match. Please try again.</p>\n{% endif %}\n\n<form class=\"form-signin\" method=\"post\" action=\"{% url 'login_view' %}\">\n{% csrf_token %}\n<h2 class=\"form-signin-heading\">Please sign in</h2>\n<input type=\"text\" class=\"form-control\" placeholder=\"{{ form.username.label }}\" name=\"username\" required=\"\" autofocus=\"\" id=\"{{ form.username.id_for_label }}\">\n<input type=\"password\" class=\"form-control\" placeholder=\"{{ form.password.label }}\" name=\"password\" required=\"\" id=\"{{ form.password.id_for_label }}\">\n<button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n<input type=\"hidden\" name=\"next\" value=\"{{ next }}\" />\n</form>\n</div>\n</div>\n{% endblock %}\n{% block script_bottom %}\n        $( document ).ready(function() {\n            // Handler for .ready() called.\n            $(\".loadingAnimation\").parent().hide();\n            $(\".AutoUpdateStatus\").parent().parent().hide();\n            $(\".ReadAllTask\").parent().parent().hide();\n            $(\".AutoUpdateButtonParent\").hide();\n        });\n{% endblock %}\n"
  },
  {
    "path": "pyscada/hmi/templates/modelProperties.html",
    "content": "{% load views_extras %}\n<div class=\"hidden {{ modelName }}-config2\" {% for field in fields %} data-{{ field.name|replace:'_|-' }}=\"{{ field.value }}\"{% endfor %}>\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/password_change.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n\n\n\n<form class=\"form-signin\" method=\"post\" action=\"\">\n{% csrf_token %}\n<h2 class=\"form-signin-heading\">Change your user profile</h2>\n{% if form.errors %}\n<div class=\"form-group has-error has-feedback\">\n\t<label class=\"control-label\" for=\"old_password\">\n\t\t<p>Your old password was entered incorrectly. </p>\n\t\t<p>Please enter it again.</p>\n\t</label>\n\t<input type=\"password\" class=\"form-control\" placeholder=\"{{ form.old_password.label }}\" name=\"old_password\" required=\"\" autofocus=\"\" id=\"{{ form.old_password.id_for_label }}\">\n\t<span class=\"glyphicon glyphicon-remove form-control-feedback\"></span>\n</div>\n{% else %}\n<input type=\"password\" class=\"form-control\" placeholder=\"{{ form.old_password.label }}\" name=\"old_password\" required=\"\" autofocus=\"\" id=\"{{ form.old_password.id_for_label }}\">\n{% endif %}\n<input type=\"password\" class=\"form-control\" placeholder=\"{{ form.new_password1.label }}\" name=\"new_password1\" required=\"\" id=\"{{ form.new_password1.id_for_label }}\">\n<input type=\"password\" class=\"form-control\" placeholder=\"{{ form.new_password2.label }}\" name=\"new_password2\" required=\"\" id=\"{{ form.new_password2.id_for_label }}\">\n<button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Change</button>\n<input type=\"hidden\" name=\"next\" value=\"{{ next }}\" />\n</form>\n{% endblock %}\n"
  },
  {
    "path": "pyscada/hmi/templates/password_change_done.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n\n<p>Password change successful</p>\n\n{% endblock %}\n"
  },
  {
    "path": "pyscada/hmi/templates/pie.html",
    "content": "<div class=\"widget-body pie-body {{ widget_extra_css_class }}\" id=\"widget-body-{{ widget_pk }}-pie-{{pie.pk}}\">\n    <div class=\"main-chart-area\">\n        <div id=\"chart-container-{{ widget_pk }}-{{pie.pk}}\" class=\"pie-container\" style=\"width: 100%; height:100%;\" data-radius='{\"radius\":\"{{pie.radius}}\",\"innerRadius\":\"{{pie.innerRadius}}\"}' >\n            <div class=\"chartTitle\">{{pie.title}}</div>\n            <div class=\"chart-placeholder\"></div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/process_flow_diagram.html",
    "content": "<div class=\"widget-body process-flow-diagram-body {{ widget_extra_css_class }}\" >\n    {% for item in process_flow_diagram.process_flow_diagram_items.all %}\n        <div class=\"hidden variable-config\" data-name=\"{{item.control_item.name}}\" data-device-id={{item.control_item.device.id}} data-key=\"{{item.control_item.key}}\" data-init-type=\"1\" data-type=\"variable\" data-device-polling_interval=\"{{ item.control_item.device.polling_interval }}\"></div>\n    {% endfor %}\n    <!--<div class=\"panel panel-primary\" style=\"height: {{ process_flow_diagram.background_image.height|add:\"68\" }}px; width: {{ process_flow_diagram.background_image.width|add:\"30\" }}px; margin-left:15px;\">-->\n    <div class=\"panel panel-primary\">\n    {% if process_flow_diagram.title %}\n        <div class=\"panel-heading\">\n            <h3 class=\"panel-title\">{{ process_flow_diagram.title }}</h3>\n        </div>\n    {% endif %}\n    {% if process_flow_diagram.type == 0 %}\n        <div class=\"panel-body\" style=\"height:inherit;\">\n          <img src=\"{{ process_flow_diagram.background_image.url }}\" style=\"width: 100%;\" alt=\"process flow diagram\">\n        </div>\n        {% for item in process_flow_diagram.process_flow_diagram_items.all %}\n            <div style=\"width: {{ item.width }}px; height: {{ item.height }}px; top: {{ item.top|add:38.6 }}px; left: {{ item.left }}px; position:absolute;\tfont-size: 12px;\" class=\"control-panel\">\n              {% include \"control_element.html\" with item=item.control_item %}\n            </div>\n        {% endfor %}\n\n    {% elif process_flow_diagram.type == 1 %}\n        <div class=\"panel-body\" style=\"height:inherit;\n            display: inline-block;\n            position: relative;\n            width: 100%;\n            padding-bottom: {{ height_width_ratio }}%;\n            padding-top: 0px;\n            vertical-align: middle;\n            overflow: hidden;\">\n            <!--<img src=\"{{ process_flow_diagram.background_image.url }}\" style=\"width: 100%;\" alt=\"process flow diagram\">-->\n            <svg version=\"1.1\" viewBox=\"0 0 {{ process_flow_diagram.url_width }} {{ process_flow_diagram.url_height }}\"\n            preserveAspectRatio=\"xMinYMin meet\"\n            class=\"svg-content\"\n            style=\"display: inline-block;\n            position: absolute;\n            top: 0;\n            left: 0;\">\n                <image xlink:href=\"{{ process_flow_diagram.background_image.url }}\" width=100% />\n                {% for item in process_flow_diagram.process_flow_diagram_items.all %}\n                    <!--\n                    <text x=\"{{ item.left }}\" y=\"{{ item.top }}\" fill=\"red\" font-size=\"{{ item.font_size }}\">\n                        {{ item.control_item.label }}\n                    </text>\n                    -->\n                    <text x=\"{{ item.left }}\" y=\"{{ item.top }}\" fill=\"black\" font-size=\"{{ item.font_size }}\"\n                    class=\"control-item type-numeric process-flow-diagram-item {{ item.control_item.web_class_str }}\"\n                    id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.control_item.web_id }}-{{ uuid }}\">\n                        Loading...\n                    </text>\n                {% endfor %}\n            </svg>\n        </div>\n    {% endif %}\n    </div>\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/status_element.html",
    "content": "{% if item.active == 1 %}\n\t<li>\n\t\t<div class=\"hidden variable-config\"\n\t\tdata-id=\"{{ item.web_id }}-{{ uuid }}\"\n\t\tdata-name=\"{{ item.name }}\"\n\t\tdata-key=\"{{ item.key }}\" \n\t\tdata-color=\"{{ var.chart_line_color_code }}\"\n\t\tdata-init-type=\"0\"\n\t\tdata-type=\"{{ item.item_type}}\"\n\t\tdata-min=\"{{ item.min }}\"\n\t\tdata-max=\"{{ item.max }}\"\n\t\tdata-value-class=\"{{ item.value_class }}\"\n\t\tdata-min-type=\"{{ item.min_type }}\"\n\t\tdata-max-type=\"{{ item.max_type }}\"\n\t\tdata-unit=\"{% if item.unit %}{{ item.unit }}{% endif %}\"\n\t\tdata-device-polling_interval=\"{{ item.variable.device.polling_interval }}\"\n\t\tdata-dictionary=\"{{ item.dictionary.dict_as_json }}\"\n        {% if item.display_value_options %}\n\t\t\t data-color-type=\"{{ item.display_value_options.color_type }}\"\n\t\t\t data-color-mode=\"{{ item.display_value_options.mode }}\"\n\t\t\t data-level-1-type=\"{{ item.display_value_options.color_level_1_type }}\"\n\t\t\t data-level-2-type=\"{{ item.display_value_options.color_level_2_type }}\"\n\t\t\t data-level-1=\"{{ item.display_value_options.color_level_1 }}\"\n\t\t\t data-level-2=\"{{ item.display_value_options.color_level_2 }}\"\n\t\t\t data-color-1=\"{{ item.display_value_options.color_1 }}\"\n\t\t\t data-color-2=\"{{ item.display_value_options.color_2 }}\"\n\t\t\t data-color-3=\"{{ item.display_value_options.color_3 }}\"\n\t\t\t data-timestamp-conversion=\"{{ item.display_value_options.timestamp_conversion }}\"\n         {% endif %}\n        ></div>\n\t\t{% if item.type == 1 %}<!-- display value -->\n               <span class=\"label label-default type-label\">{{ item.label }}: <span class=\"type-unit\"><span class=\"control-item type-numeric var-{{ item.variable.pk }}\">Loading...</span> {{ item.unit }}</span></span>\n    {% endif %}\n\t</li>\n{% endif %}\n"
  },
  {
    "path": "pyscada/hmi/templates/svg_loading_icon.html",
    "content": "<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->\n<svg fill=\"#{{ svg_loading_color|default:\"000\" }}\" width=\"38\" height=\"38\" viewBox=\"0 0 38 38\" xmlns=\"http://www.w3.org/2000/svg\">\n  <defs>\n      <linearGradient x1=\"8.042%\" y1=\"0%\" x2=\"65.682%\" y2=\"23.865%\" id=\"a\">\n          <stop stop-color=\"#{{ svg_loading_color|default:\"000\" }}\" stop-opacity=\"0\" offset=\"0%\"/>\n          <stop stop-color=\"#{{ svg_loading_color|default:\"000\" }}\" stop-opacity=\".631\" offset=\"63.146%\"/>\n          <stop stop-color=\"#{{ svg_loading_color|default:\"000\" }}\" offset=\"100%\"/>\n      </linearGradient>\n  </defs>\n  <g fill=\"none\" fill-rule=\"evenodd\">\n      <g transform=\"translate(1 1)\">\n          <path d=\"M36 18c0-9.94-8.06-18-18-18\" id=\"Oval-2\" stroke=\"url(#a)\" stroke-width=\"2\">\n              <animateTransform\n                  attributeName=\"transform\"\n                  type=\"rotate\"\n                  from=\"0 18 18\"\n                  to=\"360 18 18\"\n                  dur=\"0.9s\"\n                  repeatCount=\"indefinite\" />\n          </path>\n          <circle fill=\"#{{ svg_loading_color|default:\"000\" }}\" cx=\"36\" cy=\"18\" r=\"1\">\n              <animateTransform\n                  attributeName=\"transform\"\n                  type=\"rotate\"\n                  from=\"0 18 18\"\n                  to=\"360 18 18\"\n                  dur=\"0.9s\"\n                  repeatCount=\"indefinite\" />\n          </circle>\n      </g>\n  </g>\n</svg>\n"
  },
  {
    "path": "pyscada/hmi/templates/template_not_found.html",
    "content": "Template not found.\n"
  },
  {
    "path": "pyscada/hmi/templates/user_dropdown.html",
    "content": "{% if user.pk %}\n    <li class=\"dropdown\">\n        <!-- User -->\n        <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\"><span class=\"glyphicon  glyphicon-user\"></span>\n            {% if user.first_name %}\n                {{ user.first_name }} {{ user.last_name }}\n            {% else %}\n                {{ user.username }}\n            {% endif %}\n            <b class=\"caret\"></b></a>\n        <ul class=\"dropdown-menu\" style=\"min-width: 0px;\">\n            {% if user.is_staff %}\n                <li>\n                    <a href=\"{% url 'pyscada_admin:index' %}\" target=\"{{ link_target }}\">Admin</a>\n                </li>\n                <li class=\"divider\"></li>\n            {% endif %}\n            <li role=\"presentation\" class=\"disabled\">\n                <a role=\"menuitem\" tabindex=\"-1\" href=\"#\">{{ user.first_name }} {{ user.last_name }}</a>\n            </li>\n            <li role=\"presentation\" class=\"disabled\">\n                <a role=\"menuitem\" tabindex=\"-1\" href=\"#\">{{ user.email }}</a>\n            </li>\n            <li role=\"presentation\" class=\"disabled\">\n                <a role=\"menuitem\" tabindex=\"-1\" href=\"#\">change user info</a>\n            </li>\n            <li class=\"divider\"></li>\n            <li role=\"presentation\" class=\"refresh-rate-li hidden\">\n                <div style=\"padding: 3px 20px;\">\n                    <span>Data refresh rate:</span>\n                    <span class=\"refresh-rate-output\"></span>\n                    <span>ms</span>\n                    <input class=\"refresh-rate-input\"\n                           type=\"range\"\n                           min=\"100\"\n                           max=\"5000\"\n                           step=\"100\"\n                           value=\"2500\">\n                </div>\n            </li>\n            <li class=\"refresh-rate-divider divider hidden\"></li>\n            <li>\n                <a href=\"/accounts/password_change\" target=\"{{ link_target }}\">Change password</a>\n            </li>\n            <li class=\"divider\"></li>\n            <li><a><form action=\"/accounts/logout/\" method=\"post\">\n                {% csrf_token %}\n                <input type=\"submit\" name=\"Logout\" value=\"Logout\" style=\"border: none; background: none; padding: 0;\" />\n            </form></a></li>\n        </ul>\n    </li>\n{% endif %}\n"
  },
  {
    "path": "pyscada/hmi/templates/user_profile_change.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n\n\n\n{% if form.errors %}\n{% endif %}\n\n<form class=\"form-signin\" method=\"post\" action=\"\">\n{% csrf_token %}\n<h2 class=\"form-signin-heading\">Change your user profile</h2>\n<div class=\"form-group\">\n\t<label for=\"username\">username</label>\n\t<input type=\"text\" class=\"form-control\" placeholder=\"{{ form.username.label }}\" name=\"username\" required=\"\" autofocus=\"\" value=\"{{user.username}}\" id=\"{{ form.username.id_for_label }}\">\n</div>\n<div class=\"form-group\">\n\t<label for=\"first_name\">first name</label>\n\t<input type=\"text\" class=\"form-control\" placeholder=\"{{ form.first_name.label }}\" name=\"first_name\" required=\"\" value=\"{{user.first_name}} id=\"{{ form.first_name.id_for_label }}\">\n</div>\n<div class=\"form-group\">\n\t<label for=\"last_name\">last name</label>\n\t<input type=\"text\" class=\"form-control\" placeholder=\"{{ form.last_name.label }}\" name=\"last_name\" required=\"\" value=\"{{user.last_name}} id=\"{{ form.last_name.id_for_label }}\">\n</div>\n\n<div class=\"form-group\">\n\t<label for=\"email\">email</label>\n\t<input type=\"text\" class=\"form-control\" placeholder=\"{{ form.email.label }}\" name=\"email\" required=\"\" value=\"{{user.email}} id=\"{{ form.email.id_for_label }}\">\n</div>\n\n<button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Change</button>\n<input type=\"hidden\" name=\"next\" value=\"{{ next }}\" />\n</form>\n{% endblock %}\n{% block script_bottom %}\n        $( document ).ready(function() {\n            // Handler for .ready() called.\n            $(\".loadingAnimation\").parent().hide();\n            $(\".AutoUpdateStatus\").parent().parent().hide();\n            $(\".ReadAllTask\").parent().parent().hide();\n            $(\".AutoUpdateButtonParent\").hide();\n        });\n{% endblock %}\n"
  },
  {
    "path": "pyscada/hmi/templates/value_field.html",
    "content": "<div class=\"input-group set_value\">\n    {% if item.type == 0 %}<!-- input value -->\n        <span class=\"input-group-addon input-group-addon-label input-group-addon-label-left\" style=\"width:40%;height:34px;\">{{ item.label }}</span>\n        <span class=\"input-group-addon input-group-addon-label input-group-addon-label-right\" style=\"{% if item.unit and not form %}width:40%;{% elif item.unit or not form %}width: 50%;{% else %}width:60%;{% endif %}padding:0;border:0;\">\n            <input type=\"text\" class=\"form-control {{ item.web_class_str }}\" id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}-value\" placeholder=\"{% if item.control_element_options is not None %}{{ item.control_element_options.placeholder }}{% endif %}\">\n        </span>\n        {% if item.unit %}<span class=\"input-group-addon\" style=\"width: 10%;display: inline-block;height: 34px;\">{{ item.unit }}</span>{% endif %}\n        {% if not form %}\n            <span class=\"input-group-btn\" style=\"display:inline-block;width:10%;\"><!-- display value -->\n                <button class=\"btn btn-default write-task-set {{ item.web_class_str }}\" id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type}}\" type=\"button\" style=\"width:100%;height:34px;padding-top:0;padding-bottom:0;\">set</button>\n            </span>\n        {% endif %}\n    {% elif item.type == 1 %}<!-- display value -->\n        <span class=\"input-group-addon input-group-addon-label\">\n            {% if item.readable %}\n                <button class=\"btn btn-default read-task-set {{ item.web_class_str }}\" type=\"button\" data-key=\"{{ item.key }}\" data-type=\"{{ item.item_type}}\" style=\"width:16px;height:16px;padding:0;margin:0;\">\n                    <span class=\"glyphicon glyphicon-refresh\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Refresh data\" style=\"font-size:10px;vertical-align:text-top;\"></span>\n            </button>\n            {% endif %}\n            <span style=\"color:red;height:12px;\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Very old value (>10 polling interval)\" class=\"glyphicon glyphicon-alert hidden\"></span>\n            <span style=\"color:orange;height:12px;\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Old value (between 3 and 10 polling interval)\" class=\"glyphicon glyphicon-exclamation-sign hidden\"></span>\n            {{ item.label }}\n        </span>\n        <span class=\"input-group-addon input-group-addon-label control-item type-numeric {{ item.web_class_str }}\" id=\"{% if form %}{{ form.web_id }}-{% endif %}{{ item.web_id }}-{{ uuid }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"\" data-html=\"true\">Loading...</span>\n    {% endif %}\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templates/view.html",
    "content": "{% extends base_html %}\n{% load i18n static %}\n\n{% block title %}{{ view_title }} - PyScada Control Center{% endblock %}\n\n{% block head_css %}\n{% for css_file in css_files_list %}\n    <link href={{ css_file.src }} rel=\"stylesheet\"/>\n{% endfor %}\n{% endblock %}\n\n{% block body_confic_data %} data-data-file=\"json/cache_data/\" data-view-title=\"{{ view_link_title }}\" data-view-id=\"{{ view_object.id }}\" data-view-time-delta=\"{{ view_time_delta }}\" {% endblock %}\n\n{% block top_menu_left %}\n    {% for page in page_list %}\n        <li><a href=\"#{{page.link_title}}\" class=\"hidden-xs\">{{page.title}}</a></li>\n    {% endfor %}\n{% endblock %}\n\n{% block top_menu_left_collapsed %}\n    {% for page in page_list %}\n        <li><a href=\"#{{page.link_title}}\" class=\"navbar-toggle visible-xs\" data-toggle=\"collapse\" data-target=\".navbar-collapse\" style=\"float:left;\">{{page.title}}</a></li>\n    {% endfor %}\n{% endblock %}\n\n{% block top_menu_right %}\n    <li><a id='page-load-label'>Loading... </a></li>\n    <li><meter id='page-load-state' value=\"0\" min=\"0\" max=\"100\" style=\"margin-top: 11px;height: 2em;\">0</meter></li>\n    {% for panel in control_list %}\n        {% if panel.visible %}\n            <li class=\"dropdown\"><!-- {{ panel.title|upper }} -->\n                <a class=\"dropdown-toggle\" href=\"#\" data-toggle=\"dropdown\"><span class=\"glyphicon glyphicon-wrench\"></span> {{ panel.title }}<strong class=\"caret\"></strong></a>\n                <div class=\"dropdown-menu\" style=\"padding: 15px; width:715px;\">\n                    <div class=\"control-panel\">\n                        {% for item in panel.control_panel.items.all %}\n                            {% if item.pk in visible_control_element_list %}\n                                {% include \"control_element.html\" with item=item %}\n                            {% endif %}\n                        {% endfor %}\n                        {% for form in panel.control_panel.forms.all %}\n                            {% if form.pk in visible_form_list %}\n                                {% include \"form.html\" with form=form %}\n                            {% endif %}\n                        {% endfor %}\n                    </div>\n                </div>\n            </li>\n        {% endif %}\n    {% endfor %}\n        <li class=\"dropdown\"><!-- Date range picker -->\n            <a class=\"daterangepicker_parent hidden\" id=\"daterange\" style=\"padding-top: 15px; cursor: pointer; width: 100%\">\n                <i class=\"glyphicon glyphicon-calendar\"></i>&nbsp;\n                <span>Date range picker is loading. Please wait...</span> <strong class=\"caret\"></strong>\n            </a>\n        </li>\n\n        <li class=\"dropdown\"><!-- Charts timeline -->\n            <a class=\"dropdown-toggle timeline hidden\" id=\"timeline_slider\" href=\"#\" data-toggle=\"dropdown\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\"><span class=\"glyphicon glyphicon-resize-horizontal\"></span> Timeline slider <strong class=\"caret\"></strong></a>\n            <div class=\"dropdown-menu\" style=\"padding: 15px; width: 50vw;\">\n                <div class='col-md-12 col-sm-12' data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Display data filter\">\n                    <div id=\"timeline-border\" class=\"form-group\" style=\"height:34px; background-color: #9d9d9d;\">\n                        <div id=\"timeline\" class=\"ui-widget-content ui-resizable ui-draggable ui-draggable-handle\" style=\"background-color: #333; width: 100%; height: 34px; left: 0px; top: 0px;\">\n                            <span class=\"pull-left glyphicon glyphicon-triangle-left\" style=\"line-height:30px;color:#9d9d9d;\" aria-hidden=\"true\"></span>\n                            <span class=\"pull-right glyphicon glyphicon-triangle-right\" style=\"line-height:30px;color:#9d9d9d;\" aria-hidden=\"true\"></span>\n                            <span id=\"timeline-time-from-label\" class=\"pull-left\" style=\"padding-left:3px; line-height:34px; color:#9d9d9d\"> </span>\n                            <span id=\"timeline-time-to-label\" class=\"pull-right\" style=\"padding-right:3px; line-height:34px; color:#9d9d9d\"> </span>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </li>\n{% endblock %}\n\n{% block content %}\n    {{pages_html|safe}}\n    <div id=\"page-log\" class=\"sub-page\" style=\"display: none;\">\n        <table id=\"log-table\" class=\"table table-condensed table-hover tablesorter tablesorter-default\" width=\"100%\">\n            <colgroup>\n                <col style=\"width: 180px;\">\n                <col style=\"width: 60px;\">\n                <col>\n            </colgroup>\n            <thead>\n                <tr>\n                    <th>Date</th>\n                    <th>Level</th>\n                    <th>Message</th>\n                </tr>\n            </thead>\n            <tbody></tbody>\n        </table>\n    </div> <!-- end page-log -->\n    {% block loading_page %}\n    <!-- start page-loading -->\n    <div id=\"page-loading\" class=\"sub-page\" style=\"display: flex; align-items: center; justify-content: center;\">\n          {% include \"svg_loading_icon.html\" %}\n    </div> <!-- end page-loading -->\n    {% endblock loading_page %}\n    {% for panel in panel_list %}\n        <div class=\"side-menu  {% if panel.position == 1 %}left{% elif panel.position == 2 %}right{% endif %}\">\n            <ul class=\"status-panel\">\n                <li>{{ panel.title }}</li>\n                {% for item in panel.control_panel.items.all %}\n                    {% if item.pk in visible_control_element_list %}\n                        {% include \"status_element.html\" with item=item %}\n                    {% endif %}\n                {% endfor %}\n            </ul>\n        </div>\n    {% endfor %}\n{% endblock %}\n\n{% block include_bottom %}\n    {% for js_file in javascript_files_list %}\n        <script language=\"javascript\" type=\"text/javascript\" src={{ js_file.src }}></script>\n    {% endfor %}\n{% endblock %}\n\n{% for file in include %}\n    {% include file %}\n{% endfor %}\n"
  },
  {
    "path": "pyscada/hmi/templates/view_overview.html",
    "content": "{% extends \"base.html\" %}\n{% load i18n static %}\n\n{% block content %}\n<div class=\"row\">\n    {% for view in view_list %}\n        {% if view.visible %}\n            {% with link_url=view.url %}\n                {% if view.link_title %}\n                    {% url 'main-view' view.link_title as link_url %}\n                {% endif %}\n        <div class=\"col-sm-3\">\n        <div class=\"panel panel-default\">\n                <div class=\"panel-heading\">\n                    <h3 class=\"panel-title\"><a href=\"{{ link_url }}\" target=\"{{ link_target }}\" >{{ view.title }}</a></h3>\n                </div>\n                <div class=\"panel-body\">\n                {% if view.logo %}\n                <a href=\"{{ link_url }}\" class=\"thumbnail\" target=\"{{ link_target }}\" ><img src=\"{{ view.logo.url }}\" alt=\"{{ view.description }}\"  width=\"100%\" ></a>\n                {% else %}\n                <a href=\"{{ link_url }}\" class=\"thumbnail\" target=\"{{ link_target }}\" ><p style=\"min-height: 120px;\">{{ view.description }}</p></a>\n                {% endif %}\n            </div>\n           </div>\n        </div>\n            {% endwith %}\n        {% endif %}\n        {% if forloop.counter|divisibleby:4 %}\n    </div><div class=\"row\">\n        {% endif %}\n    {% endfor %}\n</div>\n{% endblock %}\n{% block script_bottom %}\n        $( document ).ready(function() {\n            // Handler for .ready() called.\n           $(\".loadingAnimation\").parent().hide();\n           $(\".AutoUpdateStatus\").parent().parent().hide();\n           $(\".ReadAllTask\").parent().parent().hide();\n           $(\".AutoUpdateButtonParent\").hide();\n        });\n{% endblock %}\n"
  },
  {
    "path": "pyscada/hmi/templates/widget_row.html",
    "content": "<div class=\"row\"> <!-- Widget Row {{ row }}-->\n{%if sidebar_visible and not topbar %}\n    <div class=\"legend-sidebar col-xs-12 col-sm-12 col-md-3 col-lg-2\">\n    {% for content in sidebar_content %}\n            {{ content.html|safe }}\n            <div class=\"clear-sep\"></div>\n    {% endfor %}\n    </div>\n    <div class=\"col-xs-12 col-sm-12 col-md-9 {% if topbar %}col-lg-12{% else %}col-lg-10{% endif %}\">\n        <div class=\"row\">\n{% endif %}\n{% for content in main_content %}\n    <div id=\"widget_{{content.widget.pk}}\" class=\"widget {{ content.widget.css_class }} {% if content.widget.extra_css_class %}{{ content.widget.extra_css_class }}{% endif %}\"> <!-- {{ content.widget.title }} -->\n        {% if topbar and content.topbar is not None %}{{ content.topbar|safe }}{% endif %}\n        {{ content.html|safe }}\n    </div>\n{% endfor %}\n{%if sidebar_visible and not topbar %}\n        </div>\n    </div>\n{% endif %}\n</div>\n"
  },
  {
    "path": "pyscada/hmi/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/hmi/templatetags/views_extras.py",
    "content": "from django import template\n\nregister = template.Library()\n\n\n@register.filter\ndef replace(value, arg):\n    \"\"\"\n    Replacing filter\n    Use `{{ \"aaa\"|replace:\"a|b\" }}`\n    \"\"\"\n    if len(arg.split(\"|\")) != 2:\n        return value\n\n    what, to = arg.split(\"|\")\n    return value.replace(what, to)\n"
  },
  {
    "path": "pyscada/hmi/urls.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.urls import path\nfrom . import views\nfrom pyscada.admin import admin_site\nfrom django.contrib.auth import views as auth_views\n\nurlpatterns = [\n    # Public pages\n    path(\"\", views.index, name=\"view-overview\"),\n    path(\"pyscada_admin/\", admin_site.urls),\n    path(\"accounts/logout/\", auth_views.LogoutView.as_view()),\n    path(\n        \"accounts/login/\",\n        auth_views.LoginView.as_view(template_name=\"login.html\"),\n        name=\"login_view\",\n    ),\n    path(\n        \"accounts/choose_login/\",\n        auth_views.LoginView.as_view(template_name=\"choose_login.html\"),\n        name=\"choose_login_view\",\n    ),\n    path(\n        \"accounts/password_change/\",\n        auth_views.PasswordChangeView.as_view(template_name=\"password_change.html\"),\n        name=\"password_change\",\n    ),\n    path(\n        \"accounts/password_change_done/\",\n        auth_views.PasswordChangeView.as_view(\n            template_name=\"password_change_done.html\"\n        ),\n        name=\"password_change_done\",\n    ),\n    path(\"json/cache_data/\", views.get_cache_data),\n    path(\"json/log_data/\", views.log_data),\n    path(\"form/write_task/\", views.form_write_task),\n    path(\"form/read_task/\", views.form_read_task),\n    path(\"form/read_all_task/\", views.form_read_all_task),\n    path(\"view/<link_title>/\", views.view, name=\"main-view\"),\n    path(\n        \"getHiddenConfig2/<link_title>/\",\n        views.get_hidden_config2,\n        name=\"get-hidden-config2\",\n    ),\n    path(\"view-overview/\", views.view_overview, name=\"view-overview\"),\n]\n"
  },
  {
    "path": "pyscada/hmi/views.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport traceback\n\nimport pyscada.hmi.models\nfrom pyscada.core import version as core_version\nfrom pyscada.models import VariableProperty, Variable, Device\nfrom pyscada.models import Log\nfrom pyscada.models import DeviceWriteTask, DeviceReadTask\nfrom pyscada.hmi.models import ControlItem\nfrom pyscada.hmi.models import Form\nfrom pyscada.hmi.models import GroupDisplayPermission\nfrom pyscada.hmi.models import Widget\nfrom pyscada.hmi.models import CustomHTMLPanel\nfrom pyscada.hmi.models import Chart\nfrom pyscada.hmi.models import View, ExternalView\nfrom pyscada.hmi.models import ProcessFlowDiagram\nfrom pyscada.hmi.models import Pie\nfrom pyscada.hmi.models import Page\nfrom pyscada.hmi.models import SlidingPanelMenu\nfrom pyscada.utils import gen_hiddenConfigHtml, get_group_display_permission_list\n\nfrom django.http import HttpResponse\nfrom django.template.loader import get_template\nfrom django.template.response import TemplateResponse\nfrom django.shortcuts import redirect\nfrom django.contrib.auth import logout\nfrom django.conf import settings\nfrom django.core.exceptions import PermissionDenied\nfrom django.db.models.fields.related import OneToOneRel\n\nimport time\nimport json\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef index(request):\n    if hasattr(settings, \"PYSCADA_HOME\"):\n        return redirect(settings.PYSCADA_HOME)\n    return redirect(f\"view-overview/\")\n\n\ndef check_anonymous(request):\n    if not request.user.is_authenticated and (\n        not hasattr(settings, \"PYSCADA_ALLOW_ANONYMOUS\")\n        or not settings.PYSCADA_ALLOW_ANONYMOUS\n    ):\n        return False\n    return True\n\n\ndef check_anonymous_write(request):\n    if not request.user.is_authenticated and (\n        not hasattr(settings, \"PYSCADA_ALLOW_ANONYMOUS_WRITE\")\n        or not settings.PYSCADA_ALLOW_ANONYMOUS_WRITE\n    ):\n        return False\n    return True\n\n\ndef view_overview(request):\n    if not check_anonymous(request):\n        redirect(f\"{settings.LOGIN_URL}?next={request.path}\")\n    if GroupDisplayPermission.objects.count() == 0:\n        view_list = View.objects.all()\n        ext_view_list = ExternalView.objects.all()\n    else:\n        view_list = get_group_display_permission_list(\n            View.objects, request.user.groups.all(), request.user.is_authenticated\n        )\n        ext_view_list = get_group_display_permission_list(\n            ExternalView.objects, request.user.groups.all(), request.user.is_authenticated\n        )\n\n    if not view_list.count() and not request.user.is_authenticated:\n        return redirect(f\"{settings.LOGIN_URL}?next={request.path}\")\n\n    view_list = sorted(\n            list(view_list)+list(ext_view_list),\n            key=lambda instance: instance.position\n        )\n\n    c = {\n        \"user\": request.user,\n        \"view_list\": view_list,\n        \"version_string\": core_version,\n        \"link_target\": (\n            settings.LINK_TARGET if hasattr(settings, \"LINK_TARGET\") else \"_blank\"\n        ),\n    }\n    return TemplateResponse(\n        request, \"view_overview.html\", c\n    )  # HttpResponse(t.render(c))\n\n\ndef get_hidden_config2(request, link_title):\n    if not check_anonymous(request):\n        raise PermissionDenied(\"You don't have access to this view.\")\n\n    try:\n        v = (\n            get_group_display_permission_list(\n                View.objects, request.user.groups.all(), request.user.is_authenticated\n            )\n            .filter(link_title=link_title)\n            .first()\n        )\n        if v is None:\n            raise View.DoesNotExist\n        # v = View.objects.get(link_title=link_title)\n    except View.DoesNotExist as e:\n        logger.warning(f\"{request.user} has no permission for view {link_title}\")\n        raise PermissionDenied(\"You don't have access to this view.\")\n    except View.MultipleObjectsReturned as e:\n        logger.error(f\"{e} for view link_title\", exc_info=True)\n        raise PermissionDenied(f\"Multiples views with this link : {link_title}\")\n        # return HttpResponse(status=404)\n\n    object_config_list = dict()\n    custom_fields_list = dict()\n    exclude_fields_list = dict()\n    visible_objects_lists = dict()\n\n    items = [\n        field\n        for field in GroupDisplayPermission._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    if GroupDisplayPermission.objects.count() == 0:\n        # no groups\n        for item in items:\n            item_str = item.related_model.m2m_related_model._meta.object_name.lower()\n            visible_objects_lists[\n                f\"visible_{item_str}_list\"\n            ] = item.related_model.m2m_related_model.objects.all().values_list(\n                \"pk\", flat=True\n            )\n        visible_objects_lists[\"visible_page_list\"] = v.pages.all().values_list(\n            \"pk\", flat=True\n        )\n        visible_objects_lists[\"visible_slidingpanelmenu_list\"] = (\n            v.sliding_panel_menus.all().values_list(\"pk\", flat=True)\n        )\n    else:\n        for item in items:\n            item_str = item.related_model.m2m_related_model._meta.object_name.lower()\n            visible_objects_lists[f\"visible_{item_str}_list\"] = (\n                get_group_display_permission_list(\n                    item.related_model.m2m_related_model.objects,\n                    request.user.groups.all(),\n                    request.user.is_authenticated,\n                ).values_list(\"pk\", flat=True)\n            )\n        visible_objects_lists[\"visible_page_list\"] = get_group_display_permission_list(\n            v.pages, request.user.groups.all(), request.user.is_authenticated\n        ).values_list(\"pk\", flat=True)\n        visible_objects_lists[\"visible_slidingpanelmenu_list\"] = (\n            get_group_display_permission_list(\n                v.sliding_panel_menus,\n                request.user.groups.all(),\n                request.user.is_authenticated,\n            ).values_list(\"pk\", flat=True)\n        )\n    panel_list = SlidingPanelMenu.objects.filter(\n        id__in=visible_objects_lists[\"visible_slidingpanelmenu_list\"]\n    ).filter(\n        position__in=(\n            1,\n            2,\n        )\n    )\n    control_list = SlidingPanelMenu.objects.filter(\n        id__in=visible_objects_lists[\"visible_slidingpanelmenu_list\"]\n    ).filter(position=0)\n\n    for page_pk in visible_objects_lists[\"visible_page_list\"]:\n        page = Page.objects.get(id=page_pk)\n        for widget in page.widget_set.all():\n            if widget.pk not in visible_objects_lists[\"visible_widget_list\"]:\n                continue\n            if not widget.visible:\n                continue\n            if widget.content is None:\n                continue\n            widget_extra_css_class = (\n                widget.extra_css_class.css_class\n                if widget.extra_css_class is not None\n                else \"\"\n            )\n            opts = widget.content.get_hidden_config2(\n                visible_objects_lists=visible_objects_lists,\n            )\n            if (\n                type(opts) == dict\n                and \"object_config_list\" in opts\n                and type(opts[\"object_config_list\"] == list)\n            ):\n                for obj in opts[\"object_config_list\"]:\n                    model_name = str(obj._meta.model_name).lower()\n                    if model_name not in object_config_list:\n                        object_config_list[model_name] = list()\n                    if obj not in object_config_list[model_name]:\n                        object_config_list[model_name].append(obj)\n            if (\n                type(opts) == dict\n                and \"custom_fields_list\" in opts\n                and type(opts[\"custom_fields_list\"] == list)\n            ):\n                for model in opts[\"custom_fields_list\"]:\n                    custom_fields_list[str(model).lower()] = opts[\"custom_fields_list\"][\n                        model\n                    ]\n\n            if (\n                type(opts) == dict\n                and \"exclude_fields_list\" in opts\n                and type(opts[\"exclude_fields_list\"] == list)\n            ):\n                for model in opts[\"exclude_fields_list\"]:\n                    exclude_fields_list[str(model).lower()] = opts[\n                        \"exclude_fields_list\"\n                    ][model]\n\n    # Adding SlidingPanelMenu to hidden config\n    for s_pk in visible_objects_lists[\"visible_slidingpanelmenu_list\"]:\n        s = SlidingPanelMenu.objects.get(id=s_pk)\n        if s.control_panel is not None:\n            for obj in s.control_panel._get_objects_for_html(obj=s):\n                if obj._meta.model_name not in object_config_list:\n                    object_config_list[obj._meta.model_name] = list()\n                if obj not in object_config_list[obj._meta.model_name]:\n                    object_config_list[obj._meta.model_name].append(obj)\n    # Generate html object hidden config\n    hidden_globalConfig_html = \"\"\n    for model, val in sorted(object_config_list.items(), key=lambda ele: ele[0]):\n        hidden_globalConfig_html += '<div class=\"hidden ' + str(model) + 'Config2\">'\n        for obj in val:\n            hidden_globalConfig_html += gen_hiddenConfigHtml(\n                obj,\n                custom_fields_list.get(model, None),\n                exclude_fields_list.get(model, None),\n            )\n        hidden_globalConfig_html += \"</div>\"\n\n    return HttpResponse(hidden_globalConfig_html, content_type=\"text/plain\")\n\n\ndef view(request, link_title):\n    if not check_anonymous(request):\n        redirect(f\"{settings.LOGIN_URL}?next={request.path}\")\n\n    base_template = \"base.html\"\n    view_template = \"view.html\"\n    page_template = get_template(\"content_page.html\")\n    widget_row_template = get_template(\"widget_row.html\")\n    STATIC_URL = (\n        str(settings.STATIC_URL) if hasattr(settings, \"STATIC_URL\") else \"/static/\"\n    )\n\n    try:\n        v = (\n            get_group_display_permission_list(\n                View.objects, request.user.groups.all(), request.user.is_authenticated\n            )\n            .filter(link_title=link_title, visible=True)\n            .first()\n        )\n        if v is None:\n            raise View.DoesNotExist\n        # v = View.objects.get(link_title=link_title)\n    except View.DoesNotExist as e:\n        logger.warning(f\"{request.user} has no permission for view {link_title}\")\n        return redirect(\n            f\"{settings.LOGIN_URL}?next={request.path}&message=You don't have access to this view.\"\n        )\n    except View.MultipleObjectsReturned as e:\n        logger.error(f\"{e} for view link_title\", exc_info=True)\n        return redirect(\n            f\"{settings.LOGIN_URL}?next={request.path}&message=Multiples views with this link : {link_title}\"\n        )\n\n    if v.theme is not None:\n        base_template = str(v.theme.base_filename) + \".html\"\n        view_template = str(v.theme.view_filename) + \".html\"\n\n    visible_objects_lists = {}\n    items = [\n        field\n        for field in GroupDisplayPermission._meta.get_fields()\n        if issubclass(type(field), OneToOneRel)\n    ]\n    if GroupDisplayPermission.objects.count() == 0:\n        # no groups\n        for item in items:\n            item_str = item.related_model.m2m_related_model._meta.object_name.lower()\n            visible_objects_lists[\n                f\"visible_{item_str}_list\"\n            ] = item.related_model.m2m_related_model.objects.all().values_list(\n                \"pk\", flat=True\n            )\n        visible_objects_lists[\"visible_page_list\"] = v.pages.all().values_list(\n            \"pk\", flat=True\n        )\n        visible_objects_lists[\"visible_slidingpanelmenu_list\"] = (\n            v.sliding_panel_menus.all().values_list(\"pk\", flat=True)\n        )\n    else:\n        for item in items:\n            item_str = item.related_model.m2m_related_model._meta.object_name.lower()\n            visible_objects_lists[f\"visible_{item_str}_list\"] = (\n                get_group_display_permission_list(\n                    item.related_model.m2m_related_model.objects,\n                    request.user.groups.all(),\n                    request.user.is_authenticated,\n                ).values_list(\"pk\", flat=True)\n            )\n        visible_objects_lists[\"visible_page_list\"] = get_group_display_permission_list(\n            v.pages, request.user.groups.all(), request.user.is_authenticated\n        ).values_list(\"pk\", flat=True)\n        visible_objects_lists[\"visible_slidingpanelmenu_list\"] = (\n            get_group_display_permission_list(\n                v.sliding_panel_menus,\n                request.user.groups.all(),\n                request.user.is_authenticated,\n            ).values_list(\"pk\", flat=True)\n        )\n\n    panel_list = SlidingPanelMenu.objects.filter(\n        id__in=visible_objects_lists[\"visible_slidingpanelmenu_list\"]\n    ).filter(\n        position__in=(\n            1,\n            2,\n        )\n    )\n    control_list = SlidingPanelMenu.objects.filter(\n        id__in=visible_objects_lists[\"visible_slidingpanelmenu_list\"]\n    ).filter(position=0)\n\n    pages_html = \"\"\n    object_config_list = dict()\n    custom_fields_list = dict()\n    exclude_fields_list = dict()\n    javascript_files_list = list()\n    css_files_list = list()\n    show_daterangepicker = False\n    has_flot_chart = False\n    add_context = {}\n\n    for page_pk in visible_objects_lists[\"visible_page_list\"]:\n        # process content row by row\n        page = Page.objects.get(id=page_pk)\n        current_row = 0\n        widget_rows_html = \"\"\n        main_content = list()\n        sidebar_content = list()\n        topbar = False\n\n        show_daterangepicker_temp = False\n        show_timeline_temp = False\n\n        for widget in page.widget_set.all():\n            # check if row has changed\n            if current_row != widget.row:\n                # render new widget row and reset all loop variables\n                widget_rows_html += widget_row_template.render(\n                    {\n                        \"row\": current_row,\n                        \"main_content\": main_content,\n                        \"sidebar_content\": sidebar_content,\n                        \"sidebar_visible\": len(sidebar_content) > 0,\n                        \"topbar\": topbar,\n                    },\n                    request,\n                )\n                current_row = widget.row\n                main_content = list()\n                sidebar_content = list()\n                topbar = False\n            if widget.pk not in visible_objects_lists[\"visible_widget_list\"]:\n                continue\n            if not widget.visible:\n                continue\n            if widget.content is None:\n                continue\n            widget_extra_css_class = (\n                widget.extra_css_class.css_class\n                if widget.extra_css_class is not None\n                else \"\"\n            )\n            mc, sbc, opts = widget.content.create_panel_html(\n                widget_pk=widget.pk,\n                widget_extra_css_class=widget_extra_css_class,\n                visible_objects_lists=visible_objects_lists,\n                request=request,\n                view=v,\n            )\n            # main content\n            if mc is None:\n                logger.info(\n                    f\"User {request.user} not allowed to see the content of widget {widget}\"\n                )\n            else:\n                main_content.append(dict(html=mc, widget=widget, topbar=sbc))\n            # sidebar content\n            if sbc is not None:\n                sidebar_content.append(dict(html=sbc, widget=widget))\n            # options\n            if type(opts) == dict:\n                if \"topbar\" in opts and opts[\"topbar\"] == True:\n                    topbar = True\n                if (\n                    \"show_daterangepicker\" in opts\n                    and opts[\"show_daterangepicker\"] == True\n                ):\n                    show_daterangepicker = True\n                    show_daterangepicker_temp = True\n                if \"show_timeline\" in opts and opts[\"show_timeline\"] == True:\n                    show_timeline_temp = True\n                if \"flot\" in opts and opts[\"flot\"]:\n                    has_flot_chart = True\n                if \"base_template\" in opts:\n                    base_template = opts[\"base_template\"]\n                if \"view_template\" in opts:\n                    view_template = opts[\"view_template\"]\n                if \"add_context\" in opts:\n                    add_context.update(opts[\"add_context\"])\n                if \"javascript_files_list\" in opts:\n                    for file_src in opts[\"javascript_files_list\"]:\n                        if {\"src\": file_src} not in javascript_files_list:\n                            javascript_files_list.append({\"src\": file_src})\n                if \"css_files_list\" in opts:\n                    for file_src in opts[\"css_files_list\"]:\n                        if {\"src\": file_src} not in css_files_list:\n                            css_files_list.append({\"src\": file_src})\n                if \"object_config_list\" in opts and type(\n                    opts[\"object_config_list\"] == list\n                ):\n                    for obj in opts[\"object_config_list\"]:\n                        model_name = str(obj._meta.model_name).lower()\n                        if model_name not in object_config_list:\n                            object_config_list[model_name] = list()\n                        if obj not in object_config_list[model_name]:\n                            object_config_list[model_name].append(obj)\n                if \"custom_fields_list\" in opts and type(\n                    opts[\"custom_fields_list\"] == list\n                ):\n                    for model in opts[\"custom_fields_list\"]:\n                        custom_fields_list[str(model).lower()] = opts[\n                            \"custom_fields_list\"\n                        ][model]\n\n                if \"exclude_fields_list\" in opts and type(\n                    opts[\"exclude_fields_list\"] == list\n                ):\n                    for model in opts[\"exclude_fields_list\"]:\n                        exclude_fields_list[str(model).lower()] = opts[\n                            \"exclude_fields_list\"\n                        ][model]\n            else:\n                logger.info(f\"Widget {widget} options is not a dict, it is {opts}\")\n        widget_rows_html += widget_row_template.render(\n            {\n                \"row\": current_row,\n                \"main_content\": main_content,\n                \"sidebar_content\": sidebar_content,\n                \"sidebar_visible\": len(sidebar_content) > 0,\n                \"topbar\": topbar,\n            },\n            request,\n        )\n\n        pages_html += page_template.render(\n            {\n                \"page\": page,\n                \"widget_rows_html\": widget_rows_html,\n                \"show_daterangepicker\": show_daterangepicker_temp,\n                \"show_timeline\": show_timeline_temp,\n            },\n            request,\n        )\n\n    # Generate javascript files list\n    if has_flot_chart:\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/jquery/jquery.tablesorter.min.js\"}\n        )\n        # tablesorter parser for checkbox\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/jquery/parser-input-select.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/lib/jquery.event.drag.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/lib/jquery.mousewheel.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.canvaswrapper.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.colorhelpers.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.saturated.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.browser.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.drawSeries.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.errorbars.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.uiConstants.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.logaxis.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.symbol.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.flatdata.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.navigate.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.fillbetween.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.stack.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.touchNavigate.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.hover.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.touch.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.time.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.axislabels.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.selection.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.composeImages.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.legend.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.pie.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.crosshair.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/flot/source/jquery.flot.gauge.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/jquery.flot.axisvalues.js\"}\n        )\n\n    if show_daterangepicker:\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/daterangepicker/moment.min.js\"}\n        )\n        javascript_files_list.append(\n            {\"src\": STATIC_URL + \"pyscada/js/daterangepicker/daterangepicker.min.js\"}\n        )\n\n    javascript_files_list.append(\n        {\"src\": STATIC_URL + \"pyscada/js/pyscada/pyscada_v0-9-0.js\"}\n    )\n\n    # Generate css files list\n    css_files_list.append(\n        {\"src\": STATIC_URL + \"pyscada/css/daterangepicker/daterangepicker.css\"}\n    )\n\n    # Adding SlidingPanelMenu to hidden config\n    for s_pk in visible_objects_lists[\"visible_slidingpanelmenu_list\"]:\n        s = SlidingPanelMenu.objects.get(id=s_pk)\n        if s.control_panel is not None:\n            for obj in s.control_panel._get_objects_for_html(obj=s):\n                if obj._meta.model_name not in object_config_list:\n                    object_config_list[obj._meta.model_name] = list()\n                if obj not in object_config_list[obj._meta.model_name]:\n                    object_config_list[obj._meta.model_name].append(obj)\n    # Add html object hidden config div so that it can be filled in later\n    pages_html += '<div class=\"hidden globalConfig2\">'\n    pages_html += \"</div>\"\n\n    context = {\n        \"base_html\": base_template,\n        \"include\": [],\n        \"page_list\": Page.objects.filter(\n            id__in=visible_objects_lists[\"visible_page_list\"]\n        ),\n        \"pages_html\": pages_html,\n        \"panel_list\": panel_list,\n        \"control_list\": control_list,\n        \"user\": request.user,\n        \"visible_control_element_list\": visible_objects_lists[\n            \"visible_controlitem_list\"\n        ],\n        \"visible_form_list\": visible_objects_lists[\"visible_form_list\"],\n        \"view_object\": v,\n        \"view_title\": v.title,\n        \"view_link_title\": link_title,\n        \"view_show_timeline\": v.show_timeline,\n        \"view_time_delta\": v.default_time_delta.total_seconds(),\n        \"version_string\": core_version,\n        \"link_target\": (\n            settings.LINK_TARGET if hasattr(settings, \"LINK_TARGET\") else \"_blank\"\n        ),\n        \"javascript_files_list\": javascript_files_list,\n        \"css_files_list\": css_files_list,\n    }\n    context.update(add_context)\n\n    return TemplateResponse(request, view_template, context)\n\n\ndef log_data(request):\n    if not check_anonymous(request):\n        raise PermissionDenied(\"You don't have access to this view.\")\n    if \"timestamp\" in request.POST:\n        timestamp = float(request.POST[\"timestamp\"])\n    else:\n        timestamp = (time.time() - 300) * 1000  # get log of last 5 minutes\n\n    data = Log.objects.filter(\n        level__gte=6, id__gt=int(int(timestamp) * 2097152) + 2097151\n    ).order_by(\"-timestamp\")\n    odata = []\n    for item in data:\n        odata.append(\n            {\n                \"timestamp\": item.timestamp * 1000,\n                \"level\": item.level,\n                \"message\": item.message,\n                \"username\": item.user.username if item.user else \"None\",\n            }\n        )\n    jdata = json.dumps(odata, indent=2)\n\n    return HttpResponse(jdata, content_type=\"application/json\")\n\n\ndef form_read_all_task(request):\n    if not check_anonymous(request):\n        return HttpResponse(status=401)\n    crts = []\n    for device in Device.objects.all():\n        crts.append(\n            DeviceReadTask(\n                device=device,\n                start=time.time(),\n                user=request.user if request.user.is_authenticated else None,\n            )\n        )\n    if len(crts) > 0:\n        crts[0].create_and_notificate(crts)\n    return HttpResponse(status=200)\n\n\ndef form_read_task(request):\n    if not check_anonymous(request):\n        return HttpResponse(status=401)\n    if \"key\" in request.POST and \"type\" in request.POST:\n        key = int(request.POST[\"key\"])\n        item_type = request.POST[\"type\"]\n        if GroupDisplayPermission.objects.count() == 0:\n            if item_type == \"variable\":\n                crt = DeviceReadTask(\n                    device=Variable.objects.get(pk=key).device,\n                    start=time.time(),\n                    user=request.user if request.user.is_authenticated else None,\n                )\n                crt.create_and_notificate(crt)\n                return HttpResponse(status=200)\n            elif item_type == \"variable_property\":\n                crt = DeviceReadTask(\n                    device=VariableProperty.objects.get(pk=key).variable.device,\n                    start=time.time(),\n                    user=request.user if request.user.is_authenticated else None,\n                )\n                crt.create_and_notificate(crt)\n                return HttpResponse(status=200)\n        else:\n            if item_type == \"variable\":\n                if (\n                    get_group_display_permission_list(\n                        ControlItem.objects,\n                        request.user.groups.all(),\n                        request.user.is_authenticated,\n                    )\n                    .filter(type=1, variable_id=key)\n                    .exists()\n                ):\n                    crt = DeviceReadTask(\n                        device=Variable.objects.get(pk=key).device,\n                        start=time.time(),\n                        user=request.user if request.user.is_authenticated else None,\n                    )\n                    crt.create_and_notificate(crt)\n                    return HttpResponse(status=200)\n                else:\n                    logger.warning(\n                        f\"User {request.user} has no right to add read task \"\n                        f\"for variable {Variable.objects.get(pk=key)}\"\n                    )\n                    return HttpResponse(status=404)\n            elif item_type == \"variable_property\":\n                if (\n                    get_group_display_permission_list(\n                        ControlItem.objects,\n                        request.user.groups.all(),\n                        request.user.is_authenticated,\n                    )\n                    .filter(type=1, variable_property_id=key)\n                    .exists()\n                ):\n                    crt = DeviceReadTask(\n                        device=VariableProperty.objects.get(pk=key).variable.device,\n                        start=time.time(),\n                        user=request.user if request.user.is_authenticated else None,\n                    )\n                    crt.create_and_notificate(crt)\n                    return HttpResponse(status=200)\n                else:\n                    logger.warning(\n                        f\"User {request.user} has no right to add read task \"\n                        f\"for variable property {VariableProperty.objects.get(pk=key)}\"\n                    )\n                    return HttpResponse(status=404)\n        logger.warning(f\"Wrong read task request, POST is : {request.POST}\")\n    return HttpResponse(status=404)\n\n\ndef form_write_task(request):\n    if not check_anonymous_write(request):\n        HttpResponse(status=401)\n\n    if \"key\" in request.POST and \"value\" in request.POST:\n        key = int(request.POST[\"key\"])\n        item_type = request.POST[\"item_type\"]\n        value = request.POST[\"value\"]\n        # check if float as DeviceWriteTask doesn't support string values\n        try:\n            float(value)\n        except ValueError:\n            try:\n                vp = VariableProperty.objects.get(id=key)\n                if item_type == \"variable_property\" and vp.value_class.upper() in [\n                    \"STRING\"\n                ]:\n                    VariableProperty.objects.update_property(\n                        variable_property=vp,\n                        value=value,\n                    )\n                    # TODO: write string\n                    # cwt = DeviceWriteTask(\n                    #    variable_property_id=key,\n                    #    value=value,\n                    #    start=time.time(),\n                    #    user=request.user if request.user.is_authenticated else None,\n                    # )\n                    # cwt.create_and_notificate(cwt)\n                    return HttpResponse(status=200)\n            except VariableProperty.DoesNotExist:\n                pass\n            logger.info(f\"Cannot write STRING '{value}' to {item_type} {key}\")\n            return HttpResponse(status=403)\n        if GroupDisplayPermission.objects.count() == 0:\n            if item_type == \"variable\":\n                cwt = DeviceWriteTask(\n                    variable_id=key,\n                    value=value,\n                    start=time.time(),\n                    user=request.user if request.user.is_authenticated else None,\n                )\n                cwt.create_and_notificate(cwt)\n                return HttpResponse(status=200)\n            elif item_type == \"variable_property\":\n                cwt = DeviceWriteTask(\n                    variable_property_id=key,\n                    value=value,\n                    start=time.time(),\n                    user=request.user if request.user.is_authenticated else None,\n                )\n                cwt.create_and_notificate(cwt)\n                return HttpResponse(status=200)\n        else:\n            if \"view_id\" in request.POST:\n                # for a view, get the list of variables and variable properties for which the user can retrieve and write data\n                view_id = int(request.POST[\"view_id\"])\n                vdo = View.objects.get(id=view_id).data_objects(request.user)\n            else:\n                vdo = None  # should it get data objets for all views ?\n\n            if item_type == \"variable\":\n                can_write = False\n                if vdo is not None:\n                    # filter active_variables using variables from which the user can write data\n                    if \"variable_write\" in vdo and int(key) in vdo[\"variable_write\"]:\n                        can_write = True\n                    else:\n                        logger.info(\n                            f\"variable {key} not allowed to write in view {view_id} for user {request.user}\"\n                        )\n                else:\n                    # keeping old check, remove it later\n                    if (\n                        get_group_display_permission_list(\n                            ControlItem.objects,\n                            request.user.groups.all(),\n                            request.user.is_authenticated,\n                        )\n                        .filter(type=0, variable_id=key)\n                        .exists()\n                    ):\n                        can_write = True\n                    else:\n                        logger.debug(\n                            \"Missing group display permission for write task (variable %s)\"\n                            % key\n                        )\n                if can_write:\n                    cwt = DeviceWriteTask(\n                        variable_id=key,\n                        value=value,\n                        start=time.time(),\n                        user=request.user if request.user.is_authenticated else None,\n                    )\n                    cwt.create_and_notificate(cwt)\n                    return HttpResponse(status=200)\n            elif item_type == \"variable_property\":\n                can_write = False\n                if vdo is not None:\n                    # filter active_variables using variables from which the user can write data\n                    if (\n                        \"variable_property_write\" in vdo\n                        and int(key) in vdo[\"variable_property_write\"]\n                    ):\n                        can_write = True\n                    else:\n                        logger.info(\n                            f\"variable property {key} not allowed to write in view {view_id} for user {request.user}\"\n                        )\n                else:\n                    # keeping old check, remove it later\n                    if (\n                        get_group_display_permission_list(\n                            ControlItem.objects,\n                            request.user.groups.all(),\n                            request.user.is_authenticated,\n                        )\n                        .filter(type=0, variable_property_id=key)\n                        .exists()\n                    ):\n                        can_write = True\n                    else:\n                        logger.debug(\n                            \"Missing group display permission for write task (VP %s)\"\n                            % key\n                        )\n                if can_write:\n                    cwt = DeviceWriteTask(\n                        variable_property_id=key,\n                        value=value,\n                        start=time.time(),\n                        user=request.user if request.user.is_authenticated else None,\n                    )\n                    cwt.create_and_notificate(cwt)\n                    return HttpResponse(status=200)\n    else:\n        logger.debug(\"key or value missing in request : %s\" % request.POST)\n    return HttpResponse(status=404)\n\n\ndef int_filter(someList):\n    for v in someList:\n        try:\n            int(v)\n            yield v  # Keep these\n        except ValueError:\n            continue  # Skip these\n\n\ndef get_cache_data(request):\n    if not check_anonymous(request):\n        return HttpResponse(status=401)\n    if \"view_id\" in request.POST:\n        # for a view, get the list of variables and variable properties for which the user can retrieve and write data\n        view_id = int(request.POST[\"view_id\"])\n        vdo = View.objects.get(id=view_id).data_objects(request.user)\n    else:\n        vdo = None  # should it get data objets for all views ?\n\n    if \"init\" in request.POST:\n        init = bool(float(request.POST[\"init\"]))\n    else:\n        init = False\n    active_variables = []\n    if \"variables\" in request.POST:\n        active_variables = request.POST.get(\"variables\")\n        active_variables = list(int_filter(active_variables.split(\",\")))\n        if vdo is not None:\n            # filter active_variables using variables from which the user can retrieve data\n            variables_filtered = []\n            for var_pk in active_variables:\n                if \"variable\" in vdo and int(var_pk) in vdo[\"variable\"]:\n                    variables_filtered.append(var_pk)\n                else:\n                    logger.info(\n                        f\"variable {var_pk} not allowed in view {view_id} for user {request.user}\"\n                    )\n            active_variables = variables_filtered\n\n    \"\"\"\n    else:\n        active_variables = list(\n            GroupDisplayPermission.objects.filter(hmi_group__in=request.user.groups.iterator()).values_list(\n                'charts__variables', flat=True))\n        active_variables += list(\n            GroupDisplayPermission.objects.filter(hmi_group__in=request.user.groups.iterator()).values_list(\n                'control_items__variable', flat=True))\n        active_variables += list(\n            GroupDisplayPermission.objects.filter(hmi_group__in=request.user.groups.iterator()).values_list(\n                'custom_html_panels__variables', flat=True))\n        active_variables = list(set(active_variables))\n    \"\"\"\n\n    active_variable_properties = []\n    if \"variable_properties\" in request.POST:\n        active_variable_properties = request.POST.get(\"variable_properties\")\n        active_variable_properties = list(\n            int_filter(active_variable_properties.split(\",\"))\n        )\n        if vdo is not None:\n            # filter active_variable_properties using variables from which the user can retrieve data\n            variable_properties_filtered = []\n            for var_pk in active_variable_properties:\n                if (\n                    \"variable_property\" in vdo\n                    and int(var_pk) in vdo[\"variable_property\"]\n                ):\n                    variable_properties_filtered.append(var_pk)\n                else:\n                    logger.info(\n                        f\"variable property {var_pk} not allowed in view {view_id} for user {request.user}\"\n                    )\n            active_variable_properties = variable_properties_filtered\n\n    timestamp_from = time.time()\n    if \"timestamp_from\" in request.POST:\n        # JS Queries a in ms, convert to s\n        timestamp_from = float(request.POST[\"timestamp_from\"]) / 1000.0\n    if timestamp_from == 0:\n        timestamp_from = time.time() - 60\n\n    timestamp_to = time.time()\n    if \"timestamp_to\" in request.POST:\n        # JS Queries a in ms, convert to s\n        timestamp_to = min(timestamp_to, float(request.POST[\"timestamp_to\"]) / 1000.0)\n    if timestamp_to == 0:\n        timestamp_to = time.time()\n\n    # if timestamp_to - timestamp_from > 120 * 60 and not init:\n    #    timestamp_from = timestamp_to - 120 * 60\n\n    # if not init:\n    # timestamp_to = min(timestamp_from + 30, timestamp_to)\n\n    if len(active_variables) > 0:\n        data = Variable.objects.query_datapoints(\n            variable_ids=active_variables,\n            time_min=timestamp_from,\n            time_max=timestamp_to,\n            query_first_value=init,\n        )\n    else:\n        data = None\n\n    if data is None:\n        data = {\"timestamp\": 0, \"date_saved_max\":0}\n\n    # Add data for variable not logging to RecordedData model (as systemstat timestamps).\n    for v_id in active_variables:\n        if int(v_id) in data:\n            for item in data[int(v_id)]:\n                item[0] *= 1000 # convert from s to ms\n        else:\n            try:\n                v = Variable.objects.get(id=v_id)\n                v.check_last_datapoint()\n                # add 5 seconds to let the request from the server to come\n                if (\n                    v.timestamp_old is not None\n                    and v.timestamp_old <= timestamp_to + 5\n                    and v.prev_value is not None\n                ):\n                    # all website related data is in ms\n                    data[int(v_id)] = [[v.timestamp_old * 1000, v.prev_value]]\n            except:\n                logger.warning(traceback.format_exc())\n\n    data[\"variable_properties\"] = {}\n    data[\"variable_properties_last_modified\"] = {}\n\n    for item in VariableProperty.objects.filter(pk__in=active_variable_properties):\n        data[\"variable_properties\"][item.pk] = item.value()\n        data[\"variable_properties_last_modified\"][item.pk] = (\n            item.last_modified.timestamp() * 1000 # all website related data is in ms\n        )\n    data[\"timestamp\"] *= 1000\n    data[\"date_saved_max\"] *= 1000\n    data[\"server_time\"] = time.time() * 1000 # all website related data is in ms\n    return HttpResponse(json.dumps(data), content_type=\"application/json\")\n"
  },
  {
    "path": "pyscada/log/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom datetime import datetime\nfrom django.conf import settings\n\n\ndef add(\n    message,\n    level=0,\n    user=None,\n    message_short=None,\n    log_file_name=\"%s/pyscada_daemon.log\" % settings.BASE_DIR,\n):\n    \"\"\"\n    add a new massage/error notice to the log\n    <0 - Debug\n    1 - Emergency\n    2 - Critical\n    3 - Errors\n    4 - Alerts\n    5 - Warnings\n    6 - Notification (webnotice)\n    7 - Information (webinfo)\n    8 - Notification (notice)\n    9 - Information (info)\n\n    \"\"\"\n    # if not access(path.dirname(self.log_file_name), W_OK):\n    #    self.stderr.write(\"logfile path is not writeable\\n\")\n    #    sys.exit(0)\n    # if access(self.log_file_name, F_OK) and not access(self.log_file_name, W_OK):\n    #    self.stderr.write(\"logfile is not writeable\\n\")\n    #    sys.exit(0)\n\n    if message_short is None:\n        message_len = len(message)\n        if message_len > 35:\n            message_short = message[0:31] + \"...\"\n        else:\n            message_short = message\n\n    # log_ob = Log(message=message, level=level, message_short=message_short, timestamp=time())\n    # if user:\n    #    log_ob.user = user\n    # log_ob.save()\n    stdout = open(log_file_name, \"a+\")\n    stdout.write(\n        \"%s (%s,%d):%s\\n\" % (datetime.now().isoformat(\" \"), \"none\", level, message)\n    )\n    stdout.flush()\n\n\ndef debug(message, level=1, user=None, message_short=None):\n    add(message, -level, user, message_short)\n\n\ndef emerg(message, user=None, message_short=None):\n    add(message, 1, user, message_short)\n\n\ndef crit(message, user=None, message_short=None):\n    add(message, 2, user, message_short)\n\n\ndef error(message, user=None, message_short=None):\n    add(message, 3, user, message_short)\n\n\ndef alert(message, user=None, message_short=None):\n    add(message, 4, user, message_short)\n\n\ndef warning(message, user=None, message_short=None):\n    add(message, 5, user, message_short)\n\n\ndef webnotice(message, user=None, message_short=None):\n    add(message, 6, user, message_short)\n\n\ndef webinfo(message, user=None, message_short=None):\n    add(message, 7, user, message_short)\n\n\ndef notice(message, user=None, message_short=None):\n    add(message, 8, user, message_short)\n\n\ndef info(message, user=None, message_short=None):\n    add(message, 9, user, message_short)\n"
  },
  {
    "path": "pyscada/mail/__init__.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada import core\n\n__version__ = core.__version__\n____author__ = core.__author__\n"
  },
  {
    "path": "pyscada/mail/worker.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nfrom pyscada.utils.scheduler import Process as BaseProcess\nfrom pyscada.models import Mail\nfrom time import time\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass Process(BaseProcess):\n    def __init__(self, dt=5, **kwargs):\n        super(Process, self).__init__(dt=dt, **kwargs)\n\n    def loop(self):\n        \"\"\"\n        check for mails and send them\n        \"\"\"\n        for mail in Mail.objects.filter(done=False, send_fail_count__lt=3):\n            # send all emails that are not already send or failed to send less\n            # then three times\n            mail.send_mail()\n\n        for mail in Mail.objects.filter(\n            done=True, timestamp__lt=time() - 60 * 60 * 24 * 7\n        ):\n            # delete all done emails older then one week\n            mail.delete()\n        return 1, None\n"
  },
  {
    "path": "pyscada/management/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/management/commands/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/management/commands/pyscada_daemon.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nfrom pyscada.utils.scheduler import Scheduler\nfrom django.core.management.base import BaseCommand\n\n\nclass Command(BaseCommand):\n    help = \"Manage the Background Process Daemon for PyScada\"\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            \"action\",\n            choices=[\"start\", \"stop\", \"restart\", \"status\", \"init\"],\n            nargs=\"+\",\n            type=str,\n        )\n\n    def handle(self, *args, **options):\n        # init scheduler instance\n        scheduler = Scheduler(stdout=self.stdout)\n        if \"start\" == options[\"action\"][0]:\n            scheduler.start()\n        elif \"stop\" == options[\"action\"][0]:\n            scheduler.stop()\n        elif \"status\" == options[\"action\"][0]:\n            scheduler.status()\n        elif \"init\" == options[\"action\"][0]:\n            scheduler.init_db()\n        elif \"restart\" == options[\"action\"][0]:\n            scheduler.stop()\n            scheduler.start()\n"
  },
  {
    "path": "pyscada/migrations/0001_initial.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\nimport django.db.models.deletion\nfrom django.conf import settings\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"BackgroundTask\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"start\", models.FloatField(default=0)),\n                (\"timestamp\", models.FloatField(default=0)),\n                (\"progress\", models.FloatField(default=0)),\n                (\"load\", models.FloatField(default=0)),\n                (\"min\", models.FloatField(default=0)),\n                (\"max\", models.FloatField(default=0)),\n                (\"done\", models.BooleanField(default=False)),\n                (\"failed\", models.BooleanField(default=False)),\n                (\"pid\", models.IntegerField(default=0)),\n                (\"stop_daemon\", models.BooleanField(default=False)),\n                (\"label\", models.CharField(default=b\"\", max_length=400)),\n                (\"message\", models.CharField(default=b\"\", max_length=400)),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Client\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"short_name\", models.CharField(default=b\"\", max_length=400)),\n                (\n                    \"client_type\",\n                    models.CharField(\n                        default=\"generic\",\n                        max_length=400,\n                        choices=[\n                            (\"modbus\", \"Modbus Client\"),\n                            (\"systemstat\", \"Monitor Local System\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"description\",\n                    models.TextField(\n                        default=b\"\", null=True, verbose_name=\"Description\"\n                    ),\n                ),\n                (\"active\", models.BooleanField(default=True)),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"ClientWriteTask\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"value\", models.FloatField()),\n                (\"start\", models.FloatField(default=0)),\n                (\"fineshed\", models.FloatField(default=0, blank=True)),\n                (\"done\", models.BooleanField(default=False)),\n                (\"failed\", models.BooleanField(default=False)),\n                (\n                    \"user\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=settings.AUTH_USER_MODEL,\n                        null=True,\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Event\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"label\", models.CharField(default=b\"\", max_length=400)),\n                (\n                    \"level\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"informative\"),\n                            (1, \"ok\"),\n                            (2, \"warning\"),\n                            (3, \"alert\"),\n                        ],\n                    ),\n                ),\n                (\"fixed_limit\", models.FloatField(default=0, null=True, blank=True)),\n                (\n                    \"limit_type\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"value is less than limit\"),\n                            (1, \"value is less than or equal to the limit\"),\n                            (2, \"value is greater than the limit\"),\n                            (3, \"value is greater than or equal to the limit\"),\n                            (4, \"value equals the limit\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"action\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"just record\"),\n                            (1, \"record and send mail only wenn event occurs\"),\n                            (2, \"record and send mail\"),\n                            (3, \"record, send mail and change variable\"),\n                        ],\n                    ),\n                ),\n                (\"new_value\", models.FloatField(default=0, null=True, blank=True)),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Log\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"level\", models.IntegerField(default=0, verbose_name=\"level\")),\n                (\"timestamp\", models.FloatField()),\n                (\n                    \"message_short\",\n                    models.CharField(\n                        default=b\"\", max_length=400, verbose_name=\"short message\"\n                    ),\n                ),\n                (\"message\", models.TextField(default=b\"\", verbose_name=\"message\")),\n                (\n                    \"user\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=settings.AUTH_USER_MODEL,\n                        null=True,\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"MailQueue\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"subject\", models.TextField(default=b\"\", blank=True)),\n                (\"message\", models.TextField(default=b\"\", blank=True)),\n                (\n                    \"mail_from\",\n                    models.TextField(\n                        default=\"pyscada@martin-schroeder.net\", blank=True\n                    ),\n                ),\n                (\"timestamp\", models.FloatField(default=0, blank=True)),\n                (\"done\", models.BooleanField(default=False)),\n                (\n                    \"send_fail_count\",\n                    models.PositiveSmallIntegerField(default=0, blank=True),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"MailRecipient\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"subject_prefix\", models.TextField(default=b\"\", blank=True)),\n                (\"message_suffix\", models.TextField(default=b\"\", blank=True)),\n                (\"to_email\", models.EmailField(default=b\"\", max_length=75)),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"RecordedDataBoolean\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"value\", models.NullBooleanField()),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"RecordedDataCache\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        verbose_name=\"ID\",\n                        serialize=False,\n                        auto_created=True,\n                        primary_key=True,\n                    ),\n                ),\n                (\"value\", models.FloatField()),\n                (\n                    \"version\",\n                    models.PositiveIntegerField(default=0, null=True, blank=True),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"RecordedDataFloat\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"value\", models.FloatField()),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"RecordedDataInt\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"value\", models.IntegerField()),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"RecordedEvent\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"active\", models.BooleanField(default=False)),\n                (\n                    \"event\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.Event\",\n                        null=True,\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"RecordedTime\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"timestamp\", models.FloatField()),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Unit\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"unit\", models.CharField(max_length=80, verbose_name=\"Unit\")),\n                (\n                    \"description\",\n                    models.TextField(\n                        default=b\"\", null=True, verbose_name=\"Description\"\n                    ),\n                ),\n            ],\n            options={\n                \"managed\": True,\n            },\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"Variable\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"name\", models.SlugField(max_length=80, verbose_name=\"variable name\")),\n                (\n                    \"description\",\n                    models.TextField(default=b\"\", verbose_name=\"Description\"),\n                ),\n                (\"active\", models.BooleanField(default=True)),\n                (\"writeable\", models.BooleanField(default=False)),\n                (\"record\", models.BooleanField(default=True)),\n                (\n                    \"value_class\",\n                    models.CharField(\n                        default=\"FLOAT64\",\n                        max_length=15,\n                        verbose_name=\"value_class\",\n                        choices=[\n                            (\"FLOAT32\", \"REAL\"),\n                            (\"FLOAT32\", \"SINGLE\"),\n                            (\"FLOAT32\", \"FLOAT32\"),\n                            (\"FLOAT64\", \"LREAL\"),\n                            (\"FLOAT64\", \"FLOAT\"),\n                            (\"FLOAT64\", \"FLOAT64\"),\n                            (\"INT32\", \"INT32\"),\n                            (\"UINT32\", \"UINT32\"),\n                            (\"INT16\", \"INT\"),\n                            (\"INT16\", \"INT16\"),\n                            (\"UINT16\", \"WORD\"),\n                            (\"UINT16\", \"UINT\"),\n                            (\"UINT16\", \"UINT16\"),\n                            (\"BOOLEAN\", \"BOOL\"),\n                            (\"BOOLEAN\", \"BOOLEAN\"),\n                        ],\n                    ),\n                ),\n                (\n                    \"client\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.Client\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"unit\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.Unit\",\n                        null=True,\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name=\"VariableChangeHistory\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\n                    \"field\",\n                    models.PositiveSmallIntegerField(\n                        default=0,\n                        choices=[\n                            (0, \"active\"),\n                            (1, \"writable\"),\n                            (2, \"value_class\"),\n                            (3, \"variable_name\"),\n                        ],\n                    ),\n                ),\n                (\"old_value\", models.TextField(default=b\"\")),\n                (\n                    \"time\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.RecordedTime\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.Variable\",\n                        null=True,\n                    ),\n                ),\n            ],\n            options={},\n            bases=(models.Model,),\n        ),\n        migrations.AddField(\n            model_name=\"recordedevent\",\n            name=\"time_begin\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.RecordedTime\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordedevent\",\n            name=\"time_end\",\n            field=models.ForeignKey(\n                related_name=\"time_end\",\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.RecordedTime\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddataint\",\n            name=\"time\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.RecordedTime\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddataint\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddatafloat\",\n            name=\"time\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.RecordedTime\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddatafloat\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddatacache\",\n            name=\"last_change\",\n            field=models.ForeignKey(\n                related_name=\"last_change\",\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.RecordedTime\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddatacache\",\n            name=\"time\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.RecordedTime\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddatacache\",\n            name=\"variable\",\n            field=models.OneToOneField(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddataboolean\",\n            name=\"time\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.RecordedTime\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"recordeddataboolean\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"mailqueue\",\n            name=\"mail_recipients\",\n            field=models.ManyToManyField(to=\"pyscada.MailRecipient\"),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"event\",\n            name=\"mail_recipient\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                default=None,\n                blank=True,\n                to=\"pyscada.MailRecipient\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"event\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"event\",\n            name=\"variable_limit\",\n            field=models.ForeignKey(\n                related_name=\"variable_limit\",\n                on_delete=django.db.models.deletion.SET_NULL,\n                default=None,\n                blank=True,\n                to=\"pyscada.Variable\",\n                help_text=\"you can choose either an fixed limit or an variable limit that is dependent on the current value of an variable, if you choose a value other then none for varieble limit the fixed limit would be ignored\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"event\",\n            name=\"variable_to_change\",\n            field=models.ForeignKey(\n                related_name=\"variable_to_change\",\n                on_delete=django.db.models.deletion.SET_NULL,\n                default=None,\n                blank=True,\n                to=\"pyscada.Variable\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.AddField(\n            model_name=\"clientwritetask\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n                null=True,\n            ),\n            preserve_default=True,\n        ),\n        migrations.CreateModel(\n            name=\"VariableConfigFileImport\",\n            fields=[],\n            options={\n                \"proxy\": True,\n            },\n            bases=(\"pyscada.variable\",),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0002_event_hysteresis.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0001_initial\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"event\",\n            name=\"hysteresis\",\n            field=models.FloatField(default=0, blank=True),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0003_auto_20151026_1826.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0002_event_hysteresis\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"mailrecipient\",\n            name=\"to_email\",\n            field=models.EmailField(max_length=254),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0004_unit_udunit.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0001_initial\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"unit\",\n            name=\"udunit\",\n            field=models.CharField(max_length=500, verbose_name=\"udUnit\", default=\"\"),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0005_merge.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0003_auto_20151026_1826\"),\n        (\"pyscada\", \"0004_unit_udunit\"),\n    ]\n\n    operations = []\n"
  },
  {
    "path": "pyscada/migrations/0006_auto_20151130_1449.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0005_merge\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"clientwritetask\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Variable\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Variable\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddataboolean\",\n            name=\"time\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.RecordedTime\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddataboolean\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Variable\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddatacache\",\n            name=\"last_change\",\n            field=models.ForeignKey(\n                related_name=\"last_change\",\n                default=1,\n                to=\"pyscada.RecordedTime\",\n                on_delete=models.CASCADE,\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddatacache\",\n            name=\"time\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.RecordedTime\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddatacache\",\n            name=\"variable\",\n            field=models.OneToOneField(\n                default=1, to=\"pyscada.Variable\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddatafloat\",\n            name=\"time\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.RecordedTime\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddatafloat\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Variable\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddataint\",\n            name=\"time\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.RecordedTime\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddataint\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Variable\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordedevent\",\n            name=\"event\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Event\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"recordedevent\",\n            name=\"time_begin\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.RecordedTime\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"client\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Client\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"unit\",\n            field=models.ForeignKey(\n                on_delete=models.SET(1), default=1, to=\"pyscada.Unit\"\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"variablechangehistory\",\n            name=\"time\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.RecordedTime\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n        migrations.AlterField(\n            model_name=\"variablechangehistory\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                default=1, to=\"pyscada.Variable\", on_delete=models.CASCADE\n            ),\n            preserve_default=False,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0007_auto_20151201_1613.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0006_auto_20151130_1449\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\n            old_name=\"MailQueue\",\n            new_name=\"Mail\",\n        ),\n        migrations.AddField(\n            model_name=\"backgroundtask\",\n            name=\"identifier\",\n            field=models.CharField(default=b\"\", max_length=400),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0008_auto_20151201_1614.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0007_auto_20151201_1613\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"unit\",\n            name=\"udunit\",\n            field=models.CharField(default=b\"\", max_length=500, verbose_name=\"udUnit\"),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0009_auto_20160111_1802.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0008_auto_20151201_1614\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"backgroundtask\",\n            name=\"identifier\",\n            field=models.CharField(default=b\"\", max_length=400, blank=True),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"name\",\n            field=models.SlugField(\n                unique=True, max_length=80, verbose_name=\"variable name\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0010_auto_20160115_0918.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nfrom django.conf import settings\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        (\"pyscada\", \"0009_auto_20160111_1802\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\n            old_name=\"Client\",\n            new_name=\"Device\",\n        ),\n        migrations.RenameModel(\n            old_name=\"ClientWriteTask\",\n            new_name=\"DeviceWriteTask\",\n        ),\n        migrations.RenameField(\n            model_name=\"variable\",\n            old_name=\"client\",\n            new_name=\"device\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0011_auto_20160115_0920.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nfrom django.conf import settings\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        (\"pyscada\", \"0010_auto_20160115_0918\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"device\",\n            old_name=\"client_type\",\n            new_name=\"device_type\",\n        )\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0012_auto_20160119_0950.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0011_auto_20160115_0920\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"recordeddataint\",\n            name=\"value\",\n            field=models.BigIntegerField(),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0013_auto_20160204_0840.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0012_auto_20160119_0950\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Scaling\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        default=b\"\", null=True, verbose_name=\"Description\"\n                    ),\n                ),\n                (\"input_low\", models.FloatField()),\n                (\"input_high\", models.FloatField()),\n                (\"output_low\", models.FloatField()),\n                (\"output_high\", models.FloatField()),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"scaling\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Scaling\",\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0014_auto_20160210_1152.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0013_auto_20160204_0840\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"scaling\",\n            name=\"description\",\n            field=models.TextField(\n                default=b\"\", null=True, verbose_name=\"Description\", blank=True\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"scaling\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.SET_NULL,\n                blank=True,\n                to=\"pyscada.Scaling\",\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0015_auto_20160215_1522.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nfrom django.conf import settings\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        (\"pyscada\", \"0014_auto_20160210_1152\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"event\",\n            name=\"mail_recipient\",\n        ),\n        migrations.RemoveField(\n            model_name=\"mail\",\n            name=\"mail_from\",\n        ),\n        migrations.RemoveField(\n            model_name=\"mail\",\n            name=\"mail_recipients\",\n        ),\n        migrations.AddField(\n            model_name=\"event\",\n            name=\"mail_recipients\",\n            field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),\n        ),\n        migrations.AddField(\n            model_name=\"mail\",\n            name=\"to_email\",\n            field=models.EmailField(default=\"root@localhost\", max_length=254),\n            preserve_default=False,\n        ),\n        migrations.DeleteModel(\n            name=\"MailRecipient\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0016_auto_20160215_2002.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0015_auto_20160215_1522\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"device_type\",\n            field=models.CharField(\n                default=\"generic\",\n                max_length=400,\n                choices=[\n                    (\"generic\", \"no Device\"),\n                    (\"systemstat\", \"Local System Monitoring\"),\n                    (\"modbus\", \"Modbus Device\"),\n                ],\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0017_recordeddata.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0016_auto_20160215_2002\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"RecordedData\",\n            fields=[\n                (\"id\", models.BigIntegerField(serialize=False, primary_key=True)),\n                (\"value_boolean\", models.BooleanField(default=False)),\n                (\"value_int16\", models.SmallIntegerField(null=True, blank=True)),\n                (\"value_int32\", models.IntegerField(null=True, blank=True)),\n                (\"value_int64\", models.BigIntegerField(null=True, blank=True)),\n                (\"value_float64\", models.FloatField(null=True, blank=True)),\n                (\n                    \"variable\",\n                    models.ForeignKey(to=\"pyscada.Variable\", on_delete=models.CASCADE),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0018_auto_20160228_1623.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0017_recordeddata\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"variablechangehistory\",\n            name=\"time\",\n        ),\n        migrations.RemoveField(\n            model_name=\"variablechangehistory\",\n            name=\"variable\",\n        ),\n        migrations.AddField(\n            model_name=\"recordedevent\",\n            name=\"time_begin_new\",\n            field=models.FloatField(default=0),\n        ),\n        migrations.AddField(\n            model_name=\"recordedevent\",\n            name=\"time_end_new\",\n            field=models.FloatField(null=True, blank=True),\n        ),\n        migrations.DeleteModel(\n            name=\"VariableChangeHistory\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0019_datamigration_20160228_1624.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\ndef move_time_values(apps, schema_editor):\n    # We can't import the Person model directly as it may be a newer\n    # version than this migration expects. We use the historical version.\n    RecordedEvent = apps.get_model(\"pyscada\", \"RecordedEvent\")\n    for item in RecordedEvent.objects.using(schema_editor.connection.alias).all():\n        item.time_begin_new = item.time_begin.timestamp\n        if item.time_end is not None:\n            item.time_end_new = item.time_end.timestamp\n        item.save()\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0018_auto_20160228_1623\"),\n    ]\n\n    operations = [\n        migrations.RunPython(move_time_values, reverse_code=migrations.RunPython.noop),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0020_auto_20160228_1641.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0019_datamigration_20160228_1624\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"recordedevent\",\n            name=\"time_begin\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordedevent\",\n            name=\"time_end\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0021_auto_20160228_1643.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0020_auto_20160228_1641\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"recordedevent\",\n            old_name=\"time_begin_new\",\n            new_name=\"time_begin\",\n        ),\n        migrations.RenameField(\n            model_name=\"recordedevent\",\n            old_name=\"time_end_new\",\n            new_name=\"time_end\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0022_auto_20160228_2029.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0021_auto_20160228_1643\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"variable\",\n            name=\"record\",\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"cov_increment\",\n            field=models.FloatField(default=0, blank=True),\n        ),\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"device_type\",\n            field=models.CharField(\n                default=\"generic\",\n                max_length=400,\n                choices=[\n                    (\"generic\", \"no Protocol\"),\n                    (\"systemstat\", \"Local System Monitoring\"),\n                    (\"modbus\", \"Modbus Device\"),\n                ],\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0023_auto_20160314_1817.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0022_auto_20160228_2029\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"device_type\",\n            field=models.CharField(\n                default=\"generic\",\n                max_length=400,\n                choices=[\n                    (\"generic\", \"no Protocol\"),\n                    (\"systemstat\", \"Local System Monitoring\"),\n                    (\"modbus\", \"Modbus Device\"),\n                    (\"smbus\", \"SMBus/I2C Device\"),\n                ],\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0024_auto_20160517_1047.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0023_auto_20160314_1817\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"log\",\n            name=\"id\",\n            field=models.BigIntegerField(serialize=False, primary_key=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0025_auto_20160517_1734.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0024_auto_20160517_1047\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"byte_order\",\n            field=models.CharField(\n                default=\"1-0-3-2\",\n                max_length=15,\n                choices=[\n                    (\"1-0-3-2\", \"1-0-3-2\"),\n                    (\"0-1-2-3\", \"0-1-2-3\"),\n                    (\"2-3-0-1\", \"2-3-0-1\"),\n                    (\"3-2-1-0\", \"3-2-1-0\"),\n                ],\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"value_class\",\n            field=models.CharField(\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n                choices=[\n                    (\"FLOAT32\", \"REAL\"),\n                    (\"FLOAT32\", \"SINGLE\"),\n                    (\"FLOAT32\", \"FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL\"),\n                    (\"FLOAT64\", \"FLOAT\"),\n                    (\"FLOAT64\", \"DOUBLE\"),\n                    (\"FLOAT64\", \"FLOAT64\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD\"),\n                    (\"UINT32\", \"UINT32\"),\n                    (\"INT16\", \"INT\"),\n                    (\"INT16\", \"INT16\"),\n                    (\"UINT16\", \"WORD\"),\n                    (\"UINT16\", \"UINT\"),\n                    (\"UINT16\", \"UINT16\"),\n                    (\"BOOLEAN\", \"BOOL\"),\n                    (\"BOOLEAN\", \"BOOLEAN\"),\n                ],\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0026_auto_20160518_0848.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0025_auto_20160517_1734\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Color\",\n            fields=[\n                (\"id\", models.AutoField(serialize=False, primary_key=True)),\n                (\"name\", models.SlugField(max_length=80, verbose_name=\"variable name\")),\n                (\"R\", models.PositiveSmallIntegerField(default=0)),\n                (\"G\", models.PositiveSmallIntegerField(default=0)),\n                (\"B\", models.PositiveSmallIntegerField(default=0)),\n            ],\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddataboolean\",\n            name=\"time\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddataboolean\",\n            name=\"variable\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddatacache\",\n            name=\"last_change\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddatacache\",\n            name=\"time\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddatacache\",\n            name=\"variable\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddatafloat\",\n            name=\"time\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddatafloat\",\n            name=\"variable\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddataint\",\n            name=\"time\",\n        ),\n        migrations.RemoveField(\n            model_name=\"recordeddataint\",\n            name=\"variable\",\n        ),\n        migrations.AddField(\n            model_name=\"device\",\n            name=\"byte_order\",\n            field=models.CharField(\n                default=\"1-0-3-2\",\n                max_length=15,\n                choices=[\n                    (\"1-0-3-2\", \"1-0-3-2\"),\n                    (\"0-1-2-3\", \"0-1-2-3\"),\n                    (\"2-3-0-1\", \"2-3-0-1\"),\n                    (\"3-2-1-0\", \"3-2-1-0\"),\n                ],\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"chart_line_thickness\",\n            field=models.PositiveSmallIntegerField(default=3, choices=[(3, \"3Px\")]),\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"short_name\",\n            field=models.CharField(\n                default=b\"\", max_length=80, verbose_name=\"variable short name\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"byte_order\",\n            field=models.CharField(\n                default=\"default\",\n                max_length=15,\n                choices=[\n                    (\"default\", \"default (specified by device byte order)\"),\n                    (\"1-0-3-2\", \"1-0-3-2\"),\n                    (\"0-1-2-3\", \"0-1-2-3\"),\n                    (\"2-3-0-1\", \"2-3-0-1\"),\n                    (\"3-2-1-0\", \"3-2-1-0\"),\n                ],\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"value_class\",\n            field=models.CharField(\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n                choices=[\n                    (\"FLOAT32\", \"REAL (FLOAT32)\"),\n                    (\"FLOAT32\", \"SINGLE (FLOAT32)\"),\n                    (\"FLOAT32\", \"FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT  (FLOAT64)\"),\n                    (\"FLOAT64\", \"DOUBLE (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT64\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD (UINT32)\"),\n                    (\"UINT32\", \"UINT32\"),\n                    (\"INT16\", \"INT (INT16)\"),\n                    (\"INT16\", \"INT16\"),\n                    (\"UINT16\", \"WORD (UINT16)\"),\n                    (\"UINT16\", \"UINT (UINT16)\"),\n                    (\"UINT16\", \"UINT16\"),\n                    (\"BOOLEAN\", \"BOOL (BOOLEAN)\"),\n                    (\"BOOLEAN\", \"BOOLEAN\"),\n                ],\n            ),\n        ),\n        migrations.DeleteModel(\n            name=\"RecordedDataBoolean\",\n        ),\n        migrations.DeleteModel(\n            name=\"RecordedDataCache\",\n        ),\n        migrations.DeleteModel(\n            name=\"RecordedDataFloat\",\n        ),\n        migrations.DeleteModel(\n            name=\"RecordedDataInt\",\n        ),\n        migrations.DeleteModel(\n            name=\"RecordedTime\",\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"chart_line_color\",\n            field=models.ForeignKey(\n                default=None,\n                blank=True,\n                to=\"pyscada.Color\",\n                null=True,\n                on_delete=models.CASCADE,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0027_auto_20160530_1436.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0026_auto_20160518_0848\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"VariableState\",\n            fields=[],\n            options={\n                \"proxy\": True,\n            },\n            bases=(\"pyscada.variable\",),\n        ),\n        migrations.AddField(\n            model_name=\"backgroundtask\",\n            name=\"restart_daemon\",\n            field=models.BooleanField(default=False),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0028_auto_20160630_0831.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0027_auto_20160530_1436\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"short_name\",\n            field=models.CharField(\n                default=b\"\",\n                max_length=80,\n                verbose_name=\"variable short name\",\n                blank=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0029_scaling_limit_input.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0028_auto_20160630_0831\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"scaling\",\n            name=\"limit_input\",\n            field=models.BooleanField(default=0),\n            preserve_default=False,\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0030_device_polling_interval.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0029_scaling_limit_input\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"device\",\n            name=\"polling_interval\",\n            field=models.FloatField(\n                default=2.5,\n                blank=True,\n                choices=[\n                    (1, \"1 Second\"),\n                    (2.5, \"2.5 Seconds\"),\n                    (5, \"5 Seconds\"),\n                    (10, \"10 Seconds\"),\n                    (15, \"15 Seconds\"),\n                    (30, \"30 Seconds\"),\n                    (60, \"1 Minute\"),\n                    (150, \"2.5 Mintues\"),\n                    (300, \"5 Minutes\"),\n                    (360, \"6 Minutes (10 times per Hour)\"),\n                    (600, \"10 Minutes\"),\n                    (900, \"15 Minutes\"),\n                    (1800, \"30 Minutes\"),\n                    (3600, \"1 Hour\"),\n                ],\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0031_delete_variableconfigfileimport.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2016-10-11 13:36\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0030_device_polling_interval\"),\n    ]\n\n    operations = [\n        migrations.DeleteModel(\n            name=\"VariableConfigFileImport\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0032_auto_20161107_2206.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.3 on 2016-11-07 22:06\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0031_delete_variableconfigfileimport\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"VariableProperty\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\n                    \"property_class\",\n                    models.CharField(\n                        blank=True,\n                        choices=[\n                            (None, \"other or no Class specified\"),\n                            (\"device\", \"Device Property\"),\n                            (\"data_record\", \"Recorded Data\"),\n                            (\"daemon\", \"Daemon Property\"),\n                        ],\n                        default=None,\n                        max_length=255,\n                        null=True,\n                    ),\n                ),\n                (\"name\", models.CharField(blank=True, default=b\"\", max_length=255)),\n                (\"value_boolean\", models.BooleanField(default=False)),\n                (\"value_int16\", models.SmallIntegerField(blank=True, null=True)),\n                (\"value_int32\", models.IntegerField(blank=True, null=True)),\n                (\"value_int64\", models.BigIntegerField(blank=True, null=True)),\n                (\"value_float64\", models.FloatField(blank=True, null=True)),\n                (\n                    \"value_string\",\n                    models.CharField(blank=True, default=b\"\", max_length=255),\n                ),\n                (\"timestamp\", models.DateTimeField(blank=True, null=True)),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"polling_interval\",\n            field=models.FloatField(\n                choices=[\n                    (1.0, \"1 Second\"),\n                    (5.0, \"5 Seconds\"),\n                    (10.0, \"10 Seconds\"),\n                    (15.0, \"15 Seconds\"),\n                    (30.0, \"30 Seconds\"),\n                    (60.0, \"1 Minute\"),\n                    (150.0, \"2.5 Mintues\"),\n                    (300.0, \"5 Minutes\"),\n                    (360.0, \"6 Minutes (10 times per Hour)\"),\n                    (600.0, \"10 Minutes\"),\n                    (900.0, \"15 Minutes\"),\n                    (1800.0, \"30 Minutes\"),\n                    (3600.0, \"1 Hour\"),\n                ],\n                default=5,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0033_auto_20161107_2241.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.3 on 2016-11-07 22:41\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0032_auto_20161107_2206\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"device_type\",\n            field=models.CharField(\n                choices=[\n                    (\"generic\", \"no Protocol\"),\n                    (\"systemstat\", \"Local System Monitoring\"),\n                    (\"modbus\", \"Modbus Device\"),\n                    (\"smbus\", \"SMBus/I2C Device\"),\n                    (\"phant\", \"Phant Device\"),\n                    (\"visa\", \"VISA Device\"),\n                ],\n                default=\"generic\",\n                max_length=400,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0034_auto_20170213_0855.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.5 on 2017-02-13 08:55\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0033_auto_20161107_2241\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"device_type\",\n            field=models.CharField(\n                choices=[\n                    (\"generic\", \"no Protocol\"),\n                    (\"systemstat\", \"Local System Monitoring\"),\n                    (\"modbus\", \"Modbus Device\"),\n                    (\"smbus\", \"SMBus/I2C Device\"),\n                    (\"phant\", \"Phant Device\"),\n                    (\"visa\", \"VISA Device\"),\n                    (\"onewire\", \"1-Wire Device\"),\n                ],\n                default=\"generic\",\n                max_length=400,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0035_auto_20170224_1215.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2017-02-24 12:15\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0034_auto_20170213_0855\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"DeviceProtocol\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"protocol\", models.CharField(default=\"generic\", max_length=400)),\n                (\n                    \"description\",\n                    models.TextField(\n                        default=b\"\", null=True, verbose_name=\"Description\"\n                    ),\n                ),\n                (\n                    \"app_name\",\n                    models.CharField(default=\"pyscada.PROTOCOL\", max_length=400),\n                ),\n                (\n                    \"device_class\",\n                    models.CharField(default=\"pyscada.PROTOCOL.device\", max_length=400),\n                ),\n                (\"daq_daemon\", models.BooleanField()),\n                (\"single_thread\", models.BooleanField()),\n            ],\n        ),\n        migrations.RemoveField(\n            model_name=\"device\",\n            name=\"device_type\",\n        ),\n        migrations.AddField(\n            model_name=\"device\",\n            name=\"protocol\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.DeviceProtocol\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0036_auto_20170224_1245.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2017-02-24 12:45\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\ndef forwards_func(apps, schema_editor):\n    # We get the model from the versioned app registry;\n    # if we directly import it, it'll be the wrong version\n    DeviceProtocol = apps.get_model(\"pyscada\", \"DeviceProtocol\")\n    db_alias = schema_editor.connection.alias\n    DeviceProtocol.objects.using(db_alias).bulk_create(\n        [\n            DeviceProtocol(\n                pk=1,\n                protocol=\"generic\",\n                description=\"no Protocol\",\n                app_name=\"pyscada\",\n                device_class=\"None\",\n                daq_daemon=False,\n                single_thread=False,\n            ),\n        ]\n    )\n\n\ndef reverse_func(apps, schema_editor):\n    # forwards_func() creates two Country instances,\n    # so reverse_func() should delete them.\n    DeviceProtocol = apps.get_model(\"pyscada\", \"DeviceProtocol\")\n    db_alias = schema_editor.connection.alias\n    DeviceProtocol.objects.using(db_alias).filter(protocol=\"generic\").delete()\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0035_auto_20170224_1215\"),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards_func, reverse_func),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0037_auto_20170418_0931.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-04-18 09:31\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0036_auto_20170224_1245\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"BackgroundProcess\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"pid\", models.IntegerField(default=0)),\n                (\"label\", models.CharField(default=b\"\", max_length=400)),\n                (\"message\", models.CharField(default=b\"\", max_length=400)),\n                (\"enabled\", models.BooleanField(default=False)),\n                (\"done\", models.BooleanField(default=False)),\n                (\"failed\", models.BooleanField(default=False)),\n                (\n                    \"process_class\",\n                    models.CharField(\n                        default=\"pyscada.utils.scheduler.Process\",\n                        help_text=\"from pyscada.utils.scheduler import Process\",\n                        max_length=400,\n                    ),\n                ),\n                (\n                    \"process_class_kwargs\",\n                    models.CharField(\n                        default=\"{}\",\n                        help_text='arguments in json format will be passed as kwargs while the init of the process instance, example: {\"keywordA\":\"value1\", \"keywordB\":7}',\n                        max_length=400,\n                    ),\n                ),\n                (\"last_update\", models.DateTimeField(blank=True, null=True)),\n                (\"running_since\", models.DateTimeField(blank=True, null=True)),\n                (\n                    \"parent_process\",\n                    models.ForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.BackgroundProcess\",\n                    ),\n                ),\n            ],\n        )\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0038_auto_20170707_1209.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-07-07 12:09\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0037_auto_20170418_0931\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"parent_process\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.BackgroundProcess\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"pid\",\n            field=models.IntegerField(blank=True, default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"process_class\",\n            field=models.CharField(\n                blank=True,\n                default=\"pyscada.utils.scheduler.Process\",\n                help_text=\"from pyscada.utils.scheduler import Process\",\n                max_length=400,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"process_class_kwargs\",\n            field=models.CharField(\n                blank=True,\n                default=\"{}\",\n                help_text='arguments in json format will be passed as kwargs while the init of the process instance, example: {\"keywordA\":\"value1\", \"keywordB\":7}',\n                max_length=400,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0039_auto_20170711_1326.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-07-11 13:26\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0038_auto_20170707_1209\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"devicewritetask\",\n            old_name=\"fineshed\",\n            new_name=\"finished\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0040_auto_20170905_0942.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-09-05 09:42\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"export\", \"0015_remove_exporttask_backgroundtask\"),\n        (\"pyscada\", \"0039_auto_20170711_1326\"),\n    ]\n\n    operations = [\n        migrations.DeleteModel(\n            name=\"BackgroundTask\",\n        ),\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"process_class_kwargs\",\n            field=models.CharField(\n                blank=True,\n                default=\"{}\",\n                help_text='arguments in json format will be passed as kwargs while the \\n                                            init of the process instance, example: \\n                                            {\"keywordA\":\"value1\", \"keywordB\":7}',\n                max_length=400,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"action\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"just record\"),\n                    (1, \"record and send mail only when event occurs\"),\n                    (2, \"record and send mail\"),\n                    (3, \"record, send mail and change variable\"),\n                ],\n                default=0,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"variable_limit\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"you can choose either an fixed limit or an variable limit that is\\n                                        dependent on the current value of an variable, if you choose a value other then \\n                                        none for variable limit the fixed limit would be ignored\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"variable_limit\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0041_update_protocol_id.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\ndef forwards_func(apps, schema_editor):\n    # We get the model from the versioned app registry;\n    # if we directly import it, it'll be the wrong version\n    DeviceProtocol = apps.get_model(\"pyscada\", \"DeviceProtocol\")\n    Device = apps.get_model(\"pyscada\", \"Device\")\n    db_alias = schema_editor.connection.alias\n    protocol_list = []\n    protocol_ids = {}\n\n    # create intermediate device protocol schema\n    for dp in DeviceProtocol.objects.using(db_alias).all():\n        if dp.protocol in protocol_ids:\n            DeviceProtocol.objects.using(db_alias).bulk_create(\n                [\n                    DeviceProtocol(\n                        pk=dp.pk + 100,\n                        protocol=dp.protocol,\n                        description=dp.description,\n                        app_name=dp.app_name,\n                        device_class=dp.device_class,\n                        daq_daemon=dp.daq_daemon,\n                        single_thread=dp.single_thread,\n                    )\n                ]\n            )\n            protocol_list.append(\n                [dp.pk + 100, protocol_ids[dp.protocol]]\n            )  # save intermediate device protocol id\n            Device.objects.using(db_alias).filter(protocol_id=dp.pk).update(\n                protocol_id=dp.pk + 100\n            )\n        else:\n            if dp.pk < 10:\n                # todo handle that right\n                DeviceProtocol.objects.using(db_alias).bulk_create(\n                    [\n                        DeviceProtocol(\n                            pk=dp.pk + 10,\n                            protocol=dp.protocol,\n                            description=dp.description,\n                            app_name=dp.app_name,\n                            device_class=dp.device_class,\n                            daq_daemon=dp.daq_daemon,\n                            single_thread=dp.single_thread,\n                        )\n                    ]\n                )\n                Device.objects.using(db_alias).filter(protocol_id=dp.pk).update(\n                    protocol_id=dp.pk + 10\n                )\n\n    # delete all old device protocol schema\n    DeviceProtocol.objects.using(db_alias).filter(pk__range=(2, 100)).delete()\n    # create new device protocol schema\n    # migrate intermediate protocol ids to new protocol ids\n    for p_ids in protocol_list:\n        Device.objects.using(db_alias).filter(protocol_id=p_ids[0]).update(\n            protocol_id=p_ids[1]\n        )\n    # delete intermediate protocol ids\n    DeviceProtocol.objects.using(db_alias).filter(pk__gte=100).delete()\n\n\ndef reverse_func(apps, schema_editor):\n    # forwards_func() creates two Country instances,\n    # so reverse_func() should delete them.\n    pass\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0040_auto_20170905_0942\"),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards_func, reverse_func),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0042_auto_20180604_1240.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.7 on 2018-06-04 10:40\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0041_update_protocol_id\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"polling_interval\",\n            field=models.FloatField(\n                choices=[\n                    (0.1, \"100 Milliseconds\"),\n                    (0.5, \"500 Milliseconds\"),\n                    (1.0, \"1 Second\"),\n                    (5.0, \"5 Seconds\"),\n                    (10.0, \"10 Seconds\"),\n                    (15.0, \"15 Seconds\"),\n                    (30.0, \"30 Seconds\"),\n                    (60.0, \"1 Minute\"),\n                    (150.0, \"2.5 Mintues\"),\n                    (300.0, \"5 Minutes\"),\n                    (360.0, \"6 Minutes (10 times per Hour)\"),\n                    (600.0, \"10 Minutes\"),\n                    (900.0, \"15 Minutes\"),\n                    (1800.0, \"30 Minutes\"),\n                    (3600.0, \"1 Hour\"),\n                ],\n                default=5,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0043_devicewritetask_property_name.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.13 on 2018-06-29 14:01\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0042_auto_20180604_1240\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"devicewritetask\",\n            name=\"property_name\",\n            field=models.CharField(blank=True, default=\"\", max_length=255),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0044_auto_20180704_1307.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.13 on 2018-07-04 13:07\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0043_devicewritetask_property_name\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"devicewritetask\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"value_class\",\n            field=models.CharField(\n                choices=[\n                    (\"FLOAT32\", \"REAL (FLOAT32)\"),\n                    (\"FLOAT32\", \"SINGLE (FLOAT32)\"),\n                    (\"FLOAT32\", \"FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT  (FLOAT64)\"),\n                    (\"FLOAT64\", \"DOUBLE (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT64\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD (UINT32)\"),\n                    (\"UINT32\", \"UINT32\"),\n                    (\"INT16\", \"INT (INT16)\"),\n                    (\"INT16\", \"INT16\"),\n                    (\"UINT16\", \"WORD (UINT16)\"),\n                    (\"UINT16\", \"UINT (UINT16)\"),\n                    (\"UINT16\", \"UINT16\"),\n                    (\"BOOLEAN\", \"BOOL (BOOLEAN)\"),\n                    (\"BOOLEAN\", \"BOOLEAN\"),\n                ],\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0045_auto_20180705_1341.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.13 on 2018-07-05 13:41\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0044_auto_20180704_1307\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"value_class\",\n            field=models.CharField(\n                choices=[\n                    (\"FLOAT32\", \"REAL (FLOAT32)\"),\n                    (\"FLOAT32\", \"SINGLE (FLOAT32)\"),\n                    (\"FLOAT32\", \"FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT  (FLOAT64)\"),\n                    (\"FLOAT64\", \"DOUBLE (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT64\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD (UINT32)\"),\n                    (\"UINT32\", \"UINT32\"),\n                    (\"INT16\", \"INT (INT16)\"),\n                    (\"INT16\", \"INT16\"),\n                    (\"UINT16\", \"WORD (UINT16)\"),\n                    (\"UINT16\", \"UINT (UINT16)\"),\n                    (\"UINT16\", \"UINT16\"),\n                    (\"BOOLEAN\", \"BOOL (BOOLEAN)\"),\n                    (\"BOOLEAN\", \"BOOLEAN\"),\n                    (\"STRING\", \"STRING\"),\n                ],\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0046_remove_devicewritetask_property_name.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.13 on 2018-07-05 13:42\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0045_auto_20180705_1341\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"devicewritetask\",\n            name=\"property_name\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0047_recordeddatanew.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.15 on 2018-10-25 07:17\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0046_remove_devicewritetask_property_name\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"RecordedDataNew\",\n            fields=[\n                (\"id\", models.BigIntegerField(primary_key=True, serialize=False)),\n                (\n                    \"date_saved\",\n                    models.DateTimeField(blank=True, db_index=True, null=True),\n                ),\n                (\"value_boolean\", models.BooleanField(default=False)),\n                (\"value_int16\", models.SmallIntegerField(blank=True, null=True)),\n                (\"value_int32\", models.IntegerField(blank=True, null=True)),\n                (\"value_int64\", models.BigIntegerField(blank=True, null=True)),\n                (\"value_float64\", models.FloatField(blank=True, null=True)),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0047_variableproperty_unit.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-07-23 07:17\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0046_remove_devicewritetask_property_name\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"unit\",\n            field=models.ForeignKey(\n                blank=True, null=True, on_delete=models.SET(1), to=\"pyscada.Unit\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0048_datamigration_20181025_0918.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport gc\nfrom datetime import datetime\nfrom time import time\nfrom pytz import UTC\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef queryset_iterator(queryset, chunk_size=100000):\n    counter = 0\n    count = chunk_size\n    while count == chunk_size:\n        offset = counter - counter % chunk_size\n        count = 0\n        for item in queryset.all()[offset : offset + chunk_size]:\n            count += 1\n            yield item\n        counter += count\n        gc.collect()\n\n\ndef move_recorded_data(apps, schema_editor):\n    # We can't import the Person model directly as it may be a newer\n    # version than this migration expects. We use the historical version.\n    RecordedDataNew = apps.get_model(\"pyscada\", \"RecordedDataNew\")\n    RecordedData = apps.get_model(\"pyscada\", \"RecordedData\")\n\n    recoded_data_set = queryset_iterator(\n        RecordedData.objects.using(schema_editor.connection.alias).all().order_by(\"-pk\")\n    )\n    count = 0\n    count_all = 0\n    timeout = time() + 60 * 5\n    items = []\n    for item in recoded_data_set:\n        items.append(\n            RecordedDataNew(\n                id=item.id,\n                variable_id=item.variable_id,\n                value_boolean=item.value_boolean,\n                value_int16=item.value_int16,\n                value_int32=item.value_int32,\n                value_int64=item.value_int64,\n                value_float64=item.value_float64,\n                date_saved=datetime.fromtimestamp(\n                    (item.pk - item.variable.pk) / 2097152 / 1000.0, UTC\n                ),\n            )\n        )\n        if time() > timeout:\n            break\n\n        count += 1\n        if count >= 10000:\n            RecordedDataNew.objects.bulk_create(items)\n            items = []\n            count_all += count\n            count = 0\n            logger.info(\"wrote %d lines in total\\n\" % count_all)\n\n    if len(items) > 0:\n        RecordedDataNew.objects.bulk_create(items)\n        count_all += len(items)\n        logger.info(\"wrote %d lines in total\\n\" % count_all)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0047_recordeddatanew\"),\n    ]\n\n    operations = [\n        migrations.RunPython(\n            move_recorded_data, reverse_code=migrations.RunPython.noop\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0049_auto_20181025_1049.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.15 on 2018-10-25 10:49\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0048_datamigration_20181025_0918\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\"RecordedData\", \"RecordedDataOld\"),\n        migrations.RenameModel(\"RecordedDataNew\", \"RecordedData\"),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0050_merge_20181130_1143.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.16 on 2018-11-30 11:43\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0047_variableproperty_unit\"),\n        (\"pyscada\", \"0049_auto_20181025_1049\"),\n    ]\n\n    operations = []\n"
  },
  {
    "path": "pyscada/migrations/0051_auto_20181206_1107.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-12-06 11:07\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0050_merge_20181130_1143\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"value_max\",\n            field=models.FloatField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"value_min\",\n            field=models.FloatField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"value_max\",\n            field=models.FloatField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"value_min\",\n            field=models.FloatField(blank=True, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0052_auto_20181207_1019.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.14 on 2018-12-07 10:19\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0051_auto_20181206_1107\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"max_type\",\n            field=models.CharField(\n                choices=[(\"gte\", \">=\"), (\"gt\", \">\")],\n                default=\"gte\",\n                max_length=4,\n                verbose_name=\"value_class\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"min_type\",\n            field=models.CharField(\n                choices=[(\"lte\", \"<=\"), (\"lt\", \"<\")],\n                default=\"lte\",\n                max_length=4,\n                verbose_name=\"value_class\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"max_type\",\n            field=models.CharField(\n                choices=[(\"gte\", \">=\"), (\"gt\", \">\")],\n                default=\"gte\",\n                max_length=4,\n                verbose_name=\"value_class\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"min_type\",\n            field=models.CharField(\n                choices=[(\"lte\", \"<=\"), (\"lt\", \"<\")],\n                default=\"lte\",\n                max_length=4,\n                verbose_name=\"value_class\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0053_auto_20190207_1526.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.18 on 2019-02-07 15:26\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0052_auto_20181207_1019\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"value_class\",\n            field=models.CharField(\n                choices=[\n                    (\"FLOAT32\", \"REAL (FLOAT32)\"),\n                    (\"FLOAT32\", \"SINGLE (FLOAT32)\"),\n                    (\"FLOAT32\", \"FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT  (FLOAT64)\"),\n                    (\"FLOAT64\", \"DOUBLE (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT64\"),\n                    (\"FLOAT48\", \"FLOAT48\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD (UINT32)\"),\n                    (\"UINT32\", \"UINT32\"),\n                    (\"INT16\", \"INT (INT16)\"),\n                    (\"INT16\", \"INT16\"),\n                    (\"UINT16\", \"WORD (UINT16)\"),\n                    (\"UINT16\", \"UINT (UINT16)\"),\n                    (\"UINT16\", \"UINT16\"),\n                    (\"BOOLEAN\", \"BOOL (BOOLEAN)\"),\n                    (\"BOOLEAN\", \"BOOLEAN\"),\n                ],\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0053_auto_20190307_1423.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.15 on 2019-03-07 14:23\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0052_auto_20181207_1019\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"max_type\",\n            field=models.CharField(\n                choices=[(\"gte\", \">=\"), (\"gt\", \">\")], default=\"gte\", max_length=4\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"min_type\",\n            field=models.CharField(\n                choices=[(\"lte\", \"<=\"), (\"lt\", \"<\")], default=\"lte\", max_length=4\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"max_type\",\n            field=models.CharField(\n                choices=[(\"gte\", \">=\"), (\"gt\", \">\")], default=\"gte\", max_length=4\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"min_type\",\n            field=models.CharField(\n                choices=[(\"lte\", \"<=\"), (\"lt\", \"<\")], default=\"lte\", max_length=4\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0054_auto_20190208_0913.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.18 on 2019-02-08 09:13\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0053_auto_20190207_1526\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"value_class\",\n            field=models.CharField(\n                choices=[\n                    (\"FLOAT32\", \"REAL (FLOAT32)\"),\n                    (\"FLOAT32\", \"SINGLE (FLOAT32)\"),\n                    (\"FLOAT32\", \"FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT  (FLOAT64)\"),\n                    (\"FLOAT64\", \"DOUBLE (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT64\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"FLOAT48\", \"FLOAT48\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"INT48\", \"INT48\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD (UINT32)\"),\n                    (\"UINT32\", \"UINT32\"),\n                    (\"INT16\", \"INT (INT16)\"),\n                    (\"INT16\", \"INT16\"),\n                    (\"UINT16\", \"WORD (UINT16)\"),\n                    (\"UINT16\", \"UINT (UINT16)\"),\n                    (\"UINT16\", \"UINT16\"),\n                    (\"BOOLEAN\", \"BOOL (BOOLEAN)\"),\n                    (\"BOOLEAN\", \"BOOLEAN\"),\n                ],\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0054_auto_20190411_0749.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.16 on 2019-04-11 07:49\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0053_auto_20190307_1423\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"label\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"message\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"description\",\n            field=models.TextField(default=\"\", null=True, verbose_name=\"Description\"),\n        ),\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"short_name\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n        migrations.AlterField(\n            model_name=\"deviceprotocol\",\n            name=\"description\",\n            field=models.TextField(default=\"\", null=True, verbose_name=\"Description\"),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"label\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"variable_limit\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"you can choose either an fixed limit or an variable limit that is\\n                                        dependent on the current value of an variable, if you choose a value other than \\n                                        none for variable limit the fixed limit would be ignored\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"variable_limit\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"log\",\n            name=\"message\",\n            field=models.TextField(default=\"\", verbose_name=\"message\"),\n        ),\n        migrations.AlterField(\n            model_name=\"log\",\n            name=\"message_short\",\n            field=models.CharField(\n                default=\"\", max_length=400, verbose_name=\"short message\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"mail\",\n            name=\"message\",\n            field=models.TextField(blank=True, default=\"\"),\n        ),\n        migrations.AlterField(\n            model_name=\"mail\",\n            name=\"subject\",\n            field=models.TextField(blank=True, default=\"\"),\n        ),\n        migrations.AlterField(\n            model_name=\"scaling\",\n            name=\"description\",\n            field=models.TextField(\n                blank=True, default=\"\", null=True, verbose_name=\"Description\"\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"unit\",\n            name=\"description\",\n            field=models.TextField(default=\"\", null=True, verbose_name=\"Description\"),\n        ),\n        migrations.AlterField(\n            model_name=\"unit\",\n            name=\"udunit\",\n            field=models.CharField(default=\"\", max_length=500, verbose_name=\"udUnit\"),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"description\",\n            field=models.TextField(default=\"\", verbose_name=\"Description\"),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"short_name\",\n            field=models.CharField(\n                blank=True,\n                default=\"\",\n                max_length=80,\n                verbose_name=\"variable short name\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"name\",\n            field=models.CharField(blank=True, default=\"\", max_length=255),\n        ),\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"value_string\",\n            field=models.CharField(blank=True, default=\"\", max_length=255),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0055_auto_20190625_1752.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.15 on 2019-06-25 17:52\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0054_auto_20190411_0749\"),\n    ]\n\n    operations = [\n        migrations.AlterModelOptions(\n            name=\"backgroundprocess\",\n            options={\"verbose_name_plural\": \"Background Processes\"},\n        ),\n        migrations.AlterModelOptions(\n            name=\"variableproperty\",\n            options={\"verbose_name_plural\": \"variable properties\"},\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"cov_increment\",\n            field=models.FloatField(default=0, verbose_name=\"COV\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0056_auto_20190625_1823.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11.15 on 2019-06-25 18:23\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0055_auto_20190625_1752\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"pid\",\n            field=models.IntegerField(default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"hysteresis\",\n            field=models.FloatField(default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"mail\",\n            name=\"send_fail_count\",\n            field=models.PositiveSmallIntegerField(default=0),\n        ),\n        migrations.AlterField(\n            model_name=\"mail\",\n            name=\"timestamp\",\n            field=models.FloatField(default=0),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0057_auto_20191004_0912.py",
    "content": "# Generated by Django 2.2.6 on 2019-10-04 09:12\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0056_auto_20190625_1823\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"done\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"enabled\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"failed\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"protocol\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.DeviceProtocol\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"done\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"failed\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"mail\",\n            name=\"done\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.RenameModel(\"RecordedData\", \"RecordedDataNew\"),\n        migrations.AlterField(\n            model_name=\"recordeddatanew\",\n            name=\"value_boolean\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddatanew\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.RenameModel(\"RecordedDataNew\", \"RecordedData\"),\n        migrations.AlterField(\n            model_name=\"recordeddataold\",\n            name=\"value_boolean\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddataold\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"recordedevent\",\n            name=\"active\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"recordedevent\",\n            name=\"event\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Event\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"chart_line_color\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Color\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"device\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Device\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"value_boolean\",\n            field=models.BooleanField(blank=True, default=False),\n        ),\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0058_merge_20191206_1328.py",
    "content": "# Generated by Django 2.2.8 on 2019-12-06 13:28\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0057_auto_20191004_0912\"),\n        (\"pyscada\", \"0054_auto_20190208_0913\"),\n    ]\n\n    operations = []\n"
  },
  {
    "path": "pyscada/migrations/0059_auto_20200211_1049.py",
    "content": "# Generated by Django 2.2.8 on 2020-02-11 10:49\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0058_merge_20191206_1328\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"protocol\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.DeviceProtocol\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddata\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddataold\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"recordedevent\",\n            name=\"event\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Event\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"device\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Device\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0060_auto_20200914_1417.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-14 14:17\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0059_auto_20200211_1049\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variableproperty\",\n            name=\"value_string\",\n            field=models.CharField(blank=True, default=\"\", max_length=1000),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0061_devicereadtask.py",
    "content": "# Generated by Django 2.2.8 on 2020-09-14 15:31\n\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        (\"pyscada\", \"0060_auto_20200914_1417\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"DeviceReadTask\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"start\", models.FloatField(default=0)),\n                (\"finished\", models.FloatField(blank=True, default=0)),\n                (\"done\", models.BooleanField(blank=True, default=False)),\n                (\"failed\", models.BooleanField(blank=True, default=False)),\n                (\n                    \"device\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.Device\",\n                    ),\n                ),\n                (\n                    \"user\",\n                    models.ForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=settings.AUTH_USER_MODEL,\n                    ),\n                ),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n                (\n                    \"variable_property\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.VariableProperty\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0062_auto_20201002_0830.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-02 08:30\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0061_devicereadtask\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"limit_type\",\n            field=models.PositiveSmallIntegerField(\n                choices=[\n                    (0, \"value < limit\"),\n                    (1, \"value <= limit\"),\n                    (2, \"limit < value\"),\n                    (3, \"limit <= value\"),\n                    (4, \"value == limit\"),\n                ],\n                default=0,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0063_complexevent_complexeventitem_complexeventitemvariable.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-05 13:45\n\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        (\"pyscada\", \"0062_auto_20201002_0830\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ComplexEvent\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"label\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"mail_recipients\",\n                    models.ManyToManyField(to=settings.AUTH_USER_MODEL),\n                ),\n                (\n                    \"variable_to_change\",\n                    models.ForeignKey(\n                        blank=True,\n                        default=None,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"complex_variable_to_change\",\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"ComplexEventItem\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\n                    \"level\",\n                    models.PositiveSmallIntegerField(\n                        choices=[\n                            (0, \"informative\"),\n                            (1, \"ok\"),\n                            (2, \"warning\"),\n                            (3, \"alert\"),\n                        ],\n                        default=0,\n                    ),\n                ),\n                (\"send_mail\", models.BooleanField(default=False)),\n                (\"change_value\", models.BooleanField(default=False)),\n                (\"new_value\", models.FloatField(blank=True, default=0, null=True)),\n                (\n                    \"complex_event\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.ComplexEvent\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"ComplexEventItemVariable\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\n                    \"fixed_limit_low\",\n                    models.FloatField(blank=True, default=0, null=True),\n                ),\n                (\n                    \"limit_low_type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"value < limit\"), (1, \"value <= limit\")], default=0\n                    ),\n                ),\n                (\"hysteresis_low\", models.FloatField(default=0)),\n                (\n                    \"fixed_limit_high\",\n                    models.FloatField(blank=True, default=0, null=True),\n                ),\n                (\n                    \"limit_high_type\",\n                    models.PositiveSmallIntegerField(\n                        choices=[(0, \"value < limit\"), (1, \"value <= limit\")], default=0\n                    ),\n                ),\n                (\"hysteresis_high\", models.FloatField(default=0)),\n                (\n                    \"complex_event_item\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.ComplexEventItem\",\n                    ),\n                ),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"variable\",\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n                (\n                    \"variable_limit_high\",\n                    models.ForeignKey(\n                        blank=True,\n                        default=None,\n                        help_text=\"you can choose either an fixed limit or an variable limit that is\\n                                        dependent on the current value of an variable, if you choose a value other than \\n                                        none for variable limit the fixed limit would be ignored\",\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"variable_limit_high\",\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n                (\n                    \"variable_limit_low\",\n                    models.ForeignKey(\n                        blank=True,\n                        default=None,\n                        help_text=\"you can choose either an fixed limit or an variable limit that is\\n                                        dependent on the current value of an variable, if you choose a value other than \\n                                        none for variable limit the fixed limit would be ignored\",\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"variable_limit_low\",\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n                (\n                    \"variable_property\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.VariableProperty\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0064_auto_20201005_1443.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-05 14:43\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0063_complexevent_complexeventitem_complexeventitemvariable\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"complexevent\",\n            old_name=\"mail_recipients\",\n            new_name=\"complex_mail_recipients\",\n        ),\n        migrations.AddField(\n            model_name=\"complexeventitem\",\n            name=\"custom_validation\",\n            field=models.CharField(default=\"\", max_length=400),\n        ),\n        migrations.AddField(\n            model_name=\"complexeventitem\",\n            name=\"order\",\n            field=models.PositiveSmallIntegerField(default=0),\n        ),\n        migrations.AddField(\n            model_name=\"complexeventitem\",\n            name=\"stop_recording\",\n            field=models.BooleanField(default=False),\n        ),\n        migrations.AddField(\n            model_name=\"complexeventitem\",\n            name=\"validation\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"OR\"), (1, \"AND\"), (2, \"Custom\")], default=0\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"complexeventitemvariable\",\n            name=\"limit_low_type\",\n            field=models.PositiveSmallIntegerField(\n                choices=[(0, \"limit < value\"), (1, \"limit <= value\")], default=0\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"complexeventitemvariable\",\n            name=\"variable_limit_high\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"you can choose either an \\n                                            fixed limit or an variable limit that is dependent on the current value of \\n                                            an variable, if you choose a value other than  none for variable limit the \\n                                            fixed limit would be ignored\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"variable_limit_high\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"complexeventitemvariable\",\n            name=\"variable_limit_low\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"you can choose either an \\n                                            fixed limit or an variable limit that is dependent on the current value of \\n                                            an variable, if you choose a value other than  none for variable limit the \\n                                            fixed limit would be ignored\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"variable_limit_low\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0065_auto_20201005_1454.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-05 14:54\n\nfrom django.conf import settings\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0064_auto_20201005_1443\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"complexevent\",\n            name=\"complex_mail_recipients\",\n            field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),\n        ),\n        migrations.AlterField(\n            model_name=\"complexeventitem\",\n            name=\"custom_validation\",\n            field=models.CharField(blank=True, default=\"\", max_length=400, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0066_auto_20201006_0718.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-06 07:18\n\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        (\"pyscada\", \"0065_auto_20201005_1454\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\n            old_name=\"ComplexEventItem\",\n            new_name=\"ComplexEvent2\",\n        ),\n        migrations.RenameModel(\n            old_name=\"ComplexEvent\",\n            new_name=\"ComplexEventGroup\",\n        ),\n        migrations.RenameModel(\n            old_name=\"ComplexEventItemVariable\",\n            new_name=\"ComplexEventItem2\",\n        ),\n        migrations.AddField(\n            model_name=\"recordedevent\",\n            name=\"complex_event_group\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.ComplexEventGroup\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0067_auto_20201006_0719.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-06 07:19\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0066_auto_20201006_0718\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\n            old_name=\"ComplexEvent2\",\n            new_name=\"ComplexEvent\",\n        ),\n        migrations.RenameModel(\n            old_name=\"ComplexEventItem2\",\n            new_name=\"ComplexEventItem\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0068_auto_20201006_0826.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-06 08:26\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0067_auto_20201006_0719\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"complexeventitem\",\n            old_name=\"complex_event_item\",\n            new_name=\"complex_event\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0069_complexeventgroup_current_level.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-06 13:09\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0068_auto_20201006_0826\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"complexeventgroup\",\n            name=\"current_level\",\n            field=models.PositiveSmallIntegerField(default=0),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0070_auto_20201006_1327.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-06 13:27\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0069_complexeventgroup_current_level\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"complexeventgroup\",\n            name=\"current_level\",\n        ),\n        migrations.AddField(\n            model_name=\"complexevent\",\n            name=\"active\",\n            field=models.BooleanField(default=False),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0071_recordedevent_level.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-06 13:49\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0070_auto_20201006_1327\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"recordedevent\",\n            name=\"level\",\n            field=models.PositiveSmallIntegerField(blank=True, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0072_auto_20201006_1408.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-06 14:08\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0071_recordedevent_level\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"recordedevent\",\n            name=\"level\",\n        ),\n        migrations.AddField(\n            model_name=\"complexeventgroup\",\n            name=\"last_level\",\n            field=models.SmallIntegerField(default=-1),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0073_auto_20201007_0858.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-07 08:58\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0072_auto_20201006_1408\"),\n    ]\n\n    operations = [\n        migrations.RenameField(\n            model_name=\"complexevent\",\n            old_name=\"complex_event\",\n            new_name=\"complex_event_group\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0074_complexeventitem_active.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-07 11:42\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0073_auto_20201007_0858\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"complexeventitem\",\n            name=\"active\",\n            field=models.BooleanField(default=False),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0075_auto_20201007_1609.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-07 16:09\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0074_complexeventitem_active\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"complexevent\",\n            name=\"change_value\",\n        ),\n        migrations.AddField(\n            model_name=\"complexeventgroup\",\n            name=\"default_send_mail\",\n            field=models.BooleanField(\n                default=False, help_text=\"Send mail if no activated event\"\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"complexeventgroup\",\n            name=\"default_value\",\n            field=models.FloatField(\n                blank=True,\n                default=None,\n                help_text=\"Set if no activated event\",\n                null=True,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"complexevent\",\n            name=\"new_value\",\n            field=models.FloatField(\n                blank=True,\n                default=None,\n                help_text=\"For the group variable to change\",\n                null=True,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"complexeventgroup\",\n            name=\"variable_to_change\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"Change the value on event changes\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"complex_variable_to_change\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0076_mail_html_message.py",
    "content": "# Generated by Django 2.2.8 on 2020-10-14 12:19\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0075_auto_20201007_1609\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"mail\",\n            name=\"html_message\",\n            field=models.TextField(blank=True, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0077_auto_20201104_1656.py",
    "content": "# Generated by Django 2.2.8 on 2020-11-04 16:56\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0076_mail_html_message\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"recordeddata\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"recordeddataold\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0078_auto_20201123_1906.py",
    "content": "# Generated by Django 2.2.17 on 2020-11-23 19:06\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0077_auto_20201104_1656\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"backgroundprocess\",\n            name=\"process_class_kwargs\",\n            field=models.CharField(\n                blank=True,\n                default=\"{}\",\n                help_text='arguments in json format will be passed as kwargs while the\\n                                            init of the process instance, example:\\n                                            {\"keywordA\":\"value1\", \"keywordB\":7}',\n                max_length=400,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"complexeventitem\",\n            name=\"variable_limit_high\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"you can choose either an\\n                                            fixed limit or an variable limit that is dependent on the current value of\\n                                            an variable, if you choose a value other than  none for variable limit the\\n                                            fixed limit would be ignored\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"variable_limit_high\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"complexeventitem\",\n            name=\"variable_limit_low\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"you can choose either an\\n                                            fixed limit or an variable limit that is dependent on the current value of\\n                                            an variable, if you choose a value other than  none for variable limit the\\n                                            fixed limit would be ignored\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"variable_limit_low\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicereadtask\",\n            name=\"device\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Device\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicereadtask\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicereadtask\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"variable\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Variable\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"devicewritetask\",\n            name=\"variable_property\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.VariableProperty\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"variable_limit\",\n            field=models.ForeignKey(\n                blank=True,\n                default=None,\n                help_text=\"you can choose either an fixed limit or an variable limit that is\\n                                        dependent on the current value of an variable, if you choose a value other than\\n                                        none for variable limit the fixed limit would be ignored\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"variable_limit\",\n                to=\"pyscada.Variable\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0079_devicehandler.py",
    "content": "# Generated by Django 2.2.8 on 2021-06-01 09:38\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0078_auto_20201123_1906\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"DeviceHandler\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"name\", models.CharField(default=\"\", max_length=255)),\n                (\n                    \"handler_class\",\n                    models.CharField(\n                        default=\"pyscada.visa.devices.HP3456A\",\n                        help_text=\"a Base class to extend can be found at pyscada.PROTOCOL.devices.GenericDevice. Exemple : pyscada.visa.devices.HP3456A, pyscada.smbus.devices.ups_pico, pyscada.serial.devices.AirLinkGX450\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"handler_path\",\n                    models.CharField(\n                        blank=True,\n                        default=None,\n                        help_text=\"If no handler class, pyscada will look at the path. Exemple : /home/pi/my_handler.py\",\n                        max_length=255,\n                        null=True,\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0080_variableproperty_last_modified.py",
    "content": "# Generated by Django 2.2.8 on 2021-07-05 12:28\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0079_devicehandler\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"last_modified\",\n            field=models.DateTimeField(auto_now=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0081_calculatedvariable_periodfield_variablecalculatedfields.py",
    "content": "# Generated by Django 2.2.24 on 2021-11-12 10:30\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nimport pyscada.models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0080_variableproperty_last_modified\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"PeriodField\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"type\",\n                    models.SmallIntegerField(\n                        choices=[\n                            (0, \"min\"),\n                            (1, \"max\"),\n                            (2, \"total\"),\n                            (3, \"difference\"),\n                            (4, \"difference percent\"),\n                            (5, \"delta\"),\n                            (6, \"mean\"),\n                            (7, \"first\"),\n                            (8, \"last\"),\n                            (9, \"count\"),\n                            (10, \"count value\"),\n                            (11, \"range\"),\n                            (12, \"step\"),\n                            (13, \"change count\"),\n                            (14, \"distinct count\"),\n                        ]\n                    ),\n                ),\n                (\n                    \"property\",\n                    models.CharField(\n                        default=\"\",\n                        help_text=\"used for count value for exemple\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"offset_second\",\n                    models.IntegerField(\n                        default=0,\n                        help_text=\"Offset in second. Of set to 30 and length is minute will calculate between 1min30 and 2min30 etc.\",\n                    ),\n                ),\n                (\n                    \"period\",\n                    models.SmallIntegerField(\n                        choices=[\n                            (0, \"second\"),\n                            (1, \"minute\"),\n                            (2, \"hour\"),\n                            (3, \"day\"),\n                            (4, \"week\"),\n                            (5, \"month\"),\n                            (6, \"year\"),\n                        ]\n                    ),\n                ),\n                (\n                    \"period_factor\",\n                    models.PositiveSmallIntegerField(\n                        default=1,\n                        help_text=\"Example: set to 2 and choose minute to have a 2 minutes period\",\n                        validators=[pyscada.models.validate_nonzero],\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"VariableCalculatedFields\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"main_variable\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n                (\"period_fields\", models.ManyToManyField(to=\"pyscada.PeriodField\")),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"CalculatedVariable\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\n                    \"period\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.PeriodField\",\n                    ),\n                ),\n                (\n                    \"store_variable\",\n                    models.OneToOneField(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.Variable\",\n                    ),\n                ),\n                (\n                    \"variable_calculated_fields\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.VariableCalculatedFields\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0082_auto_20211112_1043.py",
    "content": "# Generated by Django 2.2.24 on 2021-11-12 10:43\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0081_calculatedvariable_periodfield_variablecalculatedfields\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"periodfield\",\n            name=\"property\",\n            field=models.CharField(\n                blank=True,\n                default=\"\",\n                help_text=\"used for count value for exemple\",\n                max_length=255,\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0083_auto_20211115_0812.py",
    "content": "# Generated by Django 2.2.24 on 2021-11-15 08:12\n\nimport datetime\nfrom django.db import migrations, models\nfrom datetime import timezone\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0082_auto_20211112_1043\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"periodfield\",\n            name=\"offset_second\",\n        ),\n        migrations.AddField(\n            model_name=\"periodfield\",\n            name=\"start_from\",\n            field=models.DateTimeField(\n                default=datetime.datetime(2021, 11, 15, 0, 0, tzinfo=timezone.utc),\n                help_text=\"Count from this DateTime and then each period_factor*periodcalculate between 1min30 and 2min30 etc.\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0084_auto_20211115_1503.py",
    "content": "# Generated by Django 2.2.24 on 2021-11-15 15:03\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0083_auto_20211115_0812\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\n            old_name=\"VariableCalculatedFields\",\n            new_name=\"CalculatedVariableSelector\",\n        ),\n        migrations.RenameModel(\n            old_name=\"PeriodField\",\n            new_name=\"PeriodicField\",\n        ),\n        migrations.AddField(\n            model_name=\"calculatedvariable\",\n            name=\"last_check\",\n            field=models.DateTimeField(blank=True, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0085_calculatedvariableselector_active.py",
    "content": "# Generated by Django 2.2.24 on 2021-11-15 15:21\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0084_auto_20211115_1503\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"calculatedvariableselector\",\n            name=\"active\",\n            field=models.BooleanField(default=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0086_auto_20211115_1612.py",
    "content": "# Generated by Django 2.2.24 on 2021-11-15 16:12\n\nimport datetime\nfrom django.db import migrations, models\nfrom datetime import timezone\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0085_calculatedvariableselector_active\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"property\",\n            field=models.CharField(\n                blank=True,\n                default=\"\",\n                help_text=\"For count value : enter the value to count\",\n                max_length=255,\n                null=True,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"start_from\",\n            field=models.DateTimeField(\n                default=datetime.datetime(2021, 11, 15, 0, 0, tzinfo=timezone.utc),\n                help_text=\"Calculate from this DateTime and then each period_factor*period\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0087_auto_20211116_1329.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-16 13:29\n\nimport datetime\nfrom django.db import migrations, models\nfrom datetime import timezone\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0086_auto_20211115_1612\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ExtendedCalculatedVariable\",\n            fields=[],\n            options={\n                \"verbose_name\": \"Calculated Variable\",\n                \"verbose_name_plural\": \"Calculated Variable\",\n                \"proxy\": True,\n                \"indexes\": [],\n                \"constraints\": [],\n            },\n            bases=(\"pyscada.variable\",),\n        ),\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"start_from\",\n            field=models.DateTimeField(\n                default=datetime.datetime(2021, 11, 16, 0, 0, tzinfo=timezone.utc),\n                help_text=\"Calculate from this DateTime and then each period_factor*period\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"type\",\n            field=models.SmallIntegerField(\n                choices=[\n                    (0, \"min\"),\n                    (1, \"max\"),\n                    (2, \"total\"),\n                    (3, \"difference\"),\n                    (4, \"difference percent\"),\n                    (5, \"delta\"),\n                    (6, \"mean\"),\n                    (7, \"first\"),\n                    (8, \"last\"),\n                    (9, \"count\"),\n                    (10, \"count value\"),\n                    (11, \"range\"),\n                    (12, \"step\"),\n                    (13, \"change count\"),\n                    (14, \"distinct count\"),\n                ],\n                help_text=\"Min: Minimum value of a field<br>Max: Maximum value of a field<br>Total: Sum of all values in a field<br>Difference: Difference between first and last value of a field<br>Difference percent: Percentage change between first and last value of a field<br>Delta: Cumulative change in value, only counts increments<br>Mean: Mean value of all values in a field<br>First: First value in a field<br>Last: Last value in a field<br>Count: Number of values in a field<br>Count value: Number of a value in a field<br>Range: Difference between maximum and minimum values of a field<br>Step: Minimal interval between values of a field<br>Change count: Number of times the field’s value changes<br>Distinct count: Number of unique values in a field\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0088_auto_20211117_1023.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-17 10:23\n\nimport datetime\nfrom django.db import migrations, models\nfrom datetime import timezone\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0087_auto_20211116_1329\"),\n    ]\n\n    operations = [\n        migrations.AlterModelOptions(\n            name=\"extendedcalculatedvariable\",\n            options={\n                \"verbose_name\": \"Calculated variable\",\n                \"verbose_name_plural\": \"Calculated variables\",\n            },\n        ),\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"start_from\",\n            field=models.DateTimeField(\n                default=datetime.datetime(2021, 11, 17, 0, 0, tzinfo=timezone.utc),\n                help_text=\"Calculate from this DateTime and then each period_factor*period\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"name\",\n            field=models.SlugField(\n                max_length=200, unique=True, verbose_name=\"variable name\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0089_calculatedvariable_state.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-17 12:37\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0088_auto_20211117_1023\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"calculatedvariable\",\n            name=\"state\",\n            field=models.CharField(default=\"\", max_length=30),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0090_auto_20211117_1239.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-17 12:39\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0089_calculatedvariable_state\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"calculatedvariable\",\n            name=\"state\",\n            field=models.CharField(default=\"\", max_length=100),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0091_auto_20211118_1019.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-18 10:19\n\nimport datetime\nfrom django.db import migrations, models\nfrom datetime import timezone\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0090_auto_20211117_1239\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"start_from\",\n            field=models.DateTimeField(\n                default=datetime.datetime(2021, 11, 18, 0, 0, tzinfo=timezone.utc),\n                help_text=\"Calculate from this DateTime and then each period_factor*period\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0092_auto_20211125_1054.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-25 10:54\n\nfrom django.db import migrations, models\nimport pyscada.models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0091_auto_20211118_1019\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"start_from\",\n            field=models.DateTimeField(\n                default=pyscada.models.start_from_default,\n                help_text=\"Calculate from this DateTime and then each period_factor*period\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0093_auto_20211125_1256.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-25 12:56\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0092_auto_20211125_1054\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Dictionary\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"name\", models.CharField(default=\"\", max_length=400)),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"DictionaryItem\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"label\", models.CharField(default=\"\", max_length=400)),\n                (\"value\", models.CharField(default=\"\", max_length=400)),\n                (\n                    \"dictionary\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.Dictionary\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"dictionary\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Dictionary\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0094_move_dictionaries.py",
    "content": "# Generated by Django 2.2.8 on 2021-11-25 12:56\n\nfrom django.db import migrations, models\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ncharts_dict = {}\n\n\ndef move_dicts(apps, schema_editor):\n    HMIDictionaries = apps.get_model(\"hmi\", \"Dictionary\")\n    HMIDictionaryItems = apps.get_model(\"hmi\", \"DictionaryItem\")\n    Dictionaries = apps.get_model(\"pyscada\", \"Dictionary\")\n    DictionaryItems = apps.get_model(\"pyscada\", \"DictionaryItem\")\n    Variables = apps.get_model(\"pyscada\", \"Variable\")\n    ControlItems = apps.get_model(\"hmi\", \"ControlItem\")\n    db_alias = schema_editor.connection.alias\n\n    hmi_dict_set = HMIDictionaries.objects.all()\n    hmi_dictitems_set = HMIDictionaryItems.objects.all()\n    var_set = Variables.objects.all()\n    count = 0\n    dict = []\n    for item in hmi_dict_set:\n        d = Dictionaries(\n            id=item.id,\n            name=item.name,\n        )\n        dict.append(d)\n        count += 1\n    logger.info(\"moved %d Dictionaries\\n\" % count)\n    Dictionaries.objects.using(db_alias).bulk_create(dict)\n\n    count = 0\n    dictitems = []\n    for item in hmi_dictitems_set:\n        di = DictionaryItems(\n            id=item.id,\n            label=item.label,\n            value=item.value,\n            dictionary_id=item.dictionary.id,\n        )\n        dictitems.append(di)\n        count += 1\n    logger.info(\"moved %d DictionaryItems\\n\" % count)\n    DictionaryItems.objects.using(db_alias).bulk_create(dictitems)\n\n    count = 0\n    variables = []\n    for item in var_set:\n        CId = ControlItems.objects.filter(\n            variable=item,\n            display_value_options__isnull=False,\n            display_value_options__dictionary__isnull=False,\n        )\n        if len(CId):\n            item.dictionary = Dictionaries.objects.get(\n                id=CId.first().display_value_options.dictionary.id\n            )\n            variables.append(item)\n            count += 1\n        else:\n            CIc = ControlItems.objects.filter(\n                variable=item,\n                control_element_options__isnull=False,\n                control_element_options__dictionary__isnull=False,\n            )\n            if len(CIc):\n                item.dictionary = Dictionaries.objects.get(\n                    id=CIc.first().control_element_options.dictionary.id\n                )\n                variables.append(item)\n                count += 1\n\n    logger.info(\"moved %d dictionaries to variables\\n\" % count)\n    Variables.objects.using(db_alias).bulk_update(variables, [\"dictionary\"])\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0093_auto_20211125_1256\"),\n        (\"hmi\", \"0054_displayvalueoption_type\"),\n    ]\n\n    operations = [\n        migrations.RunPython(move_dicts, reverse_code=migrations.RunPython.noop),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0095_auto_20211203_1949.py",
    "content": "# Generated by Django 2.2.24 on 2021-12-03 19:49\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0094_move_dictionaries\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"calculatedvariableselector\",\n            name=\"main_variable\",\n            field=models.OneToOneField(\n                on_delete=django.db.models.deletion.CASCADE, to=\"pyscada.Variable\"\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0096_auto_20211210_0924.py",
    "content": "# Generated by Django 2.2.8 on 2021-12-10 09:24\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0095_auto_20211203_1949\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"variableproperty\",\n            name=\"dictionary\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.Dictionary\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"periodicfield\",\n            name=\"property\",\n            field=models.CharField(\n                blank=True,\n                default=\"\",\n                help_text=\"Min: superior or equal this value, ex: 53.5 (use >53.5 for strictly superior)<br>Max: lower or equal this value, ex: 53.5 (use <53.5 for strictly lower)<br>Count value : enter the value to count\",\n                max_length=255,\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0097_auto_20220118_1046.py",
    "content": "# Generated by Django 2.2.8 on 2022-01-18 10:46\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0096_auto_20211210_0924\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"value_class\",\n            field=models.CharField(\n                choices=[\n                    (\"FLOAT32\", \"REAL (FLOAT32)\"),\n                    (\"FLOAT32\", \"SINGLE (FLOAT32)\"),\n                    (\"FLOAT32\", \"FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT  (FLOAT64)\"),\n                    (\"FLOAT64\", \"DOUBLE (FLOAT64)\"),\n                    (\"FLOAT64\", \"FLOAT64\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"FLOAT48\", \"FLOAT48\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"INT48\", \"INT48\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD (UINT32)\"),\n                    (\"UINT32\", \"UINT32\"),\n                    (\"INT16\", \"INT (INT16)\"),\n                    (\"INT16\", \"INT16\"),\n                    (\"UINT16\", \"WORD (UINT16)\"),\n                    (\"UINT16\", \"UINT (UINT16)\"),\n                    (\"UINT16\", \"UINT16\"),\n                    (\"INT8\", \"INT8\"),\n                    (\"UINT8\", \"UINT8\"),\n                    (\"BOOLEAN\", \"BOOL (BOOLEAN)\"),\n                    (\"BOOLEAN\", \"BOOLEAN\"),\n                ],\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0098_alter_device_polling_interval.py",
    "content": "# Generated by Django 3.2 on 2022-05-30 14:41\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0097_auto_20220118_1046\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"device\",\n            name=\"polling_interval\",\n            field=models.FloatField(\n                choices=[\n                    (0.1, \"100 Milliseconds\"),\n                    (0.5, \"500 Milliseconds\"),\n                    (1.0, \"1 Second\"),\n                    (5.0, \"5 Seconds\"),\n                    (10.0, \"10 Seconds\"),\n                    (15.0, \"15 Seconds\"),\n                    (30.0, \"30 Seconds\"),\n                    (60.0, \"1 Minute\"),\n                    (150.0, \"2.5 Mintues\"),\n                    (300.0, \"5 Minutes\"),\n                    (360.0, \"6 Minutes (10 times per Hour)\"),\n                    (600.0, \"10 Minutes\"),\n                    (900.0, \"15 Minutes\"),\n                    (1800.0, \"30 Minutes\"),\n                    (3600.0, \"1 Hour\"),\n                    (21600.0, \"6 Hours\"),\n                    (43200.0, \"12 Hours\"),\n                    (86400.0, \"1 Day\"),\n                    (604800.0, \"1 Week\"),\n                ],\n                default=5.0,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0099_alter_dictionaryitem_label.py",
    "content": "# Generated by Django 3.2 on 2023-02-01 12:12\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0098_alter_device_polling_interval\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"dictionaryitem\",\n            name=\"label\",\n            field=models.CharField(blank=True, default=\"\", max_length=400),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0100_device_instrument_handler.py",
    "content": "# Generated by Django 3.2 on 2023-02-06 15:52\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0099_alter_dictionaryitem_label\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"device\",\n            name=\"instrument_handler\",\n            field=models.ForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"pyscada.devicehandler\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0101_complexeventchangevariable.py",
    "content": "# Generated by Django 4.2 on 2023-04-24 10:15\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0100_device_instrument_handler\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"ComplexEventChangeVariable\",\n            fields=[\n                (\"id\", models.AutoField(primary_key=True, serialize=False)),\n                (\"value\", models.CharField(max_length=400)),\n                (\n                    \"complex_event\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.complexevent\",\n                    ),\n                ),\n                (\n                    \"complex_event_group\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.complexeventgroup\",\n                    ),\n                ),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        blank=True,\n                        default=None,\n                        help_text=\"Variable to change on event changes\",\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"pyscada.variable\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0102_move_complex_event_variables.py",
    "content": "# Generated by Django 4.2 on 2023-04-24 09:56\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef move_changed_variables(apps, schema_editor):\n    ComplexEventGroups = apps.get_model(\"pyscada\", \"ComplexEventGroup\")\n    ComplexEvents = apps.get_model(\"pyscada\", \"ComplexEvent\")\n    ComplexEventChangeVariables = apps.get_model(\n        \"pyscada\", \"ComplexEventChangeVariable\"\n    )\n\n    db_alias = schema_editor.connection.alias\n\n    ComplexEventGroups_set = ComplexEventGroups.objects.all()\n    ComplexEvents_set = ComplexEvents.objects.all()\n    count = 0\n    dict = []\n    for item in ComplexEventGroups_set:\n        if item.variable_to_change is not None:\n            if item.default_value is not None:\n                d = ComplexEventChangeVariables(\n                    variable=item.variable_to_change,\n                    value=item.default_value,\n                    complex_event_group=item,\n                )\n                dict.append(d)\n                count += 1\n            for item2 in ComplexEvents_set.filter(complex_event_group=item):\n                if item2.new_value is not None:\n                    d = ComplexEventChangeVariables(\n                        variable=item.variable_to_change,\n                        value=item2.new_value,\n                        complex_event=item2,\n                    )\n                    dict.append(d)\n                    count += 1\n    logger.info(\"created %d ComplexEventChangeVariables\\n\" % count)\n    ComplexEventChangeVariables.objects.using(db_alias).bulk_create(dict)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0101_complexeventchangevariable\"),\n    ]\n\n    operations = [\n        migrations.RunPython(\n            move_changed_variables, reverse_code=migrations.RunPython.noop\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0103_remove_complexevent_new_value_and_more.py",
    "content": "# Generated by Django 4.2 on 2023-04-24 10:18\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0102_move_complex_event_variables\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"complexevent\",\n            name=\"new_value\",\n        ),\n        migrations.RemoveField(\n            model_name=\"complexeventgroup\",\n            name=\"default_value\",\n        ),\n        migrations.RemoveField(\n            model_name=\"complexeventgroup\",\n            name=\"variable_to_change\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0104_rename_complexeventitem_complexeventinput_and_more.py",
    "content": "# Generated by Django 4.2 on 2023-04-24 13:25\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0103_remove_complexevent_new_value_and_more\"),\n    ]\n\n    operations = [\n        migrations.RenameModel(\n            old_name=\"ComplexEventItem\",\n            new_name=\"ComplexEventInput\",\n        ),\n        migrations.RenameModel(\n            old_name=\"ComplexEvent\",\n            new_name=\"ComplexEventLevel\",\n        ),\n        migrations.RenameModel(\n            old_name=\"ComplexEventChangeVariable\",\n            new_name=\"ComplexEventOutput\",\n        ),\n        migrations.RenameField(\n            model_name=\"complexeventinput\",\n            old_name=\"complex_event\",\n            new_name=\"complex_event_level\",\n        ),\n        migrations.RenameField(\n            model_name=\"complexeventlevel\",\n            old_name=\"complex_event_group\",\n            new_name=\"complex_event\",\n        ),\n        migrations.RenameField(\n            model_name=\"recordedevent\",\n            old_name=\"complex_event_group\",\n            new_name=\"complex_event\",\n        ),\n        migrations.RenameField(\n            model_name=\"complexeventoutput\",\n            old_name=\"complex_event\",\n            new_name=\"complex_event_level\",\n        ),\n        migrations.RenameField(\n            model_name=\"complexeventoutput\",\n            old_name=\"complex_event_group\",\n            new_name=\"complex_event\",\n        ),\n        migrations.RenameModel(\n            old_name=\"ComplexEventGroup\",\n            new_name=\"ComplexEvent\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0105_edit_generic_device_protocol.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by Django 1.10.2 on 2017-02-24 12:45\nfrom __future__ import unicode_literals\n\nfrom django.db import migrations\n\n\ndef forwards_func(apps, schema_editor):\n    # We get the model from the versioned app registry;\n    # if we directly import it, it'll be the wrong version\n    DeviceProtocol = apps.get_model(\"pyscada\", \"DeviceProtocol\")\n    db_alias = schema_editor.connection.alias\n    DeviceProtocol.objects.filter(pk=1).update(\n        device_class=\"pyscada.generic.device\", daq_daemon=True, single_thread=True\n    )\n\n\ndef reverse_func(apps, schema_editor):\n    # forwards_func() creates two Country instances,\n    # so reverse_func() should delete them.\n    DeviceProtocol = apps.get_model(\"pyscada\", \"DeviceProtocol\")\n    db_alias = schema_editor.connection.alias\n    DeviceProtocol.objects.filter(pk=1).update(\n        device_class=\"None\", daq_daemon=False, single_thread=False\n    )\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0104_rename_complexeventitem_complexeventinput_and_more\"),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards_func, reverse_func),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0106_datasource_datasourcemodel_djangodatabase_and_more.py",
    "content": "# Generated by Django 4.2.2 on 2023-12-13 11:11\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\ndef forwards_func(apps, schema_editor):\n    # We get the model from the versioned app registry;\n    # if we directly import it, it'll be the wrong version\n    DataSourceModel = apps.get_model(\"pyscada\", \"DataSourceModel\")\n    DataSource = apps.get_model(\"pyscada\", \"DataSource\")\n    DjangoDatabase = apps.get_model(\"pyscada\", \"DjangoDatabase\")\n    db_alias = schema_editor.connection.alias\n    dsm, _ = DataSourceModel.objects.using(db_alias).update_or_create(\n        inline_model_name=\"DjangoDatabase\",\n        defaults={\n            \"name\": \"Django database\",\n            \"can_add\": False,\n            \"can_change\": False,\n            \"can_select\": True,\n        },\n    )\n    ds, _ = DataSource.objects.using(db_alias).update_or_create(\n        id=1,\n        defaults={\n            \"datasource_model\": dsm,\n        },\n    )\n    dd, _ = DjangoDatabase.objects.using(db_alias).update_or_create(\n        datasource=ds,\n        defaults={\n            \"data_model_app_name\": \"pyscada\",\n            \"data_model_name\": \"RecordedData\",\n        },\n    )\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0105_edit_generic_device_protocol\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"DataSource\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"DataSourceModel\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"name\", models.CharField(max_length=100)),\n                (\"inline_model_name\", models.CharField(max_length=100)),\n                (\n                    \"can_add\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"True to enable custom data sources of this type to be added from the administration panel.\",\n                    ),\n                ),\n                (\n                    \"can_change\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"True to enable custom data sources of this type to be modified from the administration panel.\",\n                    ),\n                ),\n                (\n                    \"can_select\",\n                    models.BooleanField(\n                        default=True,\n                        help_text=\"False to hide this type of custom data source in the variable administration panel.\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"DjangoDatabase\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"data_model_app_name\", models.CharField(max_length=50)),\n                (\"data_model_name\", models.CharField(max_length=50)),\n                (\n                    \"datasource\",\n                    models.OneToOneField(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.datasource\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"datasource\",\n            name=\"datasource_model\",\n            field=models.ForeignKey(\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"pyscada.datasourcemodel\",\n            ),\n        ),\n        migrations.RunPython(forwards_func, migrations.RunPython.noop),\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"datasource\",\n            field=models.ForeignKey(\n                default=1,\n                help_text=\"Select a custom data source to read/write data for this variable.\",\n                on_delete=models.SET(1),\n                to=\"pyscada.datasource\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0107_alter_calculatedvariableselector_period_fields.py",
    "content": "# Generated by Django 4.2.5 on 2023-12-14 11:37\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0106_datasource_datasourcemodel_djangodatabase_and_more\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"calculatedvariableselector\",\n            name=\"period_fields\",\n            field=models.ManyToManyField(blank=True, to=\"pyscada.periodicfield\"),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0108_remove_calculatedvariable_period_and_more.py",
    "content": "# Generated by Django 4.2.5 on 2024-01-16 16:16\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0107_alter_calculatedvariableselector_period_fields\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"calculatedvariable\",\n            name=\"period\",\n        ),\n        migrations.RemoveField(\n            model_name=\"calculatedvariable\",\n            name=\"store_variable\",\n        ),\n        migrations.RemoveField(\n            model_name=\"calculatedvariable\",\n            name=\"variable_calculated_fields\",\n        ),\n        migrations.RemoveField(\n            model_name=\"calculatedvariableselector\",\n            name=\"main_variable\",\n        ),\n        migrations.RemoveField(\n            model_name=\"calculatedvariableselector\",\n            name=\"period_fields\",\n        ),\n        migrations.DeleteModel(\n            name=\"ExtendedCalculatedVariable\",\n        ),\n        migrations.DeleteModel(\n            name=\"CalculatedVariable\",\n        ),\n        migrations.DeleteModel(\n            name=\"CalculatedVariableSelector\",\n        ),\n        migrations.DeleteModel(\n            name=\"PeriodicField\",\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0109_alter_variable_value_class.py",
    "content": "# Generated by Django 4.2.5 on 2024-02-19 14:33\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0108_remove_calculatedvariable_period_and_more\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"variable\",\n            name=\"value_class\",\n            field=models.CharField(\n                choices=[\n                    (\"FLOAT32\", \"REAL, SINGLE or FLOAT32\"),\n                    (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n                    (\"FLOAT64\", \"LREAL, FLOAT, DOUBLE or FLOAT64\"),\n                    (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n                    (\"FLOAT48\", \"FLOAT48\"),\n                    (\"INT64\", \"INT64\"),\n                    (\"UINT64\", \"UINT64\"),\n                    (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n                    (\"INT48\", \"INT48\"),\n                    (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n                    (\"INT32\", \"INT32\"),\n                    (\"UINT32\", \"DWORD or UINT32\"),\n                    (\"INT16\", \"INT or INT16\"),\n                    (\"UINT16\", \"WORD, UINT or UINT16\"),\n                    (\"INT8\", \"INT8\"),\n                    (\"UINT8\", \"UINT8\"),\n                    (\"BOOLEAN\", \"BOOL or BOOLEAN\"),\n                ],\n                default=\"FLOAT64\",\n                max_length=15,\n                verbose_name=\"value_class\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0110_variable_readable.py",
    "content": "# Generated by Django 5.0.3 on 2024-07-01 13:34\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0109_alter_variable_value_class\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"variable\",\n            name=\"readable\",\n            field=models.BooleanField(default=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0111_devicehandlerparameter.py",
    "content": "# Generated by Django 5.1.6 on 2025-06-02 10:18\n\nimport django.db.models.deletion\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0110_variable_readable\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"DeviceHandlerParameter\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"name\", models.CharField(max_length=255)),\n                (\"value\", models.CharField(max_length=255)),\n                (\n                    \"instrument\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE, to=\"pyscada.device\"\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0112_alter_devicehandlerparameter_value.py",
    "content": "# Generated by Django 5.1.6 on 2025-06-06 13:02\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0111_devicehandlerparameter\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"devicehandlerparameter\",\n            name=\"value\",\n            field=models.CharField(blank=True, max_length=255, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0113_variablehandlerparameter.py",
    "content": "# Generated by Django 5.1.6 on 2025-06-06 14:10\n\nimport django.db.models.deletion\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0112_alter_devicehandlerparameter_value\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"VariableHandlerParameter\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"name\", models.CharField(max_length=255)),\n                (\"value\", models.CharField(blank=True, max_length=255, null=True)),\n                (\n                    \"variable\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"pyscada.variable\",\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0114_alter_devicehandlerparameter_value_and_more.py",
    "content": "# Generated by Django 5.1.6 on 2025-06-16 08:02\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"pyscada\", \"0113_variablehandlerparameter\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"devicehandlerparameter\",\n            name=\"value\",\n            field=models.CharField(blank=True, default=\"\", max_length=255),\n        ),\n        migrations.AlterField(\n            model_name=\"variablehandlerparameter\",\n            name=\"value\",\n            field=models.CharField(blank=True, default=\"\", max_length=255),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0115_remove_recordeddata_variable_and_more.py",
    "content": "# Generated by Django 5.2.10 on 2026-02-02 12:27\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('pyscada', '0114_alter_devicehandlerparameter_value_and_more'),\n    ]\n\n    operations = [\n        migrations.SeparateDatabaseAndState(\n            state_operations=[\n                migrations.RemoveField(\n                    model_name='recordeddata',\n                    name='variable',\n                ),\n                migrations.RemoveField(\n                    model_name='recordeddataold',\n                    name='variable',\n                ),\n                migrations.DeleteModel(\n                    name='DjangoDatabase',\n                ),\n                migrations.DeleteModel(\n                    name='RecordedData',\n                ),\n                migrations.DeleteModel(\n                    name='RecordedDataOld',\n                ),\n            ],\n            database_operations=[],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/0116_variable_pause_recording.py",
    "content": "# Generated by Django 5.2.10 on 2026-02-10 15:03\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('pyscada', '0115_remove_recordeddata_variable_and_more'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='variable',\n            name='pause_recording',\n            field=models.BooleanField(default=False, help_text='data will be discarded entirely while True'),\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/models.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.db import models\nfrom django.db.utils import IntegrityError, ProgrammingError\nfrom django.contrib.auth.models import User\nfrom django.conf import settings\n\nfrom django.core.exceptions import ValidationError\nfrom django.core.mail import send_mail\nfrom django.utils.timezone import now, make_aware\nfrom django.db.models.signals import post_save\nfrom django.db.models.fields.related import OneToOneRel\nfrom django.forms.models import BaseInlineFormSet\nfrom django.apps import apps\n\nfrom pyscada.utils import _get_objects_for_html as get_objects_for_html\n\nfrom asgiref.sync import sync_to_async\nfrom six import text_type\nimport traceback\nimport time\nimport datetime\nimport json\nimport signal\nfrom os import kill, waitpid\nimport os\nimport sys\n\nif os.name != \"nt\":\n    from os import WNOHANG\n\nfrom struct import *\nfrom os import getpid\nimport errno\nimport numpy as np\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ntry:\n    import channels.layers\n    from channels.exceptions import InvalidChannelLayerError\n    from channels.exceptions import ChannelFull\n    from redis.exceptions import ConnectionError\n    from asgiref.sync import async_to_sync\n    from asyncio import wait_for\n\n    try:\n        from asyncio.exceptions import TimeoutError as asyncioTimeoutError\n    except ModuleNotFoundError:\n        # for python version < 3.8\n        from asyncio import TimeoutError as asyncioTimeoutError\n    if channels.layers.get_channel_layer() is None:\n        logger.warning(\"Django Channels is not working. Missing config in settings ?\")\n        channels_driver = False\n    else:\n        try:\n\n            async def channels_test():\n                await wait_for(\n                    channels.layers.get_channel_layer().receive(\"test\"), timeout=0.1\n                )\n\n            # TODO : should we remove async_to_sync when using asgi ?\n            # I get this error : RuntimeError: You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly.\n            async_to_sync(channels_test)()\n            channels_driver = True\n        except ConnectionError as e:\n            # Redis service failed to start\n            logger.warning(\"Redis service failed to start. %s\" % e)\n            channels_driver = False\nexcept (ImportError, ModuleNotFoundError):\n    channels_driver = False\nexcept ConnectionRefusedError:\n    logger.warning(\"Django Channels is not working. redis-server not running ?\")\n    channels_driver = False\nexcept (TimeoutError, asyncioTimeoutError):\n    channels_driver = True\n\n\n# Kept for pyscada migration 81\n\n\ndef validate_nonzero(value):\n    if value == 0:\n        raise ValidationError(\n            _(\"Quantity %(value)s is not allowed\"),\n            params={\"value\": value},\n        )\n\n\ndef start_from_default():\n    return make_aware(\n        datetime.datetime.combine(datetime.date.today(), datetime.datetime.min.time())\n    )\n\n\ndef check_datasource_method(func):\n    def wrapper(self, *args, **kwargs):\n        if self.get_related_datasource() is None:\n            logger.warning(\n                f\"DataSource {self.datasource_model.inline_model_name} model not found.\"\n            )\n            return\n\n        if not hasattr(self.get_related_datasource(), func.__name__):\n            logger.warning(\n                f\"{self.get_related_datasource()} class needs to have a {func.__name__} function.\"\n            )\n            return\n        return func(self, *args, **kwargs)\n    return wrapper\n\n#\n# Manager\n#\n\nclass VariableManager(models.Manager):\n    \"\"\"\n    Manager to read and write values from/to multiple variables\n    \"\"\"\n\n    def _get_variables_by_datasource(self, **kwargs):\n        if \"variables\" in kwargs:\n            kwargs[\"items\"] = kwargs[\"variables\"]\n        elif \"variable\" in kwargs:\n            kwargs[\"items\"] = self.filter(id=kwargs[\"variable\"].id)\n        elif \"variable_ids\" in kwargs:\n            kwargs[\"items\"] = self.filter(id__in=kwargs[\"variable_ids\"])\n        elif \"variable_id\" in kwargs:\n            kwargs[\"items\"] = self.filter(id=kwargs[\"variable_id\"])\n        items = kwargs[\"items\"] if \"items\" in kwargs else []\n        datasource_dict = {}\n        for item in items:\n            if item.datasource not in datasource_dict:\n                datasource_dict[item.datasource] = []\n            datasource_dict[item.datasource].append(item)\n        return datasource_dict\n\n    def _send_cov_notification(self, variable):\n        \"\"\"\n        Sends a COV Notification via the Django Signal interface\n        :param value:\n        :return:\n        \"\"\"\n        for app_config in apps.get_app_configs():\n            if hasattr(app_config, \"pyscada_send_cov_notification\") and callable(\n                app_config.pyscada_send_cov_notification\n            ):\n                try:\n                    app_config.pyscada_send_cov_notification(variable=variable)\n                except:\n                    logger.error(\n                        f\"{self}, unhandled exception in COV Receiver application\",\n                        exc_info=True,\n                    )\n\n    def read_multiple(self, **kwargs):\n        logger.info(\n            \"the use of 'read_multiple' method is deprecated use 'query_datapoints' instead\"\n        )\n        return self.query_datapoints(**kwargs)\n\n    def query_datapoints(self,\n        variable_ids:list,\n        time_min=0,\n        time_max=None,\n        query_first_value=False,\n        **kwargs):\n        \"\"\"query data from the database\n        Args:\n            variable_ids: list of variable ids\n            time_min (float, optional):\n            time_max (float, optional):\n            query_first_value (bool, optional):\n\n        Returns:\n            dict: {\n                variable_id: [[],..],\n                \"timestamp\": max_timestamp_value,\n                \"date_saved_max\": max_date_saved_value\n                }\n        \"\"\"\n        data = {}\n        datasource_dict = self._get_variables_by_datasource(variable_ids=variable_ids)\n        for datasource in datasource_dict:\n            related_datasource = datasource.get_related_datasource()\n            if related_datasource is None:\n                data = data\n                logger.info(\n                    f\"Cannot read values for variables using {datasource} datasource. No related data source defined.\"\n                )\n                continue\n            # use only the variable ids for this datasource\n            variable_ids = [v.id for v in datasource_dict[datasource]]\n            data_temp = related_datasource.query_datapoints(\n                variable_ids=variable_ids,\n                time_min=time_min,\n                time_max=time_max,\n                query_first_value=query_first_value,\n                **kwargs\n                )\n            if data_temp is None:\n                continue\n            timestamp = max(data.get(\"timestamp\", 0), data_temp.get(\"timestamp\", 0))\n            date_saved_max = max(\n                data.get(\"date_saved_max\", 0), data_temp.get(\"date_saved_max\", 0)\n            )\n            data = data | data_temp\n            data[\"timestamp\"] = timestamp\n            data[\"date_saved_max\"] = date_saved_max\n\n        return data\n\n    def write_multiple(self, **kwargs):\n        \"\"\"writes cached datapoints to the database (deprecated)\n        \"\"\"\n        logger.info(\n            \"the use of 'write_multiple' method is deprecated use 'write_datapoints' instead\"\n        )\n        return self.write_datapoints(**kwargs)\n\n    def write_datapoints(self, items: list, date_saved=None, **kwargs):\n        \"\"\"writes cached datapoints to the database\n\n        Args:\n            items:  list of variable instances\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n            **kwargs: Arbitrary keyword arguments. Will be passed to the datasource\n                write_datapoints function call\n\n        Returns:\n            True\n        \"\"\"\n        datasource_dict = self._get_variables_by_datasource(items=items, **kwargs)\n        for datasource in datasource_dict:\n            related_datasource = datasource.get_related_datasource()\n            if related_datasource is None:\n                logger.info(\n                    f\"Cannot write values for variables using {datasource} datasource. No related data source defined.\"\n                )\n                continue\n\n            # use only the variable for this datasource\n            items = [v for v in datasource_dict[datasource]]\n            for item in items: # FIXME move to a better location\n                if item.cached_values_to_write:\n                    self._send_cov_notification(item)\n            related_datasource.write_datapoints(items=items, date_saved=date_saved, **kwargs)\n\n        return True\n\n    def write_raw_datapoints(self, datapoints, date_saved=None, **kwargs):\n        \"\"\"writes raw datapoints to the database\n\n        Args:\n            datapoints:  {variable_id: [[timestamp, value, date_saved],...]} with\n                timestamp in s\n                value\n                date_saved as datetime, timestamp in s, None or ommited\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n            **kwargs: Arbitrary keyword arguments. Will be passed to the datasource\n                write_raw_datapoints function call\n\n        Returns:\n            True\n        \"\"\"\n        datasource_dict = self._get_variables_by_datasource(variable_ids=datapoints.keys(), **kwargs)\n        for datasource in datasource_dict:\n            related_datasource = datasource.get_related_datasource()\n            if related_datasource is None:\n                logger.info(\n                    f\"Cannot write values for variables using {datasource} datasource. No related data source defined.\"\n                )\n                continue\n            # use only the variable for this datasource\n            datapoints_out = {v.pk: datapoints[v.pk] for v in datasource_dict[datasource]}\n            related_datasource.write_raw_datapoints(datapoints=datapoints_out, date_saved=date_saved, **kwargs)\n\n        return True\n\nclass VariablePropertyManager(models.Manager):\n    \"\"\" \"\"\"\n\n    def update_or_create_property(\n        self,\n        variable,\n        name,\n        value,\n        value_class=\"string\",\n        property_class=None,\n        timestamp=None,\n        **kwargs,\n    ):\n        \"\"\"\n\n        :param variable: Variable Object\n        :param name: Property Name (DEVICE:PROPERTY_NAME)\n        :param value: a value\n        :param value_class: type or class of the value\n        :param property_class: class of the property\n        :param timestamp:\n        :return: VariableProperty Object\n        \"\"\"\n        if type(variable) == Variable:\n            kwargs = {\"name\": name.upper(), \"variable_id\": variable.pk}\n        elif type(variable) == int or type(variable) == float:\n            kwargs = {\"name\": name.upper(), \"variable_id\": variable}\n        else:\n            logger.debug(\n                \"update_or_create_property failed with variable : \"\n                + str(variable)\n                + \" - and property name : \"\n                + str(name)\n            )\n            return None\n\n        vp = (\n            super(VariablePropertyManager, self).get_queryset().filter(**kwargs).first()\n        )\n        kwargs[\"value_class\"] = value_class.upper()\n        if timestamp is not None:\n            kwargs[\"timestamp\"] = timestamp\n        if property_class is not None:\n            kwargs[\"property_class\"] = property_class\n        if value_class.upper() in [\"STRING\"]:\n            kwargs[\"value_string\"] = str(value)[\n                : VariableProperty._meta.get_field(\"value_string\").max_length\n            ]\n        elif value_class.upper() in [\n            \"FLOAT\",\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT32\",\n            \"SINGLE\",\n            \"REAL\",\n        ]:\n            kwargs[\"value_float64\"] = value\n        elif value_class.upper() in [\"INT64\", \"UINT32\", \"DWORD\"]:\n            kwargs[\"value_int64\"] = value\n        elif value_class.upper() in [\"WORD\", \"UINT\", \"UINT16\", \"INT32\"]:\n            kwargs[\"value_int32\"] = value\n        elif value_class.upper() in [\"INT16\", \"INT8\", \"UINT8\"]:\n            kwargs[\"value_int16\"] = value\n        elif value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            kwargs[\"value_boolean\"] = value\n        if vp:\n            # update\n            for key, value in kwargs.items():\n                setattr(vp, key, value)\n            vp.save()\n            self._send_cov_notification(vp)\n        else:\n            # create\n            vp = VariableProperty(**kwargs)\n            vp.save()\n            self._send_cov_notification(vp)\n\n        return vp\n\n    def get_property(self, variable, name, **kwargs):\n        if type(variable) == Variable:\n            vp = (\n                super(VariablePropertyManager, self)\n                .get_queryset()\n                .filter(variable_id=variable.pk, name=name.upper(), **kwargs)\n                .first()\n            )\n        elif type(variable) == int or type(variable) == float:\n            vp = (\n                super(VariablePropertyManager, self)\n                .get_queryset()\n                .filter(variable_id=variable, name=name.upper(), **kwargs)\n                .first()\n            )\n        else:\n            return None\n        if vp:\n            return vp\n        else:\n            return None\n\n    def update_property(\n        self, variable_property=None, variable=None, name=None, value=None, **kwargs\n    ):\n        if type(variable_property) == VariableProperty:\n            vp = (\n                super(VariablePropertyManager, self)\n                .get_queryset()\n                .filter(pk=variable_property.pk)\n                .first()\n            )\n        elif type(variable_property) == int or type(variable_property) == float:\n            vp = (\n                super(VariablePropertyManager, self)\n                .get_queryset()\n                .filter(pk=variable_property)\n                .first()\n            )\n        elif type(variable) == Variable:\n            vp = (\n                super(VariablePropertyManager, self)\n                .get_queryset()\n                .filter(variable_id=variable.pk, name=name.upper(), **kwargs)\n                .first()\n            )\n        elif type(variable) == int or type(variable) == float:\n            vp = (\n                super(VariablePropertyManager, self)\n                .get_queryset()\n                .filter(variable_id=variable, name=name.upper(), **kwargs)\n                .first()\n            )\n        else:\n            return None\n        if vp:\n            value_class = vp.value_class\n            if value_class.upper() in [\"STRING\"]:\n                value = \"\" if value is None else value\n                vp.value_string = value[\n                    : VariableProperty._meta.get_field(\"value_string\").max_length\n                ]\n            elif value_class.upper() in [\n                \"FLOAT\",\n                \"FLOAT64\",\n                \"DOUBLE\",\n                \"FLOAT32\",\n                \"SINGLE\",\n                \"REAL\",\n            ]:\n                vp.value_float64 = value\n            elif value_class.upper() in [\"INT64\", \"UINT32\", \"DWORD\"]:\n                vp.value_int64 = value\n            elif value_class.upper() in [\"WORD\", \"UINT\", \"UINT16\", \"INT32\"]:\n                vp.value_int32 = value\n            elif value_class.upper() in [\"INT16\", \"INT8\", \"UINT8\"]:\n                vp.value_int16 = value\n            elif value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n                value = False if value is None else value\n                vp.value_boolean = value\n            vp.last_modified = now()\n            try:\n                vp.save()\n                self._send_cov_notification(vp)\n            except ValueError as e:\n                logger.error(\"Error while saving VP value : \" + str(e), exc_info=True)\n            return vp\n        else:\n            return None\n\n    def _send_cov_notification(self, vp):\n        for app_config in apps.get_app_configs():\n            if hasattr(app_config, \"pyscada_send_cov_notification\") and callable(\n                app_config.pyscada_send_cov_notification\n            ):\n                try:\n                    app_config.pyscada_send_cov_notification(variable_property=vp)\n                except:\n                    logger.error(\n                        f\"{self}, unhandled exception in COV Receiver application\",\n                        exc_info=True,\n                    )\n\n\n#\n# Models\n#\nclass Color(models.Model):\n    id = models.AutoField(primary_key=True)\n    name = models.SlugField(max_length=80, verbose_name=\"variable name\")\n    R = models.PositiveSmallIntegerField(default=0)\n    G = models.PositiveSmallIntegerField(default=0)\n    B = models.PositiveSmallIntegerField(default=0)\n\n    def __str__(self):\n        return (\n            \"rgb(\" + str(self.R) + \", \" + str(self.G) + \", \" + str(self.B) + \", \" + \")\"\n        )\n\n    def color_code(self):\n        return \"#%02x%02x%02x\" % (self.R, self.G, self.B)\n\n    def color_rect_html(self):\n        return (\n            '<div style=\"width:4px;height:0;border:5px solid #%02x%02x%02x;overflow:hidden\"></div>'\n            % (self.R, self.G, self.B)\n        )\n\n\nclass DeviceProtocol(models.Model):\n    id = models.AutoField(primary_key=True)\n    protocol = models.CharField(max_length=400, default=\"generic\")\n    description = models.TextField(default=\"\", verbose_name=\"Description\", null=True)\n    app_name = models.CharField(max_length=400, default=\"pyscada.PROTOCOL\")\n    device_class = models.CharField(max_length=400, default=\"pyscada.PROTOCOL.device\")\n    daq_daemon = models.BooleanField()\n    single_thread = models.BooleanField()\n\n    def __str__(self):\n        return self.protocol\n\n\nclass DeviceHandler(models.Model):\n    name = models.CharField(default=\"\", max_length=255)\n    handler_class = models.CharField(\n        default=\"pyscada.visa.devices.HP3456A\",\n        max_length=255,\n        help_text=\"a Base class to extend can be found at \"\n        \"pyscada.PROTOCOL.devices.GenericDevice. \"\n        \"Exemple : pyscada.visa.devices.HP3456A, \"\n        \"pyscada.smbus.devices.ups_pico, \"\n        \"pyscada.serial.devices.AirLinkGX450\",\n    )\n    handler_path = models.CharField(\n        default=None,\n        max_length=255,\n        null=True,\n        blank=True,\n        help_text=\"If no handler class, pyscada will look at the path. \"\n        \"Exemple : /home/pi/my_handler.py\",\n    )\n\n    def __str__(self):\n        return self.name\n\n    def save(self, *args, **kwargs):\n        # TODO : select only devices of selected variables\n        post_save.send_robust(sender=DeviceHandler, instance=Device.objects.first())\n        super(DeviceHandler, self).save(*args, **kwargs)\n\n    def get_device_parameters(self):\n        parameters = {}\n        try:\n            if self.handler_path is not None:\n                sys.path.append(self.handler_path)\n            if self.handler_class in sys.modules:\n                del sys.modules[self.handler_class]\n            mod = __import__(self.handler_class, fromlist=[\"Handler\"])\n            if hasattr(mod, \"pyscada_device_parameters\"):\n                parameters = getattr(mod, \"pyscada_device_parameters\")\n        except ModuleNotFoundError as e:\n            logger.info(e)\n        except Exception as e:\n            logger.info(e)\n        return parameters\n\n    def get_variable_parameters(self):\n        parameters = {}\n        try:\n            if self.handler_path is not None:\n                sys.path.append(self.handler_path)\n            if self.handler_class in sys.modules:\n                del sys.modules[self.handler_class]\n            mod = __import__(self.handler_class, fromlist=[\"Handler\"])\n            if hasattr(mod, \"pyscada_variable_parameters\"):\n                parameters = getattr(mod, \"pyscada_variable_parameters\")\n        except ModuleNotFoundError as e:\n            logger.info(e)\n        except Exception as e:\n            logger.info(e)\n        return parameters\n\n\nclass Device(models.Model):\n    id = models.AutoField(primary_key=True)\n    short_name = models.CharField(max_length=400, default=\"\")\n    description = models.TextField(default=\"\", verbose_name=\"Description\", null=True)\n    active = models.BooleanField(default=True)\n    byte_order_choices = (\n        (\"1-0-3-2\", \"1-0-3-2\"),\n        (\"0-1-2-3\", \"0-1-2-3\"),\n        (\"2-3-0-1\", \"2-3-0-1\"),\n        (\"3-2-1-0\", \"3-2-1-0\"),\n    )\n    byte_order = models.CharField(\n        max_length=15, default=\"1-0-3-2\", choices=byte_order_choices\n    )\n    polling_interval_choices = (\n        (0.1, \"100 Milliseconds\"),\n        (0.5, \"500 Milliseconds\"),\n        (1.0, \"1 Second\"),\n        (5.0, \"5 Seconds\"),\n        (10.0, \"10 Seconds\"),\n        (15.0, \"15 Seconds\"),\n        (30.0, \"30 Seconds\"),\n        (60.0, \"1 Minute\"),\n        (150.0, \"2.5 Mintues\"),\n        (300.0, \"5 Minutes\"),\n        (360.0, \"6 Minutes (10 times per Hour)\"),\n        (600.0, \"10 Minutes\"),\n        (900.0, \"15 Minutes\"),\n        (1800.0, \"30 Minutes\"),\n        (3600.0, \"1 Hour\"),\n        (21600.0, \"6 Hours\"),\n        (43200.0, \"12 Hours\"),\n        (86400.0, \"1 Day\"),\n        (604800.0, \"1 Week\"),\n    )\n    polling_interval = models.FloatField(\n        default=polling_interval_choices[3][0], choices=polling_interval_choices\n    )\n    protocol = models.ForeignKey(DeviceProtocol, null=True, on_delete=models.CASCADE)\n    instrument_handler = models.ForeignKey(\n        DeviceHandler, null=True, blank=True, on_delete=models.SET_NULL\n    )\n\n    def __str__(self):\n        # display protocol for the JS filter for inline variables (hmi.static.pyscada.js.admin)\n        if self.protocol is not None:\n            return self.protocol.protocol + \"-\" + self.short_name\n        else:\n            return \"generic-\" + self.short_name\n\n    def get_device_instance(self):\n        try:\n            mod = __import__(self.protocol.device_class, fromlist=[\"Device\"])\n            device_class = getattr(mod, \"Device\")\n            return device_class(self)\n        except:\n            logger.error(\n                f\"{self.short_name}({getpid()}), unhandled exception\", exc_info=True\n            )\n            return None\n\n    def save(self, *args, **kwargs):\n        result = super().save(*args, **kwargs)\n        if self.instrument_handler is not None:\n            parameters = self.instrument_handler.get_device_parameters()\n            for parameter in parameters:\n                dhp, created = DeviceHandlerParameter.objects.update_or_create(\n                    name=parameter, instrument=self\n                )\n        return result\n\n\nclass DeviceHandlerParameter(models.Model):\n    name = models.CharField(max_length=255)\n    value = models.CharField(max_length=255, default=\"\", blank=True)\n    instrument = models.ForeignKey(Device, on_delete=models.CASCADE)\n\n    def __str__(self):\n        return self.name\n\n\nclass Unit(models.Model):\n    id = models.AutoField(primary_key=True)\n    unit = models.CharField(max_length=80, verbose_name=\"Unit\")\n    description = models.TextField(default=\"\", verbose_name=\"Description\", null=True)\n    udunit = models.CharField(max_length=500, verbose_name=\"udUnit\", default=\"\")\n\n    def __str__(self):\n        return self.unit\n\n    class Meta:\n        managed = True\n\n\nclass Scaling(models.Model):\n    id = models.AutoField(primary_key=True)\n    description = models.TextField(\n        default=\"\", verbose_name=\"Description\", null=True, blank=True\n    )\n    input_low = models.FloatField()\n    input_high = models.FloatField()\n    output_low = models.FloatField()\n    output_high = models.FloatField()\n    limit_input = models.BooleanField()\n\n    def __str__(self):\n        if self.description:\n            return self.description\n        else:\n            return (\n                str(self.id)\n                + \"_[\"\n                + str(self.input_low)\n                + \":\"\n                + str(self.input_high)\n                + \"] -> [\"\n                + str(self.output_low)\n                + \":\"\n                + str(self.output_low)\n                + \"]\"\n            )\n\n    def scale_value(self, input_value):\n        input_value = float(input_value)\n        if self.limit_input:\n            input_value = max(min(input_value, self.input_high), self.input_low)\n        norm_value = (input_value - self.input_low) / (self.input_high - self.input_low)\n        return norm_value * (self.output_high - self.output_low) + self.output_low\n\n    def scale_output_value(self, input_value):\n        input_value = float(input_value)\n        norm_value = (input_value - self.output_low) / (\n            self.output_high - self.output_low\n        )\n        return norm_value * (self.input_high - self.input_low) + self.input_low\n\n\nclass Dictionary(models.Model):\n    id = models.AutoField(primary_key=True)\n    name = models.CharField(max_length=400, default=\"\")\n\n    def __str__(self):\n        return text_type(str(self.id) + \": \" + self.name)\n\n    def dict_as_json(self):\n        items_list = dict()\n        for item in self.dictionaryitem_set.all():\n            items_list[float(item.value)] = item.label\n        return json.dumps(items_list)\n\n    def get_label(self, value):\n        label_found = None\n        for item in self.dictionaryitem_set.all():\n            if float(item.value) == float(value):\n                if label_found is None:\n                    label_found = item.label\n                else:\n                    logger.info(\n                        \"Dictionary %s has various items with value = %s\"\n                        % (str(self), value)\n                    )\n                    return None\n        return label_found or value\n\n    def append(self, label, value, silent=False, update=None):\n        if update is None:\n            try:\n                _, created = DictionaryItem.objects.get_or_create(\n                    label=label, value=value, dictionary=self\n                )\n                if not created and not silent:\n                    logger.warning(\n                        \"Item ({}:{}) for dictionary {} already exist\".format(\n                            label, value, self\n                        )\n                    )\n            except DictionaryItem.MultipleObjectsReturned:\n                logger.warning(\n                    f\"MultipleObjectsReturned for label={label}, value={value}, dictionary={self}. Keep the first one.\"\n                )\n                for di in DictionaryItem.objects.filter(\n                    label=label, value=value, dictionary=self\n                )[1:]:\n                    di.delete()\n        elif update == \"label\":\n            try:\n                DictionaryItem.objects.update_or_create(\n                    value=value,\n                    dictionary=self,\n                    defaults={\n                        \"label\": label,\n                    },\n                )\n            except DictionaryItem.MultipleObjectsReturned:\n                logger.warning(\n                    f\"MultipleObjectsReturned for value={value}, dictionary={self}. Keep the first one.\"\n                )\n                for di in DictionaryItem.objects.filter(value=value, dictionary=self)[\n                    1:\n                ]:\n                    di.delete()\n                DictionaryItem.objects.update_or_create(\n                    value=value,\n                    dictionary=self,\n                    defaults={\n                        \"label\": label,\n                    },\n                )\n        elif update == \"value\":\n            try:\n                DictionaryItem.objects.update_or_create(\n                    label=label,\n                    dictionary=self,\n                    defaults={\n                        \"value\": value,\n                    },\n                )\n            except DictionaryItem.MultipleObjectsReturned:\n                logger.warning(\n                    f\"MultipleObjectsReturned for label={label}, dictionary={self}. Keep the first one.\"\n                )\n                for di in DictionaryItem.objects.filter(label=label, dictionary=self)[\n                    1:\n                ]:\n                    di.delete()\n                DictionaryItem.objects.update_or_create(\n                    label=label,\n                    dictionary=self,\n                    defaults={\n                        \"value\": value,\n                    },\n                )\n\n    def remove(self, label=None, value=None):\n        if label is not None and value is not None:\n            DictionaryItem.objects.filter(label=label, value=value).delete()\n        elif label is not None:\n            DictionaryItem.objects.filter(label=label).delete()\n        elif value is not None:\n            DictionaryItem.objects.filter(value=value).delete()\n\n    def _get_objects_for_html(\n        self, list_to_append=None, obj=None, exclude_model_names=None\n    ):\n        list_to_append = get_objects_for_html(list_to_append, self, exclude_model_names)\n        if obj is None:\n            for item in self.dictionaryitem_set.all():\n                list_to_append = get_objects_for_html(\n                    list_to_append, item, [\"dictionary\"]\n                )\n\n        return list_to_append\n\n\nclass DictionaryItem(models.Model):\n    id = models.AutoField(primary_key=True)\n    label = models.CharField(max_length=400, default=\"\", blank=True)\n    value = models.CharField(max_length=400, default=\"\")\n    dictionary = models.ForeignKey(\n        Dictionary, blank=True, null=True, on_delete=models.CASCADE\n    )\n\n    def __str__(self):\n        return text_type(str(self.id) + \": \" + self.label)\n\n\nclass VariableProperty(models.Model):\n    id = models.AutoField(primary_key=True)\n    variable = models.ForeignKey(\"Variable\", null=True, on_delete=models.CASCADE)\n    property_class_choices = (\n        (None, \"other or no Class specified\"),\n        (\"device\", \"Device Property\"),\n        (\"data_record\", \"Recorded Data\"),\n        (\"daemon\", \"Daemon Property\"),\n    )\n    property_class = models.CharField(\n        default=None,\n        blank=True,\n        null=True,\n        max_length=255,\n        choices=property_class_choices,\n    )\n    value_class_choices = (\n        (\"FLOAT32\", \"REAL (FLOAT32)\"),\n        (\"FLOAT32\", \"SINGLE (FLOAT32)\"),\n        (\"FLOAT32\", \"FLOAT32\"),\n        (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n        (\"FLOAT64\", \"LREAL (FLOAT64)\"),\n        (\"FLOAT64\", \"FLOAT  (FLOAT64)\"),\n        (\"FLOAT64\", \"DOUBLE (FLOAT64)\"),\n        (\"FLOAT64\", \"FLOAT64\"),\n        (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n        (\"INT64\", \"INT64\"),\n        (\"UINT64\", \"UINT64\"),\n        (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n        (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n        (\"INT32\", \"INT32\"),\n        (\"UINT32\", \"DWORD (UINT32)\"),\n        (\"UINT32\", \"UINT32\"),\n        (\"INT16\", \"INT (INT16)\"),\n        (\"INT16\", \"INT16\"),\n        (\"UINT16\", \"WORD (UINT16)\"),\n        (\"UINT16\", \"UINT (UINT16)\"),\n        (\"UINT16\", \"UINT16\"),\n        (\"BOOLEAN\", \"BOOL (BOOLEAN)\"),\n        (\"BOOLEAN\", \"BOOLEAN\"),\n        (\"STRING\", \"STRING\"),\n    )\n    value_class = models.CharField(\n        max_length=15,\n        default=\"FLOAT64\",\n        verbose_name=\"value_class\",\n        choices=value_class_choices,\n    )\n    name = models.CharField(default=\"\", blank=True, max_length=255)\n    value_boolean = models.BooleanField(default=False, blank=True)  # boolean\n    value_int16 = models.SmallIntegerField(null=True, blank=True)  # int16, uint8, int8\n    value_int32 = models.IntegerField(\n        null=True, blank=True\n    )  # uint8, int16, uint16, int32\n    value_int64 = models.BigIntegerField(null=True, blank=True)  # uint32, int64\n    value_float64 = models.FloatField(null=True, blank=True)  # float64\n    value_string = models.CharField(default=\"\", blank=True, max_length=1000)\n    timestamp = models.DateTimeField(blank=True, null=True)\n    unit = models.ForeignKey(Unit, on_delete=models.SET(1), blank=True, null=True)\n    objects = VariablePropertyManager()\n    value_min = models.FloatField(null=True, blank=True)\n    value_max = models.FloatField(null=True, blank=True)\n    min_type_choices = (\n        (\"lte\", \"<=\"),\n        (\"lt\", \"<\"),\n    )\n    max_type_choices = (\n        (\"gte\", \">=\"),\n        (\"gt\", \">\"),\n    )\n    min_type = models.CharField(max_length=4, default=\"lte\", choices=min_type_choices)\n    max_type = models.CharField(max_length=4, default=\"gte\", choices=max_type_choices)\n    last_modified = models.DateTimeField(auto_now=True)\n    dictionary = models.ForeignKey(\n        Dictionary, blank=True, null=True, on_delete=models.SET_NULL\n    )\n\n    last_value = None\n    value_changed = False\n\n    class Meta:\n        verbose_name_plural = \"variable properties\"\n\n    def __str__(self):\n        return f\"{self.variable}-{self.get_property_class_display()} : {self.name}\"\n\n    def value(self):\n        value_class = self.value_class\n        if value_class.upper() in [\"STRING\"]:\n            return self.value_string\n        elif value_class.upper() in [\n            \"FLOAT\",\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT32\",\n            \"SINGLE\",\n            \"REAL\",\n        ]:\n            return self.value_float64\n        elif value_class.upper() in [\"INT64\", \"UINT32\", \"DWORD\"]:\n            return self.value_int64\n        elif value_class.upper() in [\"WORD\", \"UINT\", \"UINT16\", \"INT32\"]:\n            return self.value_int32\n        elif value_class.upper() in [\"INT16\", \"INT8\", \"UINT8\"]:\n            return self.value_int16\n        elif value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return self.value_boolean\n        return None\n\n    def web_key(self):\n        return \"%d-%s\" % (self.variable.pk, self.name.upper().replace(\":\", \"-\"))\n\n    def item_type(self):\n        return \"variable_property\"\n\n    def convert_string_value(self, value):\n        if self.dictionary is None:\n            d = Dictionary(name=str(self.name) + \"_auto_created\")\n            d.save()\n            self.dictionary = d\n            Variable.objects.bulk_update([self], [\"dictionary\"])\n            self.refresh_from_db()\n        if not len(self.dictionary.dictionaryitem_set.filter(label=str(value))):\n            max_value = 0\n            for di in self.dictionary.dictionaryitem_set.all():\n                max_value = max(float(max_value), float(di.value))\n            DictionaryItem.objects.get_or_create(\n                label=str(value), value=int(max_value) + 1, dictionary=self.dictionary\n            )\n            # logger.debug('new value : %s' % (int(max_value) + 1))\n            return int(max_value) + 1\n        elif len(self.dictionary.dictionaryitem_set.filter(label=str(value))) == 1:\n            # logger.debug('value found : %s' % self.dictionary.dictionaryitem_set.get(label=str(value)).value)\n            return float(self.dictionary.dictionaryitem_set.get(label=str(value)).value)\n        else:\n            logger.warning(\n                \"%s duplicate values found of %s in dictionary %s\"\n                % (\n                    len(self.dictionary.dictionaryitem_set.filter(label=str(value))),\n                    value,\n                    self.dictionary,\n                )\n            )\n            return float(\n                self.dictionary.dictionaryitem_set.filter(label=str(value))\n                .first()\n                .value\n            )\n\n\nclass DataSourceModel(models.Model):\n    \"\"\"\n    Used to define a data source type.\n    The data source base model have a foreign key to this model to specify the configuration :\n    - the name,\n    - can add, modify on select in the admin panel,\n    - the model name of the inline having the specific config (fields, functions, manager).\n    \"\"\"\n\n    name = models.CharField(max_length=100)\n    inline_model_name = models.CharField(max_length=100)\n    can_add = models.BooleanField(\n        default=False,\n        help_text=\"True to enable custom data sources of this type to be added from the administration panel.\",\n    )\n    can_change = models.BooleanField(\n        default=False,\n        help_text=\"True to enable custom data sources of this type to be modified from the administration panel.\",\n    )\n    can_select = models.BooleanField(\n        default=True,\n        help_text=\"False to hide this type of custom data source in the variable administration panel.\",\n    )\n\n    def __str__(self):\n        return self.name\n\n\nclass DataSource(models.Model):\n    \"\"\"\n    The base model for all the data sources.\n    A data source needs to inherit from this class, and should have the basic functions :\n    - last_datapoint,\n    - query_datapoints,\n    - write_datapoints,\n    - write_raw_datapoints,\n    \"\"\"\n\n    datasource_model = models.ForeignKey(DataSourceModel, on_delete=models.CASCADE)\n\n    def __str__(self):\n        if self.get_related_datasource() is not None:\n            return self.get_related_datasource().__str__()\n        else:\n            # logger.warning(f\"No related datasource for DataSource {self}\")\n            return f\"Data source model {self.datasource_model} ({self.id}) : related datasource not found\"\n\n    def get_related_datasource(self):\n        # Test if model does not exist\n        if hasattr(self, self.datasource_model.inline_model_name.lower()):\n            return getattr(self, self.datasource_model.inline_model_name.lower())\n        return None\n\n    def datasource_check(self, items, items_as_id=False, ids_model=None):\n        \"\"\"\n        For a list of items (variables), check if the datasource is the correct one.\n        \"\"\"\n        valid_items = []\n        items_to_check = []\n        if items_as_id:\n            if ids_model is None:\n                logger.warning(\n                    f\"Cannot check datasource, item are ids and no model is passed.\"\n                )\n                return valid_items\n            else:\n                for item in items:\n                    items_to_check.append(ids_model.objects.get(id=item))\n        else:\n            items_to_check = items\n\n        for item in items_to_check:\n            if (\n                self.get_related_datasource() is None\n                or item.get_related_datasource() is None\n            ):\n                logger.info(\n                    f\"Cannot check datasource for variable {item} because not related datasource is defined.\"\n                )\n                continue\n            if (\n                item.get_related_datasource().__class__\n                != self.get_related_datasource().__class__\n            ):\n                logger.warning(\n                    f\"{self.get_related_datasource().__class__} is not the data source of {item} : {item.get_related_datasource().__class__} - {self.datasource_model.inline_model_name.lower()} - {self.__dict__}\"\n                )\n            else:\n                if items_as_id:\n                    valid_items.append(item.pk)\n                else:\n                    valid_items.append(item)\n        return valid_items\n\n    def _send_cov_notification(self, variable):\n        \"\"\"\n        Sends a COV Notification via the Django Signal interface\n        :param value:\n        :return:\n        \"\"\"\n        for app_config in apps.get_app_configs():\n            if hasattr(app_config, \"pyscada_send_cov_notification\") and callable(\n                app_config.pyscada_send_cov_notification\n            ):\n                try:\n                    app_config.pyscada_send_cov_notification(variable=variable)\n                except:\n                    logger.error(\n                        f\"{self}, unhandled exception in COV Receiver application\",\n                        exc_info=True,\n                    )\n\n    def last_value(self, **kwargs):\n        logger.info(\n            \"the use of 'last_value' method is deprecated use 'last_datapoint' instead\"\n        )\n        return self.last_datapoint(**kwargs)\n\n    @check_datasource_method\n    def last_datapoint(self, variable, use_date_saved=False, **kwargs):\n        return self.get_related_datasource().last_datapoint(\n            variable=variable, use_date_saved=use_date_saved, **kwargs)\n\n\n    def read_multiple(\n        self,\n        **kwargs,\n    ):\n        logger.info(\n            \"the use of 'read_multiple' method is deprecated use 'query_datapoints' instead\"\n        )\n        return self.query_datapoints(**kwargs)\n\n    @check_datasource_method\n    def query_datapoints(self, **kwargs):\n        logger.info(\n            \"the driect use of 'query_datapoints' method is deprecated use 'Variable.objects.query_datapoints' instead\"\n        )\n        return self.get_related_datasource().query_datapoints(**kwargs)\n\n\n    def write_multiple(self, **kwargs):\n        logger.info(\n            \"the use of 'write_multiple' method is deprecated use 'write_datapoints' instead\"\n        )\n        return self.write_datapoints(**kwargs)\n\n    @check_datasource_method\n    def write_datapoints(self, items: list, **kwargs):\n        logger.info(\n            \"the driect use of 'write_datapoints' method is deprecated use 'Variable.objects.write_datapoints' instead\"\n        )\n        for item in items:\n            if len(item.cached_values_to_write):\n                self._send_cov_notification(item)\n\n        self.get_related_datasource().write_datapoints(items=items, **kwargs)\n        return True\n\n    @check_datasource_method\n    def write_raw_datapoints(self, datapoints, date_saved=None, **kwargs):\n        \"\"\"writes raw datapoints to the database in the form\n        Args:\n            datapoints:  {variable_id: [[timestamp, value, date_saved],...]} with\n                timestamp in s\n                value\n                date_saved as datetime, timestamp in s, None or ommited\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n            **kwargs: Arbitrary keyword arguments. Will be passed to the datasource\n                write_raw_datapoints function call\n\n        Returns:\n            None\n        \"\"\"\n        logger.info(\n            \"the driect use of 'write_raw_datapoints' method is deprecated use 'Variable.objects.write_raw_datapoints' instead\"\n        )\n        return self.get_related_datasource().write_raw_datapoints(datapoints=datapoints, date_saved=date_saved, **kwargs)\n\n\n\n\nclass Variable(models.Model):\n    \"\"\"\n    Stores a variable entry, related to :mod:`pyscada.Device`,\n    :mod:`pyscada.Unit`, (optional) :mod:`pyscada.Scaling`,\n    (optional) :mod:`pyscada.Color` and (optional) :mod:`pyscada.Dictionary`.\n    \"\"\"\n\n    id = models.AutoField(primary_key=True)\n    name = models.SlugField(max_length=200, verbose_name=\"variable name\", unique=True)\n    description = models.TextField(default=\"\", verbose_name=\"Description\")\n    device = models.ForeignKey(Device, null=True, on_delete=models.CASCADE)\n    active = models.BooleanField(default=True)\n    unit = models.ForeignKey(Unit, on_delete=models.SET(1))\n    readable = models.BooleanField(default=True)\n    writeable = models.BooleanField(default=False)\n    pause_recording = models.BooleanField(default=False, help_text=\"data will be discarded entirely while True\")\n\n    value_class_choices = (\n        (\"FLOAT32\", \"REAL, SINGLE or FLOAT32\"),\n        (\"UNIXTIMEF32\", \"UNIXTIMEF32\"),\n        (\"FLOAT64\", \"LREAL, FLOAT, DOUBLE or FLOAT64\"),\n        (\"UNIXTIMEF64\", \"UNIXTIMEF64\"),\n        (\"FLOAT48\", \"FLOAT48\"),\n        (\"INT64\", \"INT64\"),\n        (\"UINT64\", \"UINT64\"),\n        (\"UNIXTIMEI64\", \"UNIXTIMEI64\"),\n        (\"INT48\", \"INT48\"),\n        (\"UNIXTIMEI32\", \"UNIXTIMEI32\"),\n        (\"INT32\", \"INT32\"),\n        (\"UINT32\", \"DWORD or UINT32\"),\n        (\"INT16\", \"INT or INT16\"),\n        (\"UINT16\", \"WORD, UINT or UINT16\"),\n        (\"INT8\", \"INT8\"),\n        (\"UINT8\", \"UINT8\"),\n        (\"BOOLEAN\", \"BOOL or BOOLEAN\"),\n    )\n    scaling = models.ForeignKey(\n        Scaling, null=True, blank=True, on_delete=models.SET_NULL\n    )\n    value_class = models.CharField(\n        max_length=15,\n        default=\"FLOAT64\",\n        verbose_name=\"value_class\",\n        choices=value_class_choices,\n    )\n    cov_increment = models.FloatField(default=0, verbose_name=\"COV\")\n    byte_order_choices = (\n        (\n            \"default\",\n            \"default (specified by device byte order)\",\n        ),\n        (\"1-0-3-2\", \"1-0-3-2\"),\n        (\"0-1-2-3\", \"0-1-2-3\"),\n        (\"2-3-0-1\", \"2-3-0-1\"),\n        (\"3-2-1-0\", \"3-2-1-0\"),\n    )\n    short_name = models.CharField(\n        default=\"\", max_length=80, verbose_name=\"variable short name\", blank=True\n    )\n    chart_line_color = models.ForeignKey(\n        Color, null=True, default=None, blank=True, on_delete=models.SET_NULL\n    )\n    chart_line_thickness_choices = ((3, \"3Px\"),)\n    chart_line_thickness = models.PositiveSmallIntegerField(\n        default=3, choices=chart_line_thickness_choices\n    )\n    value_min = models.FloatField(null=True, blank=True)\n    value_max = models.FloatField(null=True, blank=True)\n    min_type_choices = (\n        (\"lte\", \"<=\"),\n        (\"lt\", \"<\"),\n    )\n    max_type_choices = (\n        (\"gte\", \">=\"),\n        (\"gt\", \">\"),\n    )\n    min_type = models.CharField(max_length=4, default=\"lte\", choices=min_type_choices)\n    max_type = models.CharField(max_length=4, default=\"gte\", choices=max_type_choices)\n    dictionary = models.ForeignKey(\n        Dictionary, blank=True, null=True, on_delete=models.SET_NULL\n    )\n    datasource = models.ForeignKey(\n        DataSource,\n        default=1,\n        on_delete=models.SET(1),\n        help_text=\"Select a custom data source to read/write data for this variable.\",\n    )\n\n    objects = VariableManager()\n\n    def save(self, *args, **kwargs):\n        # delete related protocol models if it doesn't correspond to the device protocol.\n        # it should be done when changing the device of a variable if the device protocol changes.\n        related_variables = [\n            field\n            for field in Variable._meta.get_fields()\n            if issubclass(type(field), OneToOneRel)\n        ]\n        for v in related_variables:\n            if (\n                hasattr(self, v.name) and hasattr(getattr(self, v.name), \"protocol_id\")\n                and getattr(self, v.name).protocol_id != self.device.protocol.id\n            ):\n                getattr(self, v.name).delete()\n        result = super().save(*args, **kwargs)\n\n        if self.device.instrument_handler is not None:\n            parameters = self.device.instrument_handler.get_variable_parameters()\n            for parameter in parameters:\n                vhp, created = VariableHandlerParameter.objects.update_or_create(\n                    name=parameter, variable=self\n                )\n        return result\n\n    @property\n    def datasource_model(self):\n        return self.datasource.datasource_model\n\n    def get_related_datasource(self):\n        return self.datasource.get_related_datasource()\n\n    def hmi_name(self):\n        if self.short_name and self.short_name != \"-\" and self.short_name != \"\":\n            return self.short_name\n        else:\n            return self.name\n\n    def chart_line_color_code(self):\n        if self.chart_line_color and self.chart_line_color.id != 1:\n            return self.chart_line_color.color_code()\n        else:\n            c = 51\n            c_id = self.pk + 1\n            c = c % c_id\n            while c >= 51:\n                c_id = c_id - c\n                c = c % c_id\n            return Color.objects.get(id=c_id).color_code()\n\n    \"\"\"\n    M: Mantissa\n    E: Exponent\n    S: Sign\n    uint 0            uint 1\n    byte 0   byte 1   byte 2   byte 3\n    1-0-3-2 MMMMMMMM MMMMMMMM SEEEEEEE EMMMMMMM\n    0-1-2-3 MMMMMMMM MMMMMMMM EMMMMMMM SEEEEEEE\n    2-3-0-1 EMMMMMMM SEEEEEEE MMMMMMMM MMMMMMMM\n    3-2-1-0 SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM\n    \"\"\"\n\n    byte_order = models.CharField(\n        max_length=15, default=\"default\", choices=byte_order_choices\n    )\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        # for RecodedVariable\n        self.value = None\n        self.prev_value = None\n        self.store_value = False\n        self.timestamp_old = None\n        self.timestamp = None\n\n        self.erase_cache()\n\n    def erase_cache(self):\n        # for read values\n        self.cached_values_to_write = []\n\n    def __str__(self):\n        return str(self.id) + \" - \" + self.name\n\n    def add_attr(self, **kwargs):\n        for key in kwargs:\n            setattr(self, key, kwargs[key])\n\n    def item_type(self):\n        return \"variable\"\n\n    def get_bits_by_class(self):\n        \"\"\"\n        `BOOLEAN`\t\t\t\t\t\t\t1\t1/16 WORD\n        `UINT8` `BYTE`\t\t\t\t\t\t8\t1/2 WORD\n        `INT8`\t\t\t\t\t\t\t\t8\t1/2 WORD\n        `UNT16` `WORD`\t\t\t\t\t\t16\t1 WORD\n        `INT16`\t`INT`\t\t\t\t\t\t16\t1 WORD\n        `UINT32` `DWORD`\t\t\t\t\t32\t2 WORD\n        `INT32`\t\t\t\t\t\t\t\t32\t2 WORD\n        `FLOAT32` `REAL` `SINGLE` \t\t\t32\t2 WORD\n        `FLOAT48` 'INT48'                  \t48\t3 WORD\n        `FLOAT64` `LREAL` `FLOAT` `DOUBLE`\t64\t4 WORD\n        \"\"\"\n        if self.value_class.upper() in [\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT\",\n            \"LREAL\",\n            \"UNIXTIMEI64\",\n            \"UNIXTIMEF64\",\n            \"INT64\",\n            \"UINT64\",\n        ]:\n            return 64\n        if self.value_class.upper() in [\"FLOAT48\", \"INT48\"]:\n            return 48\n        if self.value_class.upper() in [\n            \"FLOAT32\",\n            \"SINGLE\",\n            \"INT32\",\n            \"UINT32\",\n            \"DWORD\",\n            \"BCD32\",\n            \"BCD24\",\n            \"REAL\",\n            \"UNIXTIMEI32\",\n            \"UNIXTIMEF32\",\n        ]:\n            return 32\n        if self.value_class.upper() in [\n            \"INT16\",\n            \"INT\",\n            \"WORD\",\n            \"UINT\",\n            \"UINT16\",\n            \"BCD16\",\n        ]:\n            return 16\n        if self.value_class.upper() in [\"INT8\", \"UINT8\", \"BYTE\", \"BCD8\"]:\n            return 8\n        if self.value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return 1\n        else:\n            return 16\n\n    @check_datasource_method\n    def last_datapoint(self, use_date_saved=False):\n        \"\"\"returns the last datapoint from the database\n        as list in the form [timestamp, value]\n        \"\"\"\n        datasource_object = self.get_related_datasource()\n        return datasource_object.last_datapoint(variable=self, use_date_saved=use_date_saved)\n\n    @check_datasource_method\n    def write_datapoints(self, **kwargs):\n        \"\"\"writes the cached data to the database\n        Args:\n            **kwargs: Arbitrary keyword arguments. Will be passed to the datasource\n                write_raw_datapoints function call\n        \"\"\"\n        datasource_object = self.get_related_datasource()\n        return datasource_object.write_datapoints(items=[self],**kwargs)\n\n    @check_datasource_method\n    def write_raw_datapoints(self, datapoints: list, date_saved=None, **kwargs):\n        \"\"\"consumes a list of [timestamp, value, (date_saved)] items to be written to\n        the database.\n        the value must be already in the right form, no checks on the timestamps or\n        values are performt. If the date_saved value is missing it will be substituted\n        by now()\n\n        Args:\n            datapoints:  [[timestamp, value, date_saved],...] with\n                timestamp in s\n                value\n                date_saved as datetime, timestamp in s, None or ommited\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n            **kwargs: Arbitrary keyword arguments. Will be passed to the datasource\n                write_raw_datapoints function call\n\n        Returns:\n            None\n        \"\"\"\n        datapoints_out = {}\n        datapoints_out[self.pk] = datapoints\n        datasource_object = self.get_related_datasource()\n        return datasource_object.write_raw_datapoints(datapoints=datapoints_out, date_saved=date_saved, **kwargs)\n\n    @check_datasource_method\n    def query_datapoints(self, **kwargs):\n        \"\"\"returns a dict with datapoints from the DB\"\"\"\n        datasource_object = self.get_related_datasource()\n        if datasource_object is None:\n            logger.error(\"datasource not found\")\n            return None\n        data = datasource_object.query_datapoints(variable_ids=[self.pk],**kwargs)\n        if self.pk not in data:\n            return None, None, None\n        return data[self.pk], data['timestamp'], data['date_saved_max']\n\n    def query_prev_value(self):\n        logger.info(\n            \"the use of 'query_prev_value' method is deprecated use 'check_last_datapoint' instead\"\n        )\n        return self.check_last_datapoint()\n\n    def check_last_datapoint(self):\n        \"\"\"\n        get the last value and timestamp from the database and store it\n        in self.prev_value and self.timestamp_old\n        Args:\n            None\n\n        Returns:\n            status (bool): True if old value was updated, otherwise False\n        \"\"\"\n\n        val = self.last_datapoint()\n        if val:\n            self.prev_value = val[1]\n            self.timestamp_old = val[0]\n            return True\n        else:\n            return False\n\n    def update_value(self, value=None, timestamp=None):\n        logger.error(\n            f\"update_value is depreceated, use update_values instead. Variable {self}.\"\n        )\n        return self.update_values([value], [timestamp])\n\n    def _update_value(self, value=None, timestamp=None):\n        \"\"\"\n        update the value in the instance and detect value state change\n        \"\"\"\n\n        try:\n            value = float(value)\n        except ValueError:\n            # Add string value in dictionary and replace the string by the dictionary value\n            if type(value) == str:\n                value = self.convert_string_value(value)\n            else:\n                logger.info(\n                    \"Value read for %s format not supported : %s\" % (self, type(value))\n                )\n                value = None\n        except TypeError as e:\n            if value is not None:\n                logger.debug(e)\n\n        if (\n            self.scaling is None\n            or value is None\n            or self.value_class.upper() in [\"BOOL\", \"BOOLEAN\"]\n        ):\n            self.value = value\n        else:\n            self.value = self.scaling.scale_value(value)\n        try:\n            self.timestamp = float(timestamp)\n        except ValueError as e:\n            logger.error(f\"{self} - wrong timestamp : {e}\")\n            return False\n        self.store_value = False\n        if self.prev_value is None:\n            # no prev value in the cache, always store the value\n            self.store_value = True\n            self.timestamp_old = self.timestamp\n        elif self.value is None:\n            # value could not be queried\n            self.store_value = False\n        elif self.pause_recording:\n            self.store_value = False\n        elif abs(self.prev_value - self.value) <= self.cov_increment:\n            if self.timestamp_old is None:\n                self.store_value = True\n                self.timestamp_old = self.timestamp\n            else:\n                if (self.timestamp - self.timestamp_old) >= 3600:\n                    # store at least every hour one value\n                    # store Value if old Value is older than 1 hour\n                    self.store_value = True\n                    self.timestamp_old = self.timestamp\n\n        else:\n            # value has changed\n            self.store_value = True\n            self.timestamp_old = self.timestamp\n        self.prev_value = self.value\n        return self.store_value\n\n    def update_values(self, value_list, timestamp_list, erase_cache=False):\n        if erase_cache:\n            self.erase_cache()\n\n        has_value = False\n        if not isinstance(value_list, list):\n            if (\n                isinstance(value_list, bool)\n                or isinstance(value_list, int)\n                or isinstance(value_list, float)\n                or isinstance(value_list, str)\n            ):\n                value_list = [value_list]\n            else:\n                logger.warning(f\"{self} - Value list wrong type : {type(value_list)}\")\n                return False\n        if not isinstance(timestamp_list, list):\n            if (\n                isinstance(timestamp_list, bool)\n                or isinstance(timestamp_list, int)\n                or isinstance(timestamp_list, float)\n                or isinstance(timestamp_list, str)\n            ):\n                try:\n                    timestamp_list = [float(timestamp_list)]\n                except ValueError as e:\n                    logger.warning(f\"{self} - wrong timestamp : {e}\")\n                    return False\n            else:\n                logger.warning(\n                    f\"{self} - Timestamp list wrong type : {type(timestamp_list)}\"\n                )\n                return False\n        if len(value_list) != len(timestamp_list):\n            logger.warning(\n                f\"{self} - value and timestamp lists have not the same length ({len(value_list)}, {len(timestamp_list)})\"\n            )\n            return False\n\n        update_false_count = 0\n        for i in range(0, len(value_list)):\n            if self._update_value(value_list[i], timestamp_list[i]):\n                if self.value_class.upper() == \"BOOLEAN\":\n                    self.value = bool(self.value)\n                self.cached_values_to_write.append((self.timestamp, self.value))\n            else:\n                update_false_count += 1\n            has_value = True\n        if len(value_list):\n            logger.debug(\n                f\"{self} updated {len(self.cached_values_to_write)} - {update_false_count} values\"\n            )\n        return has_value\n\n    def decode_value(self, value):\n        #\n        if self.byte_order == \"default\":\n            byte_order = self.device.byte_order\n        else:\n            byte_order = self.byte_order\n\n        if self.value_class.upper() in [\"FLOAT32\", \"SINGLE\", \"REAL\", \"UNIXTIMEF32\"]:\n            target_format = \"f\"\n            source_format = \"2H\"\n        elif self.value_class.upper() in [\"UINT32\", \"DWORD\", \"UNIXTIMEI32\"]:\n            target_format = \"I\"\n            source_format = \"2H\"\n        elif self.value_class.upper() in [\"INT32\"]:\n            target_format = \"i\"\n            source_format = \"2H\"\n        elif self.value_class.upper() in [\"FLOAT48\"]:\n            target_format = \"f\"\n            source_format = \"3H\"\n        elif self.value_class.upper() in [\"INT48\"]:\n            target_format = \"q\"\n            source_format = \"3H\"\n        elif self.value_class.upper() in [\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT\",\n            \"LREAL\",\n            \"UNIXTIMEF64\",\n        ]:\n            target_format = \"d\"\n            source_format = \"4H\"\n        elif self.value_class.upper() in [\"UINT64\"]:\n            target_format = \"Q\"\n            source_format = \"4H\"\n        elif self.value_class.upper() in [\"INT64\", \"UNIXTIMEI64\"]:\n            target_format = \"q\"\n            source_format = \"4H\"\n        elif self.value_class.upper() in [\"INT16\", \"INT\"]:\n            if byte_order in [\"1-0-3-2\", \"3-2-1-0\"]:\n                # only convert to from uint to int\n                return unpack(\"h\", pack(\"H\", value[0]))[0]\n            else:\n                # swap bytes\n                return unpack(\">h\", pack(\"<H\", value[0]))[0]\n        elif self.value_class.upper() in [\"BCD32\", \"BCD24\", \"BCD16\"]:\n            target_format = \"f\"\n            source_format = \"2H\"\n            return value[0]\n        else:\n            return value[0]\n\n        #\n        if source_format == \"2H\":\n            if byte_order == \"1-0-3-2\":\n                return unpack(target_format, pack(source_format, value[0], value[1]))[0]\n            if byte_order == \"3-2-1-0\":\n                return unpack(target_format, pack(source_format, value[1], value[0]))[0]\n            if byte_order == \"0-1-2-3\":\n                return unpack(\n                    target_format,\n                    pack(\n                        source_format,\n                        unpack(\">H\", pack(\"<H\", value[0]))[0],\n                        unpack(\">H\", pack(\"<H\", value[1]))[0],\n                    ),\n                )[0]\n            if byte_order == \"2-3-0-1\":\n                return unpack(\n                    target_format,\n                    pack(\n                        source_format,\n                        unpack(\">H\", pack(\"<H\", value[1]))[0],\n                        unpack(\">H\", pack(\"<H\", value[0]))[0],\n                    ),\n                )[0]\n        elif source_format == \"3H\":\n            source_format = \"4H\"\n            if byte_order == \"1-0-3-2\":\n                return unpack(\n                    target_format, pack(source_format, 0, value[0], value[1], value[2])\n                )[0]\n            if byte_order == \"3-2-1-0\":\n                return unpack(\n                    target_format, pack(source_format, value[2], value[1], value[0], 0)\n                )[0]\n            if byte_order == \"0-1-2-3\":\n                return unpack(\n                    target_format,\n                    pack(\n                        source_format,\n                        0,\n                        unpack(\">H\", pack(\"<H\", value[0]))[0],\n                        unpack(\">H\", pack(\"<H\", value[1]))[0],\n                        unpack(\">H\", pack(\"<H\", value[2]))[0],\n                    ),\n                )[0]\n            if byte_order == \"2-3-0-1\":\n                return unpack(\n                    target_format,\n                    pack(\n                        source_format,\n                        0,\n                        unpack(\">H\", pack(\"<H\", value[2]))[0],\n                        unpack(\">H\", pack(\"<H\", value[1]))[0],\n                        unpack(\">H\", pack(\"<H\", value[0]))[0],\n                    ),\n                )[0]\n            source_format = \"3H\"\n        else:\n            if byte_order == \"1-0-3-2\":\n                return unpack(\n                    target_format,\n                    pack(source_format, value[0], value[1], value[2], value[3]),\n                )[0]\n            if byte_order == \"3-2-1-0\":\n                return unpack(\n                    target_format,\n                    pack(source_format, value[3], value[2], value[1], value[0]),\n                )[0]\n            if byte_order == \"0-1-2-3\":\n                return unpack(\n                    target_format,\n                    pack(\n                        source_format,\n                        unpack(\">H\", pack(\"<H\", value[0])),\n                        unpack(\">H\", pack(\"<H\", value[1])),\n                        unpack(\">H\", pack(\"<H\", value[2])),\n                        unpack(\">H\", pack(\"<H\", value[3])),\n                    ),\n                )[0]\n            if byte_order == \"2-3-0-1\":\n                return unpack(\n                    target_format,\n                    pack(\n                        source_format,\n                        unpack(\">H\", pack(\"<H\", value[3])),\n                        unpack(\">H\", pack(\"<H\", value[2])),\n                        unpack(\">H\", pack(\"<H\", value[1])),\n                        unpack(\">H\", pack(\"<H\", value[0])),\n                    ),\n                )[0]\n\n    def encode_value(self, value):\n        if self.value_class.upper() in [\"FLOAT32\", \"SINGLE\", \"REAL\", \"UNIXTIMEF32\"]:\n            source_format = \"f\"\n            target_format = \"2H\"\n        elif self.value_class.upper() in [\"UINT32\", \"DWORD\", \"UNIXTIMEI32\"]:\n            source_format = \"I\"\n            target_format = \"2H\"\n        elif self.value_class.upper() in [\"INT32\"]:\n            source_format = \"i\"\n            target_format = \"2H\"\n        elif self.value_class.upper() in [\"FLOAT48\"]:\n            source_format = \"f\"\n            target_format = \"3H\"\n        elif self.value_class.upper() in [\"INT48\"]:\n            source_format = \"q\"\n            target_format = \"3H\"\n        elif self.value_class.upper() in [\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT\",\n            \"LREAL\",\n            \"UNIXTIMEF64\",\n        ]:\n            source_format = \"d\"\n            target_format = \"4H\"\n        elif self.value_class.upper() in [\"UINT64\"]:\n            source_format = \"Q\"\n            target_format = \"4H\"\n        elif self.value_class.upper() in [\"INT64\", \"UNIXTIMEI64\"]:\n            source_format = \"q\"\n            target_format = \"4H\"\n        elif self.value_class.upper() in [\"BCD32\", \"BCD24\", \"BCD16\"]:\n            source_format = \"f\"\n            target_format = \"2H\"\n            return (value,)\n        elif self.value_class.upper() in [\"BOOLEAN\", \"BOOL\"]:\n            return (bool(value),)\n        else:\n            return (value,)\n\n        if source_format not in [\"f\", \"d\"]:\n            if value != float(int(value)):\n                logger.info(\n                    f\"Variable ({self.__str__()}) : the read value ({value}) is not an integer, but the value class ({self.value_class.upper()}) represents an integer. The decimal part will be lost.\"\n                )\n            value = int(value)\n\n        output = unpack(target_format, pack(source_format, value))\n        #\n        if self.byte_order == \"default\":\n            byte_order = self.device.byte_order\n        else:\n            byte_order = self.byte_order\n        if target_format == \"2H\":\n            if byte_order == \"1-0-3-2\":\n                return output\n            if byte_order == \"3-2-1-0\":\n                return [output[1], output[0]]\n            if byte_order == \"0-1-2-3\":\n                return [\n                    unpack(\">H\", pack(\"<H\", output[0])),\n                    unpack(\">H\", pack(\"<H\", output[1])),\n                ]\n            if byte_order == \"2-3-0-1\":\n                return [\n                    unpack(\">H\", pack(\"<H\", output[1])),\n                    unpack(\">H\", pack(\"<H\", output[0])),\n                ]\n        elif target_format == \"3H\":\n            if byte_order == \"1-0-3-2\":\n                return output\n            if byte_order == \"3-2-1-0\":\n                return [output[2], output[1], output[0]]\n            if byte_order == \"0-1-2-3\":\n                return [\n                    unpack(\">H\", pack(\"<H\", output[0]))[0],\n                    unpack(\">H\", pack(\"<H\", output[1]))[0],\n                    unpack(\">H\", pack(\"<H\", output[2]))[0],\n                ]\n            if byte_order == \"2-3-0-1\":\n                return [\n                    unpack(\">H\", pack(\"<H\", output[2]))[0],\n                    unpack(\">H\", pack(\"<H\", output[1]))[0],\n                    unpack(\">H\", pack(\"<H\", output[0]))[0],\n                ]\n        else:\n            if byte_order == \"1-0-3-2\":\n                return output\n            if byte_order == \"3-2-1-0\":\n                return [output[3], output[2], output[1], output[0]]\n            if byte_order == \"0-1-2-3\":\n                return [\n                    unpack(\">H\", pack(\"<H\", output[0])),\n                    unpack(\">H\", pack(\"<H\", output[1])),\n                    unpack(\">H\", pack(\"<H\", output[2])),\n                    unpack(\">H\", pack(\"<H\", output[3])),\n                ]\n            if byte_order == \"2-3-0-1\":\n                return [\n                    unpack(\">H\", pack(\"<H\", output[3])),\n                    unpack(\">H\", pack(\"<H\", output[2])),\n                    unpack(\">H\", pack(\"<H\", output[1])),\n                    unpack(\">H\", pack(\"<H\", output[0])),\n                ]\n\n    def convert_string_value(self, value):\n        try:\n            return float(value)\n        except ValueError:\n            if self.dictionary is None:\n                d = Dictionary(name=str(self.name) + \"_auto_created\")\n                d.save()\n                self.dictionary = d\n                Variable.objects.bulk_update([self], [\"dictionary\"])\n                self.refresh_from_db()\n            if not len(self.dictionary.dictionaryitem_set.filter(label=str(value))):\n                max_value = 0\n                for di in self.dictionary.dictionaryitem_set.all():\n                    max_value = max(float(max_value), float(di.value))\n                DictionaryItem.objects.get_or_create(\n                    label=str(value),\n                    value=int(max_value) + 1,\n                    dictionary=self.dictionary,\n                )\n                # logger.debug('new value : %s' % (int(max_value) + 1))\n                return int(max_value) + 1\n            elif len(self.dictionary.dictionaryitem_set.filter(label=str(value))) == 1:\n                # logger.debug('value found : %s' % self.dictionary.dictionaryitem_set.get(label=str(value)).value)\n                return float(\n                    self.dictionary.dictionaryitem_set.get(label=str(value)).value\n                )\n            else:\n                logger.warning(\n                    \"%s duplicate values found of %s in dictionary %s\"\n                    % (\n                        len(\n                            self.dictionary.dictionaryitem_set.filter(label=str(value))\n                        ),\n                        value,\n                        self.dictionary,\n                    )\n                )\n                return float(\n                    self.dictionary.dictionaryitem_set.filter(label=str(value))\n                    .first()\n                    .value\n                )\n\n    def _get_objects_for_html(\n        self, list_to_append=None, obj=None, exclude_model_names=None\n    ):\n        list_to_append = get_objects_for_html(list_to_append, self, exclude_model_names)\n        return list_to_append\n\n    def get_protocol_variable(self):\n        related_variables = [\n            field\n            for field in Variable._meta.get_fields()\n            if issubclass(type(field), OneToOneRel)\n        ]\n        for v in related_variables:\n            try:\n                if (\n                    hasattr(self, v.name)\n                    and hasattr(getattr(self, v.name), \"protocol_id\")\n                    and hasattr(self, \"device\")\n                    and getattr(self, v.name).protocol_id == self.device.protocol.id\n                ):\n                    return getattr(self, v.name)\n            except (ProgrammingError, OperationalError) as e:\n                logger.debug(e)\n        return None\n\n\nclass VariableHandlerParameter(models.Model):\n    name = models.CharField(max_length=255)\n    value = models.CharField(max_length=255, default=\"\", blank=True)\n    variable = models.ForeignKey(Variable, on_delete=models.CASCADE)\n\n    def __str__(self):\n        return self.name\n\n\nclass DeviceWriteTask(models.Model):\n    id = models.AutoField(primary_key=True)\n    variable = models.ForeignKey(\n        Variable, blank=True, null=True, on_delete=models.SET_NULL\n    )\n    variable_property = models.ForeignKey(\n        VariableProperty, blank=True, null=True, on_delete=models.SET_NULL\n    )\n    value = models.FloatField()\n    user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)\n    start = models.FloatField(default=0)  # TODO DateTimeField\n    finished = models.FloatField(default=0, blank=True)  # TODO DateTimeField\n    done = models.BooleanField(default=False, blank=True)\n    failed = models.BooleanField(default=False, blank=True)\n\n    def __str__(self):\n        if self.variable:\n            return self.variable.name\n        elif self.variable_property:\n            return (\n                self.variable_property.variable.name\n                + \" : \"\n                + self.variable_property.name\n            )\n        else:\n            return str(self.id)\n\n    @property\n    def get_device_id(self):\n        if self.variable:\n            return self.variable.device.pk\n        elif self.variable_property:\n            return self.variable_property.variable.device.pk\n        else:\n            return 0\n\n    def create_and_notificate(self, dwts):\n        if type(dwts) != list:\n            dwts = [dwts]\n        DeviceWriteTask.objects.bulk_create(dwts)\n        if channels_driver:\n            scheduler = BackgroundProcess.objects.filter(id=1)\n            if len(scheduler):\n                scheduler_pid = scheduler.first().pid\n            else:\n                logger.warning(\"No PID found for the scheduler\")\n                scheduler_pid = None\n            for dwt in dwts:\n                try:\n                    device_id = dwt.get_device_id\n                    for bp in BackgroundProcess.objects.all():\n                        _device_id = bp.get_device_id()\n                        if (\n                            type(_device_id) == list\n                            and len(_device_id) > 0\n                            and dwt.get_device_id in _device_id\n                        ):\n                            device_id = _device_id[0]\n                            logger.debug(device_id)\n                    channel_layer = channels.layers.get_channel_layer()\n                    channel_layer.capacity = 1\n                    async_to_sync(channel_layer.send)(\n                        str(scheduler_pid) + \"_DeviceAction_for_\" + str(device_id),\n                        {\"DeviceWriteTask\": str(dwt.get_device_id)},\n                    )\n                except ChannelFull:\n                    logger.info(\n                        \"Channel full : \"\n                        + str(scheduler_pid)\n                        + \"_DeviceAction_for_\"\n                        + str(dwt.get_device_id)\n                    )\n                except (\n                    AttributeError,\n                    ConnectionRefusedError,\n                    InvalidChannelLayerError,\n                ) as e:\n                    logger.debug(e)\n\n    async def acreate_and_notificate(self, dwts):\n        return await sync_to_async(self.create_and_notificate)(dwts)\n        if type(dwts) != list:\n            dwts = [dwts]\n        await DeviceWriteTask.objects.abulk_create(dwts)\n        if channels_driver:\n            scheduler = BackgroundProcess.objects.filter(id=1)\n            if await scheduler.acount():\n                scheduler_pid = (await scheduler.afirst()).pid\n            else:\n                logger.warning(\"No PID found for the scheduler\")\n                scheduler_pid = None\n            async for dwt in dwts:\n                device_id = dwt.get_device_id\n                try:\n                    for bp in BackgroundProcess.objects.all():\n                        _device_id = bp.get_device_id()\n                        if (\n                            type(_device_id) == list\n                            and len(_device_id) > 0\n                            and dwt.get_device_id in _device_id\n                        ):\n                            device_id = _device_id[0]\n                            logger.debug(device_id)\n                    channel_layer = channels.layers.get_channel_layer()\n                    channel_layer.capacity = 1\n                    await channel_layer.send(\n                        str(scheduler_pid) + \"_DeviceAction_for_\" + str(device_id),\n                        {\"DeviceWriteTask\": str(device_id)},\n                    )\n                except ChannelFull:\n                    logger.info(\n                        \"Channel full : \"\n                        + str(scheduler_pid)\n                        + \"_DeviceAction_for_\"\n                        + str(device_id)\n                    )\n                except (\n                    AttributeError,\n                    ConnectionRefusedError,\n                    InvalidChannelLayerError,\n                ) as e:\n                    logger.debug(e)\n\n\nclass DeviceReadTask(models.Model):\n    id = models.AutoField(primary_key=True)\n    device = models.ForeignKey(Device, blank=True, null=True, on_delete=models.SET_NULL)\n    variable = models.ForeignKey(\n        Variable, blank=True, null=True, on_delete=models.SET_NULL\n    )\n    variable_property = models.ForeignKey(\n        VariableProperty, blank=True, null=True, on_delete=models.SET_NULL\n    )\n    user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)\n    start = models.FloatField(default=0)  # TODO DateTimeField\n    finished = models.FloatField(default=0, blank=True)  # TODO DateTimeField\n    done = models.BooleanField(default=False, blank=True)\n    failed = models.BooleanField(default=False, blank=True)\n\n    def __str__(self):\n        if self.variable:\n            return self.variable.name\n        elif self.variable_property:\n            return (\n                self.variable_property.variable.name\n                + \" : \"\n                + self.variable_property.name\n            )\n        elif self.device:\n            return self.device.short_name\n        else:\n            return str(self.id)\n\n    @property\n    def get_device_id(self):\n        if self.device:\n            return self.device.pk\n        elif self.variable:\n            return self.variable.device.pk\n        elif self.variable_property:\n            return self.variable_property.variable.device.pk\n        else:\n            return 0\n\n    def create_and_notificate(self, drts):\n        if type(drts) != list:\n            drts = [drts]\n        DeviceReadTask.objects.bulk_create(drts)\n        if channels_driver:\n            scheduler = BackgroundProcess.objects.filter(id=1)\n            if len(scheduler):\n                scheduler_pid = scheduler.first().pid\n            else:\n                logger.warning(\"No PID found for the scheduler\")\n                scheduler_pid = None\n            for drt in drts:\n                try:\n                    device_id = drt.get_device_id\n                    for bp in BackgroundProcess.objects.all():\n                        _device_id = bp.get_device_id()\n                        if (\n                            type(_device_id) == list\n                            and len(_device_id) > 0\n                            and drt.get_device_id in _device_id\n                        ):\n                            device_id = _device_id[0]\n                    channel_layer = channels.layers.get_channel_layer()\n                    channel_layer.capacity = 1\n                    async_to_sync(channel_layer.send)(\n                        str(scheduler_pid) + \"_DeviceAction_for_\" + str(device_id),\n                        {\"DeviceReadTask\": str(drt.get_device_id)},\n                    )\n                except ChannelFull:\n                    logger.info(\n                        \"Channel full : \"\n                        + str(scheduler_pid)\n                        + \"_DeviceAction_for_\"\n                        + str(drt.get_device_id)\n                    )\n                except (\n                    AttributeError,\n                    ConnectionRefusedError,\n                    InvalidChannelLayerError,\n                ) as e:\n                    logger.debug(e)\n\n\nclass Log(models.Model):\n    # id \t\t\t\t= models.AutoField(primary_key=True)\n    id = models.BigIntegerField(primary_key=True)\n    level = models.IntegerField(default=0, verbose_name=\"level\")\n    timestamp = models.FloatField()  # TODO DateTimeField\n    message_short = models.CharField(\n        max_length=400, default=\"\", verbose_name=\"short message\"\n    )\n    message = models.TextField(default=\"\", verbose_name=\"message\")\n    user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)\n\n    def __init__(self, *args, **kwargs):\n        if \"timestamp\" in kwargs:\n            timestamp = kwargs[\"timestamp\"]\n        else:\n            timestamp = time.time()\n            kwargs[\"timestamp\"] = timestamp\n        if \"id\" not in kwargs:\n            if \"level\" in kwargs:\n                kwargs[\"id\"] = int(\n                    int(int(timestamp * 1000) * 2097152) + kwargs[\"level\"]\n                )\n            else:\n                kwargs[\"id\"] = int(int(int(timestamp * 1000) * 2097152) + 0)\n        super(Log, self).__init__(*args, **kwargs)\n\n    def __str__(self):\n        return self.message\n\n\nclass BackgroundProcess(models.Model):\n    id = models.AutoField(primary_key=True)\n    pid = models.IntegerField(default=0)\n    label = models.CharField(max_length=400, default=\"\")\n    message = models.CharField(max_length=400, default=\"\")\n    enabled = models.BooleanField(default=False, blank=True)\n    done = models.BooleanField(default=False, blank=True)\n    failed = models.BooleanField(default=False, blank=True)\n    parent_process = models.ForeignKey(\n        \"BackgroundProcess\", null=True, on_delete=models.SET_NULL, blank=True\n    )\n    process_class = models.CharField(\n        max_length=400,\n        blank=True,\n        default=\"pyscada.utils.scheduler.Process\",\n        help_text=\"from pyscada.utils.scheduler import Process\",\n    )\n    process_class_kwargs = models.CharField(\n        max_length=400,\n        default=\"{}\",\n        blank=True,\n        help_text=\"\"\"arguments in json format will be passed as kwargs while the\n                                            init of the process instance, example:\n                                            {\"keywordA\":\"value1\", \"keywordB\":7}\"\"\",\n    )\n    last_update = models.DateTimeField(null=True, blank=True)\n    running_since = models.DateTimeField(null=True, blank=True)\n\n    class Meta:\n        verbose_name_plural = \"Background Processes\"\n\n    def __str__(self):\n        return self.label + \": \" + self.message\n\n    def get_device_id(self):\n        try:\n            kwargs = json.loads(self.process_class_kwargs)\n        except:\n            kwargs = {}\n        if \"device_id\" in kwargs:\n            return kwargs[\"device_id\"]\n        elif \"device_ids\" in kwargs:\n            return kwargs[\"device_ids\"]\n        else:\n            return None\n\n    def get_process_instance(self):\n        # kwargs = dict(s.split(\"=\") for s in self.process_class_kwargs.split())\n        try:\n            kwargs = json.loads(self.process_class_kwargs)\n        except:\n            kwargs = {}\n        #\n        kwargs[\"label\"] = self.label\n        kwargs[\"process_id\"] = self.pk\n        kwargs[\"parent_process_id\"] = self.parent_process.pk\n\n        class_name = self.process_class.split(\".\")[-1]\n        class_path = self.process_class.replace(\".\" + class_name, \"\")\n        try:\n            mod = __import__(class_path, fromlist=[class_name.__str__()])\n            process_class = getattr(mod, class_name.__str__())\n            return process_class(**kwargs)\n        except:\n            logger.error(\n                f\"{self.label}({getpid()}), unhandled exception\", exc_info=True\n            )\n            return None\n\n    def restart(self):\n        \"\"\"\n        restarts the process and all its child's\n\n        :return:\n        \"\"\"\n        if self.pid != 0 and self.pid is not None:\n            try:\n                kill(self.pid, signal.SIGUSR1)\n                logger.debug(\"%d: send SIGUSR1 to %d\" % (self.pk, self.pid))\n                return True\n            except OSError as e:\n                return False\n\n    def _stop(self, signum=signal.SIGTERM):\n        \"\"\"\n        stops the process and all its child's\n\n        :return:\n        \"\"\"\n        if self.pid != 0 and self.pid is not None:\n            logger.debug(\"send %s to daemon\" % signum)\n            try:\n                kill(self.pid, signum)\n            except OSError as e:\n                if e.errno == errno.ESRCH:\n                    try:\n                        logger.debug(\n                            \"%s: process id %d is terminated\" % (self, self.pid)\n                        )\n                        return True\n                    except:\n                        return False\n            try:\n                while True:\n                    wpid, status = waitpid(self.pid, WNOHANG)\n                    if not wpid:\n                        break\n            except:\n                pass\n            return False\n\n    def stop(self, signum=signal.SIGTERM, cleanup=False):\n        if cleanup:\n            self.done = True\n            self.save()\n            timeout = time.time() + 30  # 30s timeout\n            while time.time() < timeout:\n                if self._stop(signum=signum):\n                    return True\n                time.sleep(1)\n            if self._stop(signum=signal.SIGKILL):\n                self.delete()\n        else:\n            return self._stop(signum=signum)\n\n\nclass ComplexEvent(models.Model):\n    id = models.AutoField(primary_key=True)\n    label = models.CharField(max_length=400, default=\"\")\n    complex_mail_recipients = models.ManyToManyField(User, blank=True)\n    default_send_mail = models.BooleanField(\n        default=False, help_text=\"Send mail if no activated event\"\n    )\n    last_level = models.SmallIntegerField(default=-1)\n\n    def __str__(self):\n        return self.label\n\n    def do_event_check(self):\n        \"\"\" \"\"\"\n        item_found = None\n        timestamp = time.time()\n        active = False\n        var_list_final = {}\n        vp_list_final = {}\n\n        # check all level by inceasing order, keep the last level\n        for item in self.complexeventlevel_set.all().order_by(\"order\"):\n            (is_valid, var_list, vp_list) = item.is_valid()\n            if (\n                item_found is None\n                and not active\n                and self.last_level != item.level\n                and is_valid\n            ):\n                # logger.debug(\"item %s is valid : level %s\" % (item, item.level))\n                item_found = item\n                var_list_final = var_list\n                vp_list_final = vp_list\n                self.last_level = item.level\n                self.save()\n                prev_event = RecordedEvent.objects.filter(\n                    complex_event=self, active=True\n                )\n                if prev_event:\n                    if item.stop_recording:  # Stop recording\n                        # logger.debug(\"stop recording\")\n                        prev_event = prev_event.last()\n                        prev_event.active = False\n                        prev_event.time_end = timestamp\n                        prev_event.save()\n                else:\n                    if not item.stop_recording:  # Start Recording\n                        # logger.debug(\"start recording\")\n                        prev_event = RecordedEvent(\n                            complex_event=self, time_begin=timestamp, active=True\n                        )\n                        prev_event.save()\n            active = active or item.active\n\n        # for the highest level, compose mail and change output values\n        if item_found is not None:\n            if item_found.send_mail:  # Send Mail\n                (\n                    subject,\n                    message,\n                    html_message,\n                ) = self.compose_mail(item_found, var_list_final, vp_list_final)\n                for recipient in self.complex_mail_recipients.exclude(email=\"\"):\n                    Mail(\n                        None,\n                        subject,\n                        message,\n                        html_message,\n                        recipient.email,\n                        time.time(),\n                    ).save()\n\n            # Change values\n            for var_to_change in item_found.complexeventoutput_set.all():\n                if (\n                    var_to_change.value is not None\n                    and var_to_change.variable is not None\n                ):\n                    user, _ = User.objects.get_or_create(username=\"ComplexEvents\")\n                    dwt = DeviceWriteTask(\n                        variable=var_to_change.variable,\n                        value=var_to_change.value,\n                        user=user,\n                        start=timestamp,\n                    )\n                    dwt.create_and_notificate(dwt)\n\n        elif not active and self.last_level != -1:\n            self.last_level = -1\n            self.save()\n            for var_to_change in self.complexeventoutput_set.all():\n                if (\n                    var_to_change.value is not None\n                    and var_to_change.variable is not None\n                ):\n                    user, _ = User.objects.get_or_create(username=\"ComplexEvents\")\n                    dwt = DeviceWriteTask(\n                        variable=var_to_change.variable,\n                        value=var_to_change.value,\n                        user=user,\n                        start=timestamp,\n                    )\n                    dwt.create_and_notificate(dwt)\n            if self.default_send_mail:\n                (\n                    subject,\n                    message,\n                    html_message,\n                ) = self.compose_mail(None, {}, {})\n                for recipient in self.complex_mail_recipients.exclude(email=\"\"):\n                    Mail(\n                        None,\n                        subject,\n                        message,\n                        html_message,\n                        recipient.email,\n                        time.time(),\n                    ).save()\n\n            # logger.debug(\"level = -1\")\n            # No active event : stop recording\n            prev_event = RecordedEvent.objects.filter(complex_event=self, active=True)\n            if prev_event:\n                # logger.debug(\"stop recording2\")\n                prev_event = prev_event.last()\n                prev_event.active = False\n                prev_event.time_end = timestamp\n                prev_event.save()\n\n    def compose_mail(self, item_found, var_list, vp_list):\n        if hasattr(settings, \"EMAIL_PREFIX\"):\n            subject_str = settings.EMAIL_PREFIX\n        else:\n            subject_str = \"\"\n\n        if item_found is not None and item_found.active:\n            if item_found.level == 0:  # infomation\n                subject_str += \" - Information - \"\n            elif item_found.level == 1:  # Ok\n                subject_str += \" - Ok - \"\n            elif item_found.level == 2:  # warning\n                subject_str += \" - Warning! - \"\n            elif item_found.level == 3:  # alert\n                subject_str += \" - Alert! - \"\n            subject_str += self.label + \" - An event is active\"\n            message_str = \"The event group \" + self.label + \" has been triggered<br>\"\n            message_str += (\n                \"Level : \" + item_found.level_choices[item_found.level][1] + \"<br>\"\n            )\n            message_str += (\n                \"Validation : \"\n                + item_found.validation_choices[item_found.validation][1]\n                + \"<br>\"\n            )\n        else:\n            subject_str += \" - Information - \"\n            subject_str += self.label + \" No active event\"\n            message_str = \"The event group \" + self.label + \" has no active events<br>\"\n\n        message_str += \"Date : \" + str(datetime.datetime.now().isoformat()) + \"<br><br>\"\n\n        for i in var_list:\n            message_str += (\n                str(var_list[i][\"type\"])\n                + \" : \"\n                + str(var_list[i][\"name\"])\n                + \" (\"\n                + str(i)\n                + \") : \"\n            )\n            in_limit_str = (\n                \"<span style='color:red;'>\" + str(var_list[i][\"in_limit\"]) + \"</span>\"\n                if var_list[i][\"in_limit\"]\n                else str(var_list[i][\"in_limit\"])\n            )\n            message_str += in_limit_str + \"<br>\"\n            message_str += \"Last value on \"\n            message_str += str(\n                datetime.datetime.isoformat(\n                    datetime.datetime.utcfromtimestamp(var_list[i][\"datetime\"])\n                )\n            )\n            message_str += \" = \"\n            if var_list[i][\"label\"] is None:\n                message_str += str(var_list[i][\"value\"]) + \"<br>\"\n            else:\n                message_str += (\n                    str(var_list[i][\"label\"])\n                    + \" (\"\n                    + str(var_list[i][\"value\"])\n                    + \")<br>\"\n                )\n\n            message_str += \"Limit rules : \"\n            if var_list[i][\"limit_low_type\"] == 0:\n                limit_low_type = \"< \"\n            else:\n                limit_low_type = \"<= \"\n            if var_list[i][\"limit_high_type\"] == 0:\n                limit_high_type = \"< \"\n            else:\n                limit_high_type = \"<= \"\n            if (\n                var_list[i][\"hysteresis_low\"] == 0\n                or var_list[i][\"limit_low_value\"] is None\n            ) and (\n                var_list[i][\"hysteresis_high\"] == 0\n                or var_list[i][\"limit_high_value\"] is None\n            ):\n                if var_list[i][\"limit_low_value\"] is not None:\n                    message_str += str(var_list[i][\"limit_low_value\"]) + str(\n                        limit_low_type\n                    )\n                message_str += \" value \"\n                if var_list[i][\"limit_high_value\"] is not None:\n                    message_str += str(limit_high_type) + str(\n                        var_list[i][\"limit_high_value\"]\n                    )\n                message_str += \"<br><br>\"\n            else:\n                message_str += \"To enter the limit : <br>\"\n                if var_list[i][\"limit_low_value\"] is not None:\n                    message_str += str(\n                        var_list[i][\"limit_low_value\"] + var_list[i][\"hysteresis_low\"]\n                    )\n                    message_str += str(limit_low_type)\n                message_str += \" value \"\n                if var_list[i][\"limit_high_value\"] is not None:\n                    message_str += str(limit_high_type)\n                    message_str += str(\n                        var_list[i][\"limit_high_value\"] - var_list[i][\"hysteresis_high\"]\n                    )\n                message_str += \"<br>\"\n                message_str += \"To leave the limit : <br>\"\n                if var_list[i][\"limit_low_value\"] is not None:\n                    message_str += str(\n                        var_list[i][\"limit_low_value\"] - var_list[i][\"hysteresis_low\"]\n                    )\n                    message_str += str(limit_low_type)\n                message_str += \" value \"\n                if var_list[i][\"limit_high_value\"] is not None:\n                    message_str += str(limit_high_type)\n                    message_str += str(\n                        var_list[i][\"limit_high_value\"] + var_list[i][\"hysteresis_high\"]\n                    )\n                message_str += \"<br><br>\"\n        for i in vp_list:\n            message_str += (\n                str(vp_list[i][\"type\"])\n                + \" : \"\n                + str(vp_list[i][\"name\"])\n                + \" (\"\n                + str(i)\n                + \") : \"\n            )\n            in_limit_str = (\n                \"<span style='color:red;'>\" + str(vp_list[i][\"in_limit\"]) + \"</span>\"\n                if vp_list[i][\"in_limit\"]\n                else str(vp_list[i][\"in_limit\"])\n            )\n            message_str += in_limit_str + \"<br>\"\n            message_str += \"Last value on \"\n            message_str += str(datetime.datetime.isoformat(vp_list[i][\"datetime\"]))\n            message_str += \" = \"\n            if vp_list[i][\"label\"] is None:\n                message_str += str(vp_list[i][\"value\"]) + \"<br>\"\n            else:\n                message_str += (\n                    str(vp_list[i][\"label\"]) + \" (\" + str(vp_list[i][\"value\"]) + \")<br>\"\n                )\n            message_str += \"Limit rules : \"\n            if vp_list[i][\"limit_low_type\"] == 0:\n                limit_low_type = \"<\"\n            else:\n                limit_low_type = \"<=\"\n            if vp_list[i][\"limit_high_type\"] == 0:\n                limit_high_type = \"<\"\n            else:\n                limit_high_type = \"<=\"\n            if vp_list[i][\"hysteresis_low\"] == 0 and vp_list[i][\"hysteresis_high\"] == 0:\n                if vp_list[i][\"limit_low_value\"] is not None:\n                    message_str += str(vp_list[i][\"limit_low_value\"]) + str(\n                        limit_low_type\n                    )\n                message_str += \" value \"\n                if vp_list[i][\"limit_high_value\"] is not None:\n                    message_str += str(limit_high_type) + str(\n                        vp_list[i][\"limit_high_value\"]\n                    )\n                message_str += \"<br>\"\n            else:\n                message_str += \"To enter the limit : <br>\"\n                if vp_list[i][\"limit_low_value\"] is not None:\n                    message_str += str(\n                        vp_list[i][\"limit_low_value\"] + vp_list[i][\"hysteresis_low\"]\n                    )\n                    message_str += str(limit_low_type)\n                message_str += \" value \"\n                if vp_list[i][\"limit_high_value\"] is not None:\n                    message_str += str(limit_high_type)\n                    message_str += str(\n                        vp_list[i][\"limit_high_value\"] - vp_list[i][\"hysteresis_high\"]\n                    )\n                message_str += \"<br>\"\n                message_str += \"To leave the limit : <br>\"\n                if vp_list[i][\"limit_low_value\"] is not None:\n                    message_str += str(\n                        vp_list[i][\"limit_low_value\"] - vp_list[i][\"hysteresis_low\"]\n                    )\n                    message_str += str(limit_low_type)\n                message_str += \" value \"\n                if vp_list[i][\"limit_high_value\"] is not None:\n                    message_str += str(limit_high_type)\n                    message_str += str(\n                        vp_list[i][\"limit_high_value\"] + vp_list[i][\"hysteresis_high\"]\n                    )\n                message_str += \"<br><br>\"\n        return subject_str, \"\", message_str\n\n\nclass ComplexEventLevel(models.Model):\n    id = models.AutoField(primary_key=True)\n    level_choices = (\n        (0, \"informative\"),\n        (1, \"ok\"),\n        (2, \"warning\"),\n        (3, \"alert\"),\n    )\n    level = models.PositiveSmallIntegerField(default=0, choices=level_choices)\n    send_mail = models.BooleanField(default=False)\n    order = models.PositiveSmallIntegerField(default=0)\n    stop_recording = models.BooleanField(default=False)\n    validation_choices = (\n        (0, \"OR\"),\n        (1, \"AND\"),\n        (2, \"Custom\"),\n    )\n    validation = models.PositiveSmallIntegerField(default=0, choices=validation_choices)\n    custom_validation = models.CharField(\n        max_length=400, default=\"\", blank=True, null=True\n    )\n    active = models.BooleanField(default=False)\n    complex_event = models.ForeignKey(ComplexEvent, on_delete=models.CASCADE)\n\n    def is_valid(self):\n        valid = False\n        vars_infos = {}\n        vp_infos = {}\n        if self.validation == 0:  # OR\n            valid = False\n        elif self.validation == 1 and self.complexeventinput_set.count():  # AND\n            valid = True\n        for item in self.complexeventinput_set.all():\n            (in_limit, item_info) = item.in_limit()\n            if in_limit is None:\n                if self.validation == 1:\n                    valid = False\n                continue\n            if in_limit:\n                if self.validation == 0:\n                    valid = True\n            else:\n                if self.validation == 1:\n                    valid = False\n            if item.get_type() == \"variable\" and len(item_info):\n                vars_infos[item.get_id()] = item_info\n            elif item.get_type() == \"variable_property\" and len(item_info):\n                vp_infos[item.get_id()] = item_info\n        if self.active != valid:\n            self.active = valid\n            self.save()\n        return valid, vars_infos, vp_infos\n\n    def __str__(self):\n        return self.complex_event.label + \"-\" + self.level_choices[self.level][1]\n\n\nclass ComplexEventOutput(models.Model):\n    id = models.AutoField(primary_key=True)\n    variable = models.ForeignKey(\n        Variable,\n        blank=True,\n        null=True,\n        default=None,\n        on_delete=models.SET_NULL,\n        help_text=\"Variable to change on event changes\",\n    )\n    value = models.CharField(max_length=400)\n    complex_event = models.ForeignKey(\n        ComplexEvent, blank=True, null=True, on_delete=models.CASCADE\n    )\n    complex_event_level = models.ForeignKey(\n        ComplexEventLevel, blank=True, null=True, on_delete=models.CASCADE\n    )\n\n\nclass ComplexEventInput(models.Model):\n    id = models.AutoField(primary_key=True)\n    fixed_limit_low = models.FloatField(default=0, blank=True, null=True)\n    variable_limit_low = models.ForeignKey(\n        Variable,\n        blank=True,\n        null=True,\n        default=None,\n        on_delete=models.SET_NULL,\n        related_name=\"variable_limit_low\",\n        help_text=\"\"\"you can choose either an\n                                            fixed limit or an variable limit that is dependent on the current value of\n                                            an variable, if you choose a value other than  none for variable limit the\n                                            fixed limit would be ignored\"\"\",\n    )\n    limit_low_type_choices = (\n        (\n            0,\n            \"limit < value\",\n        ),\n        (\n            1,\n            \"limit <= value\",\n        ),\n    )\n    limit_low_type = models.PositiveSmallIntegerField(\n        default=0, choices=limit_low_type_choices\n    )\n    hysteresis_low = models.FloatField(default=0)\n    variable = models.ForeignKey(\n        Variable,\n        related_name=\"variable\",\n        blank=True,\n        null=True,\n        on_delete=models.CASCADE,\n    )\n    variable_property = models.ForeignKey(\n        VariableProperty, blank=True, null=True, on_delete=models.CASCADE\n    )\n    fixed_limit_high = models.FloatField(default=0, blank=True, null=True)\n    variable_limit_high = models.ForeignKey(\n        Variable,\n        blank=True,\n        null=True,\n        default=None,\n        on_delete=models.SET_NULL,\n        related_name=\"variable_limit_high\",\n        help_text=\"\"\"you can choose either an\n                                            fixed limit or an variable limit that is dependent on the current value of\n                                            an variable, if you choose a value other than  none for variable limit the\n                                            fixed limit would be ignored\"\"\",\n    )\n    limit_high_type_choices = (\n        (\n            0,\n            \"value < limit\",\n        ),\n        (\n            1,\n            \"value <= limit\",\n        ),\n    )\n    limit_high_type = models.PositiveSmallIntegerField(\n        default=0, choices=limit_high_type_choices\n    )\n    hysteresis_high = models.FloatField(default=0)\n    active = models.BooleanField(default=False)\n    complex_event_level = models.ForeignKey(ComplexEventLevel, on_delete=models.CASCADE)\n\n    def in_limit(self):\n        item_value = None\n        item_date = None\n        item_type = None\n        item_name = None\n        item_dict_label = None\n        limit_low = None\n        limit_high = None\n\n        if self.variable is not None and self.variable.active:\n            if self.variable.check_last_datapoint():\n                item_value = self.variable.prev_value\n                item_date = self.variable.timestamp_old\n            item_type = \"variable\"\n            item_name = self.variable.name\n        elif self.variable_property is not None:\n            item_value = self.variable_property.value()\n            item_date = self.variable_property.last_modified\n            if type(item_value) != int and type(item_value) != float:\n                item_value = None\n            item_type = \"variable_property\"\n            item_name = self.variable_property.name\n\n        var_info = {\n            \"value\": item_value,\n            \"datetime\": item_date,\n            \"type\": item_type,\n            \"name\": item_name,\n            \"limit_low_type\": self.limit_low_type_choices[self.limit_low_type][0],\n            \"limit_low_value\": limit_low,\n            \"hysteresis_low\": self.hysteresis_low,\n            \"limit_high_type\": self.limit_high_type_choices[self.limit_high_type][0],\n            \"limit_high_value\": limit_high,\n            \"hysteresis_high\": self.hysteresis_high,\n            \"label\": item_dict_label,\n            \"in_limit\": None,\n        }\n\n        if item_value is not None:\n            if self.variable_limit_low is not None:\n                if self.variable_limit_low.check_last_datapoint():\n                    limit_low = self.variable_limit_low.prev_value\n                else:\n                    limit_low = None\n            else:\n                limit_low = self.fixed_limit_low\n            if self.variable_limit_high is not None:\n                if self.variable_limit_high.check_last_datapoint():\n                    limit_high = self.variable_limit_high.prev_value\n                else:\n                    limit_high = None\n            else:\n                limit_high = self.fixed_limit_high\n            if limit_low is None and limit_high is None:\n                return None, var_info\n            var_info[\"limit_low_value\"] = limit_low\n            var_info[\"limit_high_value\"] = limit_high\n            if self.variable is not None and self.variable.dictionary is not None:\n                var_info[\"label\"] = self.variable.dictionary.get_label(item_value)\n            elif (\n                self.variable_property is not None\n                and self.variable_property.dictionary is not None\n            ):\n                var_info[\"label\"] = self.variable_property.dictionary.get_label(\n                    item_value\n                )\n\n            actived = self.active\n            if (\n                limit_low is not None\n                and self.limit_low_type == 0\n                and item_value\n                <= (limit_low + self.hysteresis_low * np.power(-1, self.active))\n            ):\n                var_info[\"in_limit\"] = False\n                self.active = False\n            elif (\n                limit_low is not None\n                and self.limit_low_type == 1\n                and item_value\n                < (limit_low + self.hysteresis_low * np.power(-1, self.active))\n            ):\n                var_info[\"in_limit\"] = False\n                self.active = False\n            elif (\n                limit_high is not None\n                and self.limit_high_type == 0\n                and (limit_high - self.hysteresis_high * np.power(-1, self.active))\n                <= item_value\n            ):\n                var_info[\"in_limit\"] = False\n                self.active = False\n            elif (\n                limit_high is not None\n                and self.limit_high_type == 1\n                and (limit_high - self.hysteresis_high * np.power(-1, self.active))\n                < item_value\n            ):\n                var_info[\"in_limit\"] = False\n                self.active = False\n            else:\n                var_info[\"in_limit\"] = True\n                self.active = True\n            if actived != self.active:\n                self.save()\n            return self.active, var_info\n        return None, var_info\n\n    def get_id(self):\n        if self.variable is not None:\n            return self.variable.pk\n        elif self.variable_property is not None:\n            return self.variable_property.pk\n\n    def get_type(self):\n        if self.variable is not None:\n            return \"variable\"\n        elif self.variable_property is not None:\n            return \"variable_property\"\n\n\nclass Event(models.Model):\n    id = models.AutoField(primary_key=True)\n    label = models.CharField(max_length=400, default=\"\")\n    variable = models.ForeignKey(Variable, null=True, on_delete=models.CASCADE)\n    level_choices = (\n        (0, \"informative\"),\n        (1, \"ok\"),\n        (2, \"warning\"),\n        (3, \"alert\"),\n    )\n    level = models.PositiveSmallIntegerField(default=0, choices=level_choices)\n    fixed_limit = models.FloatField(default=0, blank=True, null=True)\n    variable_limit = models.ForeignKey(\n        Variable,\n        blank=True,\n        null=True,\n        default=None,\n        on_delete=models.SET_NULL,\n        related_name=\"variable_limit\",\n        help_text=\"\"\"you can choose either an fixed limit or an variable limit that is\n                                        dependent on the current value of an variable, if you choose a value other than\n                                        none for variable limit the fixed limit would be ignored\"\"\",\n    )\n    limit_type_choices = (\n        (\n            0,\n            \"value < limit\",\n        ),\n        (\n            1,\n            \"value <= limit\",\n        ),\n        (2, \"limit < value\"),\n        (3, \"limit <= value\"),\n        (4, \"value == limit\"),\n    )\n    limit_type = models.PositiveSmallIntegerField(default=0, choices=limit_type_choices)\n    hysteresis = models.FloatField(default=0)\n    action_choices = (\n        (0, \"just record\"),\n        (1, \"record and send mail only when event occurs\"),\n        (2, \"record and send mail\"),\n        (3, \"record, send mail and change variable\"),\n    )\n    action = models.PositiveSmallIntegerField(default=0, choices=action_choices)\n    mail_recipients = models.ManyToManyField(User)\n    variable_to_change = models.ForeignKey(\n        Variable,\n        blank=True,\n        null=True,\n        default=None,\n        on_delete=models.SET_NULL,\n        related_name=\"variable_to_change\",\n    )\n    new_value = models.FloatField(default=0, blank=True, null=True)\n\n    def __str__(self):\n        return self.label\n\n    def do_event_check(self):\n        \"\"\"\n        compare the actual value with the limit value\n\n        (0,'value is below the limit',),\n        (1,'value is less than or equal to the limit',),\n        (2,'value is greater than the limit'),\n        (3,'value is greater than or equal to the limit'),\n        (4,'value equals the limit'),\n        \"\"\"\n\n        def compose_mail(active):\n            if hasattr(settings, \"EMAIL_PREFIX\"):\n                subject_str = settings.EMAIL_PREFIX\n            else:\n                subject_str = \"\"\n\n            if active:\n                if self.level == 0:  # infomation\n                    subject_str += \" Information \"\n                elif self.level == 1:  # Ok\n                    subject_str += \" \"\n                elif self.level == 2:  # warning\n                    subject_str += \" Warning! \"\n                elif self.level == 3:  # alert\n                    subject_str += \" Alert! \"\n                subject_str += self.variable.name + \" exceeded the limit\"\n            else:\n                subject_str += \" Information \"\n                subject_str += self.variable.name + \" is back in limit\"\n            message_str = \"The Event \" + self.label + \" has been triggered\\n\"\n            message_str += (\n                \"Value of \"\n                + self.variable.name\n                + \" is \"\n                + actual_value.__str__()\n                + \" \"\n                + self.variable.unit.unit\n            )\n            message_str += (\n                \" Limit is \" + limit_value.__str__() + \" \" + self.variable.unit.unit\n            )\n            return subject_str, message_str\n\n        #\n        # get recorded event\n        prev_event = RecordedEvent.objects.filter(event=self, active=True)\n        if prev_event:\n            prev_value = True\n        else:\n            prev_value = False\n        # get the actual value\n        # actual_value = RecordedDataCache.objects.filter(variable=self.variable).last() # TODO change to RecordedData\n        if not self.variable.check_last_datapoint():\n            return False\n        timestamp = self.variable.timestamp_old\n        actual_value = self.variable.prev_value\n        # determine the limit type, variable or fixed\n        if self.variable_limit:\n            # item has a variable limit\n            # get the limit value\n            # limit_value = RecordedDataCache.objects.filter(variable=self.variable_limit) # TODO change to RecordedData\n            if not self.variable_limit.check_last_datapoint():\n                return False  # No previous value for this variable\n            if timestamp < self.variable_limit.timestamp_old:\n                # when limit value has changed after the actual value take that time\n                timestamp = self.variable_limit.timestamp_old\n            limit_value = self.variable_limit.prev_value  # get value\n        else:\n            # item has a fixed limit\n            limit_value = self.fixed_limit\n\n        if self.limit_type == 0:\n            if prev_value:\n                limit_check = actual_value < (limit_value + self.hysteresis)\n            else:\n                limit_check = actual_value < (limit_value - self.hysteresis)\n        elif self.limit_type == 1:\n            if prev_value:\n                limit_check = actual_value <= (limit_value + self.hysteresis)\n            else:\n                limit_check = actual_value <= (limit_value - self.hysteresis)\n        elif self.limit_type == 4:\n            limit_check = (\n                limit_value + self.hysteresis\n                >= actual_value\n                >= limit_value - self.hysteresis\n            )\n        elif self.limit_type == 3:\n            if prev_value:\n                limit_check = actual_value >= (limit_value - self.hysteresis)\n            else:\n                limit_check = actual_value >= (limit_value + self.hysteresis)\n        elif self.limit_type == 2:\n            if prev_value:\n                limit_check = actual_value > (limit_value - self.hysteresis)\n            else:\n                limit_check = actual_value > (limit_value + self.hysteresis)\n        else:\n            return False\n\n        # record event\n        if limit_check:  # value is outside of the limit\n            if not prev_event:\n                # if there is no previus event record the Event\n                prev_event = RecordedEvent(\n                    event=self, time_begin=timestamp, active=True\n                )\n                prev_event.save()\n\n                if self.action >= 1:\n                    # compose and send mail\n                    (\n                        subject,\n                        message,\n                    ) = compose_mail(True)\n                    for recipient in self.mail_recipients.exclude(email=\"\"):\n                        Mail(\n                            None, subject, message, None, recipient.email, time.time()\n                        ).save()\n\n                if self.action >= 3:\n                    # do action\n                    if self.variable_to_change:\n                        DeviceWriteTask(\n                            variable=self.variable_to_change,\n                            value=self.new_value,\n                            start=timestamp,\n                        )\n        else:  # back inside of limit\n            if prev_event:  #\n                prev_event = prev_event.last()\n                prev_event.active = False\n                prev_event.time_end = timestamp\n                prev_event.save()\n\n                if self.action >= 2:\n                    # compose and send mail\n                    (\n                        subject,\n                        message,\n                    ) = compose_mail(False)\n                    for recipient in self.mail_recipients.exclude(email=\"\"):\n                        Mail(\n                            None, subject, message, None, recipient.email, time.time()\n                        ).save()\n\n\nclass RecordedEvent(models.Model):\n    id = models.AutoField(primary_key=True)\n    event = models.ForeignKey(Event, null=True, on_delete=models.CASCADE)\n    complex_event = models.ForeignKey(ComplexEvent, null=True, on_delete=models.CASCADE)\n    time_begin = models.FloatField(default=0)  # TODO DateTimeField\n    time_end = models.FloatField(null=True, blank=True)  # TODO DateTimeField\n    active = models.BooleanField(default=False, blank=True)\n\n    def __str__(self):\n        if self.event:\n            return self.event.label\n        elif self.complex_event:\n            return self.complex_event.label\n\n\nclass Mail(models.Model):\n    id = models.AutoField(primary_key=True)\n    subject = models.TextField(default=\"\", blank=True)\n    message = models.TextField(default=\"\", blank=True)\n    html_message = models.TextField(null=True, blank=True)\n    to_email = models.EmailField(max_length=254)\n    timestamp = models.FloatField(default=0)  # TODO DateTimeField\n    done = models.BooleanField(default=False, blank=True)\n    send_fail_count = models.PositiveSmallIntegerField(default=0)\n\n    def send_mail(self):\n        # TODO check email limit\n        # blocked_recipient = [] # list of blocked mail recipoients\n        # mail_count_limit = 200 # send max 200 Mails per 24h per user\n        #\n        # for recipient in mail.mail_recipients.exclude(to_email__in=blocked_recipient):\n        # \tif recipient.mail_set.filter(timestamp__gt=time()-(60*60*24)).count() > self.mail_count_limit:\n        # \t\tblocked_recipient.append(recipient.pk)\n        if self.send_fail_count >= 3 or self.done:\n            # only try to send an email three times\n            return False\n        # send the mail\n        try:\n            if send_mail(\n                self.subject,\n                self.message,\n                settings.DEFAULT_FROM_EMAIL,\n                [self.to_email],\n                fail_silently=False,\n                html_message=self.html_message,\n            ):\n                self.done = True\n                self.timestamp = time.time()\n                self.save()\n                return True\n            else:\n                self.send_fail_count = self.send_fail_count + 1\n                self.timestamp = time.time()\n                self.save()\n                return False\n        except Exception as e:\n            logger.warning(f\"Send mail exception : {e}\")\n            self.send_fail_count = self.send_fail_count + 1\n            self.timestamp = time.time()\n            self.save()\n            return False\n\n    def __str__(self):\n        return self.id.__str__()\n"
  },
  {
    "path": "pyscada/signals.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada.models import (\n    Variable,\n    Device,\n    Scaling,\n    BackgroundProcess,\n    VariableProperty,\n    DeviceHandler,\n)\nfrom pyscada.admin import VariableState\n\nfrom django.dispatch import receiver\nfrom django.db.models.signals import (\n    post_save,\n    pre_delete,\n)\n\nimport signal\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\n@receiver(pre_delete, sender=Variable)\n@receiver(post_save, sender=Variable)\ndef _variable(sender, instance, **kwargs):\n    \"\"\"\n    Send signal to restart the bp associated for the device of this variable\n    \"\"\"\n    if type(instance) is Variable or type(instance) is VariableState:\n        logger.debug(\n            f\"post_save or pre_delete {type(instance).__name__}.{instance}-{getattr(instance, 'id', 'new')}\"\n        )\n        try:\n            post_save.send_robust(\n                sender=type(instance.device), instance=instance.device\n            )\n        except Exception as e:\n            logger.debug(e)\n\n\n@receiver(pre_delete, sender=Scaling)\n@receiver(post_save, sender=Scaling)\ndef _scaling(sender, instance, **kwargs):\n    \"\"\"\n    Send signal to restart the bp associated for each device of a variable using this scaling\n    \"\"\"\n    if type(instance) is Scaling:\n        logger.debug(\n            f\"post_save or pre_delete {type(instance).__name__}.{instance}-{getattr(instance, 'id', 'new')}\"\n        )\n        try:\n            for variable in instance.variable_set.all():\n                post_save.send_robust(\n                    sender=type(variable.device), instance=variable.device\n                )\n        except Exception as e:\n            logger.debug(e)\n\n\n@receiver(pre_delete, sender=DeviceHandler)\n@receiver(post_save, sender=DeviceHandler)\ndef _device_handler_post_save(sender, instance, **kwargs):\n    \"\"\"\n    Send signal to restart the bp associated for each device using this device handler\n    \"\"\"\n    if type(instance) is DeviceHandler:\n        logger.debug(\n            f\"post_save or pre_delete {type(instance).__name__}.{instance}-{getattr(instance, 'id', 'new')}\"\n        )\n        try:\n            for device in instance.device_set.all():\n                post_save.send_robust(sender=type(device), instance=device)\n        except Exception as e:\n            logger.debug(e)\n\n\n@receiver(post_save, sender=VariableProperty)\ndef _vp_post_save(sender, instance, **kwargs):\n    if type(instance) is VariableProperty:\n        # TODO: handle VP change but no value change\n        pass\n\n\n@receiver(post_save, sender=Device)\ndef _reinit_daq_daemons(sender, instance, **kwargs):\n    \"\"\"\n    Update the daq daemon configuration when changes be applied in the models\n    \"\"\"\n    if type(instance) is Device:\n        logger.debug(\n            f\"post_save {type(instance).__name__}.{instance}-{getattr(instance, 'id', 'new')}\"\n        )\n        try:\n            bp = BackgroundProcess.objects.get(\n                done=False,\n                failed=False,\n                label=f\"pyscada.{instance.protocol.protocol}-{instance.id}\",\n            )\n        except BackgroundProcess.DoesNotExist:\n            try:\n                # for modbus protocol\n                bp = BackgroundProcess.objects.get(\n                    done=False,\n                    failed=False,\n                    label__startswith=f\"pyscada.{instance.protocol.protocol}-{instance.id}-\",\n                )\n            except BackgroundProcess.DoesNotExist:\n                # new device, add it to the parent process list\n                # todo select only one device not all for that protocol\n                if instance.protocol_id == 1:\n                    # generic device has no parent process to restart\n                    return False\n                try:\n                    bp = BackgroundProcess.objects.get(pk=instance.protocol_id)\n                except BackgroundProcess.DoesNotExist:\n                    logger.debug(\n                        f\"BackgroundProcess for protocol {instance.protocol_id} not found\"\n                    )\n                    return False\n        except Exception as e:\n            logger.debug(e)\n            return False\n        logger.debug(bp.label)\n        bp.restart()\n        return True\n    else:\n        logger.debug(f\"Unknown post_save from {type(sender)} to {type(instance)}\")\n\n\n@receiver(pre_delete, sender=Device)\ndef _del_daq_daemons(sender, instance, **kwargs):\n    \"\"\"\n    Delete the daq daemon when device is deleted\n    \"\"\"\n    if type(instance) is Device:\n        logger.debug(\n            f\"pre_delete {type(instance).__name__}.{instance}-{getattr(instance, 'id', 'new')}\"\n        )\n        try:\n            bp = BackgroundProcess.objects.get(\n                done=False,\n                failed=False,\n                label=f\"pyscada.{instance.protocol.protocol}-{instance.id}\",\n            )\n        except BackgroundProcess.DoesNotExist:\n            try:\n                # for modbus protocol\n                bp = BackgroundProcess.objects.get(\n                    done=False,\n                    failed=False,\n                    label__startswith=f\"pyscada.{instance.protocol.protocol}-{instance.id}\",\n                )\n            except BackgroundProcess.DoesNotExist:\n                # BP not created, cannot stop\n                return False\n        except Exception as e:\n            logger.debug(e)\n            return False\n        bp.stop(signum=signal.SIGKILL)\n        return True\n    else:\n        logger.debug(f\"Unknown pre_delete from {type(sender)} to {type(instance)}\")\n"
  },
  {
    "path": "pyscada/single_value_datasource/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom pyscada import core\n\n__version__ = core.__version__\n__author__ = core.__author__\n__email__ = core.__email__\n__description__ = (\n    \"Single Value Datasource for PyScada a Python and Django based Open Source SCADA System\"\n)\n__app_name__ = \"single_value_datasource\"\n"
  },
  {
    "path": "pyscada/single_value_datasource/apps.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport logging\n\nfrom django.apps import AppConfig\nfrom django.utils.translation import gettext_lazy as _\n\nlogger = logging.getLogger(__name__)\n\n\nclass PyScada1SingleValueDatasourceConfig(AppConfig):\n    name = \"pyscada.single_value_datasource\"\n    verbose_name = _(\"PyScada Single Value Datasource\")\n    default_auto_field = \"django.db.models.AutoField\"\n"
  },
  {
    "path": "pyscada/single_value_datasource/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.10 on 2026-02-12 11:04\n\nimport django.db.models.deletion\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('pyscada', '0116_variable_pause_recording'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='DjangoSingleValue',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('datasource', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='pyscada.datasource')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='SingleValueRecordedData',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('date_saved', models.DateTimeField(db_index=True)),\n                ('value_boolean', models.BooleanField(blank=True, default=False)),\n                ('value_int16', models.SmallIntegerField(blank=True, null=True)),\n                ('value_int32', models.IntegerField(blank=True, null=True)),\n                ('value_int64', models.BigIntegerField(blank=True, null=True)),\n                ('value_float64', models.FloatField(blank=True, null=True)),\n                ('timestamp', models.FloatField(db_index=True)),\n                ('variable', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='pyscada.variable')),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "pyscada/single_value_datasource/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "pyscada/single_value_datasource/models.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport logging\nimport time\n\nfrom django.core.cache import cache\nfrom django.db import models\nfrom django.utils.timezone import now\n\nfrom pyscada.models import DataSource, Variable\nfrom pyscada.utils import timestamp_to_datetime\n\nlogger = logging.getLogger(__name__)\n\n\nclass DjangoSingleValue(models.Model):\n    datasource = models.OneToOneField(DataSource, on_delete=models.CASCADE)\n\n    def __str__(self):\n        return f\"Django single Value Datasource\"\n\n    def last_datapoint(self, variable=None, use_date_saved=False, **kwargs):\n        \"\"\"returns the last data for a given Variable in the cache and checks for stale\n        data\n\n        Args:\n            variable_id: Primary Key of the Variable for which the data is stored\n            new_data: in the form [[timestamp, value, (date_saved)], ...]\n            date_saved (optional): date when the data was saved\n\n        Returns:\n            last element\n        \"\"\"\n        if variable is None:\n            logger.info(\n                \"No variable defined for DjangoSingleValue last_datapoint function\"\n            )\n            return None\n        if not hasattr(variable, \"singlevaluerecordeddata\"):\n            logger.info(\n                \"variable has no singlevaluerecordeddata attr\"\n            )\n            return None\n        recorded_data = variable.singlevaluerecordeddata\n        if recorded_data is None:\n            return None\n        data = [recorded_data.timestamp, recorded_data.value]\n\n        if data is None:\n            return None\n\n        return data[0:2]\n\n    def query_datapoints(\n        self,\n        variable_ids=[],\n        time_min=0,\n        time_max=None,\n        query_first_value=False,\n        time_min_excluded=False,\n        time_max_excluded=False,\n        **kwargs,\n    ):\n        \"\"\"returns all data for a list of Variables in the cache in a given periode\n\n        Args:\n            variable_ids: Primary Key of the Variable for which the data is stored\n            time_min (optional):\n            time_min (optional):\n            query_first_value (optional):\n            time_min_excluded (optional):\n            time_max_excluded (optional):\n\n\n        Returns:\n            datapoints\n        \"\"\"\n        if time_max is None:\n            time_max = time.time()\n\n        variable_ids = self.datasource.datasource_check(\n            items=variable_ids, items_as_id=True, ids_model=Variable\n        )\n        output = {}\n        output[\"timestamp\"] = 0\n        output[\"date_saved_max\"] = 0\n\n        for variable_id in variable_ids:\n            data = SingleValueRecordedData.objects.filter(variable_id=variable_id).last()\n\n            if data is None:\n                continue\n            timestamp = data.timestamp\n            value = data.value\n            date_saved = data.date_saved.timestamp()\n\n            if timestamp > time_max or (time_max_excluded and timestamp >= time_max):\n                    continue\n\n            if query_first_value:\n                # short cut\n                output[variable_id] = [[timestamp, value],]\n                output[\"timestamp\"] = max(output[\"timestamp\"], timestamp)\n                output[\"date_saved_max\"] = max(output[\"date_saved_max\"], date_saved)\n                continue\n\n            if timestamp < time_min or (time_min_excluded and timestamp <= time_min):\n                continue\n\n            output[variable_id] = [[timestamp, value],]\n            output[\"timestamp\"] = max(output[\"timestamp\"], timestamp)\n            output[\"date_saved_max\"] = max(output[\"date_saved_max\"], date_saved)\n\n        return output\n\n    def write_datapoints(self, items=[], date_saved=None, **kwargs):\n        \"\"\"\n        Args:\n            datapoints:  { variable_id: [[timestamp, value, date_saved]] } with\n                timestamp in s and date_saved as datetime, timestamp in s or None\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n\n        Returns:\n            None\n        \"\"\"\n        items = self.datasource.datasource_check(items)\n        date_saved = date_saved if date_saved is not None else now()\n        for item in items:\n            logger.debug(f\"{item} has {len(item.cached_values_to_write)} to write.\")\n            if not hasattr(item, \"date_saved\") or item.date_saved is None:\n                item.date_saved = date_saved\n\n            try:\n                recorded_data = item.singlevaluerecordeddata\n            except Variable.singlevaluerecordeddata.RelatedObjectDoesNotExist:\n                recorded_data = None\n\n            if recorded_data is None:\n                recorded_data = SingleValueRecordedData(pk=1, variable=item)\n\n            if recorded_data.update_data(\n                new_data=item.cached_values_to_write,\n                date_saved=item.date_saved,\n                ):\n                recorded_data.save()\n\n            item.date_saved = None\n            item.erase_cache()\n\n    def write_raw_datapoints(self, datapoints: dict, date_saved=None):\n        \"\"\"writes raw datapoints to the database in the form\n\n        Args:\n            datapoints:  { variable_id: [[timestamp, value, date_saved]] } with\n                timestamp in s and date_saved as datetime, timestamp in s or None\n            date_saved (datetime, optional): time when the data was saved. Defaults to\n                now()\n\n        Returns:\n            None\n        \"\"\"\n        for variable_id in datapoints.keys():\n            for datapoint in datapoints[variable_id]:\n                if len(datapoint) == 2:\n                    if date_saved is None:\n                        datapoint.append(now())\n                    else:\n                        datapoint.append(date_saved)\n\n                elif len(datapoint) == 3:\n                    if datapoint[2] is None:\n                        if date_saved is None:\n                            datapoint[2] = now()\n                        else:\n                            datapoint[2] = date_saved\n\n                    elif type(datapoint[2]) is int or type(datapoint[2]) is float:\n                        datapoint[2] = timestamp_to_datetime(datapoint[2])\n\n                recorded_data = SingleValueRecordedData.objects.filter(variable_id=variable_id).last()\n                if recorded_data is None:\n                    recorded_data = SingleValueRecordedData(variable_id=variable_id)\n\n                if recorded_data.update_data(\n                    new_data=[datapoint[0:2]],\n                    date_saved=datapoint[2],\n                    ):\n                    recorded_data.save()\n\n\nclass SingleValueRecordedData(models.Model):\n    variable = models.OneToOneField(Variable, null=True, on_delete=models.SET_NULL)\n    date_saved = models.DateTimeField(db_index=True)\n    value_boolean = models.BooleanField(default=False, blank=True)  # boolean\n    value_int16 = models.SmallIntegerField(null=True, blank=True)  # int16, uint8, int8\n    value_int32 = models.IntegerField(\n        null=True, blank=True\n    )  # uint8, int16, uint16, int32\n    value_int64 = models.BigIntegerField(null=True, blank=True)  # uint32, int64, int48\n    value_float64 = models.FloatField(null=True, blank=True)  # float64, float48\n    timestamp = models.FloatField(db_index=True)\n\n\n    @property\n    def value(self):\n        \"\"\"\n        return the stored value\n        \"\"\"\n        if self.variable is None:\n            return None\n\n        if self.variable.value_class.upper() in [\n            \"FLOAT\",\n            \"FLOAT64\",\n            \"DOUBLE\",\n            \"FLOAT32\",\n            \"SINGLE\",\n            \"REAL\",\n            \"FLOAT48\",\n        ]:\n            return self.value_float64\n        elif self.variable.scaling and not self.variable.value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return self.value_float64\n        elif self.variable.value_class.upper() in [\"UINT64\"]:\n            # moving the int64 range [2**63, 2**63 - 1] stored as a django\n            # BigIntegerField to the int64 [0, 2**64 - 1]\n            value = self.value_int64\n            if value is None:\n                return value\n            return value + 2**63\n        elif self.variable.value_class.upper() in [\"INT64\", \"UINT32\", \"DWORD\", \"INT48\"]:\n            return self.value_int64\n        elif self.variable.value_class.upper() in [\"WORD\", \"UINT\", \"UINT16\", \"INT32\"]:\n            return self.value_int32\n        elif self.variable.value_class.upper() in [\"INT16\", \"INT8\", \"UINT8\"]:\n            return self.value_int16\n        elif self.variable.value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            return self.value_boolean\n        else:\n            logger.warning(\n                f\"The {self.variable.value_class.upper()} variable value class is not defined in RecordedData value function. Default reading value as float.\"  # noqa: E501\n            )\n            return self.value_float64\n\n\n    @value.setter\n    def value(self, value):\n        if self.variable.value_class.upper() in [\n                \"FLOAT\",\n                \"FLOAT64\",\n                \"DOUBLE\",\n                \"FLOAT32\",\n                \"SINGLE\",\n                \"REAL\",\n                \"FLOAT48\",\n            ]:\n            self.value_float64 = float(value)\n        elif self.variable.scaling and not self.variable.value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n                self.value_float64 = float(value)\n        elif self.variable.value_class.upper() in [\"UINT64\"]:\n            # moving the uint64 range [0, 2**64 - 1] to the int64\n            # [-2**63, 2**63 - 1] to be stored as a django BigIntegerField\n            self.value_int64 = int(value) - 2**63\n            # See https://docs.djangoproject.com/en/stable/ref/models/fields/#bigintegerfield # noqa: E501\n            if (\n                self.value_int64 < -9223372036854775808\n                or self.value_int64 > 9223372036854775807\n            ):\n                raise ValueError(\n                    f\"Saving value to RecordedData for {self.variable} with value class {self.variable.value_class.upper()} should be in the interval [0:18446744073709551615], it is {self.value_int64 + 2**63}\"  # noqa: E501\n                )\n        elif self.variable.value_class.upper() in [\n            \"INT64\",\n            \"UINT32\",\n            \"DWORD\",\n            \"INT48\",\n        ]:\n            self.value_int64 = int(value)\n            # See https://docs.djangoproject.com/en/stable/ref/models/fields/#bigintegerfield # noqa: E501\n            if (\n                self.value_int64 < -9223372036854775808\n                or self.value_int64 > 9223372036854775807\n            ):\n                raise ValueError(\n                    f\"Saving value to RecordedData for {self.variable} with value class {self.variable.value_class.upper()} should be in the interval [-9223372036854775808:9223372036854775807], it is {self.value_int64}\"  # noqa: E501\n                )\n        elif self.variable.value_class.upper() in [\n            \"WORD\",\n            \"UINT\",\n            \"UINT16\",\n            \"INT32\",\n        ]:\n            self.value_int32 = int(value)\n            # See https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield # noqa: E501\n            if (\n                self.value_int32 < -2147483648\n                or self.value_int32 > 2147483647\n            ):\n                raise ValueError(\n                    f\"Saving value to RecordedData for {self.variable} with value class {self.variable.value_class.upper()} should be in the interval [-2147483648:2147483647], it is {self.value_int32}\"  # noqa: E501\n                )\n        elif self.variable.value_class.upper() in [\n            \"INT16\",\n            \"INT8\",\n            \"UINT8\",\n            \"INT\",\n        ]:\n            self.value_int16 = int(value)\n            # See https://docs.djangoproject.com/en/stable/ref/models/fields/#smallintegerfield # noqa: E501\n            if self.value_int16 < -32768 or self.value_int16 > 32767:\n                raise ValueError(\n                    f\"Saving value to RecordedData for {self.variable} with value class {self.variable.value_class.upper()} should be in the interval [-32768:32767], it is {self.value_int16}\"  # noqa: E501\n                )\n        elif self.variable.value_class.upper() in [\"BOOL\", \"BOOLEAN\"]:\n            self.value_boolean = bool(value)\n        else:\n            logger.warning(\n                f\"The {self.variable.value_class.upper()} variable value class is not defined in RecordedData __init__ function. Default storing value as float.\"  # noqa: E501\n            )\n            self.value_float64 = float(value)\n\n\n    def update_data(self, new_data:list, date_saved)-> bool:\n        \"\"\"will take a list of [timestamp, value] pairs an write the latest to the DB\n\n        \"\"\"\n        data_timestamp_max = 0\n        last_value = None\n        for timestamp, value in new_data:\n            if self.timestamp is not None and timestamp < self.timestamp:\n                continue\n            if timestamp < data_timestamp_max:\n                continue\n\n            data_timestamp_max = timestamp\n            last_value = value\n        if last_value is None:\n            return False\n\n        self.value = last_value\n        self.timestamp = data_timestamp_max\n        self.date_saved = date_saved\n        return True\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "pyscada/tests.py",
    "content": "\"\"\"\nThis file demonstrates writing tests using the unittest module. These will pass\nwhen you run \"manage.py test\".\n\nReplace this with more appropriate tests for your application.\n\"\"\"\n\nfrom django.test import TestCase\nfrom django.apps import apps\nfrom pyscada.models import Variable, Device, Unit, DataSource\nfrom datetime import datetime\nfrom pytz import UTC\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass SimpleTest(TestCase):\n    def test_basic_addition(self):\n        \"\"\"\n        Tests that 1 + 1 always equals 2.\n        \"\"\"\n        self.assertEqual(1 + 1, 2)\n\n\nclass VariableTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit\n        )\n\n\n    def test_variable_read_1(self):\n        result = Variable.objects.query_datapoints(variable_ids=[self.v.pk])\n        self.assertDictEqual(\n            result, {\"timestamp\": 0, \"date_saved_max\": 0}\n        )\n\n    def test_variable_read_2(self):\n        result = self.v.last_datapoint()\n        self.assertIsNone(result)\n\n    def test_variable_read_3(self):\n        result = self.v.query_datapoints()\n        self.assertTupleEqual(\n            result, (None,None,None,)\n        )\n\nclass ReadVariableTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit\n        )\n        self.v.update_values([1, 10, 100, 1000], [0, 1, 2, 3])\n        Variable.objects.write_datapoints(\n            items=[\n                self.v,\n            ],\n            date_saved=datetime.fromtimestamp(5, UTC),\n        )\n\n    def test_variable_read(self):\n        \"\"\"Variable query datapoints test\"\"\"\n        self.assertEqual(self.v.check_last_datapoint(), True)\n\n        logger.debug(\"test_variable_read 1\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=True,\n            time_max_excluded=True,\n        )\n        self.assertDictEqual(\n            result, {\"timestamp\": 2.0, self.v.id: [[2.0, 100.0]], \"date_saved_max\": 5.0}\n        )\n\n        logger.debug(\"test_variable_read 2\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=True,\n        )\n        self.assertEqual(result, {\"timestamp\": 0, \"date_saved_max\": 0})\n\n        logger.debug(\"test_variable_read 3\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=True,\n            time_max_excluded=False,\n        )\n        self.assertDictEqual(\n            result,\n            {\n                \"timestamp\": 3.0,\n                self.v.id: [[2.0, 100.0], [3.0, 1000.0]],\n                \"date_saved_max\": 5.0,\n            },\n        )\n\n        logger.debug(\"test_variable_read 4\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=False,\n        )\n        self.assertDictEqual(\n            result,\n            {\"timestamp\": 3.0, self.v.id: [[3.0, 1000.0]], \"date_saved_max\": 5.0},\n        )\n\n        logger.debug(\"test_variable_read 5\")\n        result = self.v.query_datapoints(\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=False,\n        )\n        self.assertEqual(\n            result,\n            ([[3.0, 1000.0]], 3.0, 5.0)\n        )\n    def test_variable_last_datapoint(self):\n        logger.debug(\"test_variable_last_datapoint 1\")\n        result = self.v.last_datapoint()\n        self.assertEqual(\n            result,\n            ([3.0, 1000.0])\n        )\n\n\nclass WriteRawVariableTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit\n        )\n\n    def test_variable_method_write_1(self):\n        logger.debug(\"test_variable_method_write 1\")\n        self.v.write_raw_datapoints([[0, 1], [1, 10], [2, 100], [3, 1000]], date_saved=datetime.fromtimestamp(5, UTC))\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[0.0, 1.0], [1.0, 10.0], [2.0, 100.0], [3.0, 1000.0]], 3.0, 5.0)\n        )\n\n\n    def test_variable_method_write_2(self):\n        logger.debug(\"test_variable_method_write 2\")\n        self.v.write_raw_datapoints([\n            [0, 1, datetime.fromtimestamp(5, UTC)],\n            [1, 10, datetime.fromtimestamp(6, UTC)],\n            [2, 100, datetime.fromtimestamp(7, UTC)],\n            [3, 1000, datetime.fromtimestamp(8, UTC)]\n        ])\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[0.0, 1.0], [1.0, 10.0], [2.0, 100.0], [3.0, 1000.0]], 3.0, 8.0)\n        )\n\n    def test_variable_method_write_3(self):\n        logger.debug(\"test_variable_method_write 3\")\n        self.v.write_raw_datapoints(\n            [[0, 1, None], [1, 10, None], [2, 100, None], [3, 1000, None]],\n            date_saved=datetime.fromtimestamp(8, UTC)\n        )\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[0.0, 1.0], [1.0, 10.0], [2.0, 100.0], [3.0, 1000.0]], 3.0, 8.0)\n        )\n\n    def test_variable_method_write_4(self):\n        logger.debug(\"test_variable_method_write 4\")\n        self.v.write_raw_datapoints([[0, 1, 5], [1, 10, 6], [2, 100, 7], [3, 1000, 8]])\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[0.0, 1.0], [1.0, 10.0], [2.0, 100.0], [3.0, 1000.0]], 3.0, 8.0)\n        )\n\n    def test_variable_manager_write(self):\n        Variable.objects.write_raw_datapoints(\n            datapoints={\n                self.v.pk: [[4, 2], [5, 20], [6,200], [7, 2000]]\n                },\n            date_saved=datetime.fromtimestamp(5, UTC),\n        )\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[4.0, 2.0], [5.0, 20.0], [6.0, 200.0], [7.0, 2000.0]], 7.0, 5.0)\n        )\n\nclass VariableCacheTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        dts = DataSource.objects.filter(datasource_model__inline_model_name=\"DjangoCache\").last()\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit, datasource=dts\n        )\n\n    def test_variable_read_1(self):\n        result = Variable.objects.query_datapoints(variable_ids=[self.v.pk])\n        self.assertDictEqual(\n            result, {\"timestamp\": 0, \"date_saved_max\": 0}\n        )\n\n    def test_variable_read_2(self):\n        result = self.v.last_datapoint()\n        self.assertIsNone(result)\n\n    def test_variable_read_3(self):\n        result = self.v.query_datapoints()\n        self.assertTupleEqual(\n            result, (None,None,None,)\n        )\n\nclass WriteCacheVariableTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        dts = DataSource.objects.filter(datasource_model__inline_model_name=\"DjangoCache\").last()\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit, datasource=dts\n        )\n        self.time = round(datetime.now().timestamp(), 0)\n        from django.core.cache import cache\n        cache.clear()\n\n    def test_variable_method_write_1(self):\n        logger.debug(\"test_variable_method_write 1\")\n\n        #self.v.write_raw_datapoints([[0, 1], [1, 10], [2, 100], [3, 1000]], date_saved=datetime.fromtimestamp(5, UTC))\n        self.v.write_raw_datapoints([\n            [self.time-5, 1],\n            [self.time-4, 10],\n            [self.time-3, 100],\n            [self.time-2, 1000]],\n            date_saved=datetime.fromtimestamp(self.time-0.5, UTC)\n            )\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-5, 1],\n            [self.time-4, 10],\n            [self.time-3, 100],\n            [self.time-2, 1000]], self.time-2, self.time-0.5)\n        )\n\n\n    def test_variable_method_write_2(self):\n        logger.debug(\"test_variable_method_write 2\")\n        self.v.write_raw_datapoints([\n            [self.time-5, 1, datetime.fromtimestamp(5, UTC)],\n            [self.time-4, 10, datetime.fromtimestamp(6, UTC)],\n            [self.time-3, 100, datetime.fromtimestamp(7, UTC)],\n            [self.time-2, 1000, datetime.fromtimestamp(8, UTC)]\n        ])\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-5, 1],\n            [self.time-4, 10],\n            [self.time-3, 100],\n            [self.time-2, 1000]], self.time-2, 8.0)\n        )\n\n    def test_variable_method_write_3(self):\n        logger.debug(\"test_variable_method_write 3\")\n        self.v.write_raw_datapoints([\n            [self.time-5, 1, None],\n            [self.time-4, 10, None],\n            [self.time-3, 100, None],\n            [self.time-2, 1000, None]],\n            date_saved=datetime.fromtimestamp(self.time-8, UTC)\n        )\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-5, 1],\n            [self.time-4, 10],\n            [self.time-3, 100],\n            [self.time-2, 1000]], self.time-2, self.time-8.0)\n        )\n\n    def test_variable_method_write_4(self):\n        logger.debug(\"test_variable_method_write 4\")\n        self.v.write_raw_datapoints([\n            [self.time-5, 1, self.time+5],\n            [self.time-4, 10, self.time+6],\n            [self.time-3, 100, self.time+7],\n            [self.time-2, 1000, self.time+8]])\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-5, 1],\n            [self.time-4, 10],\n            [self.time-3, 100],\n            [self.time-2, 1000]], self.time-2, self.time+8.0))\n\n    def test_variable_manager_write(self):\n        Variable.objects.write_raw_datapoints(\n            datapoints={\n                self.v.pk: [\n                    [self.time-8, 2],\n                    [self.time-9, 20],\n                    [self.time-10,200],\n                    [self.time-11, 2000]]\n                },\n            date_saved=datetime.fromtimestamp(5, UTC),\n        )\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[self.time-8, 2.0], [self.time-9, 20.0], [self.time-10, 200.0], [self.time-11, 2000.0]], self.time-8, 5.0)\n        )\n\nclass ReadVariableCacheTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        dts = DataSource.objects.filter(datasource_model__inline_model_name=\"DjangoCache\").last()\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit, datasource=dts\n        )\n\n        self.time = round(datetime.now().timestamp(), 0)\n        from django.core.cache import cache\n        cache.clear()\n\n        self.v.update_values([1, 10, 100, 1000], [self.time-5, self.time-4, self.time-3, self.time-2])\n        Variable.objects.write_datapoints(\n            items=[\n                self.v,\n            ],\n            date_saved=datetime.fromtimestamp(self.time-1, UTC),\n        )\n\n    def test_variable_read_1(self):\n        \"\"\"Variable query datapoints test\"\"\"\n        self.assertEqual(self.v.check_last_datapoint(), True)\n\n        logger.debug(\"test_variable_read 1\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=True,\n            time_max_excluded=True,\n        )\n        self.assertDictEqual(\n            result, {\"timestamp\": self.time-3, self.v.id: [[self.time-3, 100.0]], \"date_saved_max\": self.time-1}\n        )\n\n    def test_variable_read_2(self):\n        logger.debug(\"test_variable_read 2\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=True,\n        )\n        self.assertEqual(result, {\"timestamp\": 0, \"date_saved_max\": 0})\n\n    def test_variable_read_3(self):\n        logger.debug(\"test_variable_read 3\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=True,\n            time_max_excluded=False,\n        )\n        self.assertDictEqual(\n            result,\n            {\n                \"timestamp\": self.time-2,\n                self.v.id: [[self.time-3, 100.0], [self.time-2, 1000.0]],\n                \"date_saved_max\": self.time-1,\n            },\n        )\n\n    def test_variable_read_4(self):\n        logger.debug(\"test_variable_read 4\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=False,\n        )\n        self.assertDictEqual(\n            result,\n            {\"timestamp\": self.time-2, self.v.id: [[self.time-2, 1000.0]], \"date_saved_max\": self.time-1},\n        )\n\n    def test_variable_read_5(self):\n        logger.debug(\"test_variable_read 5\")\n        result = self.v.query_datapoints(\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=False,\n        )\n        self.assertEqual(\n            result,\n            ([[self.time-2, 1000.0]], self.time-2, self.time-1)\n        )\n\n    def test_variable_last_datapoint(self):\n        logger.debug(\"test_variable_last_datapoint 1\")\n        result = self.v.last_datapoint()\n        self.assertEqual(\n            result,\n            ([self.time-2, 1000.0])\n        )\n\nclass WriteSingleValueVariableTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        dts = DataSource.objects.filter(datasource_model__inline_model_name=\"DjangoSingleValue\").last()\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit, datasource=dts\n        )\n        self.time = round(datetime.now().timestamp(), 0)\n        from django.core.cache import cache\n        cache.clear()\n\n    def test_variable_method_write_1(self):\n        logger.debug(\"test_variable_method_write 1\")\n\n        #self.v.write_raw_datapoints([[0, 1], [1, 10], [2, 100], [3, 1000]], date_saved=datetime.fromtimestamp(5, UTC))\n        self.v.write_raw_datapoints([\n            [self.time-5, 1],\n            [self.time-4, 10],\n            [self.time-3, 100],\n            [self.time-2, 1000]],\n            date_saved=datetime.fromtimestamp(self.time-0.5, UTC)\n            )\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-2, 1000]], self.time-2, self.time-0.5)\n        )\n\n\n    def test_variable_method_write_2(self):\n        logger.debug(\"test_variable_method_write 2\")\n        self.v.write_raw_datapoints([\n            [self.time-5, 1, datetime.fromtimestamp(5, UTC)],\n            [self.time-4, 10, datetime.fromtimestamp(6, UTC)],\n            [self.time-3, 100, datetime.fromtimestamp(7, UTC)],\n            [self.time-2, 1000, datetime.fromtimestamp(8, UTC)]\n        ])\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-2, 1000]], self.time-2, 8.0)\n        )\n\n    def test_variable_method_write_3(self):\n        logger.debug(\"test_variable_method_write 3\")\n        self.v.write_raw_datapoints([\n            [self.time-5, 1, None],\n            [self.time-4, 10, None],\n            [self.time-3, 100, None],\n            [self.time-2, 1000, None]],\n            date_saved=datetime.fromtimestamp(self.time-8, UTC)\n        )\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-2, 1000]], self.time-2, self.time-8.0)\n        )\n\n    def test_variable_method_write_4(self):\n        logger.debug(\"test_variable_method_write 4\")\n        self.v.write_raw_datapoints([\n            [self.time-5, 1, self.time+5],\n            [self.time-4, 10, self.time+6],\n            [self.time-3, 100, self.time+7],\n            [self.time-2, 1000, self.time+8]])\n\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([\n            [self.time-2, 1000]], self.time-2, self.time+8.0))\n\n    def test_variable_manager_write(self):\n        Variable.objects.write_raw_datapoints(\n            datapoints={\n                self.v.pk: [\n                    [self.time-8, 2],\n                    [self.time-9, 20],\n                    [self.time-10,200],\n                    [self.time-11, 2000]]\n                },\n            date_saved=datetime.fromtimestamp(5, UTC),\n        )\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[self.time-8, 2.0],], self.time-8, 5.0)\n        )\n    def test_write_multiple(self):\n        self.v.update_values([1, 10 ], [self.time-10, self.time-9])\n        Variable.objects.write_datapoints(\n            items=[\n                self.v,\n            ],\n            date_saved=datetime.fromtimestamp(self.time-1, UTC),\n        )\n        # second write\n        self.v.date_saved = datetime.fromtimestamp(self.time-2, UTC)\n        self.v.update_values([100, 2000], [self.time-8, self.time-7])\n        self.v.write_datapoints()\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[self.time-7, 2000],], self.time-7, self.time-2)\n        )\n        # 3td write\n        self.v.date_saved = datetime.fromtimestamp(self.time-1, UTC)\n        self.v.update_values([30000, 400000], [self.time-6, self.time-5])\n        self.v.write_datapoints()\n        # 4th write\n        self.v.date_saved = datetime.fromtimestamp(self.time-1, UTC)\n        self.v.update_values([50000, 600000], [self.time-7, self.time-8])\n        self.v.write_datapoints()\n        result = self.v.query_datapoints()\n        self.assertEqual(\n            result, ([[self.time-5, 400000.0],], self.time-5, self.time-1)\n        )\n\n\nclass VariableSingleValueTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        dts = DataSource.objects.filter(datasource_model__inline_model_name=\"DjangoSingleValue\").last()\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit, datasource=dts\n        )\n\n    def test_variable_read_1(self):\n        result = Variable.objects.query_datapoints(variable_ids=[self.v.pk])\n        self.assertDictEqual(\n            result, {\"timestamp\": 0, \"date_saved_max\": 0}\n        )\n\n    def test_variable_read_2(self):\n        result = self.v.last_datapoint()\n        self.assertIsNone(result)\n\n    def test_variable_read_3(self):\n        result = self.v.query_datapoints()\n        self.assertTupleEqual(\n            result, (None,None,None,)\n        )\n\nclass ReadVariableSingleValueTest(TestCase):\n    def setUp(self):\n        apps.get_app_config(\"pyscada\").pyscada_app_init()\n\n        d, created = Device.objects.get_or_create(\n            short_name=\"dev\", description=\"dev\", protocol_id=1\n        )\n        unit, created = Unit.objects.get_or_create(\n            unit=\"unit\", description=\"unit\", udunit=\"unit\"\n        )\n        dts = DataSource.objects.filter(datasource_model__inline_model_name=\"DjangoSingleValue\").last()\n        self.v, created = Variable.objects.get_or_create(\n            name=\"var\", description=\"var\", device=d, unit=unit, datasource=dts\n        )\n\n        self.time = round(datetime.now().timestamp(), 0)\n        from django.core.cache import cache\n        cache.clear()\n\n        self.v.update_values([1, 10, 100, 1000], [self.time-5, self.time-4, self.time-3, self.time-2])\n        Variable.objects.write_datapoints(\n            items=[\n                self.v,\n            ],\n            date_saved=datetime.fromtimestamp(self.time-1, UTC),\n        )\n\n    def test_variable_read_1(self):\n        \"\"\"Variable query datapoints test\"\"\"\n        self.assertEqual(self.v.check_last_datapoint(), True)\n\n        logger.debug(\"test_variable_read 1\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=True,\n            time_max_excluded=True,\n        )\n        self.assertDictEqual(\n            result, {\"timestamp\": 0, \"date_saved_max\": 0}\n        )\n\n    def test_variable_read_2(self):\n        logger.debug(\"test_variable_read 2\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=True,\n        )\n        self.assertEqual(result, {\"timestamp\": 0, \"date_saved_max\": 0})\n\n    def test_variable_read_3(self):\n        logger.debug(\"test_variable_read 3\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=True,\n            time_max_excluded=False,\n        )\n        self.assertDictEqual(\n            result,\n            {\n                \"timestamp\": self.time-2,\n                self.v.id: [[self.time-2, 1000.0]],\n                \"date_saved_max\": self.time-1,\n            },\n        )\n\n    def test_variable_read_4(self):\n        logger.debug(\"test_variable_read 4\")\n        result = Variable.objects.query_datapoints(\n            variable_ids=[self.v.pk],\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=False,\n        )\n        self.assertDictEqual(\n            result,\n            {\"timestamp\": self.time-2, self.v.id: [[self.time-2, 1000.0]], \"date_saved_max\": self.time-1},\n        )\n\n    def test_variable_read_5(self):\n        logger.debug(\"test_variable_read 5\")\n        result = self.v.query_datapoints(\n            time_min=self.v.timestamp_old,\n            time_max=self.v.timestamp_old,\n            query_first_value=False,\n            time_max_excluded=False,\n        )\n        self.assertEqual(\n            result,\n            ([[self.time-2, 1000.0]], self.time-2, self.time-1)\n        )\n\n    def test_variable_last_datapoint(self):\n        logger.debug(\"test_variable_last_datapoint 1\")\n        result = self.v.last_datapoint()\n        self.assertEqual(\n            result,\n            ([self.time-2, 1000.0])\n        )\n"
  },
  {
    "path": "pyscada/utils/__init__.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport re\nfrom datetime import datetime\nfrom pytz import UTC\nimport numpy as np\nfrom django.utils.timezone import now\nfrom django.db.utils import ProgrammingError\nfrom django.template.loader import get_template\nfrom django.contrib.auth.models import Group\nfrom django.conf import settings\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef _get_objects_for_html(list_to_append=None, obj=None, exclude_model_names=None):\n    if obj is not None:\n        if exclude_model_names is None:\n            exclude_model_names = list()\n        if list_to_append is None:\n            list_to_append = set()\n\n        if obj not in list_to_append:\n            list_to_append.update([obj])\n        # ForeignKey and OneToOne\n        for field in obj._meta.local_fields:\n            if (\n                (type(field).many_to_one or type(field).one_to_one)\n                and hasattr(obj, field.name)\n                and getattr(obj, field.name) is not None\n                and field.name not in exclude_model_names\n            ):\n                if hasattr(field.related_model, \"_get_objects_for_html\"):\n                    list_to_append.update(\n                        getattr(obj, field.name)._get_objects_for_html(list_to_append)\n                    )\n                else:\n                    list_to_append.update(\n                        _get_objects_for_html(list_to_append, getattr(obj, field.name))\n                    )\n        # ManyToMany\n        for fields in obj._meta.local_many_to_many:\n            for field in getattr(obj, fields.name).all():\n                if fields.name not in exclude_model_names:\n                    if hasattr(field, \"_get_objects_for_html\"):\n                        list_to_append.update(\n                            field._get_objects_for_html(list_to_append)\n                        )\n                    else:\n                        list_to_append.update(\n                            _get_objects_for_html(list_to_append, field)\n                        )\n        # Related OneToOne\n        for field in obj._meta.related_objects:\n            try:\n                if (\n                    field.one_to_one\n                    and hasattr(obj, field.name)\n                    and field.name not in exclude_model_names\n                    and getattr(obj, field.name) not in list_to_append\n                ):\n                    name = field.field.name\n                    field = getattr(obj, field.name)\n                    if hasattr(field, \"_get_objects_for_html\"):\n                        list_to_append.update(\n                            field._get_objects_for_html(\n                                list_to_append, exclude_model_names=[name]\n                            )\n                        )\n                    else:\n                        list_to_append.update(\n                            _get_objects_for_html(\n                                list_to_append, field, exclude_model_names=[name]\n                            )\n                        )\n            except ProgrammingError as e:\n                logger.info(f\"{e} A module is not installed ?\")\n    return list_to_append\n\n\ndef get_group_display_permission_list(items, groups, authenticated=True):\n    \"\"\"\n    @params:\n        items: QuerySet of items to filter\n        groups: QuerySet of groups to filter by\n    @return:\n        QuerySet of items filtered\n    \"\"\"\n    if not authenticated:\n        if (\n            not hasattr(settings, \"PYSCADA_ALLOW_ANONYMOUS\")\n            or not settings.PYSCADA_ALLOW_ANONYMOUS\n        ):\n            return items.none()\n        from pyscada.hmi.models import GroupDisplayPermission\n\n        if not GroupDisplayPermission.objects.filter(\n            unauthenticated_users=True\n        ).count():\n            # unauthenticated GroupDisplayPermission is missing\n            return items.none()\n\n        result = items.filter(\n            groupdisplaypermission__group_display_permission__hmi_group__isnull=True,\n            groupdisplaypermission__type=0,\n            groupdisplaypermission__group_display_permission__unauthenticated_users=True,\n        ).distinct()\n        if (\n            items.exists()\n            and items.first()\n            .groupdisplaypermission.model.objects.filter(\n                type=1,\n                group_display_permission__hmi_group=None,\n                group_display_permission__unauthenticated_users=True,\n            )\n            .exists()\n        ):\n            result = (\n                result\n                | items.exclude(\n                    groupdisplaypermission__group_display_permission__hmi_group__isnull=True,\n                    groupdisplaypermission__type=1,\n                    groupdisplaypermission__group_display_permission__unauthenticated_users=True,\n                ).distinct()\n            )\n    elif len(groups) == 0:\n        result = items.filter(\n            groupdisplaypermission__group_display_permission__hmi_group__isnull=True,\n            groupdisplaypermission__type=0,\n        ).distinct()\n        if (\n            items.exists()\n            and items.first()\n            .groupdisplaypermission.model.objects.filter(\n                type=1, group_display_permission__hmi_group=None\n            )\n            .exists()\n        ):\n            result = (\n                result\n                | items.exclude(\n                    groupdisplaypermission__group_display_permission__hmi_group__isnull=True,\n                    groupdisplaypermission__type=1,\n                ).distinct()\n            )\n    else:\n        result = items.filter(\n            groupdisplaypermission__group_display_permission__hmi_group__in=groups,\n            groupdisplaypermission__type=0,\n        ).distinct()\n        if (\n            items.exists()\n            and items.first()\n            .groupdisplaypermission.model.objects.filter(\n                type=1, group_display_permission__hmi_group__in=groups\n            )\n            .exists()\n        ):\n            result = (\n                result\n                | items.exclude(\n                    groupdisplaypermission__group_display_permission__hmi_group__in=groups,\n                    groupdisplaypermission__type=1,\n                ).distinct()\n            )\n    return result\n\n\ndef gen_hiddenConfigHtml(obj, custom_fields=None, exclude_fields_list=None):\n    \"\"\"\n    Get an object and return an html with a hidden div containing the object\n    config\n\n    :param obj: an object from a model\n    :param custom_fields: list of fields to add to the result\n\n    :return: the html of the config of the object\n    \"\"\"\n    fields = list()\n    for field in obj._meta.local_many_to_many:\n        # For ManyToManyField\n        l = \"\"\n        for o in field.value_from_object(obj):\n            l += str(o.pk) + \",\"\n        fields.append(\n            dict(\n                name=field.name,\n                value=l,\n            )\n        )\n    for field in obj._meta.local_fields:\n        value = field.value_from_object(obj)\n        if type(value) == datetime:\n            value = value.timestamp()\n        fields.append(\n            dict(\n                name=field.name,\n                value=value,\n            )\n        )\n    if type(custom_fields) == list:\n        for field in custom_fields:\n            if type(field) == dict and \"name\" in field and \"value\" in field:\n                fields.append(\n                    dict(\n                        name=field[\"name\"],\n                        value=field[\"value\"],\n                    )\n                )\n\n    if type(exclude_fields_list) == list:\n        for field in exclude_fields_list:\n            for key in range(len(fields)):\n                if field == fields[key][\"name\"]:\n                    fields.pop(key)\n                    break\n\n    return get_template(\"modelProperties.html\").render(\n        dict(\n            modelName=obj._meta.model_name,\n            fields=fields,\n        )\n    )\n\n\ndef extract_numbers_from_str(value_str):\n    match = re.match(r\"(([^0-9,^-]+)?)(?P<number>-?[0-9]+[.]?[0-9]+)\", value_str, re.I)\n    if match:\n        match = match.groupdict()\n        return float(match[\"number\"])\n    else:\n        return None\n\n\ndef decode_bcd(values):\n    \"\"\"\n    decode bcd as int to dec\n    \"\"\"\n\n    bin_str_out = \"\"\n    if isinstance(values, int):\n        bin_str_out = bin(values)[2:].zfill(16)\n        bin_str_out = bin_str_out[::-1]\n    else:\n        for value in values:\n            bin_str = bin(value)[2:].zfill(16)\n            bin_str = bin_str[::-1]\n            bin_str_out = bin_str + bin_str_out\n\n    dec_num = 0\n    for i in range(len(bin_str_out) / 4):\n        bcd_num = int(bin_str_out[(i * 4) : (i + 1) * 4][::-1], 2)\n        if bcd_num > 9:\n            dec_num = -dec_num\n        else:\n            dec_num = dec_num + (bcd_num * pow(10, i))\n    return dec_num\n\n\ndef validate_value_class(class_str):\n    if class_str.upper() in [\"FLOAT64\", \"DOUBLE\", \"FLOAT\", \"LREAL\", \"UNIXTIMEF64\"]:\n        return \"FLOAT64\"\n    if class_str.upper() in [\"FLOAT32\", \"SINGLE\", \"REAL\", \"UNIXTIMEF32\"]:\n        return \"FLOAT32\"\n    if class_str.upper() in [\"UINT64\"]:\n        return \"UINT64\"\n    if class_str.upper() in [\"INT64\", \"UNIXTIMEI64\"]:\n        return \"INT64\"\n    if class_str.upper() in [\"INT32\"]:\n        return \"INT32\"\n    if class_str.upper() in [\"UINT32\", \"DWORD\", \"UNIXTIMEI32\"]:\n        return \"UINT32\"\n    if class_str.upper() in [\"INT16\", \"INT\"]:\n        return \"INT16\"\n    if class_str.upper() in [\"UINT\", \"UINT16\", \"WORD\"]:\n        return \"UINT16\"\n    if class_str.upper() in [\"INT8\"]:\n        return \"INT8\"\n    if class_str.upper() in [\"UINT8\", \"BYTE\"]:\n        return \"UINT8\"\n    if class_str.upper() in [\"BOOL\", \"BOOLEAN\"]:\n        return \"BOOLEAN\"\n    else:\n        return \"FLOAT64\"\n\n\ndef _cast(value, class_str):\n    if class_str.upper() in [\n        \"FLOAT64\",\n        \"DOUBLE\",\n        \"FLOAT\",\n        \"LREAL\",\n        \"FLOAT32\",\n        \"SINGLE\",\n        \"REAL\",\n        \"UNIXTIMEF32\",\n        \"UNIXTIMEF64\",\n    ]:\n        return float(value)\n    if class_str.upper() in [\n        \"INT32\",\n        \"UINT32\",\n        \"DWORD\",\n        \"INT16\",\n        \"INT\",\n        \"UINT\",\n        \"UINT16\",\n        \"WORD\",\n        \"INT8\",\n        \"UINT8\",\n        \"BYTE\",\n    ]:\n        return int(value)\n    if class_str.upper() in [\"BOOL\", \"BOOLEAN\"]:\n        return value.lower() == \"true\"\n    else:\n        return value\n\n\ndef datetime_now():\n    return now()\n\n\ndef timestamp_to_datetime(timestamp, tz=UTC):\n    return datetime.fromtimestamp(timestamp, tz)\n\n\ndef blow_up_data(data, timevalues, mean_value_period, no_mean_value=True):\n    out_data = np.zeros(len(timevalues))\n    # i                            # time data index\n    ii = 0  # source data index\n    # calculate mean values\n    last_value = None\n    max_ii = len(data) - 1\n    for i in range(len(timevalues)):  # iter over time values\n        if ii >= max_ii + 1:\n            # if not more data in data source break\n            if last_value is not None:\n                out_data[i] = last_value\n                continue\n        # init mean value vars\n        tmp = 0.0  # sum\n        tmp_i = 0.0  # count\n\n        if data[ii][0] < timevalues[i]:\n            if ii == max_ii:\n                last_value = data[ii][1]\n            else:\n                # skip elements that are befor current time step\n                while data[ii][0] < timevalues[i] and ii < max_ii:\n                    last_value = data[ii][1]\n                    ii += 1\n\n        if ii >= max_ii:\n            if last_value is not None:\n                out_data[i] = last_value\n                continue\n        # calc mean value\n        if timevalues[i] <= data[ii][0] < timevalues[i] + mean_value_period:\n            # there is data in time range\n            while (\n                timevalues[i] <= data[ii][0] < timevalues[i] + mean_value_period\n                and ii < max_ii\n            ):\n                # calculate mean value\n                if no_mean_value:\n                    tmp = data[ii][1]\n                    tmp_i = 1\n                else:\n                    tmp += data[ii][1]\n                    tmp_i += 1\n\n                last_value = data[ii][1]\n                ii += 1\n            # calc and store mean value\n            if tmp_i > 0:\n                out_data[i] = tmp / tmp_i\n            else:\n                out_data[i] = data[ii][1]\n                last_value = data[ii][1]\n        else:\n            # there is no data in time range, keep last value, not mean value\n            if last_value is not None:\n                out_data[i] = last_value\n    return np.asarray(out_data)\n\n\ndef min_pass(my_marks, my_pass, compare=\"gte\"):\n    min_value = None\n    for x in my_marks:\n        if x >= my_pass and compare == \"gte\":\n            min_value = x\n            break\n        elif x > my_pass and compare == \"gt\":\n            min_value = x\n            break\n    if min_value is not None:\n        for x in my_marks:\n            if min_value > x >= my_pass and compare == \"gte\":\n                min_value = x\n            elif min_value > x > my_pass and compare == \"gt\":\n                min_value = x\n    return min_value\n\n\ndef max_pass(my_marks, my_pass, compare=\"lte\"):\n    max_value = None\n    for x in my_marks:\n        if x <= my_pass and compare == \"lte\":\n            max_value = x\n            break\n        elif x < my_pass and compare == \"lt\":\n            max_value = x\n            break\n    if max_value is not None:\n        for x in my_marks:\n            if max_value < x <= my_pass and compare == \"lte\":\n                max_value = x\n            elif max_value < x < my_pass and compare == \"lt\":\n                max_value = x\n    return max_value\n\n\ndef set_bit(v, index, x):\n    \"\"\"Set the index:th bit of v to 1 if x is truthy, else to 0, and return the new value.\"\"\"\n    mask = 1 << index  # Compute mask, an integer with just bit 'index' set.\n    v &= ~mask  # Clear the bit indicated by the mask (if x is False)\n    if x:\n        v |= mask  # If x was True, set the bit indicated by the mask.\n    return v  # Return the result, we're done.\n"
  },
  {
    "path": "pyscada/utils/scheduler.py",
    "content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\"\"\"\n - > master_process\n\n    - > mail master\n\n    - > export master\n\n     - > export task A\n     - > export task B\n\n    - > event master\n\n    - > modbus master(registers a process for every device/port)\n\n     - > modbus device A (IP 1)\n     - > modbus device B (IP 2)\n     - > modbus device C, D (RTU, TTY1)\n     - > modbus device E, F (RTU, TTY2)\n\n    - > onewire (registers a process for every device/port)\n\n     - > onewire device G, H (server a)\n\n    - > visa (registers a process for every device/port)\n\n     - > visa device A\n\n    - > systemstat master\n    - > smbus (registers a process for every device/port)\n    - > jofra350 (registers a process for every device/port)\n\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport errno\nfrom os import (\n    umask,\n    access,\n    W_OK,\n    kill,\n    remove,\n    setsid,\n    path,\n    fork,\n    F_OK,\n    WNOHANG,\n    getpid,\n    waitpid,\n)\nimport signal\nimport sys\nimport traceback\nfrom time import time, sleep\nfrom datetime import datetime\nimport json\n\nfrom django.conf import settings\nfrom django.db import connection, connections\nfrom django.db.utils import OperationalError\nfrom django.db.transaction import TransactionManagementError\nfrom django.db.models import Q\nfrom django.db.utils import IntegrityError\nfrom django.utils.timezone import now\nfrom django.apps import apps\n\nfrom pyscada.models import (\n    BackgroundProcess,\n    DeviceWriteTask,\n    Device,\n    DeviceReadTask,\n    Variable,\n    VariableProperty,\n)\nfrom pyscada.utils import set_bit\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import channels.layers\n    from channels.exceptions import InvalidChannelLayerError\n    from channels.exceptions import ChannelFull\n    from asgiref.sync import async_to_sync\n    from asyncio import wait_for\n    from aioredis.errors import ConnectionClosedError\n\n    try:\n        from asyncio.exceptions import TimeoutError as asyncioTimeoutError\n    except ModuleNotFoundError:\n        # for python version < 3.8\n        from asyncio import TimeoutError as asyncioTimeoutError\n    if channels.layers.get_channel_layer() is None:\n        logger.warning(\"Django Channels is not working. Missing config in settings ?\")\n        raise ConnectionRefusedError\n    else:\n\n        async def channels_test():\n            await wait_for(\n                channels.layers.get_channel_layer().receive(\"test\"), timeout=0.1\n            )\n\n        async_to_sync(channels_test)()\n        channels_driver = True\nexcept (ImportError, ModuleNotFoundError):\n    channels_driver = False\nexcept ConnectionRefusedError:\n    logger.warning(\"Django Channels is not working. redis-server not running ?\")\n    channels_driver = False\nexcept (TimeoutError, asyncioTimeoutError):\n    channels_driver = True\n\n\ndef check_db_connection():\n    \"\"\"\n    from: https://stackoverflow.com/questions/7835272/django-operationalerror-2006-mysql-server-has-gone-away\n    \"\"\"\n    # mysql is lazily connected to in django.\n    # connection.connection is None means\n    # you have not connected to mysql before\n    if connection.connection and not connection.is_usable():\n        # destroy the default mysql connection\n        # after this line, when you use ORM methods\n        # django will reconnect to the default mysql\n        logger.debug(\"deleted default connection\")\n        del connections._connections.default\n\n\ndef close_db_connection():\n    if connection.connection is not None:\n        connection.connection.close()\n        connection.connection = None\n\n\nclass Scheduler(object):\n    \"\"\"\n    Manages and monitor all the sub processes.\n    \"\"\"\n\n    PROCESSES = {}\n    SIG_QUEUE = []\n    SIGNALS = [signal.SIGTERM, signal.SIGUSR1, signal.SIGHUP, signal.SIGUSR2]\n    if hasattr(settings, \"PID_FILE_NAME\"):\n        pid_file_name = str(settings.PID_FILE_NAME)\n    else:\n        pid_file_name = \"/tmp/pyscada_daemon.pid\"\n\n    def __init__(\n        self,\n        daemon_name=\"pyscada.utils.scheduler.Scheduler\",\n        run_as_daemon=True,\n        stdout=sys.stdout,\n        stdin=sys.stdin,\n        stderr=sys.stderr,\n        pid_file_name=pid_file_name,\n    ):\n        \"\"\" \"\"\"\n        self.pid_file_name = pid_file_name\n        self.run_as_daemon = run_as_daemon\n\n        self.pid = None\n        self.process_id = None\n        self.stdout = stdout\n        self.stdin = stdin\n        self.stderr = stderr\n\n        if not access(path.dirname(self.pid_file_name), W_OK) and self.run_as_daemon:\n            self.stdout.write(\"pidfile path is not writeable\\n\")\n            sys.exit(0)\n        if (\n            access(self.pid_file_name, F_OK)\n            and not access(self.pid_file_name, W_OK)\n            and self.run_as_daemon\n        ):\n            self.stdout.write(\"pidfile file is not writeable\\n\")\n            sys.exit(0)\n\n        self.label = daemon_name\n\n    def init_db(self, sig=0):\n        \"\"\" \"\"\"\n        for process in BackgroundProcess.objects.filter(done=False, pid__gt=0):\n            try:\n                kill(process.pid, sig)\n                self.stderr.write(\"init db aborted, at least one process is alive\\n\")\n                return False\n            except OSError as e:\n                if e.errno == errno.ESRCH:  # no such process\n                    process.message = \"stopped\"\n                    process.pid = 0\n                    process.last_update = now()\n                    process.save()\n                elif e.errno == errno.EPERM:  # Operation not permitted\n                    self.stderr.write(\n                        \"can't stop process %d: %s with pid %d, 'Operation not permitted'\\n\"\n                        % (process.pk, process.label, process.pid)\n                    )\n\n        BackgroundProcess.objects.all().delete()\n        # add the Scheduler Process\n        parent_process = BackgroundProcess(\n            pk=1,\n            label=\"pyscada.utils.scheduler.Scheduler\",\n            enabled=True,\n            process_class=\"pyscada.utils.scheduler.Process\",\n        )\n        parent_process.save()\n        # check for processes to add in init block of each app\n        if \"pyscada.core\" not in settings.INSTALLED_APPS:\n            # append core to initialize pyscada.event and pyscada.mail\n            settings.INSTALLED_APPS.append(\"pyscada.core\")\n        for app in settings.INSTALLED_APPS:\n            m = __import__(app, fromlist=[str(\"a\")])\n            self.stderr.write(\"app %s\\n\" % app)\n            if hasattr(m, \"parent_process_list\"):\n                for process in m.parent_process_list:\n                    self.stderr.write(\"--> add %s\\n\" % process[\"label\"])\n                    if \"enabled\" not in process:\n                        process[\"enabled\"] = True\n                    if \"parent_process\" not in process:\n                        process[\"parent_process\"] = parent_process\n                    bp = BackgroundProcess(**process)\n                    bp.save()\n\n        self.stderr.write(\"init db completed\\n\")\n        return True\n\n    def demonize(self):\n        \"\"\"\n        do the double fork magic\n        \"\"\"\n        # check if a process is already running\n        if access(self.pid_file_name, F_OK):\n            # read the pid file\n            pid = self.read_pid()\n\n            try:\n                kill(pid, 0)  # check if process is running\n                self.stderr.write(\"process is already running\\n\")\n                return False\n            except OSError as e:\n                if e.errno == errno.ESRCH:\n                    # process is dead\n                    self.delete_pid(force_del=True)\n                else:\n                    self.stderr.write(\n                        \"demonize failed, something went wrong: %d (%s)\\n\"\n                        % (e.errno, e.strerror)\n                    )\n                    return False\n\n        try:\n            pid = fork()\n            if pid > 0:\n                # Exit from the first parent\n                timeout = time() + 60\n                while self.read_pid() is None:\n                    self.stderr.write(\"waiting for pid..\\n\")\n                    sleep(0.5)\n                    if time() > timeout:\n                        break\n                self.stderr.write(\"pid is %d\\n\" % self.read_pid())\n                sys.exit(0)\n        except OSError as e:\n            self.stderr.write(\n                \"demonize failed in 1. Fork: %d (%s)\\n\" % (e.errno, e.strerror)\n            )\n            sys.exit(1)\n\n        # Decouple from parent environment\n        # os.chdir(\"/\")\n        setsid()\n        umask(0)\n\n        # Do the Second fork\n        try:\n            pid = fork()\n            if pid > 0:\n                # Exit from the second parent\n                sys.exit(0)\n        except OSError as e:\n            self.stderr.write(\n                \"demonize failed in 2. Fork: %d (%s)\\n\" % (e.errno, e.strerror)\n            )\n            sys.exit(1)\n\n        # Redirect standard file descriptors\n        # sys.stdout.flush()\n        # sys.stderr.flush()\n        # si = file(self.stdin, 'r')\n        # so = file(self.stdout, 'a+')\n        # se = file(self.stderr, 'a+',\n        # os.dup2(si.fileno(), sys.stdin.fileno())\n        # os.dup2(so.fileno(), sys.stdout.fileno())\n        # os.dup2(se.fileno(), sys.stderr.fileno())\n\n        # Write the PID file\n        self.write_pid()\n        return True\n\n    def write_pid(self, pid=None):\n        if pid is None:\n            pid = getpid()\n        with open(self.pid_file_name, \"w+\") as f:\n            f.write(\"%d\\n\" % pid)\n        logger.info(\"created pid %d\" % pid)\n\n    def read_pid(self):\n        if not access(self.pid_file_name, F_OK):\n            return None\n\n        # try reading the pid during 5 seconds\n        for i in range(1, 6):\n            try:\n                with open(self.pid_file_name, \"r\") as f:\n                    pid = int(f.read().strip())\n                return pid\n            except ValueError:\n                sleep(1)\n        return None\n\n    def delete_pid(self, force_del=False):\n        \"\"\"\n        delete the pid file\n        \"\"\"\n        pid = self.read_pid()\n        if pid != getpid() or force_del:\n            logger.debug(\"process %d tried to delete pid\" % getpid())\n            return False\n\n        if access(self.pid_file_name, F_OK):\n            try:\n                remove(self.pid_file_name)  # remove the old pid file\n                logger.debug(\"delete pid (%d)\" % getpid())\n            except:\n                logger.debug(\"can't delete pid file\")\n\n    def start(self):\n        \"\"\"\n        start the scheduler\n        \"\"\"\n        #  demonize\n        if self.run_as_daemon:\n            if not self.demonize():\n                self.delete_pid()\n                sys.exit(0)\n        # recreate the DB connection\n        close_db_connection()\n\n        try:\n            master_process = BackgroundProcess.objects.filter(\n                parent_process__isnull=True, label=self.label, enabled=True\n            ).first()\n        except OperationalError as e:\n            logger.error(\"Cant't connect to the DB\", exc_info=True)\n            # self.delete_pid(force_del=True)\n            sys.exit(0)\n        self.pid = getpid()\n        if not master_process:\n            self.delete_pid(force_del=True)\n            logger.error(\"No such scheduler process in BackgroundProcesses\")\n            sys.exit(0)\n\n        # kill all the sub processes\n        for process in BackgroundProcess.objects.filter(done=False, pid__gt=0):\n            try:\n                kill(process.pid, 9)\n            except Exception as e:\n                logger.debug(f\"{e} for pid {process.pid} {process.label}\")\n        # Init the DB\n        if not self.init_db():\n            logger.debug(\"Init DB failed\\n\")\n            sys.exit(0)\n\n        self.process_id = master_process.pk\n        master_process.pid = self.pid\n        master_process.last_update = now()\n        master_process.running_since = now()\n        master_process.done = False\n        master_process.failed = False\n        master_process.message = \"init master process\"\n        master_process.save()\n        BackgroundProcess.objects.filter(\n            parent_process__pk=self.process_id, done=False\n        ).update(message=\"stopped\")\n        for parent_process in BackgroundProcess.objects.filter(\n            parent_process__pk=self.process_id, done=False\n        ):\n            for process in BackgroundProcess.objects.filter(\n                parent_process__pk=parent_process.pk, done=False\n            ):\n                try:\n                    kill(process.pid, 0)\n                except OSError as e:\n                    if e.errno == errno.ESRCH:\n                        process.delete()\n                        continue\n                logger.debug(\"process %d is alive\" % process.pk)\n                process.stop()\n\n            # clean up\n            BackgroundProcess.objects.filter(\n                parent_process__pk=parent_process.pk, done=False\n            ).delete()\n\n        # register signals\n        [signal.signal(s, self.signal) for s in self.SIGNALS]\n        # signal.signal(signal.SIGCHLD, self.handle_chld)\n        # start the main loop\n        self.run()\n        self.delete_pid()\n        sys.exit(0)\n\n    def init_apps(self):\n        for app_config in apps.get_app_configs():\n            if hasattr(app_config, \"pyscada_app_init\") and callable(\n                app_config.pyscada_app_init\n            ):\n                try:\n                    app_config.pyscada_app_init()\n                except:\n                    logger.error(\n                        f\"{app_config}, unhandled exception in application initialization\",\n                        exc_info=True,\n                    )\n\n    def run(self):\n        \"\"\"\n        the main loop\n        \"\"\"\n        try:\n            master_process = BackgroundProcess.objects.filter(\n                pk=self.process_id\n            ).first()\n\n            if not master_process:\n                self.delete_pid(force_del=True)\n                self.stderr.write(\"No such scheduler process in BackgroundProcesses\")\n                sys.exit(0)\n\n            master_process.last_update = now()\n            master_process.message = \"init apps\"\n            master_process.save(update_fields=[\"last_update\", \"message\"])\n            self.init_apps()\n\n            master_process.last_update = now()\n            master_process.message = \"init child processes\"\n            master_process.save(update_fields=[\"last_update\", \"message\"])\n\n            self.manage_processes()\n\n            while True:\n                # handle signals\n                sig = self.SIG_QUEUE.pop(0) if len(self.SIG_QUEUE) else None\n\n                # check the DB connection\n                check_db_connection()\n\n                # update the Process\n                master_process.last_update = now()\n                master_process.message = \"running..\"\n                master_process.save(update_fields=[\"last_update\", \"message\"])\n                if sig is None:\n                    self.manage_processes()\n                elif sig not in self.SIGNALS:\n                    logger.error(f\"{self.label}, unhandled signal {sig}\", exc_info=True)\n                    continue\n                elif sig == signal.SIGTERM:\n                    logger.debug(\"%s, termination signal\" % self.label)\n                    raise StopIteration\n                elif sig == signal.SIGHUP:\n                    # todo handle sighup\n                    logger.debug(\n                        f\"Received signal.SIGHUP for {self.label}. Nothing to do.\"\n                    )\n                elif sig == signal.SIGUSR1:\n                    # restart all child processes\n                    logger.debug(\n                        \"PID %d, LABEL %s, processed SIGUSR1 (%d) signal\"\n                        % (self.pid, self.label, sig)\n                    )\n                    self.restart()\n                elif sig == signal.SIGUSR2:\n                    # write the process status to stdout\n                    self.status()\n                    logger.debug(\n                        f\"Received signal.SIGUSR2 for {self.label}. Writting the process status to stdout.\"\n                    )\n                close_db_connection()\n                sleep(5)\n        except StopIteration:\n            self.stop()\n            self.delete_pid()\n            sys.exit(0)\n        except SystemExit:\n            raise\n        except OperationalError:\n            logger.debug(\"%s, DB connection lost in run\" % self.label)\n            self.stop()\n            self.delete_pid()\n            sys.exit(0)\n        except:\n            logger.error(\n                f\"{self.label}({getpid()}), unhandled exception\", exc_info=True\n            )\n\n    def manage_processes(self):\n        \"\"\" \"\"\"\n        # check for new processes to spawn\n        process_list = []\n        for process in BackgroundProcess.objects.filter(\n            parent_process__pk=self.process_id\n        ):\n            process_list.append(process)\n            process_list += list(process.backgroundprocess_set.filter())\n\n        for process in process_list:\n            #\n            if (\n                not process.enabled\n                or not process.parent_process.enabled\n                or process.done\n            ):\n                if process.pk in self.PROCESSES:\n                    timeout = time() + 60  # wait max 60 seconds\n                    while True:\n                        if process.pk not in self.PROCESSES or time() > timeout:\n                            try:\n                                self.kill_process(process.pk, signal.SIGKILL)\n                            except Exception as e:\n                                logger.debug(e)\n                            break\n                        self.kill_process(process.pk)\n                        sleep(1)\n                    continue\n                continue\n\n            if process.pk in self.PROCESSES:\n                continue\n\n            if (\n                process.parent_process.pk not in self.PROCESSES\n                and process.parent_process.pk != self.process_id\n            ):\n                continue\n\n            # spawn new process\n            process_inst = process.get_process_instance()\n            if process_inst is not None:\n                self.spawn_process(process_inst)\n                logger.debug(\"process %s started\" % process.label)\n            else:\n                logger.debug(\"process %s returned None\" % process.label)\n\n        # check all running processes\n        process_list = list(self.PROCESSES.values())\n        for process in process_list:\n            try:\n                kill(process.pid, 0)\n                waitpid(process.pid, WNOHANG)\n            except OSError as e:\n                if e.errno == errno.ESRCH:\n                    logger.debug(\"process %d is dead\" % process.process_id)\n\n                    try:\n                        self.PROCESSES.pop(process.process_id)\n                    except Exception as e:\n                        logger.debug(e)\n                    # process is dead, delete process\n                    if process.parent_process_id == self.process_id:\n                        p = BackgroundProcess.objects.filter(\n                            pk=process.process_id\n                        ).first()\n                        if p:\n                            p.pid = 0\n                            p.last_update = now()\n                            p.failed = True\n                            p.save()\n                    else:\n                        # delete grandchild process\n                        BackgroundProcess.objects.filter(pk=process.process_id).delete()\n\n    def handle_chld(self, sig, _frame):\n        \"\"\"\n        SIGCHLD handling\n        :param sig:\n        :param _frame:\n        :return:\n        \"\"\"\n        try:\n            while True:\n                wpid, status = waitpid(-1, WNOHANG)\n                if not wpid:\n                    break\n                    # self.stdout.write('%d,%d\\n' % (wpid, status))\n        except:\n            pass\n\n    def restart(self):\n        \"\"\"\n        restart all child processes\n        \"\"\"\n        BackgroundProcess.objects.filter(pk=self.process_id).update(\n            last_update=now(), message=\"restarting..\"\n        )\n        timeout = time() + 60  # wait max 60 seconds\n        self.kill_processes(signal.SIGTERM)\n        while self.PROCESSES and time() < timeout:\n            sleep(0.1)\n        self.kill_processes(signal.SIGKILL)\n        self.manage_processes()\n        logger.debug(\"BD %d: restarted\" % self.process_id)\n\n    def stop(self, sig=signal.SIGTERM):\n        \"\"\"\n        stop the scheduler and stop all processes\n        \"\"\"\n\n        if self.pid is None:\n            self.pid = self.read_pid()\n        if self.pid is None:\n            try:\n                sp = BackgroundProcess.objects.filter(pk=1).first()\n            except OperationalError as e:\n                logger.error(\"Cant't connect to the DB\", exc_info=True)\n                self.delete_pid(force_del=True)\n                sys.exit(0)\n            if sp:\n                self.pid = sp.pid\n        if self.pid is None or self.pid == 0:\n            # todo : raise exception\n            logger.error(\"Can't determine process id exiting.\", exc_info=True)\n            return False\n        if self.pid != getpid():\n            # calling from outside the daemon instance\n            logger.debug(\"send sigterm to daemon\")\n            try:\n                kill(self.pid, sig)\n                return True\n            except OSError as e:\n                if e.errno == errno.ESRCH:\n                    return False\n                else:\n                    return False\n\n        logger.debug(\"start termination of the daemon\")\n        BackgroundProcess.objects.filter(pk=self.process_id).update(\n            last_update=now(), message=\"stopping..\"\n        )\n\n        timeout = time() + 60  # wait max 60 seconds\n        self.kill_processes(signal.SIGTERM)\n        while self.PROCESSES and time() < timeout:\n            self.kill_processes(signal.SIGTERM)\n            sleep(1)\n        self.kill_processes(signal.SIGKILL)\n        BackgroundProcess.objects.filter(pk=self.process_id).update(\n            last_update=now(), message=\"stopped\"\n        )\n        logger.debug(\"termination of the daemon done\")\n        return True\n\n    def kill_process(self, process_id, sig=signal.SIGTERM):\n        \"\"\" \"\"\"\n        p = self.PROCESSES[process_id]\n        try:\n            kill(p.pid, sig)\n            logger.debug(\"try to terminate process id %d - label %s\" % (p.pid, p.label))\n        except OSError as e:\n            if e.errno == errno.ESRCH:\n                try:\n                    self.PROCESSES.pop(process_id)\n                    logger.debug(\n                        \"%s: process id %d is terminated\" % (self.label, p.pid)\n                    )\n                    return True\n                except:\n                    return False\n        try:\n            while True:\n                wpid, status = waitpid(p.pid, WNOHANG)\n                if not wpid:\n                    break\n                    # self.stdout.write('%d,%d\\n' % (wpid, status))\n        except:\n            pass\n        return False\n\n    def kill_processes(self, sig=signal.SIGTERM):\n        \"\"\" \"\"\"\n        process_ids = list(self.PROCESSES.keys())\n        for process_id in process_ids:\n            self.kill_process(process_id, sig)\n\n    def spawn_process(self, process=None):\n        \"\"\"\n        spawn a new process\n        \"\"\"\n        if process is None:\n            return False\n        # start new child process\n        pid = fork()\n        if pid != 0:\n            # parent process\n            process.pid = pid\n            self.PROCESSES[process.process_id] = process\n            connections.close_all()\n            return True\n        # child process\n        process.pid = getpid()\n        # connection.connection.close()\n        # connection.connection = None\n        process.pre_init_process()\n        process.init_process()\n        process.run()\n        sys.exit(0)\n\n    def status(self):\n        \"\"\"\n        write the current daemon status to stdout\n        \"\"\"\n\n        if self.pid is None:\n            self.pid = self.read_pid()\n        if self.pid is None:\n            sp = BackgroundProcess.objects.filter(pk=1).first()\n            if sp:\n                self.pid = sp.pid\n        if self.pid is None or self.pid == 0:\n            self.stderr.write(\n                \"%s: can't determine process id exiting.\\n\"\n                % datetime.now().isoformat(\" \")\n            )\n            return False\n        if self.pid != getpid():\n            # calling from outside the daemon instance\n            try:\n                kill(self.pid, signal.SIGUSR2)\n                return True\n            except OSError as e:\n                if e.errno == errno.ESRCH:\n                    return False\n                else:\n                    return False\n\n        process_list = []\n        for process in BackgroundProcess.objects.filter(\n            parent_process__pk=self.process_id\n        ):\n            process_list.append(process)\n            process_list += list(process.backgroundprocess_set.filter())\n        for process in process_list:\n            logger.debug(\n                \"%s, parrent process_id %d\" % (self.label, process.parent_process.pid)\n            )\n            logger.debug(\"%s, process_id %d\" % (process.label, process.pid))\n\n    def signal(self, signum=None, _frame=None):\n        \"\"\"\n        handle signals\n        \"\"\"\n        logger.debug(\n            \"PID %d, LABEL %s, received signal: %d\" % (self.pid, self.label, signum)\n        )\n        if signum not in self.SIG_QUEUE:\n            self.SIG_QUEUE.append(signum)\n\n\nclass Process(object):\n    def __init__(self, dt=5, **kwargs):\n        self.pid = None\n        self.dt_set = dt\n        self.process_id = 0\n        self.parent_process_id = 0\n        self.label = \"\"\n        self.dwt_received = False\n        self.drt_received = False\n        self.next_message = \"running..\"\n        # register signals\n        self.SIG_QUEUE = []\n        self.SIGNALS = [signal.SIGTERM, signal.SIGUSR1, signal.SIGHUP, signal.SIGUSR2]\n        self.grouped_ids = {}\n        scheduler = BackgroundProcess.objects.filter(id=1)\n        if len(scheduler):\n            self.scheduler_pid = scheduler.first().pid\n        else:\n            logger.warning(\"No PID found for the scheduler\")\n            self.scheduler_pid = None\n        #\n        for key, value in kwargs.items():\n            setattr(self, key, value)\n\n    def pre_init_process(self):\n        \"\"\"\n        will be executed after process fork\n        \"\"\"\n        connections.close_all()\n        # update process info\n        BackgroundProcess.objects.filter(pk=self.process_id).update(\n            pid=self.pid,\n            last_update=now(),\n            running_since=now(),\n            done=False,\n            failed=False,\n            message=\"init process..\",\n        )\n\n        [signal.signal(s, signal.SIG_DFL) for s in self.SIGNALS]  # reset\n        [signal.signal(s, self.signal) for s in self.SIGNALS]  # set\n\n    def init_process(self):\n        \"\"\"\n        override this.\n        \"\"\"\n        return True\n\n    def run(self):\n        BackgroundProcess.objects.filter(pk=self.process_id).update(\n            last_update=now(), message=self.next_message\n        )\n        exec_loop = True\n        try:\n            while True:\n                t_start = time()\n                # handle signals\n                sig = self.SIG_QUEUE.pop(0) if len(self.SIG_QUEUE) else None\n\n                # check the DB connection\n                check_db_connection()\n\n                # update progress\n                BackgroundProcess.objects.filter(pk=self.process_id).update(\n                    last_update=now(), message=self.next_message\n                )\n                if sig is None and exec_loop:\n                    # run loop action\n                    status, data = self.loop()\n                    if data is not None:\n                        # write data to the database\n                        for item in data:\n                            if len(item):\n                                logger.debug(\n                                    f\"{self.label} write multiple variables : {item}\"\n                                )\n                                Variable.objects.write_datapoints(items=item)\n                    if status == 1:  # Process OK\n                        pass\n                    elif status == -1:\n                        # some thing went wrong\n                        # todo handle\n                        # raise StopIteration\n                        BackgroundProcess.objects.filter(pk=self.process_id).update(\n                            last_update=now(), failed=True, message=\"failed\"\n                        )\n                        exec_loop = False\n                    elif status == 0:\n                        # loop is done exit\n                        BackgroundProcess.objects.filter(pk=self.process_id).update(\n                            last_update=now(), done=True, message=\"done\"\n                        )\n                        logger.debug(f\"{self.label} is done : stopping\")\n                        raise StopIteration\n                        exec_loop = False\n                    else:\n                        logger.info(f\"Unknown {self.label} loop status : {status}\")\n                elif sig is None:\n                    logger.debug(f\"empty loop for {self.label} : stopping\")\n                    raise StopIteration\n                elif sig not in self.SIGNALS:\n                    logger.debug(\"%s, unhandled signal %d\" % (self.label, sig))\n                    continue\n                elif sig == signal.SIGTERM:\n                    logger.debug(\n                        f\"PID {self.pid}, LABEL {self.label}, process SIGTERM ({sig}) signal\"\n                    )\n                    raise StopIteration\n                elif sig == signal.SIGHUP:\n                    logger.debug(\n                        f\"PID {self.pid}, LABEL {self.label}, process SIGHUP ({sig}) signal\"\n                    )\n                    raise StopIteration\n                elif sig == signal.SIGUSR1:\n                    logger.debug(\n                        f\"PID {self.pid}, LABEL {self.label}, process SIGUSR1 ({sig}) signal\"\n                    )\n                    if not self.restart():\n                        logger.debug(\"restart failed\")\n                        raise StopIteration\n                elif sig == signal.SIGUSR2:\n                    logger.debug(\n                        f\"PID {self.pid}, LABEL {self.label}, process SIGUSR2 ({sig}) signal : passing\"\n                    )\n                    # todo handle restart\n                    pass\n                else:\n                    logger.debug(f\"signal {sig} not catched for {self.label}\")\n\n                close_db_connection()\n\n                dt = self.dt_set - (time() - t_start)\n                if dt > 0:\n                    try:\n                        if (\n                            hasattr(self, \"device_ids\")\n                            and not hasattr(self, \"device_id\")\n                            and len(self.device_ids) > 0\n                        ):\n                            self.device_id = self.device_ids[0]\n                        if (\n                            channels_driver\n                            and hasattr(self, \"device_id\")\n                            and self.device_id is not None\n                        ):\n                            message = (\n                                str(self.scheduler_pid)\n                                + \"_DeviceAction_for_\"\n                                + str(self.device_id)\n                            )\n                            async_to_sync(self.waiting_action_receiver)(dt, message)\n                        elif (\n                            channels_driver\n                            and hasattr(self, \"process_id\")\n                            and self.process_id != 0\n                        ):\n                            message = (\n                                str(self.scheduler_pid)\n                                + \"_ProcessAction_for_\"\n                                + str(self.process_id)\n                            )\n                            async_to_sync(self.waiting_action_receiver)(dt, message)\n                        else:\n                            # logger.debug(\"sleep for %s - %s\" % (self.process_id, dt))\n                            raise ConnectionResetError\n                    except ConnectionResetError:\n                        sleep(dt)\n                    except OSError as e:\n                        logger.warning(e)\n                        sleep(dt)\n                    except Exception as e:\n                        logger.warning(e)\n\n        except StopIteration:\n            self.stop()\n            sys.exit(0)\n        except OperationalError as e:\n            logger.error(f\"{self.label}, DB connection lost\", exc_info=True)\n            self.stop()\n            sys.exit(0)\n        except:\n            logger.error(f\"{self.label}, unhandled exception\", exc_info=True)\n            self.stop()\n            sys.exit(0)\n\n    async def waiting_action_receiver(self, dt, message):\n        if not hasattr(self, \"channel_layer\"):\n            try:\n                self.channel_layer = channels.layers.get_channel_layer()\n                if self.channel_layer is not None:\n                    self.channel_layer.capacity = 1\n            except (ConnectionRefusedError, InvalidChannelLayerError):\n                # logger.debug(\"sleep for %s - %s\" % (self.process_id, dt))\n                sleep(dt)\n                return None\n\n        if self.channel_layer is not None:\n            try:\n                a = await wait_for(self.channel_layer.receive(message), timeout=dt)\n            except asyncioTimeoutError:\n                pass\n            except (ConnectionRefusedError, ConnectionClosedError) as e:\n                logger.debug(\"Channels or Redis error:\" + str(e))\n                sleep(dt)\n            else:\n                if \"DeviceReadTask\" in a:\n                    self.drt_received = True\n                if \"DeviceWriteTask\" in a:\n                    self.dwt_received = True\n                if \"ProcessSignal\" in a:\n                    logger.debug(\n                        \"Received ProcessSignal %s on channel_layer for %s\"\n                        % (a[\"ProcessSignal\"], self.label)\n                    )\n\n                # BUG : need to empty the channel_layer, if not the channel layer keep the message for each run loop\n                try:\n                    await wait_for(self.channel_layer.receive(message), timeout=0.01)\n                except asyncioTimeoutError:\n                    pass\n        else:\n            # logger.debug(\"sleep for %s - %s\" % (self.process_id, dt))\n            sleep(dt)\n\n    def loop(self):\n        \"\"\"\n        override this\n        \"\"\"\n        return 1, None\n\n    def cleanup(self):\n        \"\"\"\n        override this\n        \"\"\"\n        pass\n\n    def signal(self, signum=None, _frame=None):\n        \"\"\"\n        receive signals\n        \"\"\"\n        logger.debug(\n            \"PID %d, LABEL %s, received signal: %d\" % (self.pid, self.label, signum)\n        )\n        if signum not in self.SIG_QUEUE:\n            self.SIG_QUEUE.append(signum)\n        if channels_driver:\n            if not hasattr(self, \"channel_layer\"):\n                try:\n                    self.channel_layer = channels.layers.get_channel_layer()\n                    if self.channel_layer is not None:\n                        self.channel_layer.capacity = 1\n                except (ConnectionRefusedError, InvalidChannelLayerError):\n                    return None\n\n            if self.channel_layer is not None:\n                message = None\n                if (\n                    hasattr(self, \"device_ids\")\n                    and not hasattr(self, \"device_id\")\n                    and len(self.device_ids) > 0\n                ):\n                    self.device_id = self.device_ids[0]\n                if hasattr(self, \"device_id\") and self.device_id is not None:\n                    message = (\n                        str(self.scheduler_pid)\n                        + \"_DeviceAction_for_\"\n                        + str(self.device_id)\n                    )\n                elif hasattr(self, \"process_id\") and self.process_id != 0:\n                    message = (\n                        str(self.scheduler_pid)\n                        + \"_ProcessAction_for_\"\n                        + str(self.process_id)\n                    )\n                try:\n                    if message is not None:\n                        async_to_sync(self.channel_layer.send)(\n                            message, {\"ProcessSignal\": str(signum)}\n                        )\n                except (ChannelFull, ConnectionRefusedError):\n                    logger.info(\n                        \"Channel full : \"\n                        + str(self.scheduler_pid)\n                        + \"_ProcessAction_for_\"\n                        + str(self.process_id)\n                    )\n                except (RuntimeWarning, RuntimeError) as e:\n                    logger.debug(\n                        f\"Failed to send {message} ProcessSignal {signum} : {e}\"\n                    )\n\n    def stop(self, signum=None, _frame=None):\n        \"\"\"\n        handel's a termination signal\n        \"\"\"\n        try:\n            BackgroundProcess.objects.filter(pk=self.process_id).update(\n                pid=0, last_update=now(), message=\"stopping..\"\n            )\n            # run the cleanup\n            self.cleanup()\n            BackgroundProcess.objects.filter(pk=self.process_id).update(\n                pid=0, last_update=now(), message=\"stopped\"\n            )\n            logger.debug(\"Process %s(%s) is stopped\" % (self.label, self.pid))\n        except OperationalError:\n            logger.debug(\"%s, DB connection lost in stop function\" % self.label)\n            try:\n                self.cleanup()\n            except TransactionManagementError:\n                logger.debug(\n                    \"%s, TransactionManagementError in cleanup function\" % self.label\n                )\n\n    def restart(self):\n        \"\"\"\n        override this\n        \"\"\"\n        return None\n\n\nclass SingleDeviceDAQProcessWorker(Process):\n    processes = []\n    device_filter = dict(daqdevice__isnull=False)\n    process_class = \"pyscada.utils.scheduler.SingleDeviceDAQProcess\"\n    bp_label = \"pyscada.device_class-%s\"\n\n    def __init__(self, dt=5, **kwargs):\n        super(Process, self).__init__(dt=dt, **kwargs)\n\n    def create_bp(self, item):\n        bp = BackgroundProcess(\n            label=self.bp_label % item.pk,\n            message=\"waiting..\",\n            enabled=True,\n            parent_process_id=self.process_id,\n            process_class=self.process_class,\n            process_class_kwargs=json.dumps({\"device_id\": item.pk}),\n        )\n        bp.save()\n        self.processes.append(\n            {\"id\": bp.id, \"key\": item.pk, \"device_id\": item.pk, \"failed\": 0}\n        )\n\n    def init_process(self):\n        self.processes = []\n        for process in BackgroundProcess.objects.filter(\n            parent_process__pk=self.process_id, done=False\n        ):\n            try:\n                kill(process.pid, 0)\n            except OSError as e:\n                if e.errno == errno.ESRCH:\n                    process.delete()\n                    continue\n            logger.debug(\"process %d is alive\" % process.pk)\n            process.stop(cleanup=True)\n\n        # clean up\n        BackgroundProcess.objects.filter(parent_process__pk=self.process_id).delete()\n\n        self.grouped_ids = {}\n        for item in Device.objects.filter(active=True, **self.device_filter):\n            self.create_bp(item)\n\n    def loop(self):\n        \"\"\" \"\"\"\n        # Add missing devices\n        for item in Device.objects.filter(active=True, **self.device_filter):\n            item_found = False\n            for process in self.processes:\n                if process[\"device_id\"] == item.id:\n                    item_found = True\n            if not item_found:\n                self.create_bp(item)\n\n        # check if all processes are running\n        for process in self.processes:\n            try:\n                if Device.objects.filter(pk=process[\"device_id\"]).count() < 1:\n                    self.processes.remove(process)\n                    logger.debug(\n                        \"Device %s not found for process %s. Process removed.\"\n                        % (process[\"device_id\"], process[\"id\"])\n                    )\n                    continue\n                if Device.objects.filter(pk=process[\"device_id\"], active=1).count() < 1:\n                    self.processes.remove(process)\n                    logger.debug(\n                        \"Device %s not active. Process removed.\" % process[\"device_id\"]\n                    )\n                    continue\n                if BackgroundProcess.objects.filter(pk=process[\"id\"]).count() != 1:\n                    # Process is dead, spawn new instance\n                    if process[\"failed\"] < 3:\n                        bp = BackgroundProcess(\n                            label=self.bp_label % process[\"key\"],\n                            message=\"waiting..\",\n                            enabled=True,\n                            parent_process_id=self.process_id,\n                            process_class=self.process_class,\n                            process_class_kwargs=json.dumps(\n                                {\"device_id\": process[\"device_id\"]}\n                            ),\n                        )\n                        bp.save()\n                        process[\"id\"] = bp.id\n                    elif process[\"failed\"] == 3:\n                        # todo : raise exception\n                        logger.error(\n                            f\"process {self.bp_label % process['key']} failed 3 times\",\n                            exc_info=True,\n                        )\n                    else:\n                        logger.warning(\n                            f\"process {self.bp_label % process['key']} failed more than 3 times\"\n                        )\n                    process[\"failed\"] += 1\n            except:\n                logger.debug(\n                    \"%s, unhandled exception\\n%s\" % (self.label, traceback.format_exc())\n                )\n\n        return 1, None\n\n    def cleanup(self):\n        # todo cleanup\n        pass\n\n    def restart(self):\n        for process in self.processes:\n            try:\n                bp = BackgroundProcess.objects.get(pk=process[\"id\"])\n                if bp.stop(cleanup=True):\n                    process[\"failed\"] -= 1\n                    self.processes.remove(process)\n            except BackgroundProcess.DoesNotExist:\n                logger.debug(\n                    f\"BackgroundProcess for {self.label} does not exist : failed to restart.\"\n                )\n            except:\n                logger.debug(\n                    \"%s, unhandled exception\\n%s\" % (self.label, traceback.format_exc())\n                )\n        self.init_process()\n        return True\n\n\nclass MultiDeviceDAQProcessWorker(Process):\n    processes = []\n    device_filter = dict(daqdevice__isnull=False)\n    process_class = \"pyscada.utils.scheduler.MultiDeviceDAQProcess\"\n    bp_label = \"pyscada.device_class-%s\"\n\n    def __init__(self, dt=5, **kwargs):\n        super(Process, self).__init__(dt=dt, **kwargs)\n\n    def create_bp(self, key, values):\n        bp = BackgroundProcess(\n            label=self.bp_label % key,\n            message=\"waiting..\",\n            enabled=True,\n            parent_process_id=self.process_id,\n            process_class=self.process_class,\n            process_class_kwargs=json.dumps({\"device_ids\": [i.pk for i in values]}),\n        )\n        bp.save()\n        self.processes.append(\n            {\"id\": bp.id, \"key\": key, \"device_ids\": [i.pk for i in values], \"failed\": 0}\n        )\n\n    def init_process(self):\n        self.processes = []\n        for process in BackgroundProcess.objects.filter(\n            parent_process__pk=self.process_id, done=False\n        ):\n            try:\n                kill(process.pid, 0)\n            except OSError as e:\n                if e.errno == errno.ESRCH:\n                    process.delete()\n                    continue\n            logger.debug(\"process %d is alive\" % process.pk)\n            process.stop()\n\n        # clean up\n        BackgroundProcess.objects.filter(\n            parent_process__pk=self.process_id, done=False\n        ).delete()\n\n        self.grouped_ids = {}\n        for item in Device.objects.filter(active=True, **self.device_filter):\n            if self.gen_group_id(item) not in self.grouped_ids:\n                self.grouped_ids[self.gen_group_id(item)] = []\n            self.grouped_ids[self.gen_group_id(item)].append(item)\n\n        for key, values in self.grouped_ids.items():\n            self.create_bp(key, values)\n\n    def loop(self):\n        \"\"\" \"\"\"\n\n        # check if a device was deleted, then stop the BackgroundProcess and delete in process list\n        for process in self.processes:\n            process_has_changed = False\n            for device_id in process[\"device_ids\"]:\n                if not Device.objects.filter(active=True, pk=device_id).count():\n                    # device removed or not active\n                    process_has_changed = True\n            if process_has_changed:\n                bp = BackgroundProcess.objects.filter(pk=process[\"id\"])\n                if bp.count():\n                    logger.debug(\n                        \"Stop BP %s and remove process %s\" % (bp.first(), process)\n                    )\n                    bp.first().stop(cleanup=True)\n                    self.processes.remove(process)\n\n        # Add missing devices to process list and create BP\n        self.grouped_ids = {}\n        for item in Device.objects.filter(active=True, **self.device_filter):\n            self.grouped_ids[self.gen_group_id(item)] = [item]\n\n        for key, values in self.grouped_ids.items():\n            item_found = False\n            for process in self.processes:\n                for item in values:\n                    if item.id in process[\"device_ids\"]:\n                        item_found = True\n            if not item_found:\n                self.create_bp(key, values)\n\n        # check if all processes are running\n        for process in self.processes:\n            try:\n                if BackgroundProcess.objects.filter(pk=process[\"id\"]).count() != 1:\n                    # Process is dead, spawn new instance\n                    if process[\"failed\"] < 3:\n                        bp = BackgroundProcess(\n                            label=self.bp_label % process[\"key\"],\n                            message=\"waiting..\",\n                            enabled=True,\n                            parent_process_id=self.process_id,\n                            process_class=self.process_class,\n                            process_class_kwargs=json.dumps(\n                                {\"device_ids\": process[\"device_ids\"]}\n                            ),\n                        )\n                        bp.save()\n                        process[\"id\"] = bp.id\n                    elif process[\"failed\"] == 3:\n                        # todo : raise exception\n                        logger.error(\n                            f\"process {self.bp_label % process['key']} failed 3 times\",\n                            exc_info=True,\n                        )\n                    else:\n                        logger.warning(\n                            f\"process {self.bp_label % process['key']} failed more than 3 times\"\n                        )\n                    process[\"failed\"] += 1\n            except:\n                logger.debug(\n                    \"%s, unhandled exception\\n%s\" % (self.label, traceback.format_exc())\n                )\n\n        return 1, None\n\n    def cleanup(self):\n        # todo cleanup\n        pass\n\n    def restart(self):\n        for process in self.processes:\n            try:\n                bp = BackgroundProcess.objects.get(pk=process[\"id\"])\n                if bp.stop(cleanup=True):\n                    process[\"failed\"] -= 1\n                    self.processes.remove(process)\n            except BackgroundProcess.DoesNotExist:\n                logger.debug(\n                    f\"BackgroundProcess for {self.label} does not exist : failed to restart.\"\n                )\n            except:\n                logger.debug(\n                    \"%s, unhandled exception\\n%s\" % (self.label, traceback.format_exc())\n                )\n        self.init_process()\n        return True\n\n    def gen_group_id(self, item):\n        \"\"\"\n        override this\n        \"\"\"\n        return \"%d\" % (item.pk)\n\n\nclass SingleDeviceDAQProcess(Process):\n    def __init__(self, dt=5, **kwargs):\n        self.last_query = 0\n        self.dt_query_data = 0\n        self.device = None\n        self.device_id = None\n        super(SingleDeviceDAQProcess, self).__init__(dt=dt, **kwargs)\n\n    def init_process(self):\n        \"\"\"\n        init a standard daq process for a single device\n        \"\"\"\n        self.device = Device.objects.filter(\n            protocol__daq_daemon=1, id=self.device_id\n        ).first()\n        if not self.device:\n            logger.warning(\n                f\"Cannot initialize process for {self.device_id}. Device not found.\"\n            )\n            self.device = None\n            return False\n        if not self.device.active:\n            logger.info(\n                \"Device %s is not active. Process not initialized.\" % self.device_id\n            )\n            self.device = None\n            return False\n        self.dt_set = min(self.dt_set, self.device.polling_interval)\n        self.dt_query_data = self.device.polling_interval\n        try:\n            self.device = self.device.get_device_instance()\n        except:\n            logger.error(\n                f\"Exception while initialisation of DAQ Process for Device {self.device_id}\",\n                exc_info=True,\n            )\n            self.device = None\n            return False\n\n        return True\n\n    def loop(self):\n        if self.device is None:\n            return -1, None\n        # reset all cached values to write before checking device write and read tasks\n        for v in self.device.variables.values():\n            v.erase_cache()\n\n        # data from a write\n        data = []\n        # process write tasks\n\n        variable_as_decimal = {}\n        vp_tasks_done = []\n        # iterate on VPs write tasks\n        for task in DeviceWriteTask.objects.filter(\n            done=False,\n            start__lte=time(),\n            failed=False,\n            variable_property__variable__device_id=self.device_id,\n        ).order_by(\"start\"):\n            # scale if needed\n            if task.variable_property.variable.scaling is not None:\n                task.value = task.variable_property.variable.scaling.scale_output_value(\n                    task.value\n                )\n            # process bit proterty to change variable bit value\n            if (\n                len(task.variable_property.name.split(\"bit\")) == 2\n                and task.variable_property.name.split(\"bit\")[0] == \"\"\n                and task.variable_property.name.split(\"bit\")[1].isdigit()\n                and int(task.variable_property.name.split(\"bit\")[1])\n                < task.variable_property.variable.get_bits_by_class()\n            ):\n                if task.variable_property.variable not in variable_as_decimal:\n                    variable_as_decimal[task.variable_property.variable] = {}\n                if task.variable_property.id not in vp_tasks_done:\n                    vp_tasks_done.append(task.variable_property.id)\n                variable_as_decimal[task.variable_property.variable][\n                    task.variable_property.name.split(\"bit\")[1]\n                ] = task.value\n            # Save the VP value\n            vp = VariableProperty.objects.update_property(\n                variable_property=task.variable_property, value=task.value\n            )\n            if vp:\n                # Set the VP task as done\n                task.done = True\n                task.finished = time()\n                task.save(update_fields=[\"done\", \"finished\"])\n            else:\n                logger.debug(\"VP not found for device write task %d\" % task.pk)\n                task.failed = True\n                task.finished = time()\n                task.save(update_fields=[\"done\", \"finished\"])\n\n        # Get and change the variable value which has bit VP\n        start = time()\n        for var, item in variable_as_decimal.items():\n            if var.query_prev_value():\n                prev_value = var.prev_value\n            else:\n                prev_value = 0\n            for bit, value in item.items():\n                prev_value = set_bit(int(prev_value), int(bit), bool(value))\n            # Create a new device write task for this varaible with the new value\n            dwt = DeviceWriteTask(variable=var, value=prev_value, start=start)\n            dwt.save()\n\n        # Do all the variable write task for this device starting with the oldest\n        dwts = DeviceWriteTask.objects.filter(\n            done=False,\n            start__lte=time(),\n            failed=False,\n            variable__device_id=self.device_id,\n        ).order_by(\"start\")\n        if hasattr(self, \"dwt_received\") and self.dwt_received and dwts.count() == 0:\n            sleep(0.5)\n            logger.info(\"DeviceWriteTask bulk_created but not found, wait 0.5s\")\n            dwts = DeviceWriteTask.objects.filter(\n                Q(\n                    done=False,\n                    start__lte=time(),\n                    failed=False,\n                )\n                & (\n                    Q(variable__device_id=self.device_id)\n                    | Q(variable_property__variable__device_id=self.device_id)\n                )\n            ).order_by(\"start\")\n            if dwts.count() == 0:\n                logger.info(\"DeviceWriteTask still not found\")\n        self.dwt_received = False\n        for task in dwts:\n            if task.variable is not None and task.variable.scaling is not None:\n                task.value = task.variable.scaling.scale_output_value(task.value)\n            if self.device is not None:\n                if task.variable is not None:\n                    var_id = task.variable.id\n                else:\n                    var_id = None\n                tmp_data = self.device.write_data(var_id, task.value, task)\n                if isinstance(tmp_data, list):\n                    if len(tmp_data) > 0:\n                        task.done = True\n                        task.finished = time()\n                        task.save(update_fields=[\"done\", \"finished\"])\n                        data.append(tmp_data)\n                    else:\n                        task.failed = True\n                        task.finished = time()\n                        task.save(update_fields=[\"failed\", \"finished\"])\n                else:\n                    task.failed = True\n                    task.finished = time()\n                    task.save(update_fields=[\"failed\", \"finished\"])\n\n        drts = DeviceReadTask.objects.filter(\n            Q(\n                done=False,\n                start__lte=time(),\n                failed=False,\n            )\n            & (\n                Q(device_id=self.device_id)\n                | Q(variable__device_id=self.device_id)\n                | Q(variable_property__variable__device_id=self.device_id)\n            )\n        )\n        if hasattr(self, \"drt_received\") and self.drt_received and drts.count() == 0:\n            sleep(0.5)\n            logger.info(\"DeviceReadTask bulk_created but not found, wait 0.5s\")\n            drts = DeviceReadTask.objects.filter(\n                Q(\n                    done=False,\n                    start__lte=time(),\n                    failed=False,\n                )\n                & (\n                    Q(device_id=self.device_id)\n                    | Q(variable__device_id=self.device_id)\n                    | Q(variable_property__variable__device_id=self.device_id)\n                )\n            )\n            if drts.count() == 0:\n                logger.info(\"DeviceReadTask still not found\")\n        self.drt_received = False\n        if (time() - self.last_query > self.dt_query_data) or drts.count():\n            # TODO : Read data for a variable or a VP only\n\n            self.last_query = time()\n            # Query data\n            if self.device is not None:\n                tmp_data = self.device.request_data()\n                if isinstance(tmp_data, list):\n                    if len(tmp_data) > 0:\n                        drts.update(done=True, finished=time())\n                        data.append(tmp_data)\n                    else:\n                        drts.update(failed=True, finished=time())\n                else:\n                    drts.update(failed=True, finished=time())\n\n        if isinstance(data, list):\n            if len(data) > 0:\n                # For all variable, find existing bit VP and set the bit value\n                for l in data:\n                    for var in l:\n                        for vp in var.variableproperty_set.all():\n                            if (\n                                len(vp.name.split(\"bit\")) == 2\n                                and vp.name.split(\"bit\")[0] == \"\"\n                                and vp.name.split(\"bit\")[1].isdigit()\n                                and int(vp.name.split(\"bit\")[1])\n                                < vp.variable.get_bits_by_class()\n                                and vp.id not in vp_tasks_done\n                            ):\n                                bit = (\n                                    int(var.prev_value) >> int(vp.name.split(\"bit\")[1])\n                                ) & 1\n                                VariableProperty.objects.update_property(vp, value=bit)\n                return 1, data\n        return 1, None\n\n    def cleanup(self):\n        \"\"\"\n        mark the process as done\n        \"\"\"\n        BackgroundProcess.objects.filter(pk=self.process_id).delete()\n\n    def restart(self):\n        \"\"\"\n        just re-init\n        \"\"\"\n        # self.stop()\n        # Reset last query to resfresh all variables values\n        self.last_query = 0\n        return self.init_process()\n\n\nclass MultiDeviceDAQProcess(Process):\n    def __init__(self, dt=5, **kwargs):\n        self.device_ids = []\n        self.devices = {}\n        self.dt_query_data = 3600.0\n        self.last_query = 0\n        super(MultiDeviceDAQProcess, self).__init__(dt=dt, **kwargs)\n\n    def init_process(self):\n        \"\"\"\n        init a standard daq process for multiple devices\n        \"\"\"\n        self.devices = {}\n        # Reset dt_query_data to allow an increasing change of the polling interval from the admin\n        self.dt_query_data = 3600.0\n        for item in Device.objects.filter(\n            protocol__daq_daemon=1, id__in=self.device_ids\n        ):\n            try:\n                if not item:\n                    logger.warning(\n                        f\"Cannot add device {item.id} to process {self.process_id}. Device not found.\"\n                    )\n                    continue\n                if not item.active:\n                    logger.info(\n                        \"Device %s is not active. Not added to process %s.\"\n                        % (item.id, self.process_id)\n                    )\n                    continue\n                tmp_device = item.get_device_instance()\n                if tmp_device is not None:\n                    self.devices[item.pk] = tmp_device\n                    self.dt_set = min(self.dt_set, item.polling_interval)\n                    self.dt_query_data = min(self.dt_query_data, item.polling_interval)\n            except:\n                var = traceback.format_exc()\n                logger.error(\n                    f\"Exception while initialisation of DAQ Process for Device {item.pk}\",\n                    exc_info=True,\n                )\n        if len(self.devices.items()) == 0:\n            return False\n        return True\n\n    def loop(self):\n        data = [[]]\n        vp_tasks_done = []\n        for device_id, device in self.devices.items():\n            # reset all cached values to write before checking device write and read tasks\n            for v in device.variables.values():\n                v.erase_cache()\n\n            # process write tasks\n\n            variable_as_decimal = {}\n            # iterate on VPs write tasks\n            for task in DeviceWriteTask.objects.filter(\n                done=False,\n                start__lte=time(),\n                failed=False,\n                variable_property__variable__device_id=device_id,\n            ).order_by(\"start\"):\n                # scale if needed\n                if task.variable_property.variable.scaling is not None:\n                    task.value = (\n                        task.variable_property.variable.scaling.scale_output_value(\n                            task.value\n                        )\n                    )\n                # process bit proterty to change variable bit value\n                if (\n                    len(task.variable_property.name.split(\"bit\")) == 2\n                    and task.variable_property.name.split(\"bit\")[0] == \"\"\n                    and task.variable_property.name.split(\"bit\")[1].isdigit()\n                    and int(task.variable_property.name.split(\"bit\")[1])\n                    <= task.variable_property.variable.get_bits_by_class()\n                ):\n                    if task.variable_property.variable not in variable_as_decimal:\n                        variable_as_decimal[task.variable_property.variable] = {}\n                    if task.variable_property.id not in vp_tasks_done:\n                        vp_tasks_done.append(task.variable_property.id)\n                    variable_as_decimal[task.variable_property.variable][\n                        task.variable_property.name.split(\"bit\")[1]\n                    ] = task.value\n                # Save the VP value\n                vp = VariableProperty.objects.update_property(\n                    variable_property=task.variable_property, value=task.value\n                )\n                if vp:\n                    # Set the VP task as done\n                    task.done = True\n                    task.finished = time()\n                    task.save(update_fields=[\"done\", \"finished\"])\n                else:\n                    logger.debug(\"VP not found for device write task %d\" % task.pk)\n                    task.failed = True\n                    task.finished = time()\n                    task.save(update_fields=[\"done\", \"finished\"])\n\n            # Get and change the variable value which has bit VP\n            for var, item in variable_as_decimal.items():\n                if var.query_prev_value():\n                    prev_value = var.prev_value\n                else:\n                    prev_value = 0\n                for bit, value in item.items():\n                    prev_value = set_bit(int(prev_value), int(bit), bool(value))\n                # Create a new device write task for this varaible with the new value\n                dwt = DeviceWriteTask(variable=var, value=prev_value)\n                dwt.save()\n\n            dwts = DeviceWriteTask.objects.filter(\n                Q(\n                    done=False,\n                    start__lte=time(),\n                    failed=False,\n                )\n                & (\n                    Q(variable__device_id=device_id)\n                    | Q(variable_property__variable__device_id=device_id)\n                )\n            ).order_by(\"start\")\n            if (\n                hasattr(self, \"dwt_received\")\n                and self.dwt_received\n                and dwts.count() == 0\n            ):\n                sleep(0.5)\n                logger.info(\"DeviceWriteTask bulk_created but not found, wait 0.5s\")\n                dwts = DeviceWriteTask.objects.filter(\n                    Q(\n                        done=False,\n                        start__lte=time(),\n                        failed=False,\n                    )\n                    & (\n                        Q(variable__device_id=device_id)\n                        | Q(variable_property__variable__device_id=device_id)\n                    )\n                ).order_by(\"start\")\n                if dwts.count() == 0:\n                    logger.info(\"DeviceWriteTask still not found\")\n            for task in dwts:\n                if task.variable is not None and task.variable.scaling is not None:\n                    task.value = task.variable.scaling.scale_output_value(task.value)\n                if task.variable is not None:\n                    var_id = task.variable.id\n                else:\n                    var_id = None\n                tmp_data = device.write_data(var_id, task.value, task)\n                if isinstance(tmp_data, list):\n                    if len(tmp_data) > 0:\n                        task.done = True\n                        task.finished = time()\n                        task.save(update_fields=[\"done\", \"finished\"])\n                        data.append(tmp_data)\n                    else:\n                        task.failed = True\n                        task.finished = time()\n                        task.save(update_fields=[\"failed\", \"finished\"])\n                else:\n                    task.failed = True\n                    task.finished = time()\n                    task.save(update_fields=[\"failed\", \"finished\"])\n        self.dwt_received = False\n\n        drts = DeviceReadTask.objects.filter(\n            Q(\n                done=False,\n                start__lte=time(),\n                failed=False,\n            )\n            & (\n                Q(device_id__in=self.device_ids)\n                | Q(variable__device_id__in=self.device_ids)\n                | Q(variable_property__variable__device_id__in=self.device_ids)\n            )\n        )\n        if hasattr(self, \"drt_received\") and self.drt_received and drts.count() == 0:\n            sleep(0.5)\n            logger.info(\"DeviceReadTask bulk_created but not found, wait 0.5s\")\n            drts = DeviceReadTask.objects.filter(\n                Q(\n                    done=False,\n                    start__lte=time(),\n                    failed=False,\n                )\n                & (\n                    Q(device_id__in=self.device_ids)\n                    | Q(variable__device_id__in=self.device_ids)\n                    | Q(variable_property__variable__device_id__in=self.device_ids)\n                )\n            )\n            if drts.count() == 0:\n                logger.info(\"DeviceReadTask still not found\")\n        self.drt_received = False\n        if time() - self.last_query > self.dt_query_data or drts.count():\n            self.last_query = time()\n            for device_id, device in self.devices.items():\n                # Query data\n                tmp_data = device.request_data()\n                if isinstance(tmp_data, list):\n                    if len(tmp_data) > 0:\n                        drts.filter(device_id=device_id).update(\n                            done=True, finished=time()\n                        )\n                        if len(data[-1]) + len(tmp_data) < 998:\n                            # add to the last write job\n                            data[-1] += tmp_data\n                        else:\n                            # add to next write job\n                            data.append(tmp_data)\n                    else:\n                        drts.filter(device_id=device_id).update(\n                            failed=True, finished=time()\n                        )\n                else:\n                    drts.filter(device_id=device_id).update(\n                        failed=True, finished=time()\n                    )\n\n        if isinstance(data, list):\n            if len(data) > 0:\n                # For all variable, find existing bit VP and set the bit value\n                for l in data:\n                    for var in l:\n                        for vp in var.variableproperty_set.all():\n                            if (\n                                len(vp.name.split(\"bit\")) == 2\n                                and vp.name.split(\"bit\")[0] == \"\"\n                                and vp.name.split(\"bit\")[1].isdigit()\n                                and int(vp.name.split(\"bit\")[1])\n                                < vp.variable.get_bits_by_class()\n                                and vp.id not in vp_tasks_done\n                            ):\n                                bit = (\n                                    int(var.prev_value) >> int(vp.name.split(\"bit\")[1])\n                                ) & 1\n                                VariableProperty.objects.update_property(vp, value=bit)\n                return 1, data\n        return 1, None\n\n    def cleanup(self):\n        \"\"\"\n        mark the process as done\n        \"\"\"\n        BackgroundProcess.objects.filter(pk=self.process_id).delete()\n\n    def restart(self):\n        \"\"\"\n        just re-init\n        \"\"\"\n        # self.stop()\n        # Reset last query to resfresh all variables values\n        self.last_query = 0\n        return self.init_process()\n"
  },
  {
    "path": "pyscada/views.py",
    "content": "# -*- coding: utf-8 -*-\n"
  },
  {
    "path": "setup.py",
    "content": "# -*- coding: utf-8 -*-\nfrom setuptools import setup, find_namespace_packages\nimport os\nfrom pyscada import core\n\n\nCLASSIFIERS = [\n    \"Development Status :: 4 - Beta\",\n    \"Environment :: Web Environment\",\n    \"Environment :: Console\",\n    \"Framework :: Django\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)\",\n    \"Operating System :: POSIX\",\n    \"Operating System :: MacOS :: MacOS X\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: JavaScript\",\n    \"Topic :: Internet :: WWW/HTTP :: Dynamic Content\",\n    \"Topic :: Scientific/Engineering :: Visualization\",\n]\nsetup(\n    author=core.__author__,\n    author_email=\"info@martin-schroeder.net\",\n    name=\"PyScada\",\n    version=core.__version__,\n    description=\"A Python and Django based Open Source SCADA System\",\n    long_description=open(os.path.join(os.path.dirname(__file__), \"README.rst\")).read(),\n    url=\"http://www.github.com/pyscada/PyScada\",\n    license=\"AGPLv3\",\n    platforms=[\"OS Independent\"],\n    classifiers=CLASSIFIERS,\n    install_requires=[\n        \"django>=5.2,<5.3\",\n        \"numpy>=1.6.0\",\n        \"h5py>=2.2.1\",\n        \"pillow\",\n        \"python-daemon>=2.0.0\",\n        \"pytz\",\n        \"python-dateutil\",\n        # 'channels',\n        # 'channels-redis',\n        \"asgiref\",\n        \"monthdelta\",\n        \"six\",\n        \"concurrent-log-handler\",  # rotating logs for multiprocess\n        \"scipy\",\n    ],\n    packages=find_namespace_packages(exclude=[\"project\", \"project.*\"]),\n    include_package_data=True,\n    zip_safe=False,\n    test_suite=\"runtests.main\",\n)\n"
  },
  {
    "path": "tests/project_template/manage.py-tpl",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"{{ project_name }}.settings\")\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError:\n        # The above import may fail for some other reason. Ensure that the\n        # issue is really that Django is missing to avoid masking other\n        # exceptions on Python 2.\n        try:\n            import django\n        except ImportError:\n            raise ImportError(\n                \"Couldn't import Django. Are you sure it's installed and \"\n                \"available on your PYTHONPATH environment variable? Did you \"\n                \"forget to activate a virtual environment?\"\n            )\n        raise\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "tests/project_template/project_name/__init__.py-tpl",
    "content": ""
  },
  {
    "path": "tests/project_template/project_name/asgi.py-tpl",
    "content": "\"\"\"\nASGI config for {{ project_name }} project.\n\nIt exposes the ASGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/asgi/\n\"\"\"\n\nimport os\n\nfrom django.core.asgi import get_asgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"{{ project_name }}.settings\")\n\napplication = get_asgi_application()\n"
  },
  {
    "path": "tests/project_template/project_name/settings.py-tpl",
    "content": "\"\"\"\nDjango settings for {{ project_name }} project.\n\nGenerated by 'django-admin startproject' using Django {{ django_version }}.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/{{ docs_version }}/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/\n\"\"\"\n\nimport os\nfrom importlib import util, metadata\nfrom pathlib import Path\n\n# Build paths inside the project like this: BASE_DIR / 'subdir'.\nBASE_DIR = Path(__file__).resolve().parent.parent\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = '{{ secret_key }}'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = False\n\nALLOWED_HOSTS = ['*']\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'pyscada',\n    {{ additional_apps }}\n]\n\npyscada = __import__(\"pyscada.core\")\nif hasattr(pyscada.core, \"additional_installed_app\"):\n    for app in getattr(pyscada.core, \"additional_installed_app\"):\n        INSTALLED_APPS += [\n            app,\n        ]\n\n# Auto add pyscada plugin\nif {{ auto_add_apps }}:\n    distributions = metadata.distributions()\n    for dist in distributions:\n        if 'pyscada-' in dist.metadata['Name']:\n            lib = dist.metadata['Name'].split(\"-\")[1]\n            if util.find_spec('pyscada.' + str(lib)) is not None:\n                INSTALLED_APPS += [\n                    'pyscada.' + str(lib),\n                ]\n                pyscada = __import__(\"pyscada.\" + str(lib))\n                if hasattr(pyscada, str(lib)) and hasattr(getattr(pyscada, str(lib)), \"additional_installed_app\"):\n                    for app in getattr(getattr(pyscada, str(lib)), \"additional_installed_app\"):\n                        INSTALLED_APPS += [\n                            app,\n                        ]\n\nif util.find_spec('channels') is not None:\n    INSTALLED_APPS += [\n        'channels',\n    ]\n\n    CHANNEL_LAYERS = {\n        'default': {\n            'BACKEND': 'channels_redis.core.RedisChannelLayer',\n            'CONFIG': {\n                \"hosts\": [('127.0.0.1', 6379)],\n            },\n        },\n    }\n\nif util.find_spec('django_redis') is not None:\n    # for cache_datasource\n    CACHES = {\n        \"default\": {\n            \"BACKEND\": \"django.core.cache.backends.redis.RedisCache\",\n            \"LOCATION\": \"redis://127.0.0.1:6379\",\n        }\n    }\nelse:\n    # as fallback setup file based cache\n    CACHES = {\n        \"default\": {\n            \"BACKEND\": \"django.core.cache.backends.filebased.FileBasedCache\",\n            \"LOCATION\": \"{{ project_root }}\",\n            \"TIMEOUT\": 3600,\n            \"OPTIONS\": {\"MAX_ENTRIES\": 1000},\n        }\n    }\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\nAUTHENTICATION_BACKENDS = [\n    'django.contrib.auth.backends.ModelBackend',\n]\n\nROOT_URLCONF = '{{ project_name }}.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n            'libraries': {\n                'staticfiles': 'django.templatetags.static',\n            },\n        },\n    },\n]\n\nWSGI_APPLICATION = '{{ project_name }}.wsgi.application'\n\n\n# Database\n# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#databases\n\nDATABASES = {\n    'default': {\n        'ENGINE':   'django.db.backends.mysql',\n        'NAME':     '{{ db_name }}',\n        'USER':     '{{ db_user }}',\n        'PASSWORD': '{{ db_password }}',\n        'OPTIONS': {\n            'init_command': \"SET sql_mode='STRICT_TRANS_TABLES'\",\n        }\n   }\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/\n\nLANGUAGE_CODE = 'en-us'\n\nTIME_ZONE = 'UTC'\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\nLOGIN_REDIRECT_URL = \"/\"\n\nLOGOUT_REDIRECT_URL = \"/\"\n\nADMINS = [{{ project_admins|safe }}]\n\nMANAGERS = [{{ project_admins|safe }}]\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/\n\nSTATIC_URL = '/static/'\n\nSTATIC_ROOT = '{{ project_root }}/http/static/'\n\nMEDIA_URL = '/media/'\n\nMEDIA_ROOT = '{{ project_root }}/http/media/'\n\n# email settings\nDEFAULT_FROM_EMAIL = 'example@domain.net'\nEMAIL_HOST = '127.0.0.1'\nEMAIL_PORT = 587\nEMAIL_HOST_USER = 'example@domain.net'\nEMAIL_USE_TLS = True\nEMAIL_USE_SSL = False\nEMAIL_HOST_PASSWORD = 'aSecurePassword'\nEMAIL_PREFIX = 'A Message From PyScada'\n# for admins and managers\nSERVER_EMAIL=DEFAULT_FROM_EMAIL\nEMAIL_SUBJECT_PREFIX=EMAIL_PREFIX\nEMAIL_TIMEOUT=5\n\n# PyScada settings\n# https://github.com/pyscada/PyScada\n\n#\nPYSCADA_META = {\n    'name': 'MyFacilityName',  # FIXME\n    'description': 'my facility in nowhere'  # FIXME\n}\n\n# settings for the PYSCADA Export Module\n#\n# output_folder\t\t\tfolder were the exported files will be stored\n#\n# file_prefix           prefix of the output files (PREFIXmeasurement_data_Y_m_d_HM.[h5,csv])\n\nPYSCADA_EXPORT = {\n    'output_folder': '{{ pyscada_home }}/measurement_data_dumps',\n    'file_prefix': PYSCADA_META['name'] + '_'\n}\n\nLOGGING = {\n    'version': 1,\n    'disable_existing_loggers': False,\n    'formatters': {\n        'standard': {\n            'format': \"[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s\",\n            'datefmt': \"%d/%b/%Y %H:%M:%S\"\n        },\n    },\n    'handlers': {\n        'file': {\n            'level': 'DEBUG',\n            'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler',\n            'filename': '{{ log_file_dir }}pyscada_debug.log',\n            'formatter': 'standard',\n            'maxBytes': 15728640,  # 1024 * 1024 * 15B = 15MB\n            'backupCount': 5,\n        },\n        \"mail_admins\": {\n            \"level\": \"ERROR\",\n            \"class\": \"django.utils.log.AdminEmailHandler\",\n            \"include_html\": True,\n        },\n    },\n    'loggers': {\n        '': {\n            'handlers': ['file', 'mail_admins'],\n            'level': 'ERROR',\n            'propagate': True,\n        },\n        'django': {\n            'handlers': ['file', 'mail_admins'],\n            'level': 'INFO',\n            'propagate': False,\n        },\n        'pyscada': {\n            'handlers': ['file', 'mail_admins'],\n            'level': 'DEBUG',\n            'propagate': False,\n        },\n        'gunicorn': {\n            'handlers': ['file', 'mail_admins'],\n            'level': 'INFO',\n            'propagate': False,\n        },\n    },\n}\n{{ additional_settings }}\n"
  },
  {
    "path": "tests/project_template/project_name/urls.py-tpl",
    "content": "\"\"\"PyScadaServer URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/1.11/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.conf.urls import url, include\n    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))\n\"\"\"\nfrom django.urls import path, include\nfrom django.contrib import admin\n\nurlpatterns = [\n    path('', include('pyscada.core.urls')),\n]\n"
  },
  {
    "path": "tests/project_template/project_name/wsgi.py-tpl",
    "content": "\"\"\"\nWSGI config for {{ project_name }} project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/\n\"\"\"\n\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"{{ project_name }}.settings\")\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "tests/test.sh",
    "content": "#!/bin/bash\n\nmkdir test\ncd test\npython3 -m virtualenv ./.env\nsource ./.env/bin/activate\n\npip install ../../\npip install ../../../PyScada-Modbus/\npip install ../../../PyScada-Onewire/\npip install ../../../PyScada-Phant/\npip install ../../../PyScada-VISA/\npip install ../../../PyScada-SMBus/\npip install ../../../PyScada-SystemStat/\n\ndjango-admin startproject PyScadaServer  --template ./../project_template_with_plugins.zip\ncd PyScadaServer/\nmkdir log\npython3 manage.py migrate\npython3 manage.py collectstatic\npython3 manage.py loaddata color\npython3 manage.py loaddata units\npython3 manage.py pyscada_daemon init\nexport DJANGO_SUPERUSER_PASSWORD=\"test\"\npython3 manage.py createsuperuser --username pyscada --email pyscada@pyscada.org --noinput\npython3 manage.py runserver --insecure\ndeactivate\ncd ../../\nrm -rf test\n"
  },
  {
    "path": "tests/test_modbus.sh",
    "content": "#!/bin/bash\n\nmkdir test\ncd test\npython3 -m virtualenv ./.env\nsource ./.env/bin/activate\n\npip install ../../\npip install ../../../PyScada-Modbus/\n\ndjango-admin startproject PyScadaServer  --template ./../project_template_with_plugins_modbus.zip\ncd PyScadaServer/\nmkdir log\npython3 manage.py migrate\n# add pyscada.modbus to INSTALLED_APP and apply modbus migrations\nsed -i \"/    'pyscada.export',/a     'pyscada.modbus',\" PyScadaServer/settings.py\npython3 manage.py migrate\n\npython3 manage.py collectstatic\npython3 manage.py loaddata color\npython3 manage.py loaddata units\npython3 manage.py pyscada_daemon init\nexport DJANGO_SUPERUSER_PASSWORD=\"test\"\npython3 manage.py createsuperuser --username pyscada --email pyscada@pyscada.org --noinput\n"
  },
  {
    "path": "tests/test_without_plugins.sh",
    "content": "#!/bin/bash\n\nmkdir test\ncd test\npython3 -m virtualenv ./.env\nsource ./.env/bin/activate\n\npip install ../../\n\ndjango-admin startproject PyScadaServer  --template ./../project_template_stand-alone.zip\ncd PyScadaServer/\nmkdir log\npython3 manage.py migrate\npython3 manage.py collectstatic\npython3 manage.py loaddata color\npython3 manage.py loaddata units\npython3 manage.py pyscada_daemon init\nexport DJANGO_SUPERUSER_PASSWORD=\"test\"\npython3 manage.py createsuperuser --username pyscada --email pyscada@pyscada.org --noinput\npython3 manage.py runserver --insecure\ndeactivate\ncd ../../\nrm -rf test\n"
  },
  {
    "path": "tox.ini",
    "content": "# Tox (https://tox.readthedocs.io/) is a tool for running tests in multiple\n# virtualenvs. This configuration file helps to run the test suite on all\n# supported Python versions. To use it, \"python -m pip install tox\" and\n# then run \"tox\" from this directory.\n\n[tox]\nminversion = 4.0\nskipsdist = true\nenvlist =\n    py3\n    black\n    blacken-docs\n    flake8\n    docs\n    isort\n\n# Add environment to use the default python3 installation\n[testenv:py3]\nbasepython = python3\n\n[testenv]\nusedevelop = true\n# OBJC_DISABLE_INITIALIZE_FORK_SAFETY fixes hung tests for MacOS users. (#30806)\npassenv = DJANGO_SETTINGS_MODULE,PYTHONPATH,HOME,DISPLAY,OBJC_DISABLE_INITIALIZE_FORK_SAFETY\nsetenv =\n    PYTHONDONTWRITEBYTECODE=1\ndeps =\n    -e .\n    py{3,310,311,312,py3}: -rtests/requirements/py3.txt\n    postgres: -rtests/requirements/postgres.txt\n    mysql: -rtests/requirements/mysql.txt\n    oracle: -rtests/requirements/oracle.txt\nchangedir = tests\ncommands =\n    {envpython} runtests.py {posargs}\n\n[testenv:black]\nbasepython = python3\nusedevelop = false\ndeps = black\nchangedir = {toxinidir}\ncommands = black --check --diff .\n\n[testenv:blacken-docs]\nbasepython = python3\nusedevelop = false\nallowlist_externals =\n    make\ndeps = blacken-docs\nchangedir = docs\ncommands =\n    make black\n\n[testenv:flake8]\nbasepython = python3\nusedevelop = false\ndeps = flake8 >= 3.7.0\nchangedir = {toxinidir}\ncommands = flake8 .\n\n[testenv:docs]\nbasepython = python3\nusedevelop = false\nallowlist_externals =\n    make\ndeps =\n    Sphinx\n    pyenchant\n    sphinxcontrib-spelling\nchangedir = docs\ncommands =\n    make spelling\n\n[testenv:isort]\nbasepython = python3\nusedevelop = false\ndeps = isort >= 5.1.0\nchangedir = {toxinidir}\ncommands = isort --check-only --diff django tests scripts\n\n[testenv:javascript]\nusedevelop = false\ndeps =\nchangedir = {toxinidir}\nallowlist_externals =\n    npm\ncommands =\n    npm install\n    npm test\n"
  }
]