commit 9e2009bbf475df379ea494a1d61830931b1a4c90 Author: zhongjin Date: Fri Sep 14 19:26:46 2018 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d17857c --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules +.idea +www +.i18n-editor-metadata +nbproject +src/i18n/*/flat.txt +src/i18n/flat.txt +admin/i18n/*/flat.txt +admin/i18n/flat.txt +admin/words.js +src/js/words.js +package-lock.json +.vscode \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..de6531a --- /dev/null +++ b/.npmignore @@ -0,0 +1,13 @@ +gulpfile.js +package-lock.json +node_modules +.git +.idea +test +docs +.travis.yml +src +.gitignore +DEVELOPER.md +admin/i18n +appveyor.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c3ac8d6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +os: + - linux + - osx +language: node_js +node_js: + - '4' + - '6' + - '8' + - '10' +before_script: + - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) + - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi' + - npm -v + - node node_modules/gulp/bin/gulp default + - npm install winston@2.3.1 + - 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production' + - rm -rf ./node_modules/yunkong2.js-controller/node_modules/yunkong2.admin +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/CHANGELOG_OLD.md b/CHANGELOG_OLD.md new file mode 100644 index 0000000..000176a --- /dev/null +++ b/CHANGELOG_OLD.md @@ -0,0 +1,520 @@ +### 1.6.12 (2017-01-31) +* (bluefox) Show message rates for adapters + +### 1.6.11 (2017-01-21) +* (bluefox) Support of web extensions +* (bluefox) Fix error in expert mode on adapter tab + +### 1.6.9 (2016-12-19) +* (bluefox) Fix problem with the enums editing + +### 1.6.8 (2016-11-26) +* (bluefox) Fix problem with install of instances + +### 1.6.7 (2016-11-12) +* (bluefox) expert mode in adapters: allow install of js-controller from git, allow install specific adapter version +* (bluefox) fix confirm dialog + +### 1.6.6 (2016-11-06) +* (bluefox) update jQuery UI version +* (bluefox) show update hint for controller + +### 1.6.5 (2016-10-31) +* (bluefox) update selectID.js +* (bluefox) better log messages in GUI +* (bluefox) remove js-controller from github install + +### 1.6.4 (2016-10-14) +* (bluefox) Changes for JS adapter + +### 1.6.3 (2016-09-21) +* (bluefox) fix upload of custom installations + +### 1.6.2 (2016-09-12) +* (bluefox) fix hosts leds +* (bluefox) fix error in system settings +* (bluefox) small fixes +* (bluefox) add "upgrade all" button (experimental) + +### 1.6.1 (2016-09-03) +* (bluefox) change hosts tab + +### 1.6.0 (2016-08-30) +* (bluefox) new letsencrypt concept + +### 1.5.3 (2016-08-27) +* (bluefox) Debug outputs for letsencrypt + +### 1.5.2 (2016-08-27) +* (bluefox) use pure letsencrypt module + +### 1.5.1 (2016-08-22) +* (bluefox) fix error in instances table + +### 1.5.0 (2016-08-19) +* (bluefox) add support of Let's Encrypt + +### 1.4.1 (2016-07-30) +* (bluefox) support of multiple WEB instances +* (bluefox) fix weekdays for cron + +### 1.4.0 (2016-07-27) +* (bluefox) implement settings for autorestart +* (bluefox) do not allow edit group instances for javascript + +### 1.3.0 (2016-07-18) +* (bluefox) fix error with early logout +* (bluefox) update passport.socketio +* (bluefox) disable update button if version is incompatible + +### 1.2.7 (2016-07-11) +* (bluefox) support of chained certificates as drag&drop + +### 1.2.6 (2016-07-06) +* (bluefox) support of chained certificates + +### 1.2.5 (2016-07-05) +* (bluefox) install from github + +### 1.2.4 (2016-06-25) +* (bluefox) change color of log entries +* (bluefox) add icon to logout button +* (bluefox) hide reload by WWW only adapters + +### 1.2.3 (2016-06-05) +* (bluefox) fix memory displaying + +### 1.2.2 (2016-05-31) +* (bluefox) fix memory displaying if adapter does not run + +### 1.2.1 (2016-05-28) +* (bluefox) highlight changes of states +* (bluefox) show number of processes and free memory in % + +### 1.2.0 (2016-05-28) +* (bluefox) show RAM utilization +* (bluefox) show change log in admin tab + +### 1.1.1 (2016-05-17) +* (bluefox) fix set of states in States-Tab +* (bluefox) show history data from adapter and not from updates +* (bluefox) change default chart to flot +* (bluefox) fix error if host has no IP address +* (bluefox) show on the bottom only adapters without config and without links +* (bluefox) fix filter in adapters if upper case +* (bluefox) change file open from selectID Dialog + +### 1.1.0 (2016-04-30) +* (bluefox) change seconds to milliseconds by ts and lc + +### 1.0.3 (2016-04-30) +* (bluefox) fix write of state in the objects tab + +### 1.0.2 (2016-04-07) +* (bluefox) show npm errors of version + +### 1.0.1 (2016-03-22) +* (bluefox) show web link button + +### 1.0.0 (2016-03-15) +* (bluefox) adapter is good enough to be released +* (bluefox) fix LEDs +* (bluefox) disable double-click in Objects + +### 0.8.7 (2016-03-15) +* (bluefox) fix LED status + +### 0.8.6 (2016-03-10) +* (bluefox) show quality + +### 0.8.5 (2016-03-09) +* (bluefox) return javascript.x to non-experts view +* (bluefox) show quality of states +* (bluefox) hide experts tabs by new installations +* (installator / vtec83) russian translations + +### 0.8.4 (2016-03-08) +* (bluefox) remove script.js.* from non-expert-view +* (bluefox) fix selectId for javascript tab + +### 0.8.3 (2016-02-29) +* (bluefox) fix delete of objects tree + +### 0.8.2 (2016-02-29) +* (bluefox) disable start button if in process + +### 0.8.1 (2016-02-27) +* (bluefox) expert mode +* (bluefox) new instances page +* (bluefox) edit objects directly on the page and not in the dialog + +### 0.8.0 (2016-02-18) +* (bluefox) move enums into own file +* (bluefox) modify selectID.js to support new javascript layout +* (bluefox) add new variables: admin.x.info.updatesNumber and admin.x.info.updatesList to show available updates + +### 0.7.5 (2016-02-11) +* (bluefox) support of text2command +* (bluefox) support of noConfig flag +* (bluefox) fix tabs +* (bluefox) add support of adminTab.ignoreConfigUpdate + +### 0.7.4 (2016-01-28) +* (bluefox) pause button for logs and events + +### 0.7.3 (2016-01-21) +* (bluefox) fix groups dialog +* (bluefox) allow set max memory limit for adapters + +### 0.7.2 (2015-12-18) +* (bluefox) translate "clear"-event button + +### 0.7.1 (2015-12-14) +* (husky-koglhof) added support for up-/download of objecttrees +* (bluefox) disable chart if dialog closed. +* (bluefox) store selected history type in history dialog +* (bluefox) fix selectId dialog +* (bluefox) fix graph +* (bluefox) add title to index.html +* (bluefox) change theme +* (bluefox) fix buttons + +### 0.7.0 (2015-11-15) +* (bluefox) support of multi history + +### 0.6.6 (2015-11-02) +* (bluefox) add support of certificates (-----BEGIN PRIVATE KEY-----) + +### 0.6.5 (2015-10-31) +* (bluefox) maginfy icon by mouseover + +### 0.6.4 (2015-10-27) +* (bluefox) fix write of enums +* (bluefox) fix buttons in instance tab + +### 0.6.3 (2015-10-22) +* (bluefox) fix delete of adapter + +### 0.6.2 (2015-10-18) +* (bluefox) add confirmation by instance deletion + +### 0.6.1 (2015-10-12) +* (bluefox) fix columns resizing in adapters + +### 0.6.0 (2015-10-07) +* (bluefox) enable table resizing +* (bluefox) enable auto update of repositories +* (bluefox) implement certificate upload per drug and drop and file selector + +### 0.5.13 (2015-09-26) +* (bluefox) add to ace editor the javascript option + +### 0.5.12 (2015-09-26) +* (bluefox) update ace aditor for json + +### 0.5.11 (2015-09-15) +* (bluefox) create state of object after attributes editing +* (bluefox) remove common.type=='enum'. It must be a number +* (bluefox) show "level.time" as Time +* (bluefox) fixed: Reiter Objekte: "common.type" verschwindet / Name wird verkurzt +* (bluefox) fix Multistate-Attribute +* (homoran) Update adminAdapters.js + +### 0.5.10 (2015-09-13) +* (bluefox) change "add new object" behaviour +* (bluefox) add "install from custom URL" button (required new js-controller >= 0.7.12) +* (bluefox) add service.png +* (bluefox) show hostst in green if updates available + +### 0.5.9 (2015-08-26) +* (bluefox) add button "create object" +* (bluefox) Ubersetzungen +* (bluefox) add service group (for terminal) + +### 0.5.8 (2015-08-18) +* (bluefox) update select ID dialog +* (bluefox) copy to clipboard functionality + +### 0.5.7 (2015-08-11) +* (bluefox) try to fix log columns +* (bluefox) show boolean in States as enumerations. +* (bluefox) implement upload indicator +* (bluefox) update packages + +### 0.5.6 (2015-08-05) +* (bluefox) fix translate.js +* (bluefox) support of multilanguage for tabs +* (bluefox) improve selectID.js +* (bluefox) store settings of selectId dialog + +### 0.5.5 (2015-07-29) +* (bluefox) update packages + +### 0.5.4 (2015-07-01) +* (bluefox) fix error in "create new group" + +### 0.5.3 (2015-06-29) +* (bluefox) enable select objects in javascript + +### 0.5.2 (2015-06-29) +* (bluefox) fix delete objects +* (bluefox) fix vis group + +### 0.5.1 (2015-06-28) +* (bluefox) support of permissions +* (bluefox) confirm deleting of scripts +* (bluefox) fix license agreement for adapters +* (SmilingJack) fix scroll by adapter config +* (bluefox) support of https link in instances +* (bluefox) fix buttons after sort in jqGrid table +* (siedi)implement multiselect for selectID tree +* (bluefox) better edit object in raw mode. +* (bluefox) adjustable tabs + +### 0.5.0 (2015-06-12) +* (bluefox) support of permissions + +### 0.4.8 (2015-05-17) +* (bluefox) fix buttons after sort in jqGrid table + +### 0.4.7 (2015-05-13) +* (bluefox) fix license agreement for adapters +* (SmilingJack) fix scroll by adapter config + +### 0.4.6 (2015-05-01) +* (bluefox) confirm deleting of scripts + +### 0.4.5 (2015-04-24) +* (SmilingJack) update jquery ui to 1.11.4 +* (bluefox) remove unused libs +* (bluefox) set missing categories + +### 0.4.4 (2015-04-19) +* (bluefox) fix error with hm-rega instance + +### 0.4.3 (2015-04-19) +* (bluefox) fix error with select ID dialog in edit script +* (bluefox) fix group of installed adapter +* (bluefox) show statistics over installed adapters +* (bluefox) add "agree with statistics" checkbox + +### 0.4.2 (2015-04-17) +* (bluefox) workaround for license text + +### 0.4.1 (2015-04-17) +* (bluefox) fix click on buttons on adapter tab + +### 0.4.0 (2015-04-16) +* (bluefox) use tree for adapters +* (bluefox) implement license agreement for adapters + +### 0.3.27 (2015-04-14) +* (bluefox) save size of script editor dialog +* (bluefox) fix errors with table editor in adapter configuration +* (bluefox) update npm modules + +### 0.3.26 (2015-03-27) +* (bluefox) change save function for adapter settings +* (bluefox) fix show states in object tab + +### 0.3.23 (2015-03-22) +* (bluefox) fix error with show values in objects TAB +* (bluefox) move objects tab code into adminObjects.js + +### 0.3.22 (2015-03-20) +* (bluefox) move states to extra file +* (bluefox) speed up rendering of states +* (bluefox) store some filter settings (not all yet) +* (bluefox) support of width and height settings for configuration dialog of adapter instance +* (bluefox) enable read and upload of files (for sayit) + +### 0.3.21 (2015-03-08) +* (bluefox) fix filter in log + +### 0.3.20 (2015-03-07) +* (bluefox) support of uncolored log messages +* (bluefox) place logs in own file + +### 0.3.19 (2015-03-04) +* (bluefox) fix some errors with restart + +### 0.3.18 (2015-02-22) +* (bluefox) fix error with delete button for adapters + +### 0.3.17 (2015-02-22) +* (bluefox) fix error with refresh button for adapters (again) + +### 0.3.16 (2015-02-21) +* (bluefox) fix error with refresh button for adapters + +### 0.3.15 (2015-01-26) +* (bluefox) extend table editor in adapter settings +* (bluefox) fix error in instances. + +### 0.3.14 (2015-01-26) +* (bluefox) fix error with adapter instances with more modes (again) + +### 0.3.13 (2015-01-21) +* (bluefox) add selection of certificates to settings of admin +* (bluefox) make showMessage dialog + +### 0.3.12 (2015-01-20) +* (bluefox) add selection of certificates to settings of admin +* (bluefox) make showMessage dialog + +### 0.3.11 (2015-01-16) +* (bluefox) fix npm + +### 0.3.10 (2015-01-14) +* (bluefox) fix error with adapter instances with more modes + +### 0.3.9 (2015-01-10) +* (bluefox) support of multiple hosts if one host is down. + +### 0.3.8 (2015-01-08) +* (bluefox) fix errors with states update if filtered. Resize command putput window. + +### 0.3.7 (2015-01-07) +* (bluefox) fix errors with history state update. + +### 0.3.6 (2015-01-07) +* (bluefox) group edit of history settings. Move history settings from states to objects. + +### 0.3.5 (2015-01-06) +* (bluefox) add events filter. Fix error with alive and connected status. + +### 0.3.4 (2015-01-04) +* (bluefox) fix error with update adapters with "-" in name, like hm-rpc or hm-rega + +### 0.3.3 (2015-01-03) +* (bluefox) fix error if states without object + +### 0.3.2 (2015-01-02) +* (bluefox) fix error if states without object + +### 0.3.1 (2015-01-02) +* (bluefox) Support of npm install + +### 0.3.0 (2014-12-25) +* (bluefox) Support of debounce interval for history + +### 0.2.9 (2014-12-20) +* (bluefox) fix filter of IDs in objects + +### 0.2.8 (2014-12-20) +* (bluefox) support of controller restart + +### 0.2.7 (2014-12-19) +* (bluefox) fix time in log (web) +* (bluefox) replace enum edit with tree + +### 0.2.6 (2014-12-16) +* (bluefox) replace jqGrid with fancytree by objects + +### 0.2.5 (2014-12-07) +* (bluefox) fix object tree (some nodes was hidden) + +### 0.2.4 (2014-12-05) +* (bluefox) preload last 200 lines from yunkong2.log + +### 0.2.3 (2014-12-04) +* (bluefox) install adapter with npm + +### 0.2.2 (2014-11-29) +* (bluefox) Set language settings after license confirmed +* (bluefox) try to use npm installer for this adapter + +### 0.2.1 (2014-11-26) +* (bluefox) Charts in history dialog +* (bluefox) filter states by history +* (bluefox) show only 500 events + +### 0.2.0 (2014-11-20) +* (bluefox) support of no-"io." schema +* (bluefox) better enum editing +* (bluefox) update of object tree online + +### 0.1.9 (2014-11-15) +* (bluefox) fix scripts editor + +### 0.1.8 (2014-11-10) +* (bluefox) fix problem if js-controller does not hav the most actual version + +### 0.1.7 (2014-11-09) +* (bluefox) add log pane + +### 0.1.6 (2014-11-07) +* (bluefox) fix edit list in configuration + +### 0.1.5 (2014-11-03) +* (bluefox) support of tables in edit configuration + +### 0.1.4 (2014-11-01) +* (bluefox) update history dialog live (add new values on the fly to history table) + +### 0.1.3 (2014-11-01) +* (bluefox) add link to web service of adapter instance + +### 0.1.2 (2014-11-01) +* (bluefox) add functions to edit lists in adapter config + +### 0.1.1 (2014-10-30) +* (bluefox) support of sendToHost command for adapter config. + +### 0.1.0 (2014-10-29) +* (bluefox) update states if some adapter added or deleted. Update states if history enabled or disabled. + +### 0.0.19 (2014-10-24) +* (bluefox) fix error with repository edition + +### 0.0.18 (2014-10-20) +* (bluefox) fix error with "up to date" + +### 0.0.17 (2014-10-19) +* (bluefox) fix delete of adapter + +### 0.0.16 (2014-10-19) +* (bluefox) support of certificate list + +### 0.0.15 (2014-10-09) +* (bluefox) make possible availableModes for adapter +* (bluefox) add auto changelog +* (bluefox) improve Grunt +* (bluefox) by default enabled. + +### 0.0.14 +* (bluefox) add repositories editor + +### 0.0.13 +* (hobbyquaker) gridAdapter style +* (hobbyquaker) moved system settings to dialog + +### 0.0.12 +* (bluefox) new concept of updates/upgrades + +### 0.0.11 +* (hobbyquaker) bugfix - slashes in IDs +* (hobbyquaker) bugfix - gridHistory + +### 0.0.10 +* (hobbyquaker) more options in dialogHistory +* (hobbyquaker) add enums +* (hobbyquaker) hide not-implemented buttons (add/del object f.e.) +* (hobbyquaker) prepared tab log +* (hobbyquaker) gridAdapters: colors for release state (red = planned, orange = alpha, yellow = beta, green = stable) + +### 0.0.9 +* (hobbyquaker) history + +### 0.0.8 +* (hobbyquaker) added column "parent name" to gridStates + +### 0.0.7 +* (hobbyquaker) prepared enum members +* (hobbyquaker) hide logout button if auth disabled +* (hobbyquaker) refactoring +* (hobbyquaker) fixes + +### 0.0.5 +* (hobbyquaker) show available version, show update button if not up-to-date +* (hobbyquaker) minor fixes diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 0000000..172094b --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,53 @@ +# How to show help information in configuration dialog + +Help can be a combination of tooltip, link and text. + +``` + + + +
+``` + +id will be found automatically in the previous td. + +``` + + +
+``` + +id set with data-id. + +``` + + +
+``` + +Link will be generated automatically from common.readme and '#param1'. +e.g. if + +```common.readme = https://github.com/yunkong2/yunkong2.admin/blob/master/README.md``` + +link will be ```https://github.com/yunkong2/yunkong2.admin/blob/master/README.md#param1``` + + +``` + + +
+``` +link will be ```https://github.com/yunkong2/yunkong2.admin/blob/master/README.md#my-param-description``` + +``` + + +
+``` +link will taken from data-link. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..094c6fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2018 bluefox + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c0cc90 --- /dev/null +++ b/README.md @@ -0,0 +1,327 @@ +![Logo](admin/admin.png) +# yunkong2.admin +=================== + +[![NPM version](http://img.shields.io/npm/v/yunkong2.admin.svg)](https://www.npmjs.com/package/yunkong2.admin) +[![Downloads](https://img.shields.io/npm/dm/yunkong2.admin.svg)](https://www.npmjs.com/package/yunkong2.admin) + +[![NPM](https://nodei.co/npm/yunkong2.admin.png?downloads=true)](https://nodei.co/npm/yunkong2.admin/) + + +User interface for configuration and administration. + +## Using common.localLink + +- %ip% - yunkong2 ip address (address of the admin) +- %secure% or %protocol% - read from native.secure the value and use http or https +- %web_protocol% - looking for the first instance of web (e.g. web.0) and get "native.secure" from "system.adapter.web.0" +- %instance% - instance of the adapter +- %someField% - get someField from "native" of this adapter instance +- %web.0_bind% - get native.bind from "system.adapter.web.0" +- %native_someField% - get someField from "native" of this adapter instance + +## Scheduled restart +Some adapters re not stable or connection disappear after one or two days. +To fix this there is a scheduled restart setting. +To activate scheduled restart just define CRON condition when to restart adapter. + +It is suggested to restart in the night, when no one use the adapter, e.g. "0 3 * * *" - at 3:00 every day. + +## Let's Encrypt Certificates +Let’s Encrypt is a free, automated, and open certificate authority brought to you by the non-profit Internet Security Research Group (ISRG). + +You can read about Let’s Encrypt [here](https://letsencrypt.org/). + +Some installations use Dynamic DNS and Co to get the domain name and to reach under this domain name own web sites. +yunkong2 supports automatic request and renew of certificates from Let’s Encrypt Organisation. + +There is an option to activate free certificates from Let’s Encrypt almost in every adapter, that can start some web server and supports HTTPS. + +If you just enable the using of certificates and will not activate an automatic update the instance will try to use stored certificates. + +If the automatic update is activated the instance will try to request certificates from Let’s Encrypt and will automatically update it. + +The certificates will be first requested when the given domain address will be accessed. E.g you have "sub.domain.com" as address, when you try to access https://sub.domain.com the certificates will be first requested and it can last a little before first answer will come. + +The issuing of certificates is rather complex procedure, but if you will follow the explanation you will easy get free certificates. + +Description: + +1. The new account will be created with given email address (you must set it up in system settings) +2. Some random key will be created as password for the account. +3. After the account is created the system starts on port 80 the small web site to confirm the domain. +4. Let's encrypt use **always** port **80** to check the domain. +5. If port 80 is occupied by other service see point 4. +6. After the small web server is up the request to get certificates for given domains (system settings) will be sent to the Let's encrypt server. +7. Let's encrypt server sends back some challenge phrase as answer on the request and after a while tries to read this challenge phrase on "http://yourdomain:80/.well-known/acme-challenge/" +8. If challenge phrase from our side comes back the Let's encrypt server send us the certificates. They will be stored in the given directory (system settings). + +Sounds complex, but everything what you must do is to activate checkboxes and specify your email and domain in system settings. + +The received certificates are valid ca. 90 days. +After the certificates are received the special task will be started to automatically renew the certificates. + +The topic is rather complex and 1000 things can go wrong. If you cannot get certificates please use cloud service to reach your installation from internet. + +**Let's encrypt works only from node.js version>=4.5** + +## Todo +- move html tooltips to materialize tooltips +- tiles for hosts (additionally to table - low prior) +- tiles for instances (additionally to table - low prior) + +## Used icons +This project uses some icons from [Flaticon](https://www.flaticon.com/): +- - designed by [smalllikeart](https://www.flaticon.com/authors/smalllikeart) from [Flaticon](https://www.flaticon.com/) +- - designed by [smalllikeart](https://www.flaticon.com/authors/smalllikeart) from [Flaticon](https://www.flaticon.com/) +- - designed by [smalllikeart](https://www.flaticon.com/authors/smalllikeart) from [Flaticon](https://www.flaticon.com/) +- - Icons made by [Vectors Market](https://www.flaticon.com/authors/vectors-market) from [Flaticon](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/). +- - designed by [Pause08](https://www.flaticon.com/authors/Pause08) from [Flaticon](https://www.flaticon.com/) +- - Icons made by [Freepik](http://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/) + +## Changelog +### 3.5.9 (2018-12-09) +* (bluefox) The log output problem was fixed + +### 3.5.8 (2018-09-03) +* (bluefox) Google map was replaces with "open street map" + +### 3.5.7 (2018-08-30) +* (bluefox) Edit of the table entries in configuration dialog was corrected. + +### 3.5.6 (2018-08-22) +* (bluefox) Import and export of the instance configuration was implemented. + +### 3.5.5 (2018-08-21) +* (bluefox) Fix upload of files + +### 3.5.3 (2018-08-18) +* (bluefox) Dropdown was fixed on touch devices +* (bluefox) Speedup build of instances + +### 3.5.1 (2018-08-11) +* (bluefox) Error in custom settings was fixed + +### 3.5.0 (2018-08-03) +* (bluefox) Editing of enums was changed +* (bluefox) Logo was updated +* (bluefox) The function icons were added + +### 3.4.9 (2018-07-17) +* (bluefox) Support of the custom login screen background +* (bluefox) show tooltip about refresh on instances page +* (bluefox) Destroy tabs after they left + +### 3.4.8 (2018-07-17) +* (bluefox) fix error with add new enum +* (bluefox) try to fix error with custom settings +* (bluefox) place all titles at the top in the config +* (bluefox) add expert mode to common +* (bluefox) allow edit of enum's names in many languages + +### 3.4.7 (2018-06-25) +* (bluefox) add getInterfaces function +* (bluefox) save scroll position for some tables +* (bluefox) add info about "filtered out" + +### 3.4.6 (2018-06-18) +* (bluefox) Minor GUI fixes + +### 3.4.5 (2018-06-12) +* (bluefox) Minor GUI fixes + +### 3.4.4 (2018-06-04) +* (bluefox) add touch support for draggable and droppable +* (bluefox) edit raw value and not escaped in selectID.js +* (bluefox) allow edit of empty names in selectID.less +* (bluefox) add change with ack=true to selectID +* (bluefox) fix select for admin3 in configuration dialog +* (bluefox) add autocomplete for configs +* (bluefox) fix enums + +### 3.4.3 (2018-05-13) +* (bluefox) The button in selectID was fixed +* (bluefox) disk info was added +* (bluefox) The filter in table mode on adapter tab was showed +* (bluefox) memAvailable for RAM monitoring is used +* (bluefox) fix select problem in config dialog +* (bluefox) added the asking about unsaved scripts + +### 3.4.2 (2018-05-04) +* (BuZZy1337) fix wrong height calculation in select id dialog + +### 3.4.1 (2018-05-03) +* (bluefox) fix wait popup +* (bluefox) fix button name in config dialog +* (BuZZy1337) escape html from log entries +* (bluefox) fix objects counter +* (BuZZy1337) show current Tab in Page-Title +* (BuZZy1337) escape HTML Tags from selectID.js +* (bluefox) GUI bugfixes +* (BuZZy1337) Fix: Unable to scroll trough Dropdown on Touchscreens +* (BuZZy1337) Enhancement: Show current Tab in Pagetitle + +### 3.4.0 (2018-04-23) +* (bluefox) show error about not activated admin for cloud +* (bluefox) handle mutlilanguage names +* (bluefox) show number of objects +* (BuZZy1337) always addChips when input blurs +* (bluefox) fix select ID dialog for old styles +* (bluefox) add states view for object tab + +### 3.3.9 (2018-04-12) +* (bluefox) The user and groups deletion was corrected +* (bluefox) Force using of socket.io 2.1.0 + +### 3.3.8 (2018-04-10) +* (bluefox) Hosts selection is improved + +### 3.3.7 (2018-04-10) +* (bluefox) small UI corrections + +### 3.3.5 (2018-03-25) +* (bondrogeen) info for server redesigned +* (bondrogeen) hosts list redesigned +* (bluefox) small UI corrections + +### 3.3.4 (2018-03-17) +* (bluefox) small UI corrections + +### 3.3.3 (2018-03-15) +* (bluefox) small UI corrections + +### 3.3.1 (2018-03-11) +* (bluefox) Corrections for scenes +* (bluefox) move from socket.io 2.0.4 to 1.5.1 because of bug +* (bluefox) small fix for hosts + +### 3.3.0 (2018-03-10) +* (bluefox) Overview page was added +* (bluefox) Many bugs were fixed + +### 3.2.4 (2018-03-04) +* (bluefox) Adjust layout on mobile devices + +### 3.2.1 (2018-03-03) +* (bluefox) Many UI fixes + +### 3.2.0 (2018-02-09) +* (bluefox) The select ID dialog was fixed + +### 3.1.12 (2018-02-05) +* (bondrogeen) Configuration dialog updated +* (bondrogeen) Open menu button is fixed + +### 3.1.11 (2018-02-04) +* (bluefox) Connection LED fixed + +### 3.1.10 (2018-02-02) +* (bluefox) update material CSS +* (bluefox) fix permission error +* (bluefox) fix filter of adapters + +### 3.1.7 (2018-01-31) +* (bluefox) Fixing the role selection +* (bluefox) It runs even in IE10 + +### 3.1.6 (2018-01-30) +* (bluefox) Fixes for Firefox and MS-EDGE + +### 3.1.2 (2018-01-25) +* (bluefox) GUI corrections + +### 3.0.12 (2018-01-19) +* (bluefox) Old configuration dialogs fixed +* (bluefox) convert strings to booleans by object edit +* (DeepCoreSystem) Updates in english, german and french translations +* (bluefox) buttons layout fixed +* (bluefox) event fixes + +### 3.0.11 (2018-01-11) +* (DeepCoreSystem) French update +* (bluefox) fix error with empty ID +* (bluefox) add sort by "recently updated" +* (ldittmar) add readme and issues viewer + +### 3.0.10 (2018-01-06) +* (bluefox) Update indication +* (ldittmar) Use jQuery3 +* (AlCalzone) German translations + +### 3.0.7 (2018-01-01) +* (soef) update instances, objects and other lists +* (bluefox) rewrite interface with materialize + +### 2.0.11 (2017-10-23) +* (bluefox) Configurable event update disable threshold + +### 2.0.10 (2017-10-22) +* (soef) added use of delete-key in the objects view + +### 2.0.8 (2017-10-12) +* (soef) fix quickEdit: number with boolean value + +### 2.0.7 (2017-10-11) +* (soef) Sort option added to object view + +### 2.0.5 (2017-10-06) +* (bluefox) Show the history charts if the web server has the https option on too + +### 2.0.3 (2017-08-13) +* (bluefox) Fix user access rights for sendToHost + +### 2.0.2 (2017-08-12) +* (bluefox) Add the editing of the default access rights + +### 2.0.1 (2017-08-07) +* (bluefox) Allow access via yunkong2.pro +* (bluefox) Add node.js version recommendation + +### 1.8.3 (2017-07-24) +* (bluefox) allow access on tmp directory + +### 1.8.0 (2017-06-02) +* (bluefox) split into modules + +### 1.7.6 (2017-06-01) +* (bluefox) Fix edit of the enum name + +### 1.7.5 (2017-05-20) +* (bluefox) catch error if translated object is not text +* (bluefox) update selectID.js +* (bluefox) do not open configuration dialog for instances with no config +* (Steiger04) select multiple auch bei data-name="[eigner-name]" + +### 1.7.3 (2017-03-25) +* (bluefox) fix license dialog +* (bluefox) change color of tooltip text +* (ykuendig) update german translation +* (bluefox) add docs + +### 1.7.2 (2017-03-15) +* (bluefox) add statistics selector for no-city +* (bluefox) support of discovery by first start + +### 1.7.1 (2017-03-11) +* (apollon77) fix save button functionality +* (ykuendig) Update german translations +* (bluefox) patch repositories to support stable + +### 1.7.0 (2017-03-08) +* (bluefox) fix log +* (bluefox) show jQuery button for role button +* (apollon77) update testing setup.js +* (bluefox) fix wetty loading +* (bluefox) fix add/delete tabs +* (bluefox) implement hints for configuration dialog +* (bluefox) redirect if IP address changes +* (bluefox) add tooltip instruction +* (bluefox) wizard support +* (bluefox) fix acl error +* (bluefox) fix license agree button + +## License + +The MIT License (MIT) + +Copyright (c) 2014-2018 bluefox diff --git a/admin/admin.png b/admin/admin.png new file mode 100644 index 0000000..e3c0984 Binary files /dev/null and b/admin/admin.png differ diff --git a/admin/i18n/de/translations.json b/admin/i18n/de/translations.json new file mode 100644 index 0000000..09ae956 --- /dev/null +++ b/admin/i18n/de/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Die Einstellungen für den Administratoradapter", + "Authentication was deactivated": "Die Authentifizierung wurde deaktiviert", + "Authentication:": "Authentifikation", + "Auto update:": "Auf Updates prüfen", + "Background": "Hintergrund", + "Background color of the login screen": "Hintergrundfarbe des Anmeldebildschirms", + "Background image": "Hintergrundbild", + "Cache:": "Puffer", + "Chained certificate:": "Verkettetes Zertifikat", + "Disable authentication": "Authentifizierung deaktivieren", + "Enabled:": "Aktiviert", + "Events threshold value:": "Ereignisschwellenwert", + "Hide logo": "Logo ausblenden", + "IP:": "IP", + "Ignore warning": "Warnung ignorieren", + "Let's Encrypt settings": "Let's Encrypt Einstellungen", + "Listen on all IPs": "Auf allen IP Adressen hören", + "Login timeout(sec):": "Login Timeout(Sek)", + "Own motto": "Eigenes Motto", + "Port to check the domain:": "Port um die Domain zu prüfen", + "Port:": "Port", + "Private certificate:": "Privates Zertifikat", + "Public certificate:": "Öffentliches Zertifikat", + "Run as:": "Ausführen als", + "Secure(HTTPS):": "Verschlüsselung (HTTPS) benutzen", + "Set certificates or load it first in the system settings (right top).": "Setze Zertifikate oder installiere diese erst in den Systemeinstellungen (oben rechts).", + "Unsecure_Auth": "Das Passwort wird über unsichere Verbindung gesendet. Um Ihre Passwörter zu schützen, aktivieren Sie die sichere Verbindung (HTTPS)!", + "Use Lets Encrypt certificates:": "Let's Encrypt Zertifikate benutzen", + "Use this instance for automatic update:": "Benutze diese Instanz für automatische Updates", + "Warning!": "Warnung!", + "every 12 hours": "alle 12 Stunden", + "every 2 days": "alle 2 Tage", + "every 2 weeks": "alle 2 Wochen", + "every 3 days": "alle 3 Tage", + "every day": "jeden Tag", + "every week": "jede Woche", + "manually": "manuell", + "monthly": "monatlich", + "place here": "Platziere die Dateien hier", + "tooltip_auth": "Prüfe Login und Kennwort beim Öffnen. Man kann die Anwender im Benutzer-Reiter anlegen", + "tooltip_autoUpdate": "Prüfe regelmässig auf Adapter-Updates", + "tooltip_bind": "IP Addresse, die vom Adminadapter benutzt wird", + "tooltip_cache": "Aktiviere den Browser-Cache.", + "tooltip_certChained": "Verkettetes Zertifikat. Bitte zuerst im Systemeinstellungs-Dialog konfigurieren.", + "tooltip_certPrivate": "Privates Zertifikat. Bitte zuerst im Systemeinstellungs-Dialog konfigurieren.", + "tooltip_certPublic": "Öffentliches Zertifikat. Bitte zuerst im Systemeinstellungs-Dialog konfigurieren.", + "tooltip_defaultUser": "Diesen Anwender für das automatische Login benutzen.", + "tooltip_leEnabled": "Benutze Let's Encrypt Zertifikate", + "tooltip_lePort": "Port für Challenge-Server. Let's Encrypt kann nur auf dem Port 80 arbeiten. Bei bestimmten Einstellungen im Router, man kann den Server auf einem beliebigen Port betreiben.", + "tooltip_leUpdate": "Benutze diese Instanz um die Let's Encrypt Zertifikate alle 3 Monate upzudaten.", + "tooltip_port": "Port für die Admin-Seite. Der Port muss frei sein. Unter Linux dürfen nicht root-Anwender nur die Ports über 1000 benutzen.", + "tooltip_secure": "SSL Verschlüsselung aktivieren. Admin-Oberfläche wird unter https:// und nicht mehr unter http:// erreichbar.", + "tooltip_thresholdValue": "Wenn der Browser in 3 Sekunden zu viele Ereignisse empfängt, wird das Update beendet", + "tooltip_ttl": "Nach diese Zeit wird ein inaktiver Benutzer ausgeloggt.", + "users permissions": "Anwenderrechte" +} diff --git a/admin/i18n/en/translations.json b/admin/i18n/en/translations.json new file mode 100644 index 0000000..5c2ca11 --- /dev/null +++ b/admin/i18n/en/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "The settings for the admin adapter", + "Authentication was deactivated": "Authentication was deactivated", + "Authentication:": "Authentication", + "Auto update:": "Check for updates", + "Background": "Background", + "Background color of the login screen": "Background color of the login screen", + "Background image": "Background image", + "Cache:": "Cache", + "Chained certificate:": "Chained certificate", + "Disable authentication": "Disable authentication", + "Enabled:": "Enabled", + "Events threshold value:": "Events threshold value", + "Hide logo": "Hide logo", + "IP:": "IP", + "Ignore warning": "Ignore warning", + "Let's Encrypt settings": "Let's Encrypt settings", + "Listen on all IPs": "Listen on all IPs", + "Login timeout(sec):": "Login timeout(sec)", + "Own motto": "Own motto", + "Port to check the domain:": "Port to check the domain", + "Port:": "Port", + "Private certificate:": "Private certificate", + "Public certificate:": "Public certificate", + "Run as:": "Run as", + "Secure(HTTPS):": "Secure(HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Set certificates or load it first in the system settings (right top).", + "Unsecure_Auth": "The password will be sent via unsecure connection. To protect your passwords enable the secure connection (HTTPS)!", + "Use Lets Encrypt certificates:": "Use Let's Encrypt certificates", + "Use this instance for automatic update:": "Use this instance for automatic update", + "Warning!": "Warning!", + "every 12 hours": "every 12 hours", + "every 2 days": "every 2 days", + "every 2 weeks": "every 2 weeks", + "every 3 days": "every 3 days", + "every day": "every day", + "every week": "every week", + "manually": "manually", + "monthly": "monthly", + "place here": "place the files here", + "tooltip_auth": "Require a login and a password to access the admin page. This can be managed on the users tab.", + "tooltip_autoUpdate": "Check periodically for updates of adapter", + "tooltip_bind": "IP address where the admin will listen on", + "tooltip_cache": "Browser cache enabled.", + "tooltip_certChained": "Chained certificate. Root certificate + Public certificate. Please manage it in the system configs.", + "tooltip_certPrivate": "Private certificate. Please manage it in the system configs.", + "tooltip_certPublic": "Public certificate. Please manage it in the system configs.", + "tooltip_defaultUser": "Auto-login with this user", + "tooltip_leEnabled": "Use Let's Encrypt certificates", + "tooltip_lePort": "Port for challenge server. Let's Encrypt supports only port 80, but with corresponding settings in your router, any port can be used", + "tooltip_leUpdate": "This instance will update Let's Encrypt certificates every 3 months", + "tooltip_port": "Port for admin interface. It must be available. If running on linux with no root you cannot use ports below 1000.", + "tooltip_secure": "Enable SSL protocol. Admin will be available via https:// and not http://", + "tooltip_thresholdValue": "If the browser receives too many events in 3 seconds, refresh will be stopped", + "tooltip_ttl": "Login timeout. After this time the user will be automatically logged off.", + "users permissions": "Users permissions" +} diff --git a/admin/i18n/es/translations.json b/admin/i18n/es/translations.json new file mode 100644 index 0000000..a33c4fe --- /dev/null +++ b/admin/i18n/es/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Le impostazioni per l'adattatore di amministrazione", + "Authentication was deactivated": "La autenticación fue desactivada", + "Authentication:": "autenticazione", + "Auto update:": "Ver actualizaciones", + "Background": "Fondo", + "Background color of the login screen": "Color de fondo de la pantalla de inicio de sesión", + "Background image": "Imagen de fondo", + "Cache:": "Buffer", + "Chained certificate:": "Certificado encadenado", + "Disable authentication": "Deshabilitar autenticación", + "Enabled:": "Habilitado", + "Events threshold value:": "Valor de umbral de eventos", + "Hide logo": "Ocultar logo", + "IP:": "IP", + "Ignore warning": "Ignorar advertencia", + "Let's Encrypt settings": "Configuración de Let's Encrypt", + "Listen on all IPs": "Escuchar en todas las direcciones IP", + "Login timeout(sec):": "Tiempo de espera de inicio de sesión (seg)", + "Own motto": "Propio lema", + "Port to check the domain:": "Puerto para comprobar el dominio", + "Port:": "Porta", + "Private certificate:": "Certificado privado", + "Public certificate:": "Certificado público", + "Run as:": "Ejecutar como", + "Secure(HTTPS):": "Codificación (HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Establezca certificados o cargue primero la configuración del sistema (parte superior derecha).", + "Unsecure_Auth": "La contraseña se enviará a través de una conexión no segura. Para proteger sus contraseñas, ¡habilite la conexión segura (HTTPS)!", + "Use Lets Encrypt certificates:": "Usa certificados Let's Encrypt", + "Use this instance for automatic update:": "Utilice esta instancia para la actualización automática", + "Warning!": "¡Advertencia!", + "every 12 hours": "cada 12 horas", + "every 2 days": "cada 2 días", + "every 2 weeks": "cada 2 semanas", + "every 3 days": "cada 3 días", + "every day": "todos los días", + "every week": "todas las semanas", + "manually": "a mano", + "monthly": "mensual", + "place here": "coloca los archivos aquí", + "tooltip_auth": "Compruebe el nombre de usuario y la contraseña para acceder a la página de administración. Puede configurarlo en la página de los usuarios.", + "tooltip_autoUpdate": "Compruebe las actualizaciones del adaptador con regularidad", + "tooltip_bind": "Dirección IP, donde se ejecutará el administrador", + "tooltip_cache": "Buffer del navegador activado.", + "tooltip_certChained": "Certificado encadenado. Certificado de root + Certificado público. Configurarlo en la configuración del sistema.", + "tooltip_certPrivate": "Certificado privado. Configurarlo en la configuración del sistema.", + "tooltip_certPublic": "Certificado público. Configurarlo en la configuración del sistema.", + "tooltip_defaultUser": "Auto-login con este usuario", + "tooltip_leEnabled": "Utilice los certificados Let's Encrypt", + "tooltip_lePort": "Puerto para Challenge-Server. Let's Encrypt sólo soporta el puerto 80, pero con el router puede utilizar cualquier puerto.", + "tooltip_leUpdate": "Esta instancia actualizará Let's Encrypt certificados cada 3 meses", + "tooltip_port": "Puerto para la interfaz de administración. Tiene que estar disponible. Si se ejecuta en linux sin root, no puede utilizar puertos por debajo de 1000.", + "tooltip_secure": "Habilite el protocolo SSL. El administrador estará disponible a través de https: // y no http: //", + "tooltip_thresholdValue": "Si el explorador recibe muchos eventos en 3 segundos, se detiene la actualización", + "tooltip_ttl": "Tiempo de espera de inicio de sesión. Después de ese tiempo, el usuario se desconectar automáticamente.", + "users permissions": "permisos de usuario" +} diff --git a/admin/i18n/fr/translations.json b/admin/i18n/fr/translations.json new file mode 100644 index 0000000..6a0acf1 --- /dev/null +++ b/admin/i18n/fr/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Configuration de l'adaptateur d'administration", + "Authentication was deactivated": "L'authentification a été désactivée", + "Authentication:": "Authentification", + "Auto update:": "Vérifier les mises à jour", + "Background": "Contexte", + "Background color of the login screen": "Couleur d'arrière-plan de l'écran de connexion", + "Background image": "Image de fond", + "Cache:": "Cache", + "Chained certificate:": "Certificat chaîné", + "Disable authentication": "Désactiver l'authentification", + "Enabled:": "Activée", + "Events threshold value:": "Valeur seuil pour les événements", + "Hide logo": "Masquer le logo", + "IP:": "IP", + "Ignore warning": "Ignorer l'avertissement", + "Let's Encrypt settings": "Paramètres Let's Encrypt", + "Listen on all IPs": "Écouter sur toutes les adresses IP", + "Login timeout(sec):": "Délai d'attente de connexion (s)", + "Own motto": "Propre devise", + "Port to check the domain:": "Port pour vérifier le domaine", + "Port:": "Port", + "Private certificate:": "Certificat privé", + "Public certificate:": "Certificat public", + "Run as:": "Executer en tant que", + "Secure(HTTPS):": "Connexion sécurisée (HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Définissez des certificats ou chargez-les d'abord dans les paramètres du système (en haut à droite).", + "Unsecure_Auth": "Le mot de passe sera envoyé via une connexion non sécurisée. Pour protéger vos mots de passe, activez la connexion sécurisée (HTTPS)!", + "Use Lets Encrypt certificates:": "Utiliser les certificats Let's Encrypt", + "Use this instance for automatic update:": "Utiliser cette instance pour la mise à jour automatique", + "Warning!": "Attention!", + "every 12 hours": "toutes les 12 heures", + "every 2 days": "tous les 2 jours", + "every 2 weeks": "toutes les 2 semaines", + "every 3 days": "tous les 3 jours", + "every day": "tous les jours", + "every week": "toutes les semaines", + "manually": "manuellement", + "monthly": "mensuel", + "place here": "Placez les fichiers ici", + "tooltip_auth": "Utiliser un identifiant et un mot de passe pour accéder à la page d'administration. Vous pouvez les gérer sur l'onglet des utilisateurs.", + "tooltip_autoUpdate": "Vérifier periodiquement les mises à jour", + "tooltip_bind": "Adresse IP, où l'administrateur s'exécutera", + "tooltip_cache": "Activer la cache du navigateur", + "tooltip_certChained": "Certificat chaîné. Certificat racine + Certificat public. Veuillez le gérer dans les configs du système.", + "tooltip_certPrivate": "Certificat privé Veuillez le gérer dans les configs du système.", + "tooltip_certPublic": "Certificat public. Veuillez le gérer dans les configs du système.", + "tooltip_defaultUser": "Connexion automatique avec cet utilisateur", + "tooltip_leEnabled": "Utiliser les certificats Let's Encrypt", + "tooltip_lePort": "Port pour le serveur de challenge. Let's Encrypt ne supporte que le port 80, mais avec certaines configurations du routeur, vous pouvez utiliser n'importe quel port", + "tooltip_leUpdate": "Cette instance mettra à jour les certificats Let's Encrypt tous les 3 mois", + "tooltip_port": "Port pour l'interface d'administration. Il doit être disponible. Les utilisateurs non-adminstratifs ne peuvent pas utiliser les ports inférieurs à 1000.", + "tooltip_secure": "Activer le protocole SSL. L'administration sera disponible uniquement via https:// et non http://", + "tooltip_thresholdValue": "Si le navigateur reçoit trop d'événements en 3 secondes, la mise à jour sera arrêtée", + "tooltip_ttl": "Passé ce délai, l'utilisateur sera automatiquement déconnecté.", + "users permissions": "Autorisations des utilisateurs" +} diff --git a/admin/i18n/it/translations.json b/admin/i18n/it/translations.json new file mode 100644 index 0000000..3f94a06 --- /dev/null +++ b/admin/i18n/it/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Le impostazioni per l'adattatore di amministrazione", + "Authentication was deactivated": "L'autenticazione è stata disattivata", + "Authentication:": "Autenticazione", + "Auto update:": "Verifica aggiornamenti", + "Background": "Sfondo", + "Background color of the login screen": "Colore di sfondo della schermata di accesso", + "Background image": "Immagine di sfondo", + "Cache:": "Buffer", + "Chained certificate:": "Certificato di catena", + "Disable authentication": "Disabilitare l'autenticazione", + "Enabled:": "Abilitato", + "Events threshold value:": "Valore soglia degli eventi", + "Hide logo": "Nascondi logo", + "IP:": "IP", + "Ignore warning": "Ignora l'avviso", + "Let's Encrypt settings": "Impostazioni di Let's Encrypt", + "Listen on all IPs": "Ascolta su tutti gli IP", + "Login timeout(sec):": "Timeout di accesso (sec)", + "Own motto": "Proprio motto", + "Port to check the domain:": "Porta per controllare il dominio", + "Port:": "Porta", + "Private certificate:": "Certificato privato", + "Public certificate:": "Certificato pubblico", + "Run as:": "Correre come", + "Secure(HTTPS):": "Sicuro (HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Imposta i certificati o caricali prima nelle impostazioni di sistema (in alto a destra).", + "Unsecure_Auth": "La password verrà inviata tramite connessione non protetta. Per proteggere le tue password abilita la connessione sicura (HTTPS)!", + "Use Lets Encrypt certificates:": "Utilizza i certificati Let's Encrypt", + "Use this instance for automatic update:": "Utilizza questa istanza per l'aggiornamento automatico", + "Warning!": "Avvertimento!", + "every 12 hours": "ogni 12 ore", + "every 2 days": "ogni 2 giorni", + "every 2 weeks": "ogni 2 settimane", + "every 3 days": "ogni 3 giorni", + "every day": "ogni giorno", + "every week": "ogni settimana", + "manually": "manualmente", + "monthly": "mensile", + "place here": "posiziona i file qui", + "tooltip_auth": "Controlla login e password per accedere alla pagina di amministrazione. Puoi gestirlo nella scheda utenti.", + "tooltip_autoUpdate": "Controlla gli aggiornamenti dell'adattatore", + "tooltip_bind": "Indirizzo IP, dove verrà eseguito l'amministratore", + "tooltip_cache": "Buffer del browser abilitata.", + "tooltip_certChained": "Certificato incatenato Certificato root + Certificato pubblico. Si prega di gestirlo nelle configurazioni di sistema.", + "tooltip_certPrivate": "Certificato privato Si prega di gestirlo nelle configurazioni di sistema.", + "tooltip_certPublic": "Certificato pubblico Si prega di gestirlo nelle configurazioni di sistema.", + "tooltip_defaultUser": "Accesso automatico con questo utente", + "tooltip_leEnabled": "Utilizza Let's Encrypt certificates", + "tooltip_lePort": "Porta per server challenge. Let's Encrypt supporta solo la porta 80, ma con il router puoi usare qualsiasi porta", + "tooltip_leUpdate": "Questa istanza aggiornerà i certificati Let's Encrypt ogni 3 mesi", + "tooltip_port": "Porta per l'interfaccia di amministrazione. Deve essere disponibile. Se si esegue su Linux senza root, non è possibile utilizzare le porte inferiori a 1000.", + "tooltip_secure": "Abilita il protocollo SSL. L'amministratore sarà disponibile tramite https: // e non http: //", + "tooltip_thresholdValue": "se il browser riceve troppi eventi in 3 secondi, l'aggiornamento verrà interrotto", + "tooltip_ttl": "Timeout di accesso. Dopo questo tempo l'utente si disconnetterà automaticamente.", + "users permissions": "permessi degli utenti" +} diff --git a/admin/i18n/nl/translations.json b/admin/i18n/nl/translations.json new file mode 100644 index 0000000..31ecfc2 --- /dev/null +++ b/admin/i18n/nl/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Admin adapter instellingen", + "Authentication was deactivated": "Verificatie was gedeactiveerd", + "Authentication:": "Authenticatie", + "Auto update:": "Updates controleren", + "Background": "Achtergrond", + "Background color of the login screen": "Achtergrondkleur van het inlogscherm", + "Background image": "Achtergrond afbeelding", + "Cache:": "Cache", + "Chained certificate:": "Ketencertificaat", + "Disable authentication": "Schakel verificatie uit", + "Enabled:": "Ingeschakeld", + "Events threshold value:": "Drempelwaarde voor gebeurtenissen", + "Hide logo": "Logo verbergen", + "IP:": "IP", + "Ignore warning": "Negeer waarschuwing", + "Let's Encrypt settings": "Let's Encrypt Instellingen", + "Listen on all IPs": "Luister op alle IP's", + "Login timeout(sec):": "Aanmelding Time-out (sec)", + "Own motto": "Eigen motto", + "Port to check the domain:": "Poort om het domein te controleren", + "Port:": "Poort", + "Private certificate:": "Privé certificaat", + "Public certificate:": "Openbaar certificaat", + "Run as:": "Uitvoeren als", + "Secure(HTTPS):": "Encryptie (HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Stel certificaten in of installeer deze eerst in de systeeminstellingen (rechtsboven).", + "Unsecure_Auth": "Het wachtwoord wordt verzonden via onbeveiligde verbinding. Ter beveiliging van uw wachtwoorden schakelt u de beveiligde verbinding (HTTPS) in!", + "Use Lets Encrypt certificates:": "Gebruik Let's Encrypt-certificaten", + "Use this instance for automatic update:": "Gebruik deze instantie voor automatische update", + "Warning!": "Waarschuwing!", + "every 12 hours": "Elke 12 uur", + "every 2 days": "om de 2 dagen", + "every 2 weeks": "elke 2 weken", + "every 3 days": "om de 3 dagen", + "every day": "elke dag", + "every week": "elke week", + "manually": "handmatig", + "monthly": "maandelijks", + "place here": "plaats de bestanden hier", + "tooltip_auth": "Controleer of aanmelding en wachtwoord toegang hebben tot de beheerderspagina. U kunt gebruikers beheren op het tabblad Gebruikers.", + "tooltip_autoUpdate": "Controleer regelmatig of er updates zijn", + "tooltip_bind": "IP-adres welke de admin adapter gebruikt", + "tooltip_cache": "Browser cache ingeschakeld.", + "tooltip_certChained": "Ketencertificaat. Beheer deze in de systeemconfiguratie.", + "tooltip_certPrivate": "Privé certificaat. Beheer deze in de systeemconfiguratie.", + "tooltip_certPublic": "Openbaar certificaat. Beheer deze in de systeemconfiguratie.", + "tooltip_defaultUser": "Automatisch inloggen met deze gebruiker", + "tooltip_leEnabled": "Gebruik Let's Encrypt-certificaten", + "tooltip_lePort": "Poort voor uitgaande server. Let's Encrypt ondersteunt alleen poort 80, maar met een router kun je elke poort gebruiken", + "tooltip_leUpdate": "Deze instantie werkt Let's Encrypt-certificaten elke 3 maanden bij", + "tooltip_port": "Poort voor beheerdersinterface, deze moet beschikbaar zijn. Als u op Linux draait zonder root kunt u geen poorten onder 1000 gebruiken.", + "tooltip_secure": "Schakel SSL-protocol in. Beheerpagina is beschikbaar via https: // en niet http: //", + "tooltip_thresholdValue": "Als de browser te veel gebeurtenissen binnen 3 seconden ontvangt, wordt de update gestopt", + "tooltip_ttl": "Time-out aanmelding. Na deze tijd zal de gebruiker automatisch uitloggen.", + "users permissions": "gebruikersrechten" +} diff --git a/admin/i18n/pl/translations.json b/admin/i18n/pl/translations.json new file mode 100644 index 0000000..7273bcd --- /dev/null +++ b/admin/i18n/pl/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Ustawienia adaptera administracyjnego", + "Authentication was deactivated": "Uwierzytelnianie zostało dezaktywowane", + "Authentication:": "Poświadczenie", + "Auto update:": "Sprawdzanie aktualizacji", + "Background": "Tło", + "Background color of the login screen": "Kolor tła ekranu logowania", + "Background image": "Zdjęcie w tle", + "Cache:": "Pamięć podręczna", + "Chained certificate:": "Przykuty certyfikat", + "Disable authentication": "Wyłącz uwierzytelnianie", + "Enabled:": "Włączone", + "Events threshold value:": "Wartość progowa zdarzeń", + "Hide logo": "Ukryj logo", + "IP:": "IP", + "Ignore warning": "Zignoruj ​​ostrzeżenie", + "Let's Encrypt settings": "Zakodujmy ustawienia", + "Listen on all IPs": "Posłuchaj na wszystkich IP", + "Login timeout(sec):": "Limit czasu logowania (s)", + "Own motto": "Własne motto", + "Port to check the domain:": "Port do sprawdzenia domeny", + "Port:": "Port", + "Private certificate:": "Prywatny certyfikat", + "Public certificate:": "Certyfikat publiczny", + "Run as:": "Uruchom jako", + "Secure(HTTPS):": "Bezpieczne (HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Ustaw certyfikaty lub załaduj najpierw w ustawieniach systemu (prawy górny).", + "Unsecure_Auth": "Hasło zostanie wysłane przez połączenie bez zabezpieczeń. Aby chronić swoje hasła, włącz bezpieczne połączenie (HTTPS)!", + "Use Lets Encrypt certificates:": "Użyj Let's Encrypt certificates", + "Use this instance for automatic update:": "Użyj tej instancji do automatycznej aktualizacji", + "Warning!": "Ostrzeżenie!", + "every 12 hours": "co 12 godzin", + "every 2 days": "co 2 dni", + "every 2 weeks": "co 2 tygodnie", + "every 3 days": "co 3 dni", + "every day": "codziennie", + "every week": "co tydzień", + "manually": "ręcznie", + "monthly": "miesięczny", + "place here": "umieść pliki tutaj", + "tooltip_auth": "Sprawdź login i hasło, aby uzyskać dostęp do strony administratora. Możesz nim zarządzać na karcie użytkowników.", + "tooltip_autoUpdate": "Sprawdź aktualizacje adaptera", + "tooltip_bind": "Adres IP, na którym będzie uruchamiany administrator", + "tooltip_cache": "Pamięć podręczna przeglądarki jest włączona.", + "tooltip_certChained": "Przykuty certyfikat. Certyfikat główny + certyfikat publiczny. Zarządzaj nim w konfiguracjach systemowych.", + "tooltip_certPrivate": "Prywatny certyfikat. Zarządzaj nim w konfiguracjach systemowych.", + "tooltip_certPublic": "Certyfikat publiczny. Zarządzaj nim w konfiguracjach systemowych.", + "tooltip_defaultUser": "Automatyczne logowanie z tym użytkownikiem", + "tooltip_leEnabled": "Użyj Let's Encrypt certificates", + "tooltip_lePort": "Port dla serwera wyzwania. Let's Encrypt obsługuje tylko port 80, ale z routerem możesz użyć dowolnego portu", + "tooltip_leUpdate": "Ta instancja zaktualizuje certyfikaty Let's Encrypt co 3 miesiące", + "tooltip_port": "Port dla interfejsu administratora. Musi być dostępny. Jeśli działasz na Linuksie bez roota, nie możesz używać portów poniżej 1000.", + "tooltip_secure": "Włącz protokół SSL. Administrator będzie dostępny przez https: //, a nie http: //", + "tooltip_thresholdValue": "jeśli przeglądarka otrzyma zbyt wiele zdarzeń w ciągu 3 sekund, aktualizacja zostanie zatrzymana", + "tooltip_ttl": "Limit czasu logowania. Po tym czasie użytkownik zostanie automatycznie wylogowany.", + "users permissions": "uprawnienia użytkowników" +} diff --git a/admin/i18n/pt/translations.json b/admin/i18n/pt/translations.json new file mode 100644 index 0000000..4a5f1fc --- /dev/null +++ b/admin/i18n/pt/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Configurações do adaptador de administração", + "Authentication was deactivated": "A autenticação foi desativada", + "Authentication:": "Autenticação", + "Auto update:": "Verificar atualizações", + "Background": "Fundo", + "Background color of the login screen": "Cor de fundo da tela de login", + "Background image": "Imagem de fundo", + "Cache:": "Buffer", + "Chained certificate:": "Certificado acorrentado", + "Disable authentication": "Desativar autenticação", + "Enabled:": "Ativado", + "Events threshold value:": "Valor limiar de eventos", + "Hide logo": "Ocultar logotipo", + "IP:": "IP", + "Ignore warning": "Ignorar aviso", + "Let's Encrypt settings": "Configurações de Let's Encrypt", + "Listen on all IPs": "Ouvir todos os IPs", + "Login timeout(sec):": "Tempo limite de início de sessão (seg)", + "Own motto": "Próprio lema", + "Port to check the domain:": "Porta para verificar o domínio", + "Port:": "Porta", + "Private certificate:": "Certificado privado", + "Public certificate:": "Certificado público", + "Run as:": "Executar como", + "Secure(HTTPS):": "Codificação (HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Defina certificados ou carregue primeiro nas configurações do sistema (parte superior direita).", + "Unsecure_Auth": "A senha será enviada por meio de conexão não segura. Para proteger suas senhas, ative a conexão segura (HTTPS)!", + "Use Lets Encrypt certificates:": "Use certificados Let's Encrypt", + "Use this instance for automatic update:": "Use esta instância para atualização automática", + "Warning!": "Atenção!", + "every 12 hours": "cada 12 horas", + "every 2 days": "cada 2 dias", + "every 2 weeks": "cada 2 semanas", + "every 3 days": "cada 3 dias", + "every day": "todos os dias", + "every week": "todas as semanas", + "manually": "manualmente", + "monthly": "mensal", + "place here": "coloque os arquivos aqui", + "tooltip_auth": "Verifique onNome de usuário e a senha para acessar a página de administração. Você pode configurá-lo na página dos usuários.", + "tooltip_autoUpdate": "Verifique regularmente as atualizações do adaptador", + "tooltip_bind": "Endereço IP, onde o administrador será executado", + "tooltip_cache": "Buffer do navegador ativado.", + "tooltip_certChained": "Certificado acorrentado. Certificado de root + Certificado público. Configure-o nas configurações do sistema.", + "tooltip_certPrivate": "Certificado privado. Configure-o nas configurações do sistema.", + "tooltip_certPublic": "Certificado público. Configure-o nas configurações do sistema.", + "tooltip_defaultUser": "Auto-login com este usuário", + "tooltip_leEnabled": "Use certificados Let's Encrypt", + "tooltip_lePort": "Porta para Challenge-Server. Let's Encrypt só suporta a porta 80, mas com o roteador você pode usar qualquer porta.", + "tooltip_leUpdate": "Esta instância irá atualizar Let's Encrypt certificados a cada 3 meses", + "tooltip_port": "Porta para a interface de administração. Ele tem que estar disponível. Se estiver executando no linux sem root, você não pode usar portas abaixo de 1000.", + "tooltip_secure": "Habilite o protocolo SSL. O administrador estará disponível através de https:// e não http://", + "tooltip_thresholdValue": "Se o navegador receber muitos eventos em 3 segundos, a atualização será interrompida", + "tooltip_ttl": "Tempo limite de login. Após esse tempo, o usuário será automaticamente desconectado.", + "users permissions": "permissões de usuários" +} diff --git a/admin/i18n/ru/translations.json b/admin/i18n/ru/translations.json new file mode 100644 index 0000000..e70b001 --- /dev/null +++ b/admin/i18n/ru/translations.json @@ -0,0 +1,57 @@ +{ + "Admin adapter settings": "Настройки для адаптера admin", + "Authentication was deactivated": "Аутентификация была отключена", + "Authentication:": "Аутентификация", + "Auto update:": "Проверка обновлений", + "Background": "Фон", + "Background color of the login screen": "Цвет фона экрана входа в систему", + "Background image": "Фоновое изображение", + "Cache:": "Кэш", + "Chained certificate:": "'Chained' сертификат", + "Disable authentication": "Отключить аутентификацию", + "Enabled:": "Включено", + "Events threshold value:": "Порог отключения обновлений", + "Hide logo": "Скрыть логотип", + "IP:": "IP", + "Ignore warning": "Игнорировать предупреждение", + "Let's Encrypt settings": "Настройкт Let's Encrypt", + "Listen on all IPs": "Слушать на всех адресах", + "Login timeout(sec):": "Logout через(сек)", + "Own motto": "Собственный девиз", + "Port to check the domain:": "Порт для проверки доменного имени", + "Port:": "Порт", + "Private certificate:": "'Private' сертификат", + "Public certificate:": "'Public' сертификат", + "Run as:": "Запустить от пользователя", + "Secure(HTTPS):": "Шифрование(HTTPS)", + "Set certificates or load it first in the system settings (right top).": "Нужно выбрать сертификаты или сначала загрузить их в системных настройках (вверху справа).", + "Unsecure_Auth": "Пароль будет отправлен через незащищенное соединение. Для защиты ваших паролей активируйте безопасное соединение (HTTPS)!", + "Use Lets Encrypt certificates:": "Использовать сертификаты Let's Encrypt", + "Use this instance for automatic update:": "Обновлять сертификаты в этом драйвере", + "Warning!": "Предупреждение!", + "every 12 hours": "каждые 12 часов", + "every 2 days": "каждые 2 дня", + "every 2 weeks": "каждые 2 недели", + "every 3 days": "каждые 3 дня", + "every day": "каждый день", + "every week": "каждую неделю", + "manually": "вручную", + "monthly": "раз в месяц", + "place here": "разместите файлы здесь", + "tooltip_auth": "Активировать проверку имени и пароля.", + "tooltip_autoUpdate": "Регулярно проверять на наличие обновлений драйверов", + "tooltip_bind": "IP Адрес интерфейса на котором будет запущен админ", + "tooltip_cache": "Активировать кеш браузера", + "tooltip_certChained": "Chained сертификат. Настройте сначала сертификаты в системных настройках.", + "tooltip_certPrivate": "Приватный (private) сертификат. Настройте сначала сертификаты в системных настройках.", + "tooltip_certPublic": "Публичный (public) сертификат. Настройте сначала сертификаты в системных настройках.", + "tooltip_defaultUser": "Автоматический логин с этим пользователем.", + "tooltip_leEnabled": "Использовать сертификаты Let's Encrypt", + "tooltip_lePort": "Порт для challenge сервера Let's Encrypt. Сервер должен быть доступен из интернета на порту 80", + "tooltip_leUpdate": "Обновлять в этой инстанции Let's Encrypt сертификаты раз в 3 месяца. Обновлять их может только одна инстанция", + "tooltip_port": "Порт для интерфейса.", + "tooltip_secure": "Активировать протокол SSL. Сайт будет доступен по https:// , а не по http://", + "tooltip_thresholdValue": "если браузер получает слишком много событий за 3 секунды, обновление будет остановлено", + "tooltip_ttl": "По прошествии этого времени при бездействии пользователь будет разлогинен.", + "users permissions": "Права доступа" +} diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..69c3840 --- /dev/null +++ b/admin/index.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + +
+ +

Admin adapter settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
 

Let's Encrypt settings

+
+ diff --git a/admin/index_m.html b/admin/index_m.html new file mode 100644 index 0000000..d7c62fd --- /dev/null +++ b/admin/index_m.html @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+ + Hide logo +
+
+ + +
+
+
+
+ + Background image +
+
+
+
+
+ Upload image + +
+
+ +
+
+
+
+
+
+ place here + +
+
+
+
+
+
+ + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..338ca98 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,27 @@ +version: 'test-{build}' +environment: + matrix: + - nodejs_version: '4' + - nodejs_version: '6' + - nodejs_version: '8' + - nodejs_version: '10' +platform: + - x86 + - x64 +clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%' +install: + - ps: 'Install-Product node $env:nodejs_version $env:platform' + - ps: '$NpmVersion = (npm -v).Substring(0,1)' + - ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }' + - ps: npm --version + - npm install + - node node_modules/gulp/bin/gulp default + - npm install winston@2.3.1 + - 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production' + - if exist "node_modules/yunkong2.js-controller/node_modules/yunkong2.admin/" rmdir /s /q "node_modules/yunkong2.js-controller/node_modules/yunkong2.admin" +test_script: + - echo %cd% + - node --version + - npm --version + - npm test +build: 'off' diff --git a/docs/de/admin.md b/docs/de/admin.md new file mode 100644 index 0000000..165cc3d --- /dev/null +++ b/docs/de/admin.md @@ -0,0 +1,115 @@ +## ausführliche Beschreibung + +Der Adapter admin dient der Bedienung der gesamten yunkong2-Installation. Er stellt ein Webinterface zur Verfügung. Dieses wird unter der `:8081` aufgerufen. Dieser Adapter wird direkt bei der Installation von yunkong2 angelegt. + +Über das vom Adapter zur Verfügung gestellte GUI können u.a. folgenden Funktionen abgerufen werden: + +* Installation weiterer Adapter +* Zugriff auf Objektübersicht +* Zugriff auf die Zustandsübersicht der Objekte +* Zugriff auf Benutzer und Gruppen Administration +* Zugriff auf das Logfile +* Verwaltung der Hosts + +## Installation + +Dieser Adapter wird direkt bei der Installation von yunkong2 angelegt eine manuelle Installation ist nicht notwendig + +## Konfiguration + +![adapter_admin_konfiguration](img/admin_konfiguration.png) + +#### IP + +Hier wird die IP-Adresse unter der der Adapter erreichbar ist eingegeben. Verschiedene Ipv4 und Ipv6 Möglichkeiten stehen zur Auswahl. +**Default ist 0.0.0.0\. Dies darf nicht verändert werden!** + +#### Port + +Hier wird der Port, unter der der Administrator aufgerufen werden kann eingestellt. Falls auf dem Server mehrere Webserver laufen muss dieser Port angepasst werden, damit es nicht zu Problemen wegen doppelter Portvergabe kommt. + +#### Verschlüsselung + +Soll das sichere Protokoll https verwendet werden ist hier ein Haken zu setzen. + +#### Authentifikation + +Soll eine Authentifizierung erfolgen ist hier ein Haken zu setzen. + +## Bedienung + +Über den Webbrowser die folgende Seite aufrufen:  + +`:8081` + +## Reiter + +Die Hauptseite des Administrators besteht aus mehreren Reitern. In der Grundinstallation werden die Reiter wie in der Abbildung angezeigt. Über das Bleistift-Icon rechts oben (1) können nach der Installation zusätzlicher Adapter weitere Reiter hinzugefügt werden. Dort können auch Reiter deaktiviert werden um eine besser Übersicht zu erhalten. + +![yunkong2_adapter_admin_001a](img/admin_yunkong2_Adapter_Admin_001a.jpg) + +Ausführliche Informationen sind in den Seiten hinterlegt, die über die Überschriften verlinkt sind. + +### [Adapter](admin/tab-adapters.md) + +Hier werden die verfügbaren und installierten Adapter angezeigt und verwaltet. + +### [Instanzen](admin/tab-instances.md) + +Hier werden die bereits über den Reiter Adapter installierten Instanzen aufgelistet und können entsprechend konfiguriert werden. + +### [Objekte](admin/tab-objects.md) + +Die verwalteten Objekte (z.B. die Geräte/Variablen/Programme der CCU). Hier können Objekte angelegt und gelöscht werden. +Über die _Pfeil hoch_ und _Pfeil runter_ Knöpfe können ganze Objektstrukturen hoch- oder runtergeladen werden. +Ein weiterer Knopf ermöglicht die Anzeige der Expertenansicht. + +Werden Werte in roter Schrift angezeigt, sind sie noch nicht bestätigt (`ack = false`). + +### [Zustände](admin/tab-states.md) + +Die aktuellen Zustände der Objekte. + +### [Ereignisse](admin/tab-events.md) + +Eine Liste der laufenden Aktualisierung der Zustände. + +### [Gruppen](admin/tab-groups.md) + +Hier werden die angelegten Usergruppen angelegt und die Rechte verwaltet + +### [Benutzer](admin/tab-users.md) + +Hier können Benutzer angelegt und zu den bestehenden Gruppen hinzugefügt werden. + +### [Aufzählungen](admin/tab-enums.md) + +Hier werden die Favoriten, Gewerke und Räume aus der Homematic-CCU aufgelistet. + +### [hosts](admin/tab-hosts.md) + +Informationen über den Rechner, auf dem yunkong2 installiert ist. +Hier kann die aktuelle Version des js-Controllers upgedated werden. +Liegt eine neue Version vor, erscheint die Beschriftung des Reiters in grüner Farbe. + +### [Log](admin/tab-log.md) + +Hier wird das log angezeigt + +Im Reiter Instanzen kann bei den einzelnen Instanzen der zu loggende Loglevel eingestellt werden. +In dem Auswahlmenü wird der anzuzeigende Mindest-Loglevel ausgewählt. +Sollte ein Error auftreten, erscheint die Beschriftung des Reiters in roter Farbe. + +Nach der Installation zusätzlicher Adapter können noch weitere Reiter über das +Bleistift-Icon oben rechts (1) aktiviert werden. Die Beschreibung dieser +Reiter befindet sich bei dem entsprechenden Adapter. + +### [Systemeinstellungen](admin/tab-system.md) + +In dem sich hier öffnenden Menü werden Einstellungen wie Sprache, Zeit- und Datumsformat sowie +weitere systemweite Einstellungen getätigt. + +![Admin Systemeinstellungen](img/admin_Systemeinstellungen.jpg) + +Auch die Repositorien und Sicherheitseinstellungen können hier eingestellt werden. +Eine tiefergehende Beschreibung ist über den Link in dem Titel dieses Abschnitts zu erreichen. \ No newline at end of file diff --git a/docs/de/admin/_old.md b/docs/de/admin/_old.md new file mode 100644 index 0000000..9bcc9c4 --- /dev/null +++ b/docs/de/admin/_old.md @@ -0,0 +1,563 @@ + + + + +## ausführliche Beschreibung + +Der Adapter admin dient der Bedienung der gesamten yunkong2-Installation. Er stellt ein Webinterface zur Verfügung. Dieses wird unter der `:8081` aufgerufen. Dieser Adapter wird direkt bei der Installation von yunkong2 angelegt. Über das vom Adapter zur Verfügung gestellte GUI können u.a. folgenden Funktionen abgerufen werden: + +* Installation weiterer Adapter +* Zugriff auf Objektübersicht +* Zugriff auf die Zustandsübersicht der Objekte +* Zugriff auf Benutzer und Gruppen Administration +* Zugriff auf das Logfile +* Verwaltung der Hosts + +* * * + +## Installation + +Dieser Adapter wird direkt bei der Installation von yunkong2 angelegt eine manuelle Installation ist nicht notwendig + +* * * + +## Konfiguration + +[![adapter_admin_konfiguration](http://www.yunkong2.net/wp-content/uploads/adapter_admin_konfiguration.png)](http://www.yunkong2.net/wp-content/uploads//adapter_admin_konfiguration.png) + +#### IP + +Hier wird die IP-Adresse unter der der Adapter erreichbar ist eingegeben. Verschiedene Ipv4 und Ipv6 Möglichkeiten stehen zur Auswahl. **Default ist 0.0.0.0\. Dies darf nicht verändert werden!** + +#### Port + +Hier wird der Port, unter der der Administrator aufgerufen werden kann eingestellt. Falls auf dem Server mehrere Webserver laufen muss dieser Port angepasst werden, damit es nicht zu Problemen wegen doppelter Portvergabe kommt. + +#### Verschlüsselung + +Soll das sichere Protokoll https verwendet werden ist hier ein Haken zu setzen. + +#### Authentifikation + +Soll eine Authentifizierung erfolgen ist hier ein Haken zu setzen. + +* * * + +## Bedienung + +Über den Webbrowser die folgende Seite aufrufen: `:8081` + +* * * + +## Reiter + +Die Hauptseite des Administrators besteht aus mehreren Reitern. In der Grundinstallation werden die Reiter wie in der Abbildung angezeigt. Über das Bleistift-Icon rechts oben (1) können nach der Installation zusätzlicher Adapter weitere Reiter hinzugefügt werden. Dort können auch Reiter deaktiviert werden um eine besser Übersicht zu erhalten. [![yunkong2_adapter_admin_001a](img/yunkong2_Adapter_Admin_001a.jpg)](img/yunkong2_Adapter_Admin_001a.jpg) + +### Adapter + +Hier werden die verfügbaren und installierten Adapter angezeigt und verwaltet.   + +#### Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. ![yunkong2_adapter_admin_002aa](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_Admin_002aa.jpg)   + +##### **Die Icons im einzelnen:** + +![yunkong2_adapter_admin_002a](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_Admin_002a.jpg)   **1.)  nur installierte Adapter anzeigen** Bei Anwahl dieses Icons werden nur noch die bereits installierten Adapter angezeigt (Toggle-Funktion)   **2.) Adapter mit Updates anzeigen** Bei Anwahl dieses Icons werden nur noch Adapter angezeigt, zu denen ein Update vorliegt (Toggle-Funktion) Hinter den updatefähigen Adaptern findet sich in der Spalte **_installiert_** ein Update-Icon. Durch Klick auf diesen Button wird der entsprechende Adapter auf die neueste Version gebracht. Außerdem erscheint ein weiteres Icon in der Titelzeile: ![yunkong2_adapter_admin_002b](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_Admin_002b.jpg) Durch Anklicken dieses Icons werden alle verfügbaren Adapter aktualisiert.   **3.) Adapter aus eigener URL installieren** Über das Octocat-Icon können Adapter aus eigenen Pfaden (URL oder Dateipfade) oder Vorabversionen von GitHub installiert werden. Nach Anklicken dieses Icons öffnet sich ein entsprechendes Auswahlfenster: ![yunkong2_adapter_admin_002c_github](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_Admin_002c_GitHub.jpg) Unter dem Reiter **_Von github_** wird einfach im Pulldownmenü der gewünschte Adapter ausgewählt und die neueste Vorabversion wird installiert. Bei Anwahl des Reiters Beliebig kann ein bliebeiger Dateiupfad oder ein beliebiger URL (z.B. ein URL zu einem externen Adapterentwickler) in das Feld eingegeben werden und der entsprechende Adapter installiert werden. ![yunkong2_adapter_admin_002c_ownfile](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_Admin_002c_ownFile.jpg)   **4.) Nach Updates suchen** Bei jedem Neustart wird automatisch nach Updates gesucht. Über diesen Button kann man aber die Suche manuell anstoßen. Wenn Updates in dem unter [Systemeinstellungen](#Systemeinstellungen) eingestellten Repository zur Verfügung stehen, wird die Schrift des Reiters **_Adapter_** in grüner Farbe dargestellt.   **5.) Sortierung ändern** Mit diesem Button wird die Sortierung der Adapter auf dieser Seite geändert. Bei aktivem Button sind alle Adapter alphabetisch sortiert, wobei zuerst ein Block mit den installierten Adaptern, danach einer mit noch nicht installierten Adaptern angezeigt wird. Jeder dieser beiden Blöcke ist in sich alphabetisch sortiert. Ist dieser Button nicht aktiv, werden die Adapter nach Themen sortiert. Dann sind auch die nächsten beiden Icons sichtbar.   **6.) Alle Themengebiete zuklappen**   **7.) Alle Themengebiete aufklappen** Auf der rechten Seite befinden sich auch noch zwei Buttons ![yunkong2_adapter_admin_003a](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_Admin_003a.jpg)   **8.) Reiter editieren** Mit diesem Button kann man nicht benötigte Reiter ausblenden und nicht sichtbare einblenden.   **9.) Systemeinstellungen** Hier werden grundlegende Parameter für yunkong2 eingestellt. ![Admin Systemeinstellungen](http://www.yunkong2.net/wp-content/uploads/Systemeinstellungen.jpg) **Systemsprache** - damit kann man zwischen Systemsprachen wählen: Deutsch, Englisch, Russisch **Einheit Temperatur** - dieses Wert wird von manchen Adaptern verwendet. Möglich ist °C oder °F. **Währung** - Momentan benutzt das noch kein Adapter **Datumsformat** - wählen Sie wie das Datum im admin und vis angezeigt sein sollte. **Trennzeichen** - Komma oder Punkt für Float-Werte **Default Historyinstanz** - Diese SQL/History/InfluxDB Adapter Instanz wird benutzt defaultmäßig für flot und rickshaw (Charts). + +##### Verwahrungsorte oder Repositories + +![](http://www.yunkong2.net/wp-content/uploads//2017-01-19-09_16_49-yunkong2.admin_.png) yunkong2 kann die Adaptersliste von unterschiedlichen Quellen beziehen. Bei der Installation sind folgende Quellen eingetragen: + +* default - http://download.yunkong2.net/sources-dist.json - Wird täglich um 01:00 am Server zusammen gebaut. Sehr schnell aber konnte bis 24 Stunden ältere Info haben. +* online - https://raw.githubusercontent.com/yunkong2/yunkong2.js-controller/master/conf/sources-dist.json - Repository wird von online Quelle zusammengebaut. Kann sehr lange dauern, aber ist aktuellste Quelle +* sources - conf/sources-dist.json - Wird auch zusammengebaut und dauert auch sehr lange aber die Links können veraltet sein (es können manche Adapter fehlen) + +##### Zertifikate + +![](http://www.yunkong2.net/wp-content/uploads//2017-01-19-09_33_54-yunkong2.admin_.png) Hier ist die zentrale Stelle für die Zertifikate, die für die SSL/HTTPS Kommunikation benutzt werden. Die Zertifikate werden von admin, web, simple-api, socketio benutzt. Defaultmäßig sind Standardzertifikate installiert. Damit kann man nichts verifizieren. Nur SSL Kommunikation laufen lassen. Weil die Zertifikate offen liegen sollte man eigene (self-signed) Zertifikate benutzen, die richtige Zertifikate kaufen oder auf Let's Encrypt umsteigen. Die Kommunikation mit default Zertifikaten ist nicht sicher und falls jemand das Ziel hat den Trafik mitzulesen, dass konnte gemacht werden. Unbedingt eigene Zertifikate installieren. Z.b. unter [linux](http://guides.intertech.de/ssl_certificate_self.html). + +##### Let's Encrypt + +![](http://www.yunkong2.net/wp-content/uploads//2017-01-19-09_40_07-yunkong2.admin_.png) Let’s Encrypt is a free, automated, and open certificate authority brought to you by the non-profit Internet Security Research Group (ISRG). You can read about Let’s Encrypt [here](https://letsencrypt.org/). Some installations use Dynamic DNS and Co to get the domain name and to reach under this domain name own web sites. yunkong2 supports automatic request and renew of certificates from Let’s Encrypt Organisation. There is an option to activate free certificates from Let’s Encrypt almost in every adapter, that can start some web server and supports HTTPS. If you just enable the using of certificates and will not activate an automatic update the instance will try to use stored certificates. If the automatic update is activated the instance will try to request certificates from Let’s Encrypt and will automatically update it. The certificates will be first requested when the given domain address will be accessed. E.g you have "sub.domain.com" as address, when you try to access [https://sub.domain.com](https://sub.domain.com/) the certificates will be first requested and it can last a little before first answer will come. The issuing of certificates is rather complex procedure, but if you will follow the explanation you will easy get free certificates. Description: + +1. The new account will be created with given email address (you must set it up in system settings) +2. Some random key will be created as password for the account. +3. After the account is created the system starts on port 80 the small web site to confirm the domain. +4. Let's encrypt use **always** port **80** to check the domain. +5. If port 80 is occupied by other service see point 4. +6. After the small web server is up the request to get certificates for given domains (system settings) will be sent to the Let's encrypt server. +7. Let's encrypt server sends back some challenge phrase as answer on the request and after a while tries to read this challenge phrase on "http://yourdomain:80/.well-known/acme-challenge/" +8. If challenge phrase from our side comes back the Let's encrypt server send us the certificates. They will be stored in the given directory (system settings). + +Sounds complex, but everything what you must do is to activate checkboxes and specify your email and domain in system settings. The received certificates are valid ca. 90 days. After the certificates are received the special task will be started to automatically renew the certificates. The topic is rather complex and 1000 things can go wrong. If you cannot get certificates please use cloud service to reach your installation from internet. **Let's encrypt works only from node.js version>=4.5**   + +##### Statistics + +![](http://www.yunkong2.net/wp-content/uploads//2017-01-19-09_48_46-yunkong2.admin_.png) yunkong2 admin sendet an download.yunkong2.net folgende Information: + +
{
+  "uuid": "56cf0d20-XXXX-YYYY-BBBB-66eec47ZZZZZ",
+  "language": "de",
+  "hosts": [
+    {
+      "version": "0.15.1",
+      "platform": "Javascript/Node.js",
+      "type": "win32"
+    }
+  ],
+  "adapters": {
+    "admin": {
+      "version": "1.0.2",
+      "platform": "Javascript/Node.js"
+    },
+    "hm-rpc": {
+      "version": "1.1.2",
+      "platform": "Javascript/Node.js"
+    }
+  }
+}
+ +Das konnte unterstellt werden in dem man Statistics auf "**nichts**"  einstellt. Trotzdem die Entwickler bitten um Nachgeben: + +> _Wir haben hart gearbeitet um dieses Projekt auf die Beine zu stellen. Als Gegenleistung bitten wir Sie uns die Statistik über die Benutzung an uns zu schicken._ _Keine private Information wird zu yunkong2.org gesendet. Jedes Mal wenn Adapterliste upgedated wird, wird die anonymisierte Statistik gesendet._ + +#### Der Seiteninhalt + +  [![yunkong2_admin_adapter_inhalt01](img/yunkong2_Admin_Adapter_Inhalt01.jpg)](img/yunkong2_Admin_Adapter_Inhalt01.jpg) Auf der Seite werden die Adapter tabellarisch dargestellt. Die Tabelle besteht aus folgenden Spalten:   + +##### **1.) Name** + +In dieser Spalte werden die Namen der Adapter mit den dazugehörigen Icons aufgeführt. Ist über das Icon (5) in der Titelzeile das Gruppieren von Adaptern angewählt erscheinen hier auch die Gruppennamen.   + +##### **2.) Beschreibung** + +Hier befindet sich eine kurze Beschreibung der Funktion des Adapters   + +##### **3.) Schlüsselworte** + +Hier sind einige Suchbegriffe aufgeführt, die mit dem Adapter assoziiert werden.   + +##### **4.) Version** + +Die verfügbare Version wird hier angezeigt. Zur Übersicht wird der Entwicklungsstand eines Adapters farblich hinterlegt. (rot = in Planung; gelb = Beta; orange = Alpha; grün = Final).   + +##### **5.) installiert** + +Diese Spalte gibt verschiedene Informationen über den Installationsstatus dieses Adapters. Zum einen steht dort die Versionsnummer des installierten Adapters. Ist diese fettgedruckt liegt ein Update vor. Dahinter steht in eckigen Klammern die Anzahl der von diesem Adapter installierten Instanzen, wie viele davon aktiviert sind und wie deren Status ist. So bedeutet [2/1], dass von diesem Adapter zwei Instanzen existieren, wovon eine aktiviert ist und ohne Probleme läuft (letzteres ist an der grünen Farbe der zweiten Zahl zu erkennen). Weiter rechts befindet sich ein Update-Icon wenn zu diesem Adapter ein Update vorliegt Adapter. Durch Anklicken dieses Icons wird der Updatevorgang gestartet.   + +##### **6.) Plattform** + +Hier wird angegeben auf welcher Softwareplattform dieser Adapter beruht. Üblicherweise ist dies javascript unter nodejs.   + +##### **7.) Lizenz** + +Dies ist die Lizenz unter der der Adapter zur Verfügung gestellt wird. Die Lizenzbedingungen finden sich üblicherweise im readme. Verlangt die Lizenz, dass sie vom Enduser akzepiert werden muss, wird ein entsprechendes Fenster mit den Lizenzbedingungen beim Erstellen einer Instanz angezeigt.   + +##### **8.) Installieren** + +In dieser Spalte befinden sich verschiedene Buttons für die Installation und für Hilfe. + +* (+) Hiermit wird eine Instanz des Adapters hinzugefügt. Diese muss im Reiter Instanzen noch konfiguriert und aktiviert werden. **Nur wenn von einem Adapter mindestens eine Instanz angelegt wurde, kann der Adapter auch bei yunkong2 verwendet werden.** Bei den meisten Adaptern können beliebig viele Instanzen installiert werden, z.B. um unterschiedliche Hardware anzusprechen. Sollte dies nicht möglich sein, öffnet sich ein Fenster mit einer entsprechenden Fehlermeldung. +* (?) Wenn dieser Button aktiv ist, verlinkt er zu der Hilfeseite zu dem Adapter. Diese befindet sich üblicherweise auf GitHub, wo auch der Adapter gepflegt wird. +* (Mülleimer) Dieser Button löscht den Adapter und alle bereits installierten Instanzen + +Ist das Fragezeichen-Icon in der letzten Spalte aktiv, gelangt man von dort aus mit einem Klick auf eine weiterführende _Github_-Seite mit Informationen zu dem Adapter.     + +### Instanzen + +Hier werden die bereits über den Reiter Adapter installierten Instanzen aufgelistet und können entsprechend konfiguriert werden.   + +#### Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. Außerdem gibt es noch Informationen zu der Auslastung des Servers [![yunkong2_admin_instanzen_headline_icons](img/yunkong2_Admin_Instanzen_Headline_Icons-e1476803621402.jpg)](img/yunkong2_Admin_Instanzen_Headline_Icons-e1476803621402.jpg)   + +#### **Die Icons im einzelnen:** + +##### **1.)  Administratormodus** + +Bei Anwahl dieses Icons werden weitere Spalten zur Konfiguration der Instanzen angezeigt (Toggle-Funktion). Informationen dazu im Abschnitt Seiteninhalt.   + +##### **2.) Ansicht aktualisieren** + +Sollten gerade erst angelegte Instanzen nicht sichtbar sein, hilft ein Anklicken dieses Icons den Zustand der Seite auf den neuesten Stand zu bringen.   + +##### **3.) Statusinformationen zum Server** + +im rechten Teil der Titelzeile befinden sich Informationen über die Aktivitäten der Instanzen sowie der Auslastung des yunkong2 Servers. Die ersten Zahlen geben den bisher von den Instanzen verbrauchten Arbeitsspeicher und den restlichen freien Speicher in MB an. Dahinter den freien Speicher in %. In den eckigen Klammern steht der Name des yunkong2-Servers und die Anzahl der laufenden Prozesse. Sollte der freie Arbeitsspeicher unter 10% fallen wird die Prozentzahl rot dargestellt.     + +#### Der Seiteninhalt + +[![yunkong2_admin_instanzen_headline_columns](img/yunkong2_Admin_Instanzen_Headline_Columns.jpg)](img/yunkong2_Admin_Instanzen_Headline_Columns.jpg) Auf der Seite werden die installierten Instanzen der Adapter tabellarisch dargestellt. Die Tabelle besteht aus folgenden Spalten (Die hier beschriebenen Spalten sind zum Teil nur im Administratormodus sichtbar):   + +##### **1.) Zustand** + +Hier wird durch eine Ampel der Zustand der Instanz dargestellt. Weitere Informationen erhält man indem man mit der Maus auf dem Signal stehen bleibt. ![yunkong2_admin_instanzen_status](http://www.yunkong2.net/wp-content/uploads//yunkong2_Admin_Instanzen_Status.jpg) Nicht alle Instanzen besitzen diese Ampel. Dies ist aber kein Grund zur Panik. Dies sind entweder zeitgesteuerte Instanzen, die sich nur kurz mit dem Controller verbinden und sich dann sofort wieder abschalten oder wie z.B. vis im Hintergrund weiterlaufen.   + +##### **2.) Icon** + +Hier wird das Icon angezeigt, das yunkong2-weit für diesen Adapter verwendet wird   + +##### **3.) Instanz** + +In dieser Spalte steht der Name der Instanz. er setzt sich zusammen aus dem Namen des Adapters sowie einer Zahl, die in der Reihenfolge der Installation der Instanzen fortlaufend durchnummeriert wird. Die erste Instanz erhält die 0\. Diese Bezeichnung ist die Grundlage für die Bezeichnung der Datenpunkte in yunkong2.   + +##### **4.) aktiviert** + +Hier wird die Instanz gestartet oder angehalten. Das grüne Pause-Zeichen zeigt an, dass der Adapter läuft und durch den Klick darauf pausiert werden kann, das rote Play-Zeichen zeigt eine gestoppte Instanz, die mit einem Klick gestartet werden kann.   + +##### **5.) Konfiguration** + +Bei Anklicken dieses Icons wird ein adapterspezifisches Konfigurationsmenü geöffnet. Die entsprechenden Menüs sind bei den dazugehörigen [Adaptern](http://www.yunkong2.net/?page_id=2236&lang=de) beschrieben.   + +##### **6.) restart** + +Beim Klick auf dieses Icon wird die entsprechende Instanz neu gestartet   + +##### **7.) Mülleimer** + +Mit diesem Icon wird die entsprechende Instanz gelöscht. Andere Instanzen des selben Adapters bleiben erhalten. Auch der Adapter selbst bleibt bestehen.   + +##### **8.) Weblink** + +Hinter diesem Icon verbirgt sich ein Link auf die Website dieser Instanz. Entweder weil dieser Adapter ein eigenes Webinterface (mit anderem Port) mitbringt, oder nur einen anderen Pfad. Teilweise führt dieser Link auch auf Hilfeseiten.   + +##### **9.) Titel** + +Hier wird der Name der Instanz angegeben. Diesen Namen kann man nach den eigenene Wünschen oder Bedürfnissen änder. Dies ist insbesondere dann sinnvoll, wenn es von einem Adapter mehrere Instanzen (mit sonst gleicher Bezeischnung) gibt. Dies wäre z.B. bei hm-rpc der Fall, wenn es für RF, Wired und CuxD je eine Instanz gibt.   + +##### **10.) Zeitplanung** + +Bei Adaptern, die zeitgesteuert gestartet werden, wird hier eingetragen wann dieser Adapter starten soll. Diese Zeitplanung ist im Format eines [cronjobs](https://de.wikipedia.org/wiki/Cron#Beispiele). Zur Änderung klickt man auf den Button mit den drei Punkten. Es öffnet sich ein Eingabefenster mit sehr viel Zusatzinformationen und Hilfe. [![yunkong2_admin_instanzen_cronjob](img/yunkong2_Admin_Instanzen_Cronjob.jpg)](img/yunkong2_Admin_Instanzen_Cronjob.jpg)   + +##### **11.) Neu starten** + +Wenn diese Checkbox angehakt wird kann hier ebenfalls ein Zeitplan erstellt werden wann diese Instanz neu gestartet werden soll.   + +##### **12.) Log Stufe** + +In dieser Spalte kann der jeweilige Loglevel für die Instanz angepasst werden. Zur Verfügung stehen debug, info, warn und error. Standardmäßig steht dieser Wert auf info. Hat man den Eindruck, dass etwas nicht ganz rund läuft kann man ihn auf debug stellen. dann werden im Reiter log zu dieser Instanz auch debug Informationen ausgegeben, die helfen können einen Fehler zu finden. Umgekehrt kann man diesen Wert auch höher stellen, damit das log nicht so umfangreich wird.   + +##### **13.) RAM Limit** + +Hier kann man vorgeben wieviel Arbeitsspeicher der Instanz vorsorglich bereitgestellt werden soll. Diese Menge Speicher steht dann anderen Aufgaben nicht mehr zur Verfügung und sollte gerade bei Systemen mit wenig Arbeitsspeicher nicht zu hoch gewählt werden. Sollte die Instanz vorübergehend mehr Speicher benötigen, wird ihr dieser vom System selbstverständlich zugeteilt werden aber anschließend sofort wieder für das System freigegeben. In der Zeit, in dere eine Instanz mehr Speicher benötigt, als ihr reserviert wurde wird der benötigte Speicher rot dargestellt.   + +##### **14.) RAM Benutzung** + +Hier wird der tatsächlich von der Instanz verwendete Arbeitsspeicher angezeigt. Diese Werte werden regelmäßig aktualisiert. Nach der Aktualisierung erscheinen diese Werte kurz in grüner Schrift.     + +### Objekte + +Unter diesem Reiter befinden sich alle verwalteten Objekte. Zu jeder Instanz wird hier ein Ordner angelegt in dem sich die von ihr angelegten Datenpunkte in einer hierarchischen Struktur befinden. Hier können Objekte auch manuell angelegt und gelöscht werden. Es können ganze Objektstrukturen hoch- oder runtergeladen werden. Ein weiterer Knopf ermöglicht die Anzeige der Expertenansicht.   + +#### Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. ![yunkong2_admin_objekte_headline_icons](http://www.yunkong2.net/wp-content/uploads//yunkong2_Admin_Objekte_Headline_Icons.jpg)   + +#### **Die Icons im einzelnen:** + +##### **1.) Ansicht aktualisieren** + +Sollten gerade erst angelegte Objekte nicht sichtbar sein, hilft ein Anklicken dieses Icons den Zustand der Seite auf den neuesten Stand zu bringen.   + +##### **2.) Sortierung ändern** + +Mit diesem Button wird die Sortierung der Objekte auf dieser Seite geändert. Bei aktivem Button sind alle Objekte alphabetisch sortiert. Ist dieser Button nicht aktiv, werden die Ojekte hierarchisch nach Instanzen sortiert. Dann sind auch die nächsten beiden Icons sichtbar.   + +##### **3.) Alle Themengebiete zuklappen** + +##### **4.) Alle Themengebiete aufklappen** + +##### **5.)  Administratormodus** + +Bei Anwahl dieses Icons werden weitere Objekte angezeigt (Toggle-Funktion). Dieses sind die Datenpunkte des Systems.   + +##### **6.) hinzufügen** + +Nach Anwählen dieses Icons können weitere Objekte hinzugefügt werden. Ist ein Ordner angewählt wird dieser als _Parent_ in der Objektstruktur übernommen. Ein Konfigurationsfenster öffnet sich: ![yunkong2_admin_objekte_addobject](http://www.yunkong2.net/wp-content/uploads//yunkong2_Admin_Objekte_AddObject.jpg) Hier muss jetzt der Name für das neue Objekt ausgewählt werden, wobei als Typ gemäß der hierarchischen Struktur ein Gerät, ein Kanal oder ein Datenpunkt zur Verfügung steht. Als Datenpunkttypen stehen Logikwert, Schalter, Zeichenkette, Zahl, Werteliste, Feld, Objekt und gemischt zur Verfügung. Sobald man das Eingabefenster mit ok bestätigt öffnet sich ein weiteres Fenster: [![yunkong2_admin_objekte_addobjec02t](img/yunkong2_Admin_Objekte_AddObjec02t.jpg)](img/yunkong2_Admin_Objekte_AddObjec02t.jpg) Hier können noch einige Daten eingegeben werden. So kann dem Objekt eine Rolle und ein icon hinzugefügt werden. Unter den anderen Reitern befinden sich noch weitere Eigenschaften des Objekts. So eine Information gibt es zu jedem Objekt.   + +##### **7.) Upload** + +Mit diesem Button wird eine komplette Objektstruktur als json-Datei auf den yunkong2 Server hochgeladen   + +##### **8.) Download** + +Mit diesem Button wird die ausgewählte Objektstruktur als json-Datei vom yunkong2 Server heruntergeladen und kann gespeichert werden.     + +#### Der Seiteninhalt + +[![yunkong2_admin_objekte_headline_columns](img/yunkong2_Admin_Objekte_Headline_Columns.jpg)](img/yunkong2_Admin_Objekte_Headline_Columns.jpg) Auf der Seite werden die vorhandenen Objekte tabellarisch dargestellt. Die Tabelle besteht aus folgenden Spalten (Die Felder unter den Spaltenköpfen 1 und 2 sowie die Pulldownmenüs der weiteren Spalten dienen als Filterkriterien). Die Tabelle im Bild ist nach Hierarchie geordnet und alle Unterpunkte (nodes) wurden aufgeklappt:   + +##### **1.) ID** + +Dieses sind die obersten Ebenen der Objekthierarchie. Hier werden als oberste Ebene z.B. der Name der Instanz, darunter die jeweilige Struktur der Daten angelegt.   + +##### **2.) Name** + +In dieser Spalte wird die Bezeichnung des Objekts angegeben. Zusätzlich wird durch ein vorangestelltes Icon gezeigt um welche Hierarchieebene es sich hier handelt (Gerät, Kanal oder Datenpunkt) Die Werte dieser Spalte sind editierbar. [![yunkong2_admin_objekte_structure01](img/yunkong2_Admin_Objekte_Structure01.jpg)](img/yunkong2_Admin_Objekte_Structure01.jpg)   + +##### **3.) Typ** + +Der Typ in der Hierarchieebene, der in der Spalte _Name_ bereits durch das vorangestellte Icon ersichtlich war, wird hier noch einmal explizit genannt. Über das Pulldownmenü im Spaltenkopf kann man Nach diesen Typen filtern und sich so z.B. nur alle Datenpunkte anzeigen lassen.   + +##### 4.) Rolle + +Hier wird die Funktion dieses Objekts kurz über einen Begriff beschrieben, nach dem man wiederum filtern kann. Die Werte dieser Spalte sind editierbar.   + +##### **5.) Raum** + +Wurde dieses Objekt bereits einem Raum zugeordnet, wird dies hier angezeigt. Auch dies dient u.a. der Filterung bei der Suche nach Objekten. Die Werte dieser Spalte sind editierbar. So können die Objekte noch nachträglich Räumen zugeordnet werden. Klickt man das Feld an, öffnet sich ein Popup mit den bisher angelegten Räumen. [![yunkong2_admin_objekte_rooms](img/yunkong2_Admin_Objekte_Rooms.jpg)](img/yunkong2_Admin_Objekte_Rooms.jpg) + +##### **6.) Funktion** + +Diese Spalte enthält das Gewerk, dem das entsprechende Objekt zugeordnet ist. Die Werte dieser Spalte sind editierbar. So können die Objekte noch nachträglich Gewerken zugeordnet werden. Klickt man das Feld an, öffnet sich ein Popup mit den bisher angelegten Gewerken.   + +##### **7.) Wert** + +Handelt es sich bei dem Objekt um einen Datenpunkt, wird hier der aktuelle Wert dieses Datenpunktes angezeigt.   + +##### **8.) sonstiges** + +Klickt man das Bleistift-Icon an öffnet sich ein Fenster mit den Eigenschaften dieses Objekts. Es ist das gleiche Fenster das oben bereits beim Anlegen eines neuen Objekts erschienen ist. Hier können Eigenschaften des Objekts geändert werden. Diese Funktion ist mit äusserster Vorsicht zu benutzen und nur, wenn man genau weiß was man damit bewirkt. Der Klick auf das Mülleimer-Icon löscht dieses Objekt und **alle** in der Hierarchie darunterliegenden Objekte auch. Zur Sicherheit erscheint ein Fenster, in dem die Löschung noch einmal bestätigt werden muss. ![yunkong2_admin_objekte_delete](http://www.yunkong2.net/wp-content/uploads//yunkong2_Admin_Objekte_delete.jpg) Das Zahnrad-Icon erscheint nur, wenn mindestens eine History-Instanz installiert ist (History, InfluxDB oder SQL). Hier kann der Datenpunkt für das Loggen der historischen Daten konfiguriert werden. Nähere Informationen dazu finden sich in der Beschreibung des [History-Adapters](http://www.yunkong2.net/?page_id=144&lang=de). Über das Zahnrad in der Titelzeile kann diese Aktion zeitgleich für alle Datenpunkte durchgeführt werden, die den aktuellen Filterkriterien entsprechen. Es ist daher sorgsam zu prüfen, ob die Filterkriterien dieser Seite so ausgewählt sind, dass auch nur die gewünschten Datenpunkte dabei sind. Das Pulldownmenü zum Filtern dieser Spalte bezieht sich auf Datenpunkte mit geloggten Daten. Hier stehen _mit_, _ohne_ und _alle_ zur Verfügung.     + +### Zustände + +In diesem Reiter werden die aktuellen Zustände von allen Datenpunkten angezeigt. Die Werte können auch geändert werden. [![yunkong2_admin_states_columns](img/yunkong2_Admin_States_columns.jpg)](img/yunkong2_Admin_States_columns.jpg) + +#### Der Seiteninhalt + +Auf der Seite werden die vorhandenen Objekte tabellarisch dargestellt. Die Spalten können durch Anklicken der Spaltenköpfe nach den Inhalten der entsprechenden Spalten alphabetisch auf- oder absteigend sortiert werden (Toggle-Funktion). Die Felder darunter dienen der Filterung der Datenpunkte nach eigenen Kriterien. Die Tabelle besteht aus folgenden Spalten:   + +##### **1.) ID** + +Dies ist der eindeutige Name des entsprechenden Datenpunktes, gemäß der Struktur bestehend aus z.B. Name des Adapters.Nummer der Instanz.Gerätename.Kanalname.Datenpunktname.   + +##### **2.) Eltern Name** + +Der selbe Inhalt wie in Spalte 3 Name.   + +##### **3.) Name** + +Der Name des Datenpunktes. Dies kann ein automatisch generierter oder ein manuell vergebener Name sein, der verständlicher ist. Dieser Name muss nicht eindeutig sein.   + +##### **4.) Wert** + +Hier wird der aktuelle Wert des Datenpunktes angegeben. Dieser Wert ist editierbar   + +##### **5.) Bestätigt** + +Wurde dieser Wert geändert und dieses vom System übernommen ist der Wert _true_, ansonsten _false._   + +##### **6.) Quelle** + +Hier wird angegeben, welche Instanz die letzte Änderung des Datenpunktes durchgeführt hat.   + +##### **7.) Zeit** + +Dies ist der Zeitstempel zu dem der Datenpunkt zuletzt aktualisiert wurde.   + +##### **8.) geändert** + +Dies ist der Zeitstempel zu dem sich der Wert des Datenpunktes zuletzt geändert hat.     + +#### Der Seitenfuß + +Im Seitenfuß gibt es noch ein paar Informationen [![yunkong2_admin_states_footer](img/yunkong2_Admin_States_footer.jpg)](img/yunkong2_Admin_States_footer.jpg)   + +##### **1.) neu laden** + +Dieses Icon kann angeklickt werden um die Tabelle auf den neuesten Stand zu bringen.   + +##### **2.) Seiteninformationen** + +Der Info-Block in der Mitte des Seitenfußes gibt zum einen mit dem Pulldownmenü die Möglichkeit die Zeilen pro Seite einzustellen. Zur Verfügung stehen 20, 100, 200, 500 und 1000 Zeilen pro Seite. Weiterhin gibt es hier die Information wieviele Seiten es insgesamt gibt, sowie die Möglichkeit mit den Pfeil-Icons die Seiten vor oder zurück zu blättern.   + +##### **3.) Datenpunktinformation** + +Diese Information gibt die Gesamtanzahl der existierenden Datenpunkte an sowie den davon auf der aktuellen Seite angezeigten Bereich.       + +### Ereignisse + +In diesem Reiter werden die aktuellen Ereignisse von allen Datenpunkten angezeigt. Die Erneuerung der Anzeige kann angehalten werden. [![yunkong2_adapter_admin_ereignisse_spalten](img/yunkong2_Adapter_admin_Ereignisse_Spalten.jpg)](img/yunkong2_Adapter_admin_Ereignisse_Spalten.jpg) + +#### Der Seiteninhalt + +Auf der Seite werden die aktuellen Änderungen der Datenpunkte tabellarisch dargestellt. die neuesten Änderungen befinden sich am oberen Tabellenende. Mithilfe des Pause Buttons (8) kann die Aktualisierung der Seite angehalten werden.     Die Tabelle besteht aus folgenden Spalten: + +##### **1.) Typ (Filterfeld)** + +Dies ist der die Angabe über den Typ der Änderung. Dies kann entweder eine Änderung des Wertes sein (StateChange) oder eine Information (message)   + +##### **2.) id (Filterfeld)** + +Dies ist der eindeutige Name des entsprechenden Datenpunktes, gemäß der Struktur bestehend aus z.B. Name des Adapters.Nummer der Instanz.Gerätename.Kanalname.Datenpunktname.  + +##### **3.) Wert (Filterfeld)** + +Hier wird der aktuelle Wert des Datenpunktes angegeben.   + +##### **4.) Bestätigt ****(Filterfeld)** + +Wurde dieser Wert geändert und dieses vom System übernommen ist der Wert _true_, ansonsten _false._   + +##### **5.) Quelle ****(Filterfeld)** + +Hier wird angegeben, welche Instanz die letzte Änderung des Datenpunktes durchgeführt hat.   + +##### **6.) Zeit** **(Filterfeld)** + +Dies ist der Zeitstempel zu dem der Datenpunkt zuletzt aktualisiert wurde.   + +##### **7.) geändert ****(Filterfeld)** + +Dies ist der Zeitstempel zu dem sich der Wert des Datenpunktes zuletzt geändert hat.   + +##### **8.) Pause** + +Hält die Aktualisierung der Seite an.  Anstelle des Pausebuttons werden dann die nicht angezeigten Aktualisierungen hochgezählt.   + +##### **9.) Löschen** + +Mit dem Button wird die aktuelle Seite geleert.     + +### Gruppen + +Hier können durch den Klick auf das (+) am unteren linken Bildrand Usergruppen mit unterschiedlichen Rechten angelegt werden. [![yunkong2_adapter_admin_user_02](img/yunkong2_Adapter_admin_User_02.jpg)](img/yunkong2_Adapter_admin_User_02.jpg)   + +#### Der Seiteninhalt + +Auf der Seite werden die vorhandenen Gruppen tabellarisch dargestellt. Die Felder in den Spaltenköpfen dienen der Filterung der Tabelle nach eigenen Kriterien.   Die Tabelle besteht aus folgenden Spalten: + +##### **1.) ID** + +Dies ist der eindeutige Name der jeweiligen Gruppe, gemäß der Struktur bestehend aus sytem.group.Gruppenname.   + +##### **2.) Name** + +Der Name der Gruppe. Dieser Name ist frei wählbar. Dieser Name muss eindeutig sein.   + +##### **3.) Beschreibung** + +Hier kann eine Beschreibung hinzugefügt werden, die z.B. auf die Rechte in dieser Gruppe verweist.   + +##### **4.) Benutzer** + +Hier werden die in dem Reiter **_Benutzer_** angelegten Benutzer angezeigt und können über eine Checkbox der angewählten Gruppe zugeordnet werden.   + +##### **5.) Rechte anpassen** + +Beim Anklicken des Bleistiftsymbols öffnet sich ein weiteres Fenster, in dem die Rechte dieser Gruppe angepasst werden können. [![yunkong2_adapter_admin_user_rechte_01](img/yunkong2_Adapter_admin_User_Rechte_01.jpg)](img/yunkong2_Adapter_admin_User_Rechte_01.jpg)   + +##### **6.) Neue Gruppe erzeugen** + +Mit diesem Icon kann eine neue Gruppe erzeugt werden, die über die bisherigen Punkte konfiguriert wird.       + +### Benutzer + +Hier können Benutzer angelegt werden. Dazu links unten auf das (+) klicken. Standardmäßig ist der Administrator angelegt. [![yunkong2_adapter_admin_user_01](img/yunkong2_Adapter_admin_User_01-1.jpg)](img/yunkong2_Adapter_admin_User_01-1.jpg)   + +#### Der Seiteninhalt + +Auf der Seite werden die vorhandenen Benutzer tabellarisch dargestellt. Die Felder in den Spaltenköpfen dienen der Filterung der Tabelle nach eigenen Kriterien. Die Tabelle besteht aus folgenden Spalten:   + +##### **1.) ID** + +Dies ist der eindeutige Name des jeweiligen Benutzers, gemäß der Struktur bestehend aus sytem.user.Benutzername.   + +##### **2.) Name** + +Der Name des Benutzers. Dieser Name ist frei wählbar. Dieser Name muss eindeutig sein.   + +##### **3.) Aktiviert** + +mit dieser Checkbox kann die Vefügbarkeit eines Benutzers aktiviert oder deaktiviert werden.   + +##### **4.) Gruppen** + +Hier werden die in dem Reiter **_Gruppen_** angelegten Gruppen angezeigt. Hier können über eine Checkbox die User den entsprechenden Gruppen zugeordnet werden.   [![yunkong2_adapter_admin_user_groups](img/yunkong2_Adapter_admin_User_Groups.jpg)](img/yunkong2_Adapter_admin_User_Groups.jpg)   + +##### **5.) Neuen Benutzer erzeugen** + +Mit diesem Icon kann ein neuer Benutzer erzeugt werden, der anschließend einer bestehenden Gruppe zugeordnet werden muss.   + +##### **6.) Bestehenden Benutzer bearbeiten** + +Nach Anwählen eines bestehenden Benutzers in der Liste können mit diesem Icon die Daten dieses Benutzers bearbeitet werden.   + +##### **7.) Bestehenden Benutzer löschen** + +Mit dem Papierkorb-Icon kann ein bestehender Benutzer gelöscht werden, die bestehenden Gruppen bleiben erhalten.     + +### Aufzählungen + +Hier werden die Favoriten, Gewerke und Räume aus der Homematic-CCU aufgelistet. Es können auch eigene Aufzählungen angelegt werden, die dann z.B. in Scripts verwendet werden können. [![yunkong2_adapter_admin_enums_01](img/yunkong2_Adapter_admin_Enums_01.jpg)](img/yunkong2_Adapter_admin_Enums_01.jpg) + +#### Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. [![yunkong2_adapter_admin_enums_headers_01](img/yunkong2_Adapter_admin_Enums_Headers_01.jpg)](img/yunkong2_Adapter_admin_Enums_Headers_01.jpg) + +#### **Die Icons im einzelnen:** + +##### **1.) Ansicht aktualisieren** + +Sollten gerade erst angelegte Aufzählungen nicht sichtbar sein, hilft ein Anklicken dieses Icons den Zustand der Seite auf den neuesten Stand zu bringen.   + +##### **2.) Sortierung ändern** + +Mit diesem Button wird die Sortierung der Objekte auf dieser Seite geändert. Bei aktivem Button sind alle Objekte alphabetisch sortiert. Ist dieser Button nicht aktiv, werden die Ojekte hierarchisch nach Aufzählungen in Baumstruktur angezeigt. Dann sind auch die nächsten beiden Icons sichtbar. + +##### **3.) Alle Unterordner zuklappen** + +##### **4.) Alle Unterordner aufklappen** + +##### **5.) hinzufügen** + +Nach Anwählen dieses Icons können weitere Aufzählungen in der Grundstruktur hinzugefügt werden. Elemente innerhalb der Ordnerstruktur werden über das (+) Icon rechts (#10) angelegt. Ein Konfigurationsfenster öffnet sich: [![yunkong2_adapter_admin_enums_new](img/yunkong2_Adapter_admin_Enums_new.jpg)](img/yunkong2_Adapter_admin_Enums_new.jpg) Hier muss jetzt der Name für die neue Aufzählung ausgewählt werden, die erzeugte ID wird automatisch angepasst.   + +#### Der Seiteninhalt + +[![yunkong2_adapter_admin_enums_headers_03](img/yunkong2_Adapter_admin_Enums_Headers_03.jpg)](img/yunkong2_Adapter_admin_Enums_Headers_03.jpg) Auf der Seite werden die vorhandenen Aufzählungen sowie ihre Mitglieder tabellarisch dargestellt. Die Tabelle besteht aus folgenden Spalten (Die Felder unter den Spaltenköpfen 6, 7 und 8 dienen als Filterkriterien). Die Tabelle im Bild ist nach Hierarchie geordnet und alle Unterpunkte (nodes) wurden aufgeklappt:   + +##### **6.) ID** + +Hier werden alle Mitglieder der Aufzählungen mit ihren IDs gelistet.Diese Bezeichnung kann durch Doppelklick oder Anklicken des zugehörigen Bleistift-Icons (#9) geändert werden. Die vollständige ID von Untergeordneten Strukturen beinhaltet jeweils vorangestellt ebenfalls die übergeordneten Ebenen.   + +##### **7.) Name** + +In dieser Spalte wird die Bezeichnung des Mitglieds angegeben. Diese Bezeichnung kann durch Doppelklick oder Anklicken des zugehörigen Bleistift-Icons (#9) geändert werden   + +##### **8.) Mitglieder** + +In dieser spalte werden die Mitglieder einer Aufzählung, bei zu vielen wird nur die Anzahl angezeigt. Fährt man mit der Maus über das Feld werden alle Mitglieder in einer Bubble Info angezeigt. Weitere Informationen erhält man über das Info Icon ganz rechts (#12)   + +##### **9.) Bezeichnungen editieren** + +Nach Anklicken dieses Icons kann man die Bezeichnungen in der Spalte ID und Name editieren. Dabei erscheinen an dieser Stelle ein ok-Button in Form eines Häkchens und ein Abbrechen-Icon in Form eines (x)  + +##### **10.) Strukturelement hinzufügen** + +Nach Anklicken dieses Icons öffnet sich ein Dialogfenster in dem ein neues Mitglied innerhalb der jeweiligen Struktur angelegt werden kann. [![yunkong2_adapter_admin_enums_new_member](img/yunkong2_Adapter_admin_Enums_new_Member.jpg)](img/yunkong2_Adapter_admin_Enums_new_Member.jpg) Auch hier kann der Name individuell gewählt werden. Die zugehörige ID wird entsprechend der Struktur und des gewählten Namens automatisch erzeugt.   + +##### **11.) Element löschen** + +Mit dem Mülleimer-Icon wird das Element in dieser Zeile gelöscht   + +##### **12.) Information** + +Nach Anklicken dieses Icons wird ein weiteres Fenster mit erweiterten Informationen zu dem angewählten Element angezeigt. [![yunkong2_adapter_admin_enums_info](img/yunkong2_Adapter_admin_Enums_Info.jpg)](img/yunkong2_Adapter_admin_Enums_Info.jpg)       + +### hosts + +Der Rechner, auf dem yunkong2 installiert ist. Hier kann die aktuelle Version des js-Controllers upgedated werden. Liegt eine neue Version vor, erscheint die Beschriftung des Reiters in grüner Farbe.     + +### log + +Hier wird das log angezeigt Im Reiter Instanzen kann bei den einzelnen Instanzen der zu loggende Loglevel eingestellt werden. In dem Auswahlmenü wird der anzuzeigende Mindest-Loglevel ausgewählt. Sollte ein Error auftreten, erscheint die Beschriftung des Reiters in roter Farbe.       Nur nach Installation zusätzlicher Adapter:   + +### Skripte + +Dieser Reiter ist nur aktiv, wenn auch der javascript-Adapter installiert ist.     + +### Node-red oder Webseiten anderer Adapter + +Dieser Reiter ist nur sichtbar, wenn auch der entsprechende Adapter installiert ist (siehe nächster Punkt).  Systemweite Einstellungen, wie Sprache und Einheiten werden festgelegt. \ No newline at end of file diff --git a/docs/de/admin/_old2.md b/docs/de/admin/_old2.md new file mode 100644 index 0000000..984f60b --- /dev/null +++ b/docs/de/admin/_old2.md @@ -0,0 +1,110 @@ + + + + +## ausführliche Beschreibung + +Der Adapter admin dient der Bedienung der gesamten yunkong2-Installation. Er stellt ein Webinterface zur Verfügung. Dieses wird unter der `:8081` aufgerufen.Dieser Adapter wird direkt bei der Installation von yunkong2 angelegt. Über das vom Adapter zur Verfügung gestellte GUI können folgenden Funktionen abgerufen werden: + +* Installation weiterer Adapter +* Zugriff auf Objektübersicht +* Zugriff auf die Zustandsübersicht der Objekte +* Zugriff auf Benutzer und Gruppen Administration +* Zugriff auf das Logfile +* Verwaltung der Hosts + +* * * + +## Installation + +Dieser Adapter wird direkt bei der Installation von yunkong2 angelegt eine manuelle Installation ist nicht notwendig + +* * * + +## Konfiguration + +### Instanz Konfigurationsmöglichkeiten + +[caption id="attachment_2879" align="alignnone" width="600"]![adapter_admin_konfiguration](http://www.yunkong2.net/wp-content/uploads/adapter_admin_konfiguration.png) Admin Adapter Einstellungen[/caption] + +#### IP + +Hier wird die IP-Adresse unter der der Adapter erreichbar ist eingegeben. Verschiedene Ipv4 und Ipv6 Möglichkeiten stehen zur Auswahl. **Default ist 0.0.0.0\. Dies darf nicht verändert werden!** + +#### Port + +Hier wird der Port, unter der der Administrator aufgerufen werden kann eingestellt. Falls auf dem Server mehrere Webserver laufen muss dieser Port angepasst werden, damit es nicht zu Problemen wegen doppelter Portvergabe kommt. + +#### Verschlüsselung + +Soll das sichere Protokoll https verwendet werden ist hier ein Haken zu setzen. + +#### Authentifikation + +Soll eine Authentifizierung erfolgen ist hier ein Haken zu setzen. + +* * * + +## Bedienung + +Über den Webbrowser die folgende Seite aufrufen: `:8081` + +### Reiter + +Die Hauptseite des Administrators besteht aus mehreren Reitern. [caption id="attachment_3231" align="alignnone" width="750"]![yunkong2.admin](http://www.yunkong2.net/wp-content/uploads/Admin-Übersicht-1024x467.jpg) yunkong2.admin[/caption] + +#### Adapter + +Hier können die Instanzen eines Adapters installiert und gelöscht werden. Außerdem kann mit dem Update Icon links unten auf neue Versionen abgefragt werden. Die verfügbare und die installierte Version des Adapters wird angezeigt. Zur Übersicht wird der Entwicklungsstand eines Adapters farblich hinterlegt. (rot = in Planung; gelb = Beta; orange = Alpha; grün = Final). Ebenso können hier Updates auf eine neuere Version des Adapters durchgeführt werden. Liegt eine neue Version vor, erscheint die Beschriftung des Reiters in grüner Farbe. Ist das Fragezeichen-Icon in der letzten Spalte aktiv, gelangt man von dort aus mit einem Klick auf eine weiterführende _Github_-Seite mit Informationen zu dem Adapter. + +#### Instanzen + +Hier werden die bereits installierten Instanzen aufgelistet und können entsprechend konfiguriert werden. Wenn die Bezeichnung der Instanzen unterstrichen sind, kommt man direkt beim Anklicken der Bezeichnung auf die entsprechende Seite. Oben links kann man per Knopf die Experteneinstellungen sichtbar machen. + +#### Objekte + +Die verwalteten Objekte (z.B. die Geräte/Variablen/Programme der CCU). Hier können Objekte angelegt und gelöscht werden. Über die _Pfeil hoch_ und _Pfeil runter_ Knöpfe können ganze Objektstrukturen hoch- oder runtergeladen werden. Ein weiterer Knopf ermöglicht die Anzeige der Expertenansicht. Werden Werte in roter Schrift angezeigt, sind sie noch nicht bestätigt (`ack = false`). + +#### Zustände + +Die aktuellen Zustände der Objekte. + +#### Ereignisse + +Eine Liste der laufenden Aktualisierung der Zustände. + +#### Gruppen + +Hier können durch den Klick auf das (+) am unteren linken Bildrand Usergruppen angelegt werden. + +#### Benutzer + +Hier können Benutzer angelegt werden. Dazu links unten auf das (+) klicken. Standardmäßig ist der Administrator angelegt. + +#### Aufzählungen + +Hier werden die Favoriten, Gewerke und Räume aus der Homematic-CCU aufgelistet. + +#### hosts + +Der Rechner, auf dem yunkong2 installiert ist. Hier kann die aktuelle Version des js-Controllers upgedated werden. Liegt eine neue Version vor, erscheint die Beschriftung des Reiters in grüner Farbe. + +#### log + +Hier wird das log angezeigt Im Reiter Instanzen kann bei den einzelnen Instanzen der zu loggende Loglevel eingestellt werden. In dem Auswahlmenü wird der anzuzeigende Mindest-Loglevel ausgewählt. Sollte ein Error auftreten, erscheint die Beschriftung des Reiters in roter Farbe. Nur nach Installation zusätzlicher Adapter: + +#### Skripte + +Dieser Reiter ist nur aktiv, wenn auch der javascript-Adapter installiert ist. + +#### Node-red oder Webseiten anderer Adapter + +Dieser Reiter ist nur sichtbar, wenn auch der entsprechende Adapter installiert ist (siehe nächster Punkt). + +#### Reiter-Einstellungen + +Hier können einzelne Reiter ein- und ausgeblendet werden, wenn die entsprechenden [Adapter](http://www.yunkong2.net/?page_id=2236&lang=de) diese Funktion anbieten. + +#### Systemeinstellungen + +Systemweite Einstellungen, wie Sprache und Einheiten werden festgelegt. [caption id="attachment_3212" align="alignnone" width="600"]![Admin Systemeinstellungen](http://www.yunkong2.net/wp-content/uploads/Systemeinstellungen.jpg) yunkong2.admin Systemeinstellungen[/caption] \ No newline at end of file diff --git a/docs/de/admin/img/admin-2_ioBroker_Adapter_Admin_001a.jpg b/docs/de/admin/img/admin-2_ioBroker_Adapter_Admin_001a.jpg new file mode 100644 index 0000000..43a98fd Binary files /dev/null and b/docs/de/admin/img/admin-2_ioBroker_Adapter_Admin_001a.jpg differ diff --git a/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_02.jpg b/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_02.jpg new file mode 100644 index 0000000..2ef647b Binary files /dev/null and b/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_02.jpg differ diff --git a/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_Groups.jpg b/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_Groups.jpg new file mode 100644 index 0000000..5ef5fa3 Binary files /dev/null and b/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_Groups.jpg differ diff --git a/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_Rechte_01.jpg b/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_Rechte_01.jpg new file mode 100644 index 0000000..bf578f0 Binary files /dev/null and b/docs/de/admin/img/admin-2_ioBroker_Adapter_admin_User_Rechte_01.jpg differ diff --git a/docs/de/admin/img/admin-2_ioBroker_Admin_Adapter_Inhalt01.jpg b/docs/de/admin/img/admin-2_ioBroker_Admin_Adapter_Inhalt01.jpg new file mode 100644 index 0000000..bff045a Binary files /dev/null and b/docs/de/admin/img/admin-2_ioBroker_Admin_Adapter_Inhalt01.jpg differ diff --git a/docs/de/admin/img/admin-2_ioBroker_Admin_Instanzen_Cronjob.jpg b/docs/de/admin/img/admin-2_ioBroker_Admin_Instanzen_Cronjob.jpg new file mode 100644 index 0000000..6087ef6 Binary files /dev/null and b/docs/de/admin/img/admin-2_ioBroker_Admin_Instanzen_Cronjob.jpg differ diff --git a/docs/de/admin/img/admin-2_ioBroker_Admin_Instanzen_Headline_Icons-e1476803621402.jpg b/docs/de/admin/img/admin-2_ioBroker_Admin_Instanzen_Headline_Icons-e1476803621402.jpg new file mode 100644 index 0000000..5d06c60 Binary files /dev/null and b/docs/de/admin/img/admin-2_ioBroker_Admin_Instanzen_Headline_Icons-e1476803621402.jpg differ diff --git a/docs/de/admin/img/admin-reiter-adapter_ioBroker_Adapter_admin_adapters_icons01_20170108.jpg b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Adapter_admin_adapters_icons01_20170108.jpg new file mode 100644 index 0000000..04dc635 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Adapter_admin_adapters_icons01_20170108.jpg differ diff --git a/docs/de/admin/img/admin-reiter-adapter_ioBroker_Adapter_admin_adapters_icons02_20170108.jpg b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Adapter_admin_adapters_icons02_20170108.jpg new file mode 100644 index 0000000..0102106 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Adapter_admin_adapters_icons02_20170108.jpg differ diff --git a/docs/de/admin/img/admin-reiter-adapter_ioBroker_Admin_Adapter_Inhalt01.jpg b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Admin_Adapter_Inhalt01.jpg new file mode 100644 index 0000000..bff045a Binary files /dev/null and b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Admin_Adapter_Inhalt01.jpg differ diff --git a/docs/de/admin/img/admin-reiter-adapter_ioBroker_Image_BPi_20160910.jpg b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Image_BPi_20160910.jpg new file mode 100644 index 0000000..4006cf4 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-adapter_ioBroker_Image_BPi_20160910.jpg differ diff --git a/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_01.jpg b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_01.jpg new file mode 100644 index 0000000..1b1053b Binary files /dev/null and b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_01.jpg differ diff --git a/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Headers_01.jpg b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Headers_01.jpg new file mode 100644 index 0000000..25b21d7 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Headers_01.jpg differ diff --git a/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Headers_03.jpg b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Headers_03.jpg new file mode 100644 index 0000000..f417e37 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Headers_03.jpg differ diff --git a/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Info.jpg b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Info.jpg new file mode 100644 index 0000000..3f896e5 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_Info.jpg differ diff --git a/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_new.jpg b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_new.jpg new file mode 100644 index 0000000..14824a8 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_new.jpg differ diff --git a/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_new_Member.jpg b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_new_Member.jpg new file mode 100644 index 0000000..1b6f67b Binary files /dev/null and b/docs/de/admin/img/admin-reiter-aufzaehlungen_ioBroker_Adapter_admin_Enums_new_Member.jpg differ diff --git a/docs/de/admin/img/admin-reiter-ereignisse_ioBroker_Admin_States_footer.jpg b/docs/de/admin/img/admin-reiter-ereignisse_ioBroker_Admin_States_footer.jpg new file mode 100644 index 0000000..02a6463 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-ereignisse_ioBroker_Admin_States_footer.jpg differ diff --git a/docs/de/admin/img/admin-reiter-instanzen_ioBroker_Admin_Instanzen_Headline_Columns.jpg b/docs/de/admin/img/admin-reiter-instanzen_ioBroker_Admin_Instanzen_Headline_Columns.jpg new file mode 100644 index 0000000..f3a1c56 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-instanzen_ioBroker_Admin_Instanzen_Headline_Columns.jpg differ diff --git a/docs/de/admin/img/admin-reiter-log_ioBroker_Adapter_Admin_004b_Verwahrungsorte2.jpg b/docs/de/admin/img/admin-reiter-log_ioBroker_Adapter_Admin_004b_Verwahrungsorte2.jpg new file mode 100644 index 0000000..79bc714 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-log_ioBroker_Adapter_Admin_004b_Verwahrungsorte2.jpg differ diff --git a/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_AddObjec02t.jpg b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_AddObjec02t.jpg new file mode 100644 index 0000000..e229b35 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_AddObjec02t.jpg differ diff --git a/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Headline_Columns.jpg b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Headline_Columns.jpg new file mode 100644 index 0000000..e2dee5d Binary files /dev/null and b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Headline_Columns.jpg differ diff --git a/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Rooms.jpg b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Rooms.jpg new file mode 100644 index 0000000..952b83b Binary files /dev/null and b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Rooms.jpg differ diff --git a/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Structure01.jpg b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Structure01.jpg new file mode 100644 index 0000000..93352e3 Binary files /dev/null and b/docs/de/admin/img/admin-reiter-objekte_ioBroker_Admin_Objekte_Structure01.jpg differ diff --git a/docs/de/admin/img/admin-reiter-zustaende_ioBroker_Admin_States_columns.jpg b/docs/de/admin/img/admin-reiter-zustaende_ioBroker_Admin_States_columns.jpg new file mode 100644 index 0000000..5637adb Binary files /dev/null and b/docs/de/admin/img/admin-reiter-zustaende_ioBroker_Admin_States_columns.jpg differ diff --git a/docs/de/admin/img/admin_Enums_Headers_01.jpg b/docs/de/admin/img/admin_Enums_Headers_01.jpg new file mode 100644 index 0000000..25b21d7 Binary files /dev/null and b/docs/de/admin/img/admin_Enums_Headers_01.jpg differ diff --git a/docs/de/admin/img/admin_Ereignisse_Spalten.jpg b/docs/de/admin/img/admin_Ereignisse_Spalten.jpg new file mode 100644 index 0000000..61bc878 Binary files /dev/null and b/docs/de/admin/img/admin_Ereignisse_Spalten.jpg differ diff --git a/docs/de/admin/img/admin_User_01-1.jpg b/docs/de/admin/img/admin_User_01-1.jpg new file mode 100644 index 0000000..f253e0d Binary files /dev/null and b/docs/de/admin/img/admin_User_01-1.jpg differ diff --git a/docs/de/admin/img/ioBroker_Image_BPi_20160910.jpg b/docs/de/admin/img/ioBroker_Image_BPi_20160910.jpg new file mode 100644 index 0000000..4006cf4 Binary files /dev/null and b/docs/de/admin/img/ioBroker_Image_BPi_20160910.jpg differ diff --git a/docs/de/admin/img/tab-adapters_002aa.jpg b/docs/de/admin/img/tab-adapters_002aa.jpg new file mode 100644 index 0000000..e83c28c Binary files /dev/null and b/docs/de/admin/img/tab-adapters_002aa.jpg differ diff --git a/docs/de/admin/img/tab-adapters_002b.jpg b/docs/de/admin/img/tab-adapters_002b.jpg new file mode 100644 index 0000000..c06d830 Binary files /dev/null and b/docs/de/admin/img/tab-adapters_002b.jpg differ diff --git a/docs/de/admin/img/tab-adapters_002c_GitHub.jpg b/docs/de/admin/img/tab-adapters_002c_GitHub.jpg new file mode 100644 index 0000000..4175f1c Binary files /dev/null and b/docs/de/admin/img/tab-adapters_002c_GitHub.jpg differ diff --git a/docs/de/admin/img/tab-adapters_002c_ownFile.jpg b/docs/de/admin/img/tab-adapters_002c_ownFile.jpg new file mode 100644 index 0000000..12b29d3 Binary files /dev/null and b/docs/de/admin/img/tab-adapters_002c_ownFile.jpg differ diff --git a/docs/de/admin/img/tab-adapters_003a.jpg b/docs/de/admin/img/tab-adapters_003a.jpg new file mode 100644 index 0000000..7e069a2 Binary files /dev/null and b/docs/de/admin/img/tab-adapters_003a.jpg differ diff --git a/docs/de/admin/img/tab-adapters_Inhalt01.jpg b/docs/de/admin/img/tab-adapters_Inhalt01.jpg new file mode 100644 index 0000000..bff045a Binary files /dev/null and b/docs/de/admin/img/tab-adapters_Inhalt01.jpg differ diff --git a/docs/de/admin/img/tab-adapters_icons01_20170108-e1483882554815.jpg b/docs/de/admin/img/tab-adapters_icons01_20170108-e1483882554815.jpg new file mode 100644 index 0000000..701ef13 Binary files /dev/null and b/docs/de/admin/img/tab-adapters_icons01_20170108-e1483882554815.jpg differ diff --git a/docs/de/admin/img/tab-adapters_icons02_20170108.jpg b/docs/de/admin/img/tab-adapters_icons02_20170108.jpg new file mode 100644 index 0000000..0102106 Binary files /dev/null and b/docs/de/admin/img/tab-adapters_icons02_20170108.jpg differ diff --git a/docs/de/admin/img/tab-enums_Enums_01.jpg b/docs/de/admin/img/tab-enums_Enums_01.jpg new file mode 100644 index 0000000..1b1053b Binary files /dev/null and b/docs/de/admin/img/tab-enums_Enums_01.jpg differ diff --git a/docs/de/admin/img/tab-enums_Enums_Headers_03.jpg b/docs/de/admin/img/tab-enums_Enums_Headers_03.jpg new file mode 100644 index 0000000..f417e37 Binary files /dev/null and b/docs/de/admin/img/tab-enums_Enums_Headers_03.jpg differ diff --git a/docs/de/admin/img/tab-enums_Enums_Info.jpg b/docs/de/admin/img/tab-enums_Enums_Info.jpg new file mode 100644 index 0000000..3f896e5 Binary files /dev/null and b/docs/de/admin/img/tab-enums_Enums_Info.jpg differ diff --git a/docs/de/admin/img/tab-enums_Enums_new.jpg b/docs/de/admin/img/tab-enums_Enums_new.jpg new file mode 100644 index 0000000..14824a8 Binary files /dev/null and b/docs/de/admin/img/tab-enums_Enums_new.jpg differ diff --git a/docs/de/admin/img/tab-enums_Enums_new_Member.jpg b/docs/de/admin/img/tab-enums_Enums_new_Member.jpg new file mode 100644 index 0000000..1b6f67b Binary files /dev/null and b/docs/de/admin/img/tab-enums_Enums_new_Member.jpg differ diff --git a/docs/de/admin/img/tab-events_States_columns.jpg b/docs/de/admin/img/tab-events_States_columns.jpg new file mode 100644 index 0000000..5637adb Binary files /dev/null and b/docs/de/admin/img/tab-events_States_columns.jpg differ diff --git a/docs/de/admin/img/tab-events_States_footer.jpg b/docs/de/admin/img/tab-events_States_footer.jpg new file mode 100644 index 0000000..02a6463 Binary files /dev/null and b/docs/de/admin/img/tab-events_States_footer.jpg differ diff --git a/docs/de/admin/img/tab-groups_User_Rechte_01.jpg b/docs/de/admin/img/tab-groups_User_Rechte_01.jpg new file mode 100644 index 0000000..bf578f0 Binary files /dev/null and b/docs/de/admin/img/tab-groups_User_Rechte_01.jpg differ diff --git a/docs/de/admin/img/tab-groups_admin_User_02.jpg b/docs/de/admin/img/tab-groups_admin_User_02.jpg new file mode 100644 index 0000000..2ef647b Binary files /dev/null and b/docs/de/admin/img/tab-groups_admin_User_02.jpg differ diff --git a/docs/de/admin/img/tab-hosts_Hosts_01.jpg b/docs/de/admin/img/tab-hosts_Hosts_01.jpg new file mode 100644 index 0000000..43b698c Binary files /dev/null and b/docs/de/admin/img/tab-hosts_Hosts_01.jpg differ diff --git a/docs/de/admin/img/tab-hosts_Hosts_icons.jpg b/docs/de/admin/img/tab-hosts_Hosts_icons.jpg new file mode 100644 index 0000000..a64f59e Binary files /dev/null and b/docs/de/admin/img/tab-hosts_Hosts_icons.jpg differ diff --git a/docs/de/admin/img/tab-instances_Cronjob.jpg b/docs/de/admin/img/tab-instances_Cronjob.jpg new file mode 100644 index 0000000..6087ef6 Binary files /dev/null and b/docs/de/admin/img/tab-instances_Cronjob.jpg differ diff --git a/docs/de/admin/img/tab-instances_Headline_Columns.jpg b/docs/de/admin/img/tab-instances_Headline_Columns.jpg new file mode 100644 index 0000000..f3a1c56 Binary files /dev/null and b/docs/de/admin/img/tab-instances_Headline_Columns.jpg differ diff --git a/docs/de/admin/img/tab-instances_Icons-e1476803621402.jpg b/docs/de/admin/img/tab-instances_Icons-e1476803621402.jpg new file mode 100644 index 0000000..5d06c60 Binary files /dev/null and b/docs/de/admin/img/tab-instances_Icons-e1476803621402.jpg differ diff --git a/docs/de/admin/img/tab-instances_Inhalt00.jpg b/docs/de/admin/img/tab-instances_Inhalt00.jpg new file mode 100644 index 0000000..f90704d Binary files /dev/null and b/docs/de/admin/img/tab-instances_Inhalt00.jpg differ diff --git a/docs/de/admin/img/tab-instances_Instanzen_Status.jpg b/docs/de/admin/img/tab-instances_Instanzen_Status.jpg new file mode 100644 index 0000000..d98f8eb Binary files /dev/null and b/docs/de/admin/img/tab-instances_Instanzen_Status.jpg differ diff --git a/docs/de/admin/img/tab-log_01.jpg b/docs/de/admin/img/tab-log_01.jpg new file mode 100644 index 0000000..565b5f8 Binary files /dev/null and b/docs/de/admin/img/tab-log_01.jpg differ diff --git a/docs/de/admin/img/tab-log_icons.jpg b/docs/de/admin/img/tab-log_icons.jpg new file mode 100644 index 0000000..a899eeb Binary files /dev/null and b/docs/de/admin/img/tab-log_icons.jpg differ diff --git a/docs/de/admin/img/tab-log_instances.jpg b/docs/de/admin/img/tab-log_instances.jpg new file mode 100644 index 0000000..70f76cc Binary files /dev/null and b/docs/de/admin/img/tab-log_instances.jpg differ diff --git a/docs/de/admin/img/tab-log_loglevel.jpg b/docs/de/admin/img/tab-log_loglevel.jpg new file mode 100644 index 0000000..38d4f01 Binary files /dev/null and b/docs/de/admin/img/tab-log_loglevel.jpg differ diff --git a/docs/de/admin/img/tab-objects_AddObjec02t.jpg b/docs/de/admin/img/tab-objects_AddObjec02t.jpg new file mode 100644 index 0000000..e229b35 Binary files /dev/null and b/docs/de/admin/img/tab-objects_AddObjec02t.jpg differ diff --git a/docs/de/admin/img/tab-objects_AddObject.jpg b/docs/de/admin/img/tab-objects_AddObject.jpg new file mode 100644 index 0000000..8cc28db Binary files /dev/null and b/docs/de/admin/img/tab-objects_AddObject.jpg differ diff --git a/docs/de/admin/img/tab-objects_Headline_Columns.jpg b/docs/de/admin/img/tab-objects_Headline_Columns.jpg new file mode 100644 index 0000000..e2dee5d Binary files /dev/null and b/docs/de/admin/img/tab-objects_Headline_Columns.jpg differ diff --git a/docs/de/admin/img/tab-objects_Headline_Icons.jpg b/docs/de/admin/img/tab-objects_Headline_Icons.jpg new file mode 100644 index 0000000..27599b5 Binary files /dev/null and b/docs/de/admin/img/tab-objects_Headline_Icons.jpg differ diff --git a/docs/de/admin/img/tab-objects_Inhalt00.jpg b/docs/de/admin/img/tab-objects_Inhalt00.jpg new file mode 100644 index 0000000..45e4e24 Binary files /dev/null and b/docs/de/admin/img/tab-objects_Inhalt00.jpg differ diff --git a/docs/de/admin/img/tab-objects_Rooms.jpg b/docs/de/admin/img/tab-objects_Rooms.jpg new file mode 100644 index 0000000..952b83b Binary files /dev/null and b/docs/de/admin/img/tab-objects_Rooms.jpg differ diff --git a/docs/de/admin/img/tab-objects_Structure01.jpg b/docs/de/admin/img/tab-objects_Structure01.jpg new file mode 100644 index 0000000..93352e3 Binary files /dev/null and b/docs/de/admin/img/tab-objects_Structure01.jpg differ diff --git a/docs/de/admin/img/tab-objects_delete.jpg b/docs/de/admin/img/tab-objects_delete.jpg new file mode 100644 index 0000000..f2668e2 Binary files /dev/null and b/docs/de/admin/img/tab-objects_delete.jpg differ diff --git a/docs/de/admin/img/tab-states_columns.jpg b/docs/de/admin/img/tab-states_columns.jpg new file mode 100644 index 0000000..5637adb Binary files /dev/null and b/docs/de/admin/img/tab-states_columns.jpg differ diff --git a/docs/de/admin/img/tab-states_footer.jpg b/docs/de/admin/img/tab-states_footer.jpg new file mode 100644 index 0000000..02a6463 Binary files /dev/null and b/docs/de/admin/img/tab-states_footer.jpg differ diff --git a/docs/de/admin/img/tab-system_2017-01-19-09_33_54-ioBroker.jpg b/docs/de/admin/img/tab-system_2017-01-19-09_33_54-ioBroker.jpg new file mode 100644 index 0000000..89a25b6 Binary files /dev/null and b/docs/de/admin/img/tab-system_2017-01-19-09_33_54-ioBroker.jpg differ diff --git a/docs/de/admin/img/tab-system_2017-01-19-09_40_07-ioBroker.jpg b/docs/de/admin/img/tab-system_2017-01-19-09_40_07-ioBroker.jpg new file mode 100644 index 0000000..f301bb3 Binary files /dev/null and b/docs/de/admin/img/tab-system_2017-01-19-09_40_07-ioBroker.jpg differ diff --git a/docs/de/admin/img/tab-system_2017-01-19-09_48_46-ioBroker.jpg b/docs/de/admin/img/tab-system_2017-01-19-09_48_46-ioBroker.jpg new file mode 100644 index 0000000..0af5b7c Binary files /dev/null and b/docs/de/admin/img/tab-system_2017-01-19-09_48_46-ioBroker.jpg differ diff --git a/docs/de/admin/img/tab-system_Systemeinstellungen.jpg b/docs/de/admin/img/tab-system_Systemeinstellungen.jpg new file mode 100644 index 0000000..4b4f841 Binary files /dev/null and b/docs/de/admin/img/tab-system_Systemeinstellungen.jpg differ diff --git a/docs/de/admin/img/tab-system_Verwahrungsorte2.jpg b/docs/de/admin/img/tab-system_Verwahrungsorte2.jpg new file mode 100644 index 0000000..79bc714 Binary files /dev/null and b/docs/de/admin/img/tab-system_Verwahrungsorte2.jpg differ diff --git a/docs/de/admin/img/tab-user_01-1.jpg b/docs/de/admin/img/tab-user_01-1.jpg new file mode 100644 index 0000000..f253e0d Binary files /dev/null and b/docs/de/admin/img/tab-user_01-1.jpg differ diff --git a/docs/de/admin/img/tab-user_Groups.jpg b/docs/de/admin/img/tab-user_Groups.jpg new file mode 100644 index 0000000..5ef5fa3 Binary files /dev/null and b/docs/de/admin/img/tab-user_Groups.jpg differ diff --git a/docs/de/admin/tab-adapters.md b/docs/de/admin/tab-adapters.md new file mode 100644 index 0000000..37b6aaf --- /dev/null +++ b/docs/de/admin/tab-adapters.md @@ -0,0 +1,148 @@ +# Der Reiter Adapter + +Hier werden die verfügbaren und installierten Adapter angezeigt und verwaltet. + +![yunkong2_image_bpi_20160910](img/yunkong2_Image_BPi_20160910.jpg) + +## Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. +Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. + +![yunkong2_adapter_admin_002aa](img/tab-adapters_002aa.jpg) + +### **Die Icons im einzelnen:** + +![](img/tab-adapters_icons01_20170108-e1483882554815.jpg) + +### **1.)  nur installierte Adapter anzeigen** + +Bei Anwahl dieses Icons werden nur noch die bereits installierten Adapter angezeigt (Toggle-Funktion) + +### **2.) Adapter mit Updates anzeigen** + +Bei Anwahl dieses Icons werden nur noch Adapter angezeigt, zu denen ein Update vorliegt (Toggle-Funktion) + +Hinter den updatefähigen Adaptern findet sich in der Spalte **_installiert_** ein Update-Icon. +Durch Klick auf diesen Button wird der entsprechende Adapter auf die neueste Version gebracht. + +Außerdem erscheint ein weiteres Icon in der Titelzeile: + +![yunkong2_adapter_admin_002b](img/tab-adapters_002b.jpg) + +Durch Anklicken dieses Icons werden alle verfügbaren Adapter aktualisiert. + +### **3.) Adapter aus eigener URL installieren** + +Über das Octocat-Icon können Adapter aus eigenen Pfaden (URL oder Dateipfade) +oder Vorabversionen von GitHub installiert werden. + +Nach Anklicken dieses Icons öffnet sich ein entsprechendes Auswahlfenster: + +![yunkong2_adapter_admin_002c_github](img/tab-adapters_002c_GitHub.jpg) + +Unter dem Reiter **_Von github_** wird einfach im Pulldownmenü der gewünschte +Adapter ausgewählt und die neueste Vorabversion wird installiert. + +Bei Anwahl des Reiters Beliebig kann ein bliebeiger Dateiupfad oder ein beliebiger +URL (z.B. ein URL zu einem externen Adapterentwickler) in das Feld eingegeben werden und der entsprechende Adapter installiert werden. + +![yunkong2_adapter_admin_002c_ownfile](img/tab-adapters_002c_ownFile.jpg) + +### **4.) Expertenmodus einschalten** + +Der Expertenmodus ermöglicht es auch ältere Versionen eines Adapters zu installieren. +Wenn dieser Button angewählt ist, erscheint ganz rechts zu jedem Adapter ein pulldownmenü (4) +über das frühere Versionen installiert werden können. + +![](img/tab-adapters_icons02_20170108.jpg) + +### **5.) Nach Updates suchen** + +Bei jedem Neustart wird automatisch nach Updates gesucht. Über diesen Button kann man aber die Suche manuell anstoßen. + +Wenn Updates in dem unter [Systemeinstellungen](#Systemeinstellungen) eingestellten +Repository zur Verfügung stehen, wird die Schrift des Reiters **_Adapter_** in grüner Farbe dargestellt. + +### **5.) Sortierung ändern** + +Mit diesem Button wird die Sortierung der Adapter auf dieser Seite geändert. + +Bei aktivem Button sind alle Adapter alphabetisch sortiert, wobei zuerst ein +Block mit den installierten Adaptern, danach einer mit noch nicht installierten +Adaptern angezeigt wird. Jeder dieser beiden Blöcke ist in sich alphabetisch sortiert. + +Ist dieser Button nicht aktiv, werden die Adapter nach Themen sortiert. + +Dann sind auch die nächsten beiden Icons sichtbar. + +### **6.) Alle Themengebiete zuklappen** + +### **7.) Alle Themengebiete aufklappen** + +Auf der rechten Seite befinden sich auch noch zwei Buttons + +![yunkong2_adapter_admin_003a](img/tab-adapters_003a.jpg) + +### **8.) Reiter editieren** + +Mit diesem Button kann man nicht benötigte Reiter ausblenden und nicht sichtbare einblenden. + +### **9.) Systemeinstellungen** + +Hier werden grundlegende Parameter für yunkong2 eingestellt. + +## Der Seiteninhalt + +![yunkong2_admin_adapter_inhalt01](img/tab-adapters_Inhalt01.jpg) Auf der Seite werden +die Adapter tabellarisch dargestellt. Die Tabelle besteht aus folgenden Spalten: + +### **1.) Name** + +In dieser Spalte werden die Namen der Adapter mit den dazugehörigen Icons aufgeführt. +Ist über das Icon (5) in der Titelzeile das Gruppieren von Adaptern angewählt erscheinen hier auch die Gruppennamen. + +### **2.) Beschreibung** + +Hier befindet sich eine kurze Beschreibung der Funktion des Adapters + +### **3.) Schlüsselworte** + +Hier sind einige Suchbegriffe aufgeführt, die mit dem Adapter assoziiert werden. + +### **4.) Version** + +Die verfügbare Version wird hier angezeigt. Zur Übersicht wird der Entwicklungsstand eines +Adapters farblich hinterlegt. (rot = in Planung; gelb = Beta; orange = Alpha; grün = Final). + +### **5.) installiert** + +Diese Spalte gibt verschiedene Informationen über den Installationsstatus dieses Adapters. +Zum einen steht dort die Versionsnummer des installierten Adapters. Ist diese fettgedruckt liegt ein +Update vor. Dahinter steht in eckigen Klammern die Anzahl der von diesem Adapter installierten Instanzen, +wie viele davon aktiviert sind und wie deren Status ist. So bedeutet [2/1], dass von diesem Adapter +zwei Instanzen existieren, wovon eine aktiviert ist und ohne Probleme läuft (letzteres ist an der grünen +Farbe der zweiten Zahl zu erkennen). Weiter rechts befindet sich ein Update-Icon wenn zu diesem Adapter +ein Update vorliegt Adapter. Durch Anklicken dieses Icons wird der Updatevorgang gestartet. + +### **6.) Plattform** + +Hier wird angegeben auf welcher Softwareplattform dieser Adapter beruht. Üblicherweise ist dies +javascript unter nodejs. + +### **7.) Lizenz** + +Dies ist die Lizenz unter der der Adapter zur Verfügung gestellt wird. Die Lizenzbedingungen +finden sich üblicherweise im readme. Verlangt die Lizenz, dass sie vom Enduser akzepiert werden muss, +wird ein entsprechendes Fenster mit den Lizenzbedingungen beim Erstellen einer Instanz angezeigt. + +### **8.) Installieren** + +In dieser Spalte befinden sich verschiedene Buttons für die Installation und für Hilfe. + +![](img/tab-adapters_icons02_20170108.jpg) + +1. (+) Hiermit wird eine Instanz des Adapters hinzugefügt. Diese muss im Reiter Instanzen noch konfiguriert und aktiviert werden. Bei den meisten Adaptern können beliebig viele Instanzen installiert werden, z.B. um unterschiedliche Hardware anzusprechen. Sollte dies nicht möglich sein, öffnrt sich ein Fenster mit einer entsprechenden Fehlermeldung. +2. (?) Wenn dieser Button aktiv ist, verlinkt er zu der Hilfeseite zu dem Adapter. Diese befindet sich üblicherweise auf GitHub, wo auch der Adapter gepflegt wird. +3. (Mülleimer) Dieser Button löscht den Adapter und alle bereits installierten Instanzen +4. (Pulldownmenü) Über dieses Menü können frühere Versionen des jeweiligen Adapters installiert werden. Dieses Pulldownmenü ist nur im Expertenmodus sichtbar. \ No newline at end of file diff --git a/docs/de/admin/tab-enums.md b/docs/de/admin/tab-enums.md new file mode 100644 index 0000000..2f94df5 --- /dev/null +++ b/docs/de/admin/tab-enums.md @@ -0,0 +1,97 @@ +# Der Reiter Aufzählungen + +Hier werden die Favoriten, Gewerke und Räume aus der Homematic-CCU aufgelistet. +Es können auch eigene Aufzählungen angelegt werden, die dann z.B. in Scripts verwendet werden können. + + + +![yunkong2_adapter_admin_enums_01](img/tab-enums_Enums_01.jpg) + +## Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. +Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. + +![yunkong2_adapter_admin_enums_headers_01](img/yunkong2_Adapter_admin_Enums_Headers_01.jpg) + +### **Die Icons im einzelnen:** + +### **1.) Ansicht aktualisieren** + +Sollten gerade erst angelegte Aufzählungen nicht sichtbar sein, +hilft ein Anklicken dieses Icons den Zustand der Seite auf den neuesten Stand zu bringen. + +### **2.) Sortierung ändern** + +Mit diesem Button wird die Sortierung der Objekte auf dieser Seite geändert. + +Bei aktivem Button sind alle Objekte alphabetisch sortiert.  +Ist dieser Button nicht aktiv, werden die Ojekte hierarchisch nach Aufzählungen in Baumstruktur angezeigt. + +Dann sind auch die nächsten beiden Icons sichtbar. + +### **3.) Alle Unterordner zuklappen** + +### **4.) Alle Unterordner aufklappen** + +### **5.) hinzufügen** + +Nach Anwählen dieses Icons können weitere Aufzählungen in der Grundstruktur hinzugefügt werden. +Elemente innerhalb der Ordnerstruktur werden über das (+) Icon rechts (#10) angelegt. +Ein Konfigurationsfenster öffnet sich: + +![yunkong2_adapter_admin_enums_new](img/tab-enums_Enums_new.jpg) + +Hier muss jetzt der Name für die neue Aufzählung ausgewählt werden, +die erzeugte ID wird automatisch angepasst. + +### Der Seiteninhalt + +![yunkong2_adapter_admin_enums_headers_03](img/tab-enums_Enums_Headers_03.jpg) + +Auf der Seite werden die vorhandenen Aufzählungen sowie ihre Mitglieder tabellarisch dargestellt. + +Die Tabelle besteht aus folgenden Spalten (Die Felder unter den Spaltenköpfen 6, 7 und 8 +dienen als Filterkriterien). Die Tabelle im Bild ist nach Hierarchie geordnet und alle Unterpunkte (nodes) wurden aufgeklappt: + +### **6.) ID** + +Hier werden alle Mitglieder der Aufzählungen mit ihren IDs gelistet.Diese Bezeichnung kann +durch Doppelklick oder Anklicken des zugehörigen Bleistift-Icons (#9) geändert werden. +Die vollständige ID von Untergeordneten Strukturen beinhaltet jeweils vorangestellt +ebenfalls die übergeordneten Ebenen. + +### **7.) Name** + +In dieser Spalte wird die Bezeichnung des Mitglieds angegeben. Diese Bezeichnung kann +durch Doppelklick oder Anklicken des zugehörigen Bleistift-Icons (#9) geändert werden. + +### **8.) Mitglieder** + +In dieser spalte werden die Mitglieder einer Aufzählung, bei zu vielen wird nur die Anzahl angezeigt. +Fährt man mit der Maus über das Feld werden alle Mitglieder in einer Bubble Info angezeigt. +Weitere Informationen erhält man über das Info Icon ganz rechts (#12) + +### **9.) Bezeichnungen editieren** + +Nach Anklicken dieses Icons kann man die Bezeichnungen in der Spalte ID und Name editieren. +Dabei erscheinen an dieser Stelle ein ok-Button in Form eines Häkchens und ein Abbrechen-Icon in Form eines (x) + +### **10.) Strukturelement hinzufügen** + +Nach Anklicken dieses Icons öffnet sich ein Dialogfenster in dem ein neues Mitglied innerhalb +der jeweiligen Struktur angelegt werden kann. + +![yunkong2_adapter_admin_enums_new_member](img/tab-enums_Enums_new_Member.jpg) + +Auch hier kann der Name individuell gewählt werden. Die zugehörige ID wird entsprechend der Struktur und des gewählten Namens automatisch erzeugt. + +### **11.) Element löschen** + +Mit dem Mülleimer-Icon wird das Element in dieser Zeile gelöscht + +### **12.) Information** + +Nach Anklicken dieses Icons wird ein weiteres Fenster mit erweiterten Informationen zu dem angewählten Element angezeigt. + +![yunkong2_adapter_admin_enums_info](img/tab-enums_Enums_Info.jpg) \ No newline at end of file diff --git a/docs/de/admin/tab-events.md b/docs/de/admin/tab-events.md new file mode 100644 index 0000000..1cd89bc --- /dev/null +++ b/docs/de/admin/tab-events.md @@ -0,0 +1,67 @@ +# Der Reiter Ereignisse + +In diesem Reiter werden die aktuellen Zustände von allen Datenpunkten angezeigt. Die Werte können auch geändert werden. + +![yunkong2_admin_states_columns](img/tab-events_States_columns.jpg) + +## Der Seiteninhalt + +Auf der Seite werden die vorhandenen Objekte tabellarisch dargestellt. Die Spalten können durch Anklicken der Spaltenköpfe nach den Inhalten der entsprechenden Spalten alphabetisch auf- oder absteigend sortiert werden (Toggle-Funktion). Die Felder darunter dienen der Filterung der Datenpunkte nach eigenen Kriterien. + +Die Tabelle besteht aus folgenden Spalten: + +### **1.) ID** + +Dies ist der eindeutige Name des entsprechenden Datenpunktes, gemäß der Struktur bestehend aus z.B. Name des Adapters.Nummer der Instanz.Gerätename.Kanalname.Datenpunktname. + +### **2.) Eltern Name** + +Der selbe Inhalt wie in Spalte 3 Name. + +### **3.) Name** + +Der Name des Datenpunktes. Dies kann ein automatisch generierter oder ein manuell +vergebener Name sein, der verständlicher ist. Dieser Name muss nicht eindeutig sein. + +### **4.) Wert** + +Hier wird der aktuelle Wert des Datenpunktes angegeben. + +Dieser Wert ist editierbar + +### **5.) Bestätigt** + +Wurde dieser Wert geändert und dieses vom System übernommen ist der Wert _true_, ansonsten _false._ + +### **6.) Quelle** + +Hier wird angegeben, welche Instanz die letzte Änderung des Datenpunktes durchgeführt hat. + +### **7.) Zeit** + +Dies ist der Zeitstempel zu dem der Datenpunkt zuletzt aktualisiert wurde. + +### **8.) geändert** + +Dies ist der Zeitstempel zu dem sich der Wert des Datenpunktes zuletzt geändert hat. + +## Der Seitenfuß + +Im Seitenfuß gibt es noch ein paar Informationen + +![yunkong2_admin_states_footer](img/tab-events_States_footer.jpg) + +### **1.) neu laden** + +Dieses Icon kann angeklickt werden um die Tabelle auf den neuesten Stand zu bringen. + +### **2.) Seiteninformationen** + +Der Info-Block in der Mitte des Seitenfußes gibt zum einen mit dem Pulldownmenü die +Möglichkeit die Zeilen pro Seite einzustellen. Zur Verfügung stehen 20, 100, 200, 500 und 1000 +Zeilen pro Seite. Weiterhin gibt es hier die Information wieviele Seiten es insgesamt gibt, +sowie die Möglichkeit mit den Pfeil-Icons die Seiten vor oder zurück zu blättern. + +### **3.) Datenpunktinformation** + +Diese Information gibt die Gesamtanzahl der existierenden Datenpunkte an sowie den davon auf der aktuellen Seite angezeigten Bereich. \ No newline at end of file diff --git a/docs/de/admin/tab-groups.md b/docs/de/admin/tab-groups.md new file mode 100644 index 0000000..81f7275 --- /dev/null +++ b/docs/de/admin/tab-groups.md @@ -0,0 +1,37 @@ +# Der Reiter Gruppen + +Hier können durch den Klick auf das (+) am unteren linken Bildrand Usergruppen mit unterschiedlichen Rechten angelegt werden. + +![yunkong2_adapter_admin_user_02](img/tab-groups_admin_User_02.jpg) + +## Der Seiteninhalt + +Auf der Seite werden die vorhandenen Gruppen tabellarisch dargestellt. Die Felder in den Spaltenköpfen dienen der Filterung der Tabelle nach eigenen Kriterien. + +Die Tabelle besteht aus folgenden Spalten: + +### **1.) ID** + +Dies ist der eindeutige Name der jeweiligen Gruppe, gemäß der Struktur bestehend aus sytem.group.Gruppenname. + +### **2.) Name** + +Der Name der Gruppe. Diesname ist frei wählbar. Dieser Name muss eindeutig sein. + +### **3.) Beschreibung** + +Hier kann eine Beschreibung hinzugefügt werden, die z.B. auf die Rechte in dieser Gruppe verweist. + +### **4.) Benutzer** + +Hier werden die in dem Reiter **_Benutzer_** angelegten Benutzer angezeigt und können über eine Checkbox der angewählten Gruppe zugeordnet werden. + +### **5.) Rechte anpassen** + +Beim Anklicken des Bleistiftsymbols öffnet sich ein weiteres Fenster, in dem die Rechte dieser Gruppe angepasst werden können. + +![yunkong2_adapter_admin_user_rechte_01](img/tab-groups_User_Rechte_01.jpg) + +### **6.) Neue Gruppe erzeugen** + +Mit diesem Icon kann eine neue Gruppe erzeugt werden, die über die bisherigen Punkte konfiguriert wird. \ No newline at end of file diff --git a/docs/de/admin/tab-hosts.md b/docs/de/admin/tab-hosts.md new file mode 100644 index 0000000..368af3a --- /dev/null +++ b/docs/de/admin/tab-hosts.md @@ -0,0 +1,64 @@ +# Der Reiter Hosts + +Hier werden die verfügbaren Hosts angezeigt. + +Bei einem Standardsystem gibt es nur einen Host. Bei einem [Multihostsystem](http://www.yunkong2.net/?page_id=3068&lang=de) entsprechend mehrere. + +## Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. + +![](img/tab-hosts_Hosts_icons.jpg) + +### **Die Icons im einzelnen:** + +### **1.) Updates abrufen** + +Um zu überprüfen, ob ein Update für den js-controller vorliegt kann man auf diesen Button klicken. Wenn ein Update vorliegt erscheint die Beschriftung des Reiters in grüner Schrift und in der Spalte _**verfügbar**_ wird die neue Version angezeigt. + +### **2.) Filter** + +Mit diesem Fled kann man die Liste der Hosts nach eigenen Wünschen filtern + +## Der Seiteninhalt + +Auf der Seite werden die vorhandenen Hosts tabellarisch dargestellt. + +![](img/tab-hosts_Hosts_01.jpg) + +Die Tabelle besteht aus folgenden Spalten: + +### **3.) Name** + +Dies ist der eindeutige Name des jeweiligenHosts, der im Betriebssystem des Hosts festgelegt wurde. Dieser Name muss eindeutig sein. + +### **4.) Restart Host** + +Mit diesem Button kann der entsprechende Host neu gestartet werden. Der Klick darauf entspricht dem Befehl **_reboot_**. + +### **5.) Typ** + +Angabe auf welcher Engine der Host läuft. + +### **6.) Titel** + +vollständiger Name der Engine, üblicherweise yunkong2.js-controller + +### **7.) Plattform** + +Angabe der Softwarebasis auf der die Engine basiert. + +### **8.) Betriebssystem** + +Angabe des Betriebssystem das auf dem Host läuft. + +### **9.) Verfügbar** + +Angabe der neuesten verfügbaren Version der Engine + +Sollte eine neuere Version der Engine vorliegen kann diese über die Konsole upgedated werden. +Dieses sollte bei Verfügbarkeit immer als erstes durchgeführt werden, bevor mit dem Update der Adapter begonnen wird. + +### **9.) Installiert** + +Angabe der installierten Version der Engine \ No newline at end of file diff --git a/docs/de/admin/tab-instances.md b/docs/de/admin/tab-instances.md new file mode 100644 index 0000000..6529d74 --- /dev/null +++ b/docs/de/admin/tab-instances.md @@ -0,0 +1,123 @@ +# Der Reiter Instanzen + +Hier werden die bereits über den Reiter Adapter installierten Instanzen aufgelistet und können entsprechend konfiguriert werden. + + + +![yunkong2_admin_instanzen_inhalt00](img/tab-instances_Inhalt00.jpg) + +## Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. Zu jedem Icon gibt es eine Kontexthilfe. +Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. Außerdem gibt es noch Informationen zu der Auslastung des Servers + +![yunkong2_admin_instanzen_headline_icons](img/tab-instances_Icons-e1476803621402.jpg) + +### **Die Icons im einzelnen:** + +### **1.)  Administratormodus einschalten** + +Bei Anwahl dieses Icons werden weitere Spalten zur Konfiguration der Instanzen angezeigt (Toggle-Funktion). +Informationen dazu im Abschnitt Seiteninhalt. + +### **2.) Ansicht aktualisieren** + +Sollten gerade erst angelegte Instanzen nicht sichtbar sein, hilft ein Anklicken dieses Icons den +Zustand der Seite auf den neuesten Stand zu bringen. + +### **3.) Statusinformationen zum Server** + +im rechten Teil der Titelzeile befinden sich Informationen über die Aktivitäten der Instanzen sowie der Auslastung des yunkong2 Servers. + +Die ersten Zahlen geben den bisher von den Instanzen verbrauchten Arbeitsspeicher und den restlichen +freien Speicher in MB an. Dahinter den freien Speicher in %. In den eckigen Klammern steht der Name des yunkong2-Servers und die Anzahl der laufenden Prozesse. + +## Der Seiteninhalt + +![yunkong2_admin_instanzen_headline_columns](img/tab-instances_Headline_Columns.jpg) + +Auf der Seite werden die installierten Instanzen der Adapter tabellarisch dargestellt. + +Die Tabelle besteht aus folgenden Spalten: + +### **1.) Zustand** + +Hier wird durch eine Ampel der Zustand der Instanz dargestellt. Weitere Informationen erhält man indem man mit der Maus auf dem Signal stehen bleibt. + +![yunkong2_admin_instanzen_status](img/tab-instances_Instanzen_Status.jpg) + +Nicht alle Instanzen besitzen diese Ampel. Dies ist aber kein Grund zur Panik. Dies sind entweder zeitgesteuerte Instanzen, +die sich nur kurz mit dem Controller verbinden und sich dann sofort wieder abschalten oder wie z.B. vis im Hintergrund weiterlaufen. + +### **2.) Icon** + +Hier wird das Icon angezeigt, das yunkong2-weit für diesen Adapter verwendet wird + +### **3.) Instanz** + +In dieser Spalte steht der Name der Instanz. er setzt sich zusammen aus dem Namen des Adapters sowie einer Zahl, +die in der Reihenfolge der Installation der Instanzen fortlaufend durchnummeriert wird. Die erste Instanz erhält die 0. +Diese Bezeichnung ist die Grundlage für die Bezeichnung der Datenpunkte in yunkong2. + +### 4.) aktiviert + +Hier wird die Instanz gestartet oder angehalten. Das grüne Pause-Zeichen zeigt an, dass der Adapter läuft und durch +den Klick darauf pausiert werden kann, das rote Play-Zeichen zeigt eine gestoppte Instanz, die mit einem Klick gestartet werden kann. + +### **5.) Konfiguration** + +Bei Anklicken dieses Icons wird ein adapterspezifisches Konfigurationsmenü geöffnet. Die entsprechenden Menüs sind +bei den dazugehörigen [Adaptern](http://www.yunkong2.net/?page_id=2236&lang=de) beschrieben. + +### **6.) restart** + +Beim Klick auf dieses Icon wird die entsprechende Instanz neu gestartet + +### **7.) Mülleimer** + +Mit diesem Icon wird die entsprechende Instanz gelöscht. Andere Instanzen des selben Adapters bleiben erhalten. +Auch der Adapter selbst bleibt bestehen. + +### **8.) Weblink** + +Hinter diesem Icon verbirgt sich ein Link auf die Website dieser Instanz. Entweder weil dieser Adapter ein +eigenes Webinterface (mit anderem Port) mitbringt, oder nur einen anderen Pfad. Teilweise führt dieser Link auch auf Hilfeseiten. + +### **9.) Titel** + +Hier wird der Name der Instanz angegeben. Diesen Namen kann man nach den eigenene Wünschen oder Bedürfnissen +änder. Dies ist insbesondere dann sinnvoll, wenn es von einem Adapter mehrere Instanzen (mit sonst gleicher Bezeischnung) +gibt. Dies wäre z.B. bei hm-rpc der Fall, wenn es für RF, Wired und CuxD je eine Instanz gibt. + +### **10.) Zeitplanung** + +Bei Adaptern, die zeitgesteuert gestartet werden, wird hier eingetragen wann dieser Adapter starten soll. +Diese Zeitplanung ist im Format eines [cronjobs](https://de.wikipedia.org/wiki/Cron#Beispiele). +Zur Änderung klickt man auf den Button mit den drei Punkten. Es öffnet sich ein Eingabefenster mit sehr viel Zusatzinformationen und Hilfe. + +![yunkong2_admin_instanzen_cronjob](img/tab-instances_Cronjob.jpg) + +### **11.) Neu starten** + +Wenn diese Checkbox angehakt wird kann hier ebenfalls ein Zeitplan erstellt werden wann diese Instanz neu gestartet werden soll. + +### **12.) Log Stufe** + +In dieser Spalte kann der jeweilige Loglevel für die Instanz angepasst werden. Zur Verfügung stehen debug, +info, warn und error. Standardmäßig steht dieser Wert auf info. Hat man den Eindruck, dass etwas nicht ganz +rund läuft kann man ihn auf debug stellen. dann werden im Reiter log zu dieser Instanz auch debug Informationen +ausgegeben, die helfen können einen Fehler zu finden. Umgekehrt kann man diesen Wert auch höher stellen, +damit das log nicht so umfangreich wird. + +### **13.) RAM Limit** + +Hier kann man vorgeben wieviel Arbeitsspeicher der Instanz vorsorglich bereitgestellt werden soll. +Diese Menge Speicher steht dann anderen Aufgaben nicht mehr zur Verfügung und sollte gerade bei Systemen mit wenig +Arbeitsspeicher nicht zu hoch gewählt werden. Sollte die Instanz vorübergehend mehr Speicher benötigen, wird ihr dieser +vom System selbstverständlich zugeteilt werden aber anschließend sofort wieder für das System freigegeben. In der Zeit, +in dere eine Instanz mehr Speicher benötigt, als ihr reserviert wurde wird der benötigte Speicher rot dargestellt. + +### 14.) RAM Benutzung + +Hier wird der tatsächlich von der Instanz verwendete Arbeitsspeicher angezeigt. Diese Werte werden regelmäßig +aktualisiert. Nach der Aktualisierung erscheinen diese Werte kurz in grüner Schrift. \ No newline at end of file diff --git a/docs/de/admin/tab-log.md b/docs/de/admin/tab-log.md new file mode 100644 index 0000000..d95b4a2 --- /dev/null +++ b/docs/de/admin/tab-log.md @@ -0,0 +1,53 @@ +# Der Reiter Log + +Hier werden die Meldungen des Systems kontinuierlich ausgegeben. +Die neueste Meldung befindet sich oben. + +![](img/tab-log_01.jpg) + +## Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. +Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. + +![](img/tab-log_icons.jpg) + +### **Die Icons im einzelnen:** + +### **1.) Aktualisierung anhalten** + +Bei einem Klick auf diesen Button wird die ständige Aktualisierung der Liste angehalten. +Statt des Pause-Icons erscheint jetzt die Anzahl der neuen, nicht angezeigten Meldungen. + +### **2.) Log aktualisieren** + +Mit diesem Button wird die die Liste aktualisiert. + +### **3.) Log kopieren** + +Nach Anklicken dieses Icons erscheint die Liste als Text. Mit CTRL-A wird der gesamte +Text ausgewählt und mit CTRL-C in die Zwischenablage zur weiteren Bearbeitung eingefügt. + +### **4.) Liste löschen** + +Mit dem Klick auf dieses Icon wird nur die auf dem Bildschirm befindliche Liste gelöscht + +### **5.) Log löschen** + +Mit dem Klick auf dieses Icon wird das gesamte Log auf dem Host endgültig gelöscht. + +### Die Pulldown-Menüs + +### **Instanzen Filter** + +![](img/tab-log_instances.jpg) + +Mit diesem Pulldownmenü können die Meldungen nach der loggenden Instanz gefiltert werden. +In dem Menü werden nur die Instanzen angezeigt, zu denen es auch Einträge auf der Seite gibt. + +### **angezeigter Loglevel** + +![](img/tab-log_loglevel.jpg) + +Mit diesem Menü kann eingestellt werden welcher Schweregrad der Meldung angezeigt werden soll. +Hierbei handelt es sich jedoch nur um einen Filter der vorhandenen Liste. Um für eine Instanz das Logging in einem bestimmten Level festzulegen muss dieses im Reiter _**Instanzen**_ eingestellt werden. \ No newline at end of file diff --git a/docs/de/admin/tab-objects.md b/docs/de/admin/tab-objects.md new file mode 100644 index 0000000..511b963 --- /dev/null +++ b/docs/de/admin/tab-objects.md @@ -0,0 +1,145 @@ +# Der Reiter Objekte + +Unter diesem Reiter befinden sich alle verwalteten Objekte. Zu jeder Instanz wird hier ein Ordner angelegt in dem sich die von ihr angelegten Datenpunkte in einer hierarchischen Struktur befinden. Hier können Objekte auch manuell angelegt und gelöscht werden. Es können ganze Objektstrukturen hoch- oder runtergeladen werden. Ein weiterer Knopf ermöglicht die Anzeige der Expertenansicht. + + + +![yunkong2_admin_objekte_inhalt00](img/tab-objects_Inhalt00.jpg) + +## Die Titelzeile + +in der Titelzeile befinden sich Icons für die wichtigsten Vorgänge. Zu jedem Icon gibt es eine Kontexthilfe. Dazu einfach mit der Maus eine Weile auf dem Icon bleiben. + +![yunkong2_admin_objekte_headline_icons](img/tab-objects_Headline_Icons.jpg) + +### **Die Icons im einzelnen:** + +### **1.) Ansicht aktualisieren** + +Sollten gerade erst angelegte Objekte nicht sichtbar sein, hilft ein Anklicken dieses Icons den Zustand der Seite auf den neuesten Stand zu bringen. + +### **2.) Sortierung ändern** + +Mit diesem Button wird die Sortierung der Objekte auf dieser Seite geändert. + +Bei aktivem Button sind alle Objekte alphabetisch sortiert. Ist dieser Button nicht aktiv, werden die Ojekte hierarchisch nach Instanzen sortiert. + +Dann sind auch die nächsten beiden Icons sichtbar. + +### **3.) Alle Themengebiete zuklappen** + +### **4.) Alle Themengebiete aufklappen** + +### **5.)  Administratormodus** + +Bei Anwahl dieses Icons werden weitere Objekte angezeigt (Toggle-Funktion). Dieses sind die Datenpunkte des Systems. + +### **6.) hinzufügen** + +Nach Anwählen dieses Icons können weitere Objekte hinzugefügt werden. +Ist ein Ordner angewählt wird dieser als _Parent_ in der Objektstruktur übernommen. +Ein Konfigurationsfenster öffnet sich: + +![yunkong2_admin_objekte_addobject](img/tab-objects_AddObject.jpg) + +Hier muss jetzt der Name für das neue Objekt ausgewählt werden, wobei als Typ +gemäß der hierarchischen Struktur ein Gerät, ein Kanal oder ein Datenpunkt zur Verfügung steht. +Als Datenpunkttypen stehen Logikwert, Schalter, Zeichenkette, Zahl, Werteliste, Feld, Objekt und gemischt zur Verfügung. + +Sobald man das Eingabefenster mit ok bestätigt öffnet sich ein weiteres Fenster: + +![yunkong2_admin_objekte_addobjec02t](img/tab-objects_AddObjec02t.jpg) + +Hier können noch einige Daten eingegeben werden. So kann dem Objekt eine Rolle und ein icon hinzugefügt werden. + +Unter den anderen Reitern befinden sich noch weitere Eigenschaften des Objekts. +So eine Information gibt es zu jedem Objekt. + +### **7.) Upload** + +Mit diesem Button wird eine komplette Objektstruktur als json-Datei auf den yunkong2 Server hochgeladen + +### **8.) Download** + +Mit diesem Button wird die ausgewählte Objektstruktur als json-Datei vom yunkong2 +Server heruntergeladen und kann gespeichert werden. + +## Der Seiteninhalt + +![yunkong2_admin_objekte_headline_columns](img/tab-objects_Headline_Columns.jpg) + +Auf der Seite werden die vorhandenen Objekte tabellarisch dargestellt. + +Die Tabelle besteht aus folgenden Spalten (Die Felder unter den Spaltenköpfen 1 und 2 +sowie die Pulldownmenüs der weiteren Spalten dienen als Filterkriterien). +Die Tabelle im Bild ist nach Hierarchie geordnet und alle Unterpunkte (nodes) wurden aufgeklappt: + +### **1.) ID** + +Dieses sind die obersten Ebenen der Objekthierarchie. Hier werden als oberste Ebene z.B. +der Name der Instanz, darunter die jeweilige Struktur der Daten angelegt. + +### **2.) Name** + +In dieser Spalte wird die Bezeichnung des Objekts angegeben. Zusätzlich wird durch ein +vorangestelltes Icon gezeigt um welche Hierarchieebene es sich hier handelt (Gerät, Kanal oder Datenpunkt) + +Die Werte dieser Spalte sind editierbar. + +![yunkong2_admin_objekte_structure01](img/tab-objects_Structure01.jpg) + +### **3.) Typ** + +Der Typ in der Hierarchieebene, der in der Spalte _Name_ bereits durch das vorangestellte Icon ersichtlich war, +wird hier noch einmal explizit genannt. Über das Pulldownmenü im Spaltenkopf kann man Nach diesen +Typen filtern und sich so z.B. nur alle Datenpunkte anzeigen lassen. + +### 4.) Rolle + +Die Rolle gibt an, wie User Interfaces wie .vis und mobile mit diesem Datenpunkt umgehen sollen. +Dies ist im Prinzip die Funktion dieses Objekts kurz über einen Begriff beschrieben. +Hiernach kann man wiederum filtern. Die Werte dieser Spalte sind editierbar. + +### **5.) Raum** + +Wurde dieses Objekt bereits einem Raum zugeordnet, wird dies hier angezeigt. +Auch dies dient u.a. der Filterung bei der Suche nach Objekten. +Die Werte dieser Spalte sind editierbar. So können die Objekte noch nachträglich Räumen zugeordnet werden. +Klickt man das Feld an, öffnet sich ein Popup mit den bisher angelegten Räumen. + +![yunkong2_admin_objekte_rooms](img/tab-objects_Rooms.jpg) + +### **6.) Funktion** + +Diese Spalte enthält das Gewerk, dem das entsprechende Objekt zugeordnet ist. + +Die Werte dieser Spalte sind editierbar. So können die Objekte noch nachträglich +Gewerken zugeordnet werden. Klickt man das Feld an, öffnet sich ein Popup mit den bisher angelegten Gewerken. + +### **7.) Wert** + +Handelt es sich bei dem Objekt um einen Datenpunkt, wird hier der aktuelle Wert dieses Datenpunktes angezeigt. + +### **8.) sonstiges** + +Klickt man das Bleistift-Icon an öffnet sich ein Fenster mit den Eigenschaften dieses Objekts. +Es ist das gleiche Fenster das oben bereits beim Anlegen eines neuen Objekts erschienen ist. +Hier können Eigenschaften des Objekts geändert werden. Diese Funktion ist mit äusserster +Vorsicht zu benutzen und nur, wenn man genau weiß was man damit bewirkt. + +Der Klick auf das Mülleimer-Icon löscht dieses Objekt und **alle** in der +Hierarchie darunterliegenden Objekte auch. Zur Sicherheit erscheint ein Fenster, +in dem die Löschung noch einmal bestätigt werden muss. + +![yunkong2_admin_objekte_delete](img/tab-objects_delete.jpg) + +Das Zahnrad-Icon erscheint nur, wenn mindestens eine History-Instanz installiert ist (History, InfluxDB oder SQL). +Hier kann der Datenpunkt für das Loggen der historischen Daten konfiguriert werden. Nähere Informationen dazu +finden sich in der Beschreibung des [History-Adapters](http://www.yunkong2.net/?page_id=144&lang=de). + +Über das Zahnrad in der Titelzeile kann diese Aktion zeitgleich für alle Datenpunkte durchgeführt werden, +die den aktuellen Filterkriterien entsprechen. Es ist daher sorgsam zu prüfen, ob die Filterkriterien dieser +Seite so ausgewählt sind, dass auch nur die gewünschten Datenpunkte dabei sind. + +Das Pulldownmenü zum Filtern dieser Spalte bezieht sich auf Datenpunkte mit geloggten Daten. +Hier stehen _mit_, _ohne_ und _alle_ sowie die installierten History-Instanzen zur Verfügung. \ No newline at end of file diff --git a/docs/de/admin/tab-states.md b/docs/de/admin/tab-states.md new file mode 100644 index 0000000..40e5f02 --- /dev/null +++ b/docs/de/admin/tab-states.md @@ -0,0 +1,72 @@ +# Der Reiter Zustände + +In diesem Reiter werden die aktuellen Zustände von allen Datenpunkten angezeigt. +Die Werte können auch geändert werden. + +![yunkong2_admin_states_columns](img/tab-states_columns.jpg) + +## Der Seiteninhalt + +Auf der Seite werden die vorhandenen Objekte tabellarisch dargestellt. +Die Spalten können durch Anklicken der Spaltenköpfe nach den Inhalten der entsprechenden +Spalten alphabetisch auf- oder absteigend sortiert werden (Toggle-Funktion). +Die Felder darunter dienen der Filterung der Datenpunkte nach eigenen Kriterien. + +Die Tabelle besteht aus folgenden Spalten: + +### **1.) ID** + +Dies ist der eindeutige Name des entsprechenden Datenpunktes, gemäß der Struktur +bestehend aus z.B. Name des Adapters.Nummer der Instanz.Gerätename.Kanalname.Datenpunktname. + +### **2.) Eltern Name** + +Der selbe Inhalt wie in Spalte 3 Name. + +### **3.) Name** + +Der Name des Datenpunktes. Dies kann ein automatisch generierter oder ein manuell +vergebener Name sein, der verständlicher ist. Dieser Name muss nicht eindeutig sein. + +### **4.) Wert** + +Hier wird der aktuelle Wert des Datenpunktes angegeben. + +Dieser Wert ist editierbar + +### **5.) Bestätigt** + +Wurde dieser Wert geändert und dieses vom System übernommen ist der Wert _true_, ansonsten _false._ + +### **6.) Quelle** + +Hier wird angegeben, welche Instanz die letzte Änderung des Datenpunktes durchgeführt hat. + +### **7.) Zeit** + +Dies ist der Zeitstempel zu dem der Datenpunkt zuletzt aktualisiert wurde. + +### **8.) geändert** + +Dies ist der Zeitstempel zu dem sich der Wert des Datenpunktes zuletzt geändert hat. + +## Der Seitenfuß + +Im Seitenfuß gibt es noch ein paar Informationen + +![yunkong2_admin_states_footer](img/tab-states_footer.jpg) + +### **1.) neu laden** + +Dieses Icon kann angeklickt werden um die Tabelle auf den neuesten Stand zu bringen. + +### **2.) Seiteninformationen** + +Der Info-Block in der Mitte des Seitenfußes gibt zum einen mit dem Pulldownmenü die Möglichkeit die +Zeilen pro Seite einzustellen. Zur Verfügung stehen 20, 100, 200, 500 und 1000 Zeilen pro Seite. +Weiterhin gibt es hier die Information wieviele Seiten es insgesamt gibt, sowie die Möglichkeit +mit den Pfeil-Icons die Seiten vor oder zurück zu blättern. + +### **3.) Datenpunktinformation** + +Diese Information gibt die Gesamtanzahl der existierenden Datenpunkte an sowie den davon auf der aktuellen Seite angezeigten Bereich. \ No newline at end of file diff --git a/docs/de/admin/tab-system.md b/docs/de/admin/tab-system.md new file mode 100644 index 0000000..042b4b7 --- /dev/null +++ b/docs/de/admin/tab-system.md @@ -0,0 +1,130 @@ +# Die Systemeinstellungen + +Hier werden grundlegende Parameter für yunkong2 eingestellt. + +![Admin Systemeinstellungen](img/tab-system_Systemeinstellungen.jpg) + +## Haupteinstellungen + +### Systemsprache + +damit kann man zwischen Systemsprachen wählen: Deutsch, Englisch, Russisch + +### Einheit Temperatur + +dieses Wert wird von manchen Adaptern verwendet. Möglich ist °C oder °F. + +### Währung + +Momentan benutzt das noch kein Adapter + +### Datumsformat + +wählen Sie wie das Datum im admin und vis angezeigt sein sollte. + +### Trennzeichen + +Komma oder Punkt für Float-Werte + +### Default Historyinstanz + +Diese SQL/History/InfluxDB Adapter Instanz wird benutzt defaultmäßig für flot und rickshaw (Charts). + +## Verwahrungsorte oder Repositories + +![](img/tab-system_Verwahrungsorte2.jpg) + +yunkong2 kann die Adapterliste von unterschiedlichen Quellen beziehen. Bei der Installation sind folgende Quellen eingetragen: + +* **default** - http://download.yunkong2.net/sources-dist.json - Wird täglich um 01:00 am Server generiert. + Der Zugriff ist sehr schnell, die Versionsinformationenkönnen jedoch bis zu 24 Stunden alt sein. +* **online** - https://raw.githubusercontent.com/yunkong2/yunkong2.js-controller/master/conf/sources-dist.json - Repository + wird von einer online Quelle generiert.  Der Zugriff kann sehr lange dauern, dies ist die aktuellste Quelle +* **sources - conf/sources-dist.json** - Wird auch automatisch generiert und dauert auch sehr lange aber die Links können veraltet sein (es können manche Adapter fehlen) + +## Zertifikate + +![](img/tab-system_2017-01-19-09_33_54-yunkong2.jpg) + +Hier ist die zentrale Stelle für die Zertifikate, die für die SSL/HTTPS Kommunikation benutzt werden. Die Zertifikate werden von admin, web, simple-api, socketio benutzt. Defaultmäßig sind Standardzertifikate installiert. Damit kann man nichts verifizieren. Sie dienen nur der SSL-Kommunikation. Weil die Zertifikate offen liegen sollte man eigene (self-signed) Zertifikate benutzen, richtige Zertifikate kaufen oder auf Let's Encrypt umsteigen. Die Kommunikation mit default Zertifikaten ist nicht sicher und falls jemand das Ziel hat den Traffic mitzulesen, könnte dies gemacht werden. Unbedingt eigene Zertifikate installieren. Z.b. unter [linux](http://guides.intertech.de/ssl_certificate_self.html). + +## Let's Encrypt + +![](img/tab-system_2017-01-19-09_40_07-yunkong2.jpg) + +Let’s Encrypt ist eine kostenlose, automatisierte und Open Source _certificate authority_ der unabhängigen Internet Security Research Group (ISRG). + +Nähere Informationen zu Let’s Encrypt gibt es [hier](https://letsencrypt.org/). + +Einige Installationen benutzen Dynamic DNS o.ä. um über eine von dort vergebene Adresse, die eigene domain zu erreichen. yunkong2 unterstützt die automatische Anforderung und Erneuerung von Zertifikaten bei der Let’s Encrypt Organisation. + +Die Option die kostenlosen Zertifikate von Let’s Encrypt zu benutzen existiertin nahezu jedem Adapter, der einen Webserver starten kann und HTTPS unterstützt. + +Wenn man die Option Zertifikate zu nutzen aktiviert, jedoch nicht das automatische Update, versucht die entsprechende Instanz mit gespeicherten Zertifikaten zu arbeiten. + +Wenn die automatischen Updates aktiviert sind, versucht die Instanz Zertifikate bei Let’s Encrypt anzufordern und aktualisiert diese automatisch. + +Die Zertifikate werden beim ersten Aufruf der entsprechenden Adresse zum ersten mal angefordert. D.h. wenn man z.B. "sub.domain.com" als Adresse konfiguriert und ruft anschließend [https://sub.domain.com](https://sub.domain.com/) auf werden die Zertifikate erstmalig angefordert was ein wenig dauern kann bevor die Antwort kommt. + +Die Ausgabe der Zertifikate ist eine komplexe Prozedur, aber wenn man die folgende Erkklärung befolgt sollte es leicht sein, die kostenlosen Zertifikate zu erhalten. + +**Vorgehensweise:** + +1. Ein neues Konto mit der eingegebenen eMail-Adresse muss erstellt werden (Setup dazu in den Systemeinstellungen) +2. Ein zufälliger Schlüssel als Passwort für das Konto wird erzeugt. +3. Wenn das Konto angelegt wurde öffnet das System eine kleine Website auf Port 80 um die Adresse zu bestätigen. +4. Let's encrypt benutzt **immer** den Port **80** um die Adresse zu prüfen. +5. Falls der Port 80 bereits von einem anderen Dienst benutzt wird, kommt Punkt 4 zum tragen - also dem anderen Dienst einen anderen Port zuweisen! +6. Wenn der kleine Webserver gestartet ist wird die Anfrage nach den Zertifikaten für die angegebenen Adressen in den Systemeinstellungen an den Let's encrypt Server gesendet. +7. Der Let's Encrypt Server sendet eine challenge phrase als Antwort auf die Anfrage zurück und versucht nach einer Weile diese challenge phrase unter der Adresse "http://yourdomain:80/.well-known/acme-challenge/" zu lesen. +8. Wenn der Server diese challenge phrase von unsrere Seite zurückbekommt sendet der Let's Encrypt Server die Zertifikate. Diese werden in dem Verzeichnis, das in den Systemeinstellungen eingetragen ist gespeichert. + +Dieses klingt komplex, aber alles was man machen muss ist ein paar Checkboxen zu aktivieren und die eMail-Adresse und die Web-Adresse in den Systemeinstellungen einzutragen. + +Die erhaltenen Zertifikate sind für etwa 90 Tage gültig. Nachdem diese Zertifikate das erste mal ausgestellt wurden wird eine weiterer Task gestartet, der die Gültigkeit automatisch verlängert. + +Dieses Thema ist ziemlich komplex und tausende Dinge können schiefgehen. Wenn es damit nicht klappen sollte wird empfohlen den Cloud-Adapter für den Zugang von unterwegs zu benutzen. + +**Let's Encrypt funktioniert nur mit einer node.js version>=4.5** + +## Statistik + +![](img/tab-system_2017-01-19-09_48_46-yunkong2.jpg) + +yunkong2 admin sendet an download.yunkong2.net folgende Information: + +
+{
+	"uuid": "56cf0d20-XXXX-YYYY-BBBB-66eec47ZZZZZ",
+	"language": "de",
+	"hosts": [
+		{
+			"version": "0.15.1",
+			"platform": "Javascript/Node.js",
+			"type": "win32"
+		}
+	],
+	"adapters": {
+		"admin": {
+			"version": "1.0.2",
+			"platform": "Javascript/Node.js"
+		},
+		"hm-rpc": {
+			"version": "1.1.2",
+			"platform": "Javascript/Node.js"
+		}
+	}
+}
+
+ +Das kann deaktiviert werden indem man Statistik auf "**nichts**"  einstellt. + +Die Entwickler bitten jedoch um diese Informationen: + +
+Wir haben hart daran gearbeitet, dieses Projekt auf die Beine zu stellen.
+Als Gegenleistung bitten wir Sie, uns die Nutzungs-Statistik zu senden.
+Es werden keine privaten Informationen zu yunkong2.org gesendet. 
+Jedes Mal wenn die Adapterliste aktualisiert wird, wird auch die anonymisierte Statistik verschickt.
+Vielen Dank!
+
\ No newline at end of file diff --git a/docs/de/admin/tab-users.md b/docs/de/admin/tab-users.md new file mode 100644 index 0000000..52457d8 --- /dev/null +++ b/docs/de/admin/tab-users.md @@ -0,0 +1,43 @@ +# Der Reiter Benutzer + +Hier können Benutzer angelegt werden. Dazu links unten auf das (+) klicken. Der Administrator ist standardmäßig bereits angelegt. + + + +![yunkong2_adapter_admin_user_01](img/tab-user_01-1.jpg) + +## Der Seiteninhalt + +Auf der Seite werden die vorhandenen Benutzer tabellarisch dargestellt. Die Felder in den Spaltenköpfen dienen der Filterung der Tabelle nach eigenen Kriterien. + +Die Tabelle besteht aus folgenden Spalten: + +### **1.) ID** + +Dies ist der eindeutige Name des jeweiligen Benutzers, gemäß der Struktur bestehend aus sytem.user.Benutzername. + +### **2.) Name** + +Der Name des Benutzers. Dieser Name ist frei wählbar. Dieser Name muss eindeutig sein. + +### **3.) Aktiviert** + +mit dieser Checkbox kann die Vefügbarkeit eines Benutzers aktiviert oder deaktiviert werden. + +### **4.) Gruppen** + +Hier werden die in dem Reiter **_Gruppen_** angelegten Gruppen angezeigt. Hier können über eine Checkbox die User den entsprechenden Gruppen zugeordnet werden. + +![yunkong2_adapter_admin_user_groups](img/tab-user_Groups.jpg) + +### **5.) Neuen Benutzer erzeugen** + +Mit diesem Icon kann ein neuer Benutzer erzeugt werden, der anschließend einer bestehenden Gruppe zugeordnet werden muss. + +### **6.) Bestehenden Benutzer bearbeiten** + +Nach Anwählen eines bestehenden Benutzers in der Liste können mit diesem Icon die Daten dieses Benutzers bearbeitet werden. + +### **7.) Bestehenden Benutzer löschen** + +Mit dem Papierkorb-Icon kann ein bestehender Benutzer gelöscht werden, die bestehenden Gruppen bleiben erhalten. \ No newline at end of file diff --git a/docs/de/img/admin_Systemeinstellungen.jpg b/docs/de/img/admin_Systemeinstellungen.jpg new file mode 100644 index 0000000..557752e Binary files /dev/null and b/docs/de/img/admin_Systemeinstellungen.jpg differ diff --git a/docs/de/img/admin_ioBroker_Adapter_Admin_001a.jpg b/docs/de/img/admin_ioBroker_Adapter_Admin_001a.jpg new file mode 100644 index 0000000..43a98fd Binary files /dev/null and b/docs/de/img/admin_ioBroker_Adapter_Admin_001a.jpg differ diff --git a/docs/de/img/admin_konfiguration.png b/docs/de/img/admin_konfiguration.png new file mode 100644 index 0000000..8f75595 Binary files /dev/null and b/docs/de/img/admin_konfiguration.png differ diff --git a/docs/en/admin.md b/docs/en/admin.md new file mode 100644 index 0000000..d7f97a1 --- /dev/null +++ b/docs/en/admin.md @@ -0,0 +1,67 @@ +# Admin + +The admin adapter is used to configure the whole yunkong2-Installation and all its adapters. +It provides a web-interface, which can be opened by "http://:8081" +in the web browser. This adapter is automatically installed together with yunkong2. + +## Configuration: + +The configuration dialog of the adapter "admin" provides the following settings: +![img_002](img/admin_img_002.png) + +**IP:** the IP-address of the "admin" web-server can be chosen here. +Different IPv4 and IPv6 addresses can be selected. The default value is 0.0.0.0\. +If you think, that 0.0.0.0 is invalid setting, please let it stay there, because it +is absolutely valid. If you change the address, you will be able to reach the web-server +only through this address. **Port:** You can specify the port of the "admin" web-server. +If there are more web servers on the PC or device the port must be customized to avoid problems +of a double port allocation. **Coding:** enable this option if secure https protocol should be used. + +**Authentication:** If you want the authentication with login/password you should enable this check-box. +Default password for user "admin" is "yunkong2" **Buffer:** to speed up the load of the pages enable this option. +Normally only the developer wants to have this option unchecked. + +## Handling: + +The main page of the admin consist of several tabs. **Adapter:** Here the instances of +a adapters can be installed or deleted. With the update button +![img_005](img/admin_img_005.png) +on the top left we can get if the new versions of adapters are available. +![img_001](img/admin_img_001.jpg) + +The available and installed versions of the adapter is shown. For overall view the state of the +adapter is coloured (red=in planning; orange=alpha; yellow=beta). The updates to a newer version of +the adapter are made here also. If there is a newer version the lettering of the tab will be green. +If the question mark icon in the last column is active you can get from there to web site with information of the adapter. +The available adapter are sorted in alphabetical order. Already installed instance are in the upper part of the list. + +**Instance:** The already installed instance are listed here and can be accordingly configured. If the title of the +instance are underlined you can click on it and the corresponding web site will be opened. + +![img_003](img/admin_img_003.png) + +**Objects:** the managed objects (for example setup / variables / programs of the connected hardware) + +![img_004](img/admin_img_004.png) + +**States:** the current states (values of the objects)   +If the adapter history is installed, you can log chosen data points. +The logged data points are selected on the right and appear with a green logo. + +**Scripts:** this tab is only active if the "javascript" adapter is installed. + +**Node-red:** this tab is only visible if the "node-red" adapter installed and enabled. + +**Hosts:** the computer which yunkong2 is installed on. Here the latest version of js-controller can be installed on. +If there is a new version the letters of the tab are green. To search for a new version you have to click on the update +icon on the bottom left corner. + +**Enumeration:** here the favourites, trades and spaces from the CCU are listed. + +**Users:** here the users can be added. To do this click on the (+). By default there is an admin. + +**Groups:** if you click on the (+) on the bottom left you can create usergroups. From the pull-down menu the users get assigned to the groups. + +**Event:** A list of the running updates of the conditions. **Log:** here the log is displayed In the tab instance the the logged log level +of the single instance can be set. In the selection Menu the the displayed minimum log level is selected. If an error occurs the +lettering of the log appears in red. \ No newline at end of file diff --git a/docs/en/img/admin_img_001.jpg b/docs/en/img/admin_img_001.jpg new file mode 100644 index 0000000..55f6dc4 Binary files /dev/null and b/docs/en/img/admin_img_001.jpg differ diff --git a/docs/en/img/admin_img_002.png b/docs/en/img/admin_img_002.png new file mode 100644 index 0000000..69bf5af Binary files /dev/null and b/docs/en/img/admin_img_002.png differ diff --git a/docs/en/img/admin_img_003.png b/docs/en/img/admin_img_003.png new file mode 100644 index 0000000..b3ae626 Binary files /dev/null and b/docs/en/img/admin_img_003.png differ diff --git a/docs/en/img/admin_img_004.png b/docs/en/img/admin_img_004.png new file mode 100644 index 0000000..64c96f7 Binary files /dev/null and b/docs/en/img/admin_img_004.png differ diff --git a/docs/en/img/admin_img_005.png b/docs/en/img/admin_img_005.png new file mode 100644 index 0000000..572e012 Binary files /dev/null and b/docs/en/img/admin_img_005.png differ diff --git a/docs/pt/admin.md b/docs/pt/admin.md new file mode 100644 index 0000000..aa9a243 --- /dev/null +++ b/docs/pt/admin.md @@ -0,0 +1,94 @@ +## Descrição detalhada + +O adaptador de administração é usado para configurar todas os adaptadores no yunkong2. Ele fornece uma interface web, que pode ser chamado sob o ':8081`. Este adaptador é criado diretamente durante a instalação do yunkong2. + +Pelo GUI do adaptador pode ser usada as seguintes funções: + +* Instalação de adaptadores adicionais +* Acesso à visão geral dos objetos +* Acesso à visão geral dos estados dos objetos +* Acesso à administração de usuários e grupos +* Acesso ao arquivo de log +* Administração dos hosts + +## Instalação + +Este adaptador é criado diretamente durante a instalação do yunkong2. Uma instalação manual não é necessária + +## Configuração + +![adapter_admin_konfiguration](img/admin_konfiguration.png) + +#### IP + +Aqui é deve ser inserido o endereço IP sob o qual o adaptador pode ser alcançado. Várias opções Ipv4 e Ipv6 estão disponíveis. +**O default é 0.0.0.0\. Isso não pode ser alterado!** + +#### Porta + +Aqui é definida a porta sob a qual o administrador pode ser chamado. Se vários servidores da Web estiverem sendo executados no servidor, esta porta deve ser adaptada para que não haja problemas com portas duplicadas. + +#### Criptografia + +Se você quiser usar o protocolo seguro https, você deve marcar esta caixa. + +#### Autenticação + +Se você quiser que use uma autenticação, você deve marcar esta caixa. + +## Funcionamento + +Usando o navegador da Web, vá para a página a seguir: + +`:8081` + +## Abas + +Na página principal do administrador existem várias abas. Na instalação básica, as abas são vistas como mostrado. Usando o ícone de lápis na parte superior direita (1), outras abas podem ser adicionadas ou retiradas. + +![yunkong2_adapter_admin_001a](img/admin_yunkong2_Adapter_Admin_001a.jpg) + +Informações detalhadas são fornecidas nos links dos títulos. + +### [Adaptador](admin/tab-adapters.md) + +Aqui os adaptadores disponíveis podem ser instalados e gerenciados. + +### [Instâncias](admin/tab-instances.md) + +Aqui as instâncias já instaladas podem ser configuradas. + +### [Objetos](admin/tab-objects.md) + + + +### [Estados](admin/tab-states.md) + +Os estados atuais dos objetos. + +### [Eventos](admin/tab-events.md) + +Uma lista de atualizações dos estados. + +### [Grupos](admin/tab-groups.md) + +Aqui você pode criar os grupos de usuários e controlar os direitos desse. + +### [Usuários](admin/tab-users.md) + +Aqui você pode criar os usuários e adicionar eles aos grupos existentes. + +### [Enumerações](admin/tab-enums.md) + +Aqui estão listados os favoritos, as kategorias e os quartos. + +### [Hosts](admin/tab-hosts.md) + +Informações sobre o computador onde yunkong2 está instalado. Você pode aqui atualizar a versão do js-controler. Se uma nova versão estiver disponível, a aba aparece em verde. + +### [Log](admin/tab-log.md) + + +### [Configurações do sistema](admin/tab-system.md) + +![Configurações do administrador do sistema](img/admin_Systemeinstellungen.jpg) \ No newline at end of file diff --git a/docs/pt/img/admin_Systemeinstellungen.jpg b/docs/pt/img/admin_Systemeinstellungen.jpg new file mode 100644 index 0000000..557752e Binary files /dev/null and b/docs/pt/img/admin_Systemeinstellungen.jpg differ diff --git a/docs/pt/img/admin_ioBroker_Adapter_Admin_001a.jpg b/docs/pt/img/admin_ioBroker_Adapter_Admin_001a.jpg new file mode 100644 index 0000000..43a98fd Binary files /dev/null and b/docs/pt/img/admin_ioBroker_Adapter_Admin_001a.jpg differ diff --git a/docs/pt/img/admin_konfiguration.png b/docs/pt/img/admin_konfiguration.png new file mode 100644 index 0000000..8f75595 Binary files /dev/null and b/docs/pt/img/admin_konfiguration.png differ diff --git a/docs/ru/admin.md b/docs/ru/admin.md new file mode 100644 index 0000000..9b7eec8 --- /dev/null +++ b/docs/ru/admin.md @@ -0,0 +1,164 @@ + +## Описание + +Драйвер используется для обслуживания и настройки системы yunkong2 и всех установленных драйверов. +Он представляет собой WEB-интерфейс по адресу `:8081` и устанавливается вместе с yunkong2. + + +С помощью WEB-интерфейса, предоставляемого драйвером **admin**, реализуются следующие функции: + +* Установка дополнительных драйверов +* Обзор объектов +* Обзор состояний объектов +* Управление пользователями и группами +* Просмотр журнал (лог-файл) работы системы +* Управление хостами (работа с распределенной системой - более одного хоста) + +## Установка + +Этот драйвер устанавливается вместе с yunkong2, ручная установка не требуется. + +## Настройка + +### Параметры конфигурации + +![yunkong2.admin - driver settings](img/admin_DriverSettings.jpg) + +#### IP + +IP-адрес с которого доступен драйвер (поддерживаются IPv4 и IPv6). Значение по-умолчанию 0.0.0.0, то есть +возможно соединение на любой IP-адрес. + +**Изменять не желательно, можно потерять досуп!** + +#### Port + +Порт, по которому доступен интерфейс драйвера. На сервере может быть запущено +несколько WEB-сервисов и порт 8081 (настройка по-умолчанию) может быть занят, +необходимо исключить конфликт занятого порта. Значение можно изменять. + +#### Шифрование + +Если необходимо использовать протокол HTTPS, необходимо отметить данную опцию. + +#### Аутентификация + +Если необходима аутентификация пользователя для работы с драйвером, +необходимо отметить данную опцию (автоматически включится опция HTTPS). + +#### Кэш + +Необходимо отметить данную опцию, если планируется использовать кэш браузера. + +#### Пользователь по-умолчанию + +Если опция аутентификации отключена, то драйвер admin будет работать от имени пользователя по-умолчанию (выбирается из списка), в противном случае, от имени пользователя при аутентификации. + +#### Проверка обновлений + +Периодичность автоматической проверки обновлений системы и установленных драйверов. +Можно выбрать опцию "ручное" и тогда проверка будет осуществляться только по запросу пользователя. + +## Использование + +В адресной строке WEB-браузера наберите: `:8081` + +### Вкладки + +Главное окно интерфейса состоит из нескольких вкладок. + +![yunkong2.admin - general view](img/admin_GeneralView.jpg) + +#### Вкладка "Драйвера" + +Здесь можно установить или удалить экземпляры драйверов. В списке отображаются доступные для установки драйвера +и их версии, а так же версии установленных. Обновить информацию по версиям можно с помощью кнопки в левом +верхнем углу. В столбце **Версия** предусмотрена цветовая маркировка релиза драйвера +(красный = в планах, желтый = бета-версия, оранжевый = альфа-версия, зеленый = финальная версия). +Если установленная версия драйвера ниже версии на сервере (имеются обновления), то заголовок +станет зеленым и появится в строке драйвера кнопка обновления. Если кнопка со знаком вопроса +в последнем столбце активная, то нажав по ней, можно перейти на сайт **Github** для ознакомления с информацией об драйвере. + +#### Вкладка "Настройки драйверов" + +Здесь отображаются установленные экземпляры драйверов и осуществляется настройка/конфигурирование. +Слева сверху находится кнопка включения режима эксперта - для отображения дополнительных настроек. + +Настройки драйверов: + +* Запуск/станов экземпляра драйвера +* Открытие всплывающего окна с настройками драйвера +* Кнопка перезапуска экземпляра драйвера +* Кнопка удаления экземпляра драйвера +* Если драйвер подразумевает собственный WEB-сервис, будет доступна кнопка перехода в новом окне. + +Если щелкнуть на название драйвера в столбце **Заголовок**, можно изменить название экземпляра. +В режиме эксперта появляются еще два столбца справа: + +* Столбец **Уровень** - выбор из списка уровень подробности ведения журнала работы адаптера (debug, error, warn, info) +* Столбец **Max. RAM** - при необходимости можно ограничить выделение памяти ОЗУ для работы драйвера + +#### Вкладка "Объекты" + +На этой вкладке отображаются объекты системы (переменные, программы, устройства и пр.). +По-умолчанию, системные объекты скрыты, их можно отобразить нажав кнопку **Показать системные объекты** +слева сверху. С помощью кнопок со стрелками вверх/вниз можно загрузить/выгрузить объект(-ы) файлом JSON. +В столбце справа можно нажатием кнопки вызвать окно настроек конкретного объекта (отдельной кнопкой настройки хранения истории) +и удалить объекты. Если значения отображаются красным цветом, значит они еще не подтверждены - флаг `ack = false`. + +#### Вкладка "Состояния" + +Отображение в табличной форме состояний всех объектов системы. В шапке таблицы поля для ввода - фильтры для поиска объекта или группы объектов. + +#### Вкладка "События" + +Отображение в табличной форме изменений состояний объектов в режиме реального времени (можно приостановить, нажав справа сверху соответствующую кнопку). + +#### Вкладки "Группы" и "Пользователи" + +Добавление пользователей и групп, редактирование привилегий. + +#### Вкладка "Категории" + +Добавление/редактирование/удаление категорий (к примеру комнат для работы с адаптером **Scenes**). + +#### Вкладка "Сервера" + +Список серверов с установленным yunkong2, так же здесь отображается версия js-controller на каждом хосте. +Если имеется новая версия, то заголовок вкладки будет отображаться зеленым цветом и появится кнопка +обновления версии js-controller до актуальной. Запросить текущую версию (если отключено автоматическое обновление) +можно с помощью кнопки **Обновить информацию драйвера** в левом нижнем углу окна. +Так же возле имени хоста имеется кнопка перезагрузки js-controller (не OS). + +#### Вкладка "Лог" + +Здесь отображается журнал работы сервера. Сверху слева доступны поля для фильтрации записей. +Можно отображать записи только указанного драйвера, либо всех (включая системный js-controller); +можно выбрать уровень отображения лога (отладка, инфо, предупреждения, ошибки) и фильтровать по значениям. + +Справа сверху находятся кнопки: + +* Кнопка **Задержать вывод сообщений** - вывод сообщений на странице временно приостанавливается (например, когда сообщения появляются слишком быстро, чтобы не пропустить искомое) +* Кнопка **Обновить протокол** - обновить журнал вручную (сообщения должны выводиться в режиме онлайн при активной вкладке) +* Кнопка **Скопировать протокол** - сообщения на экране копируются в буфер обмена для дальнейшего использования (например, для вставки на форум, чтобы описать ошибку) +* Кнопки **Очистить протокол на экране** и **Очистить протокол на сервере** - соответственно очищает вывод сообщений на вкладке **Лог** и полностью удаляет сообщения из журнала на сервере (применять осторожно). + +#### Вкладка "Скрипты" + +Эта вкладка активна только если установлен драйвер **Javascript/Coffescript Script Engine**. +Здесь можно создавать/удалять/редактировать скрипты для автоматизации. +Более подробно смотри описание данного драйвера. + +#### Вкладка "Node-red" и вкладки других драйверов + +Эти вкладки видны только если включен соответствующие драйвер (см. пункт ниже). + +### Общие настройки + +Справа сверху находятся кнопки общих настроек драйвера **Admin**: + +* Кнопка **Видимость вкладок** - можно включать и отключать вкладки, а так же, при установке определенных драйверов, для которых существуют свои вкладки - добавлять их на страницу +* Кнопка **Системные настройки** - дополнительные настройки работы системы такие как: язык интерфейса, формат даты, единицы измерений, активный репозиторий и пр. (группа основные настройки); редактирование, добавление/удаление ссылок на репозитории (группа репозитории); добавление/удаление собственных сертификатов при использовании HTTPS (группа сертификаты); настройка анонимного сбора статистики (группа статистика) +* Кнопка **Выйти** - выход из системы. + +![yunkong2.admin - system settings](img/admin_SystemSettings.jpg) \ No newline at end of file diff --git a/docs/ru/img/admin_DriverSettings.jpg b/docs/ru/img/admin_DriverSettings.jpg new file mode 100644 index 0000000..6cb96e2 Binary files /dev/null and b/docs/ru/img/admin_DriverSettings.jpg differ diff --git a/docs/ru/img/admin_GeneralView.jpg b/docs/ru/img/admin_GeneralView.jpg new file mode 100644 index 0000000..1c70844 Binary files /dev/null and b/docs/ru/img/admin_GeneralView.jpg differ diff --git a/docs/ru/img/admin_SystemSettings.jpg b/docs/ru/img/admin_SystemSettings.jpg new file mode 100644 index 0000000..0705895 Binary files /dev/null and b/docs/ru/img/admin_SystemSettings.jpg differ diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..3b8c4bd --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,712 @@ +'use strict'; + +const less = require('gulp-less'); +const sass = require('gulp-sass'); +const gulp = require('gulp'); +const gutil = require('gulp-util'); +const uglify = require('gulp-uglify'); +const htmlmin = require('gulp-htmlmin'); +const concat = require('gulp-concat'); +const sourcemaps = require('gulp-sourcemaps'); +const materialize = require.resolve('materialize-css'); +const cleanCSS = require('gulp-clean-css'); +const pkg = require('./package.json'); +const iopackage = require('./io-package.json'); +const babel = require('gulp-babel'); +const fs = require('fs'); + +gulp.task('1_words', ['www (json => words.js)', 'admin (json => words.js)']); +gulp.task('2_css', ['iobCSS', 'adminCSS', 'appCSS', 'treeTableCSS', 'configCSS', 'materializeCSS']); +gulp.task('3_js', ['vendorJS', 'materializeJS', 'appJS', 'fancyTreeJS']); //compressApp is last, to give the time for 1_words to be finshed. Because words.js is used in app.js +gulp.task('4_static', ['appHTML', 'aceCopy', 'colorpickerCopy', 'appCopy']); +const fileName = 'words.js'; +const noSort = false; + +/* How to work with language scripts +-------------------------------------------------------- + you can use for edit this tool https://github.com/ldittmar81/yunkong2-i18n-editor + to do that you can convert words from words.js into 6 json files, that i18n editor can understand. Use "www (words.js => json). + + To combine from json files the words.js again, just call www (json => words.js) + + wwwWords2languages - converts src/js/words.js into 6 different json files in src/i18n/xxx/translations.json + wwwLanguages2words - converts 6 different json files from src/i18n/xxx/translations.json into src/js/words.js +-------------------------------------------------------- + You can export just a flat texts and translate them with e.g. google translator. + to do that you can convert words from words.js into 7 txt files. www (words.js => flat) + + To combine from json files the words.js again, just call www (flat => words.js) + + wwwWords2languagesFlat - converts src/js/words.js into 6 different txt files in src/i18n/xxx/flat.txt. Additionally it creates extra files with keys in src/i18n/flat.txt + wwwLanguagesFlat2words - converts src/i18n/xxx/flat.txt into src/js/words.js + + adminWordsXXx is just the same, but for /admin/words.js + */ + +function lang2data(lang, isFlat) { + var str = isFlat ? '' : '{\r\n'; + var count = 0; + for (var w in lang) { + if (lang.hasOwnProperty(w)) { + count++; + if (isFlat) { + str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\r\n'; + } else { + var key = ' "' + w.replace(/"/g, '\\"') + '": '; + str += /*padRight(*/key/*, 42)*/ + '"' + lang[w].replace(/"/g, '\\"') + '",\r\n'; + } + } + } + if (!count) return isFlat ? '' : '{\r\n}'; + if (isFlat) { + return str; + } else { + return str.substring(0, str.length - 3) + '\r\n}\r\n'; + } +} + +function readWordJs(src) { + try { + var words; + if (fs.existsSync(src + 'js/' + fileName)) { + words = fs.readFileSync(src + 'js/' + fileName).toString(); + } else { + words = fs.readFileSync(src + fileName).toString(); + } + + var lines = words.split(/\r\n|\r|\n/g); + var i = 0; + while (!lines[i].match(/^systemDictionary = {/)) { + i++; + } + lines.splice(0, i); + + // remove last empty lines + i = lines.length - 1; + while (!lines[i]) { + i--; + } + if (i < lines.length - 1) { + lines.splice(i + 1); + } + + lines[0] = lines[0].replace('systemDictionary = ', ''); + lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}'); + words = lines.join('\n'); + var resultFunc = new Function('return ' + words + ';'); + + return resultFunc(); + } catch (e) { + return null; + } +} +function padRight(text, totalLength) { + return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); +} +function writeWordJs(data, src) { + var text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n'; + text += '/*global systemDictionary:true */\n'; + text += '\'use strict\';\n\n'; + text += 'systemDictionary = {\n'; + for (var word in data) { + if (data.hasOwnProperty(word)) { + text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); + var line = ''; + for (var lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; + } + } + if (line) { + line = line.trim(); + line = line.substring(0, line.length - 1); + } + text += line + '},\n'; + } + } + text += '};\n'; + if (src.indexOf('admin') === -1) { + fs.writeFileSync(src + 'js/' + fileName, text); + } else { + fs.writeFileSync(src + fileName, text); + } +} + +const EMPTY = ''; + +function words2languages(src) { + var langs = { + 'en': {}, + 'de': {}, + 'ru': {}, + 'pt': {}, + 'nl': {}, + 'fr': {}, + 'it': {}, + 'es': {}, + 'pl': {} + }; + var data = readWordJs(src); + if (data) { + for (var word in data) { + if (data.hasOwnProperty(word)) { + for (var lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (var j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (var l in langs) { + var keys = Object.keys(langs[l]); + if (!noSort) keys.sort(); + var obj = {}; + for (var k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + if (!fs.existsSync(src + 'i18n/' + l)) { + fs.mkdirSync(src + 'i18n/' + l); + } + + fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); + } + } else { + console.error('Cannot read or parse ' + fileName); + } +} +function words2languagesFlat(src) { + var langs = { + 'en': {}, + 'de': {}, + 'ru': {}, + 'pt': {}, + 'nl': {}, + 'fr': {}, + 'it': {}, + 'es': {}, + 'pl': {} + }; + var data = readWordJs(src); + if (data) { + for (var word in data) { + if (data.hasOwnProperty(word)) { + for (var lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (var j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + var keys = Object.keys(langs.en); + if (!noSort) keys.sort(); + for (var l in langs) { + var obj = {}; + for (var k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + langs[l] = obj; + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (var ll in langs) { + if (!fs.existsSync(src + 'i18n/' + ll)) { + fs.mkdirSync(src + 'i18n/' + ll); + } + + fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en)); + } + fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n')); + } else { + console.error('Cannot read or parse ' + fileName); + } +} +function languagesFlat2words(src) { + var dirs = fs.readdirSync(src + 'i18n/'); + var langs = {}; + var bigOne = {}; + var order = ['en', 'de', 'ru', 'pt', 'nl', 'fr', 'it', 'es', 'pl']; + dirs.sort(function (a, b) { + var posA = order.indexOf(a); + var posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) return 1; + if (a < b) return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) return 1; + if (posA < posB) return -1; + return 0; + } + }); + var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split(/\r\n|\n|\r/); + + for (var l = 0; l < dirs.length; l++) { + if (dirs[l] === 'flat.txt') continue; + var lang = dirs[l]; + var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split(/\r\n|\n|\r/); + langs[lang] = {}; + keys.forEach(function (word, i) { + langs[lang][word] = values[i]; + }); + + var words = langs[lang]; + for (var word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + var aWords = readWordJs(); + + var temporaryIgnore = ['flat.txt']; + if (aWords) { + // Merge words together + for (var w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual ' + fileName + ': ' + w); + bigOne[w] = aWords[w] + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src); +} +function languages2words(src) { + var dirs = fs.readdirSync(src + 'i18n/'); + var langs = {}; + var bigOne = {}; + var order = ['en', 'de', 'ru', 'pt', 'nl', 'fr', 'it', 'es', 'pl']; + dirs.sort(function (a, b) { + var posA = order.indexOf(a); + var posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) return 1; + if (a < b) return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) return 1; + if (posA < posB) return -1; + return 0; + } + }); + for (var l = 0; l < dirs.length; l++) { + if (dirs[l] === 'flat.txt' || dirs[l] === '.i18n-editor-metadata') continue; + var lang = dirs[l]; + langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); + langs[lang] = JSON.parse(langs[lang]); + var words = langs[lang]; + for (var word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word].replace(/<\/ i>/g, '').replace(/<\/ b>/g, '').replace(/<\/ span>/g, '').replace(/% s/g, ' %s'); + } + } + } + } + // read actual words.js + var aWords = readWordJs(); + + var temporaryIgnore = ['pt', 'fr', 'nl', 'es', 'pl']; + if (aWords) { + // Merge words together + for (var w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual ' + fileName + ': ' + w); + bigOne[w] = aWords[w] + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src); +} + +gulp.task('www (words.js => json)', done => { + words2languages('./src/'); + done(); +}); + +gulp.task('www (words.js => flat)', done => { + words2languagesFlat('./src/'); + done(); +}); + +gulp.task('www (flat => words.js)', done => { + languagesFlat2words('./src/'); + done(); +}); + +gulp.task('www (json => words.js)', done => { + languages2words('./src/'); + done(); +}); + +gulp.task('admin (words.js => json)', done => { + words2languages('./admin/'); + done(); +}); + +gulp.task('admin (words.js => flat)', done => { + words2languagesFlat('./admin/'); + done(); +}); + +gulp.task('admin (flat => words.js)', done => { + languagesFlat2words('./admin/'); + done(); +}); + +gulp.task('admin (json => words.js)', done => { + languages2words('./admin/'); + done(); +}); + +gulp.task('updatePackages', done => { + iopackage.common.version = pkg.version; + iopackage.common.news = iopackage.common.news || {}; + if (!iopackage.common.news[pkg.version]) { + var news = iopackage.common.news; + var newNews = {}; + + newNews[pkg.version] = { + en: 'news', + de: 'neues', + ru: 'новое', + pt: 'novidades', + nl: 'nieuws', + fr: 'nouvelles', + it: 'notizie', + es: 'noticias', + pl: 'aktualności' + }; + iopackage.common.news = Object.assign(newNews, news); + } + fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); + done(); +}); + +gulp.task('updateReadme', done => { + var readme = fs.readFileSync('README.md').toString(); + var pos = readme.indexOf('## Changelog\n'); + if (pos !== -1) { + var readmeStart = readme.substring(0, pos + '## Changelog\n'.length); + var readmeEnd = readme.substring(pos + '## Changelog\n'.length); + + if (readme.indexOf(version) === -1) { + var timestamp = new Date(); + var date = timestamp.getFullYear() + '-' + + ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + + ('0' + (timestamp.getDate()).toString(10)).slice(-2); + + var news = ''; + if (iopackage.common.news && iopackage.common.news[pkg.version]) { + news += '* ' + iopackage.common.news[pkg.version].en; + } + + fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); + } + } + done(); +}); + +gulp.task('materializeCSS', () => { + gulp.src(['./src/materialize-css/sass/**/*.scss']) + .pipe(sass({ + paths: [ ] + })) + .pipe(concat('materialize.css')) + .pipe(cleanCSS({compatibility: 'ie8'})) + .pipe(gulp.dest('./www/lib/css')); + +}); + +gulp.task('materializeJS', () => { + return gulp.src([ + './src/materialize-css/js/global.js', + './src/materialize-css/js/component.js', + './src/materialize-css/js/anime.min.js', + './src/materialize-css/js/cash.js', + './src/materialize-css/js/cards.js', + './src/materialize-css/js/tabs.js', + './src/materialize-css/js/dropdown.js', + './src/materialize-css/js/toasts.js', + './src/materialize-css/js/modal.js', + './src/materialize-css/js/select.js', + './src/materialize-css/js/forms.js', + './src/materialize-css/js/range.js', + './src/materialize-css/js/collapsible.js', + './src/materialize-css/js/chips.js', + './src/materialize-css/js/datepicker.js', + './src/materialize-css/js/autocomplete.js', + './src/materialize-css/js/timepicker.js', + './src/materialize-css/js/tooltip.js', + './src/materialize-css/js/autocomplete.js', + './src/colorpicker/js/materialize-colorpicker.js' + ]) + .pipe(sourcemaps.init()) + .pipe(concat('materialize.js')) + .pipe(babel({ + plugins: [ + 'transform-es2015-arrow-functions', + 'transform-es2015-block-scoping', + 'transform-es2015-classes', + 'transform-es2015-template-literals' + ] + })) + //.pipe(uglify()) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/lib/js')); +}); + +gulp.task('configCSS', () => { + gulp.src([ + './src/lib/css/iob/selectID.less', + './src/less/adapter.less', + './src/less/materializeCorrect.less' + ]) + .pipe(sourcemaps.init()) + .pipe(less({ + paths: [ ] + })) + .pipe(concat('adapter.css')) + .pipe(cleanCSS({compatibility: 'ie8'})) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/css')); +}); + +gulp.task('iobCSS', () => { + return gulp.src(['./src/lib/css/iob/*.less']) + .pipe(sourcemaps.init()) + .pipe(less({ + paths: [ ] + })) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/lib/css/iob')); +}); + +// for older selectID +gulp.task('adminCSS', () => { + return gulp.src(['./src/less/admin.less']) + .pipe(sourcemaps.init()) + .pipe(less({ + paths: [ ] + })) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/css/')); +}); + +gulp.task('treeTableCSS', () => { + return gulp.src(['./src/lib/css/jquery.treetable.theme.less']) + .pipe(sourcemaps.init()) + .pipe(less({ + paths: [ ] + })) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/lib/css')); +}); + +gulp.task('fancyTreeJS', () => { + return gulp.src([ + './src/lib/js/jquery.fancytree-all.js' + ]) + .pipe(sourcemaps.init()) + .pipe(concat('jquery.fancytree-all.min.js')) + .pipe(uglify()) + .on('error', function (err) { + gutil.log(gutil.colors.red('[Error]'), err.toString()); + }) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/lib/js')); +}); + +gulp.task('appJS', () => { + return gulp.src([ + './src/js/*.js', + '!./src/js/adapter-settings.js' + ]) + .pipe(sourcemaps.init()) + .pipe(concat('app.js')) + .pipe(uglify()) + .on('error', function (err) { + gutil.log(gutil.colors.red('[Error]'), err.toString()); + }) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/js')); +}); + +gulp.task('appHTML', () => { + return gulp.src([ + './src/indexStart.html', + './src/admin*.html', + './src/indexEnd.html' + ]) + .pipe(sourcemaps.init()) + .pipe(concat('index.html')) + .pipe(htmlmin({collapseWhitespace: true, removeComments: true})) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/')); +}); + +gulp.task('appCSS', () => { + gulp.src([ + './src/less/*.less', + './src/colorpicker/less/*.less', + '!./src/less/adapter.less' + ]) + .pipe(sourcemaps.init()) + .pipe(less({ + paths: [ ] + })) + .pipe(concat('app.css')) + .pipe(cleanCSS({compatibility: 'ie8'})) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/css')); +}); + +gulp.task('vendorJS', () => { + return gulp.src([ + './src/lib/js/jquery-3.2.1.min.js', + './src/lib/js/jquery-migrate-3.0.1.js', + './src/lib/js/jquery-ui.min.js', + './src/lib/js/colResizable-1.6.js', + './src/lib/js/jquery.multiselect-1.13.min.js', + './src/lib/js/semver.min.js', + './src/lib/js/ace-1.2.0/ace.js', + './src/lib/js/loStorage.js', + './src/lib/js/translate.js', + './src/lib/js/jquery.fancytree-all.js', + './src/lib/js/jquery.treetable.js', + './src/lib/js/jquery.ui.touch-punch.min.js', + './src/lib/js/selectID.js', + './src/lib/js/cron/jquery.cron.locale.js', + './src/lib/js/cron/jquery.cron.words.js', + './src/lib/js/cron/jquery.cron.js', + './src/lib/js/cron/cron2text.js', + './src/lib/js/showdown.min.js' + ]) + .pipe(sourcemaps.init()) + .pipe(concat('vendor.js')) + .pipe(uglify()) + .on('error', function (err) { + gutil.log(gutil.colors.red('[Error]'), err.toString()); + }) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/lib/js')); +}); + +gulp.task('colorpick.min', () => { + return gulp.src([ + './src/lib/js/colResizable-1.6.js' + ]) + .pipe(sourcemaps.init()) + .pipe(concat('colResizable-1.6.min.js')) + .pipe(uglify()) + .on('error', function (err) { + gutil.log(gutil.colors.red('[Error]'), err.toString()); + }) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./www/lib/js')); +}); + +gulp.task('appCopy', ['colorpick.min'], () => { + return gulp.src([ + './src/**/*.*', + '!./src/i18n/**/*', + '!./src/*.html', + '!./src/lib/js/jquery.migrate-3.0.1.js', + '!./src/lib/js/jquery.fancytree-all.js', + '!./src/lib/js/colResizable-1.6.js', + '!./src/**/*.less', + '!./src/js/**/admin*.js', + '!./src/js/**/words.js', + '!./src/materialize-css/**/*', + '!./src/colorpicker/**/*' + ]) + .pipe(gulp.dest('./www')); +}); + +gulp.task('colorpickerCopy', () => { + return gulp.src([ + './src/colorpicker/**/*.png' + ]) + .pipe(gulp.dest('./www')); +}); +gulp.task('aceCopy', () => { + return gulp.src([ + './src/lib/js/ace-1.2.0/mode-json.js', + './src/lib/js/ace-1.2.0/worker-json.js' + ], {base: './src/lib/js/ace-1.2.0/'}) + .pipe(gulp.dest('./www')); +}); +gulp.task('copy', ['appCopy', 'aceCopy', 'colorpickerCopy']); + +gulp.task('watch', () => { + gulp.watch('./src/css/*.less', ['lessApp']); + gulp.watch('./src/lib/css/iob/*.less', ['lessApp']); + gulp.watch(['./src/materialize-css/sass/**/*.scss'], ['sassMaterialize']); + gulp.watch(['./src/js/*.js'], ['compressApp']); +}); + +gulp.task('beta', done => { + var ioPack = require('./io-package.json'); + var pack = require('./package.json'); + ioPack.common.name = 'admin-beta'; + ioPack.common.title = 'Admin Beta'; + ioPack.native.port = 9081; + fs.writeFileSync('./io-package.json', JSON.stringify(ioPack, null, 2)); + pack.name = 'yunkong2.admin-beta'; + fs.writeFileSync('./package.json', JSON.stringify(pack, null, 2)); + done(); +}); + +gulp.task('default', [ + '1_words', + '2_css', + '3_js', + '4_static']); diff --git a/io-package.json b/io-package.json new file mode 100644 index 0000000..025b046 --- /dev/null +++ b/io-package.json @@ -0,0 +1,303 @@ +{ + "common": { + "name": "admin", + "title": "Admin", + "version": "3.5.9", + "news": { + "3.5.9": { + "en": "The log output problem was fixed", + "de": "Das Protokollausgabe-Problem wurde behoben", + "ru": "Проблема с выводом журнала была исправлена", + "pt": "O problema de saída de log foi corrigido", + "nl": "Het probleem met de uitvoer van de log is opgelost", + "fr": "Le problème de sortie du journal a été résolu", + "it": "Il problema di output del log è stato risolto", + "es": "El problema de salida del registro fue arreglado", + "pl": "Problem z wyjściem dziennika został naprawiony" + }, + "3.5.8": { + "en": "Google map was replaces with open street map", + "de": "Google Karte wurde durch open street map ersetzt", + "ru": "Карта Google заменяет собой open street map", + "pt": "Mapa do Google foi substituído por open street map", + "nl": "Google-kaart werd vervangen door open street map", + "fr": "Google map était remplacé par open street map", + "it": "La mappa di Google è stata sostituita con open street map", + "es": "El mapa de Google fue reemplazado por open street map", + "pl": "Mapa Google została zamieniona na open street map" + }, + "3.5.7": { + "en": "Edit of the table entries in configuration dialog was corrected", + "de": "Die Bearbeitung der Tabelleneinträge im Konfigurationsdialog wurde korrigiert", + "ru": "Исправлено редактирование записей таблицы в диалоговом окне конфигурации", + "pt": "A edição das entradas da tabela na caixa de diálogo de configuração foi corrigida", + "nl": "Bewerking van de tabelitems in het configuratiedialoogvenster is gecorrigeerd", + "fr": "L'édition des entrées de la table de dialogue de configuration a été corrigée", + "it": "La modifica delle voci della tabella nella finestra di configurazione è stata corretta", + "es": "Se corrigió la edición de las entradas de la tabla en el cuadro de diálogo de configuración", + "pl": "Edytowano wpisy tabeli w oknie dialogowym konfiguracji" + }, + "3.5.6": { + "en": "Import and export of the instance configuration was implemented.", + "de": "Import und Export der Instanzkonfiguration wurde implementiert.", + "ru": "Был реализован импорт и экспорт конфигурации экземпляра.", + "pt": "Importação e exportação da configuração da instância foi implementada.", + "nl": "Import en export van de instance-configuratie is geïmplementeerd.", + "fr": "L'importation et l'exportation de la configuration d'instance ont été implémentées.", + "it": "È stata implementata l'importazione e l'esportazione della configurazione dell'istanza.", + "es": "Se implementó la importación y exportación de la configuración de la instancia.", + "pl": "Zaimplementowano import i eksport konfiguracji instancji." + }, + "3.5.5": { + "en": "Upload of files was corrected.", + "de": "Upload von Dateien wurde korrigiert.", + "ru": "Исправлена ​​загрузка файлов.", + "pt": "Upload de arquivos foi corrigido.", + "nl": "Upload van bestanden is gecorrigeerd.", + "fr": "Le téléchargement de fichiers a été corrigé.", + "it": "Il caricamento dei file è stato corretto.", + "es": "La carga de archivos fue corregida.", + "pl": "Przesyłanie plików zostało poprawione." + }, + "3.5.3": { + "en": "Dropdown was fixed on touch devices\nSpeedup build of instances", + "de": "Drop-down wurde auf Touch-Geräten behoben\nBeschleunigen Sie das Build von Instanzen", + "ru": "Распаковка была зафиксирована на сенсорных устройствах\nУскорение сборки экземпляров", + "pt": "O menu suspenso foi corrigido em dispositivos sensíveis ao toque\nSpeedup build de instâncias", + "nl": "Dropdown is opgelost op touch-apparaten\nSpeedup build van instanties", + "fr": "Dropdown était fixé sur les appareils tactiles\nAccélération de la création d'instances", + "it": "Il menu a discesa è stato corretto sui dispositivi touch\nSpeedup build di istanze", + "es": "El menú desplegable se corrigió en dispositivos táctiles\nSpeedup construcción de instancias", + "pl": "Rozwinięcie zostało naprawione na urządzeniach dotykowych\nPrzyspieszenie kompilacji wystąpień" + }, + "3.5.1": { + "en": "Error in custom settings was fixed", + "de": "Fehler in benutzerdefinierten Einstellungen wurde behoben", + "ru": "Исправлена ​​ошибка в пользовательских настройках", + "pt": "Erro nas configurações personalizadas foi corrigido", + "nl": "Fout in aangepaste instellingen was opgelost", + "fr": "Une erreur dans les paramètres personnalisés a été corrigée", + "it": "Errore nelle impostazioni personalizzate è stato corretto", + "es": "Se corrigió el error en la configuración personalizada", + "pl": "Naprawiono błąd w ustawieniach niestandardowych" + }, + "3.5.0": { + "en": "Editing of enums was changed\nLogo was updated\nThe function icons were added", + "de": "Die Bearbeitung der Enums wurde geändert\nDas Logo wurde aktualisiert\nDie Funktionssymbole wurden hinzugefügt", + "ru": "Изменено редактирование перечислений\nЛоготип обновлен\nЗначки функций добавлены", + "pt": "Edição de enums foi alterada\nLogo foi atualizado\nOs ícones de função foram adicionados", + "nl": "Het bewerken van de enums is gewijzigd\nLogo is bijgewerkt\nDe functiepictogrammen zijn toegevoegd", + "fr": "L'édition des énumérations a été modifiée\nLe logo a été mis à jour\nLes icônes de fonctions ont été ajoutées", + "it": "La modifica delle enumerazioni è stata modificata\nIl logo è stato aggiornato\nLe icone delle funzioni sono state aggiunte", + "es": "Se modificó la edición de enumeraciones\nLogotipo fue actualizado\nSe agregaron los íconos de función", + "pl": "Zmieniono edycję wyliczeń\nLogo zostało zaktualizowane\nDodano ikony funkcji" + }, + "3.4.9": { + "en": "Support of the custom login screen background\nshow tooltip about refresh on instances page\ntabs are now destroyed after they left", + "de": "Unterstützung des benutzerdefinierten Anmeldebildschirmhintergrunds\nzeige Tooltip zum Aktualisieren auf der Instanzenseite\nTabs werden jetzt zerstört, nachdem sie gegangen sind", + "ru": "Поддержка пользовательского экрана экрана входа в систему\nпоказать подсказку об обновлении на странице экземпляров\nвкладки теперь уничтожаются после того, как они ушли", + "pt": "Suporte do plano de fundo de tela de login personalizado\nmostre a dica de ferramenta sobre a atualização na página de instâncias\nguias agora são destruídas depois que eles saíram", + "nl": "Ondersteuning van de aangepaste inlogschermachtergrond\ntooltip weergeven voor vernieuwen op pagina met instanties\ntabbladen worden nu vernietigd nadat ze zijn vertrokken", + "fr": "Prise en charge de l'arrière-plan de l'écran de connexion personnalisé\naffiche une info-bulle sur l'actualisation de la page des instances\nles onglets sont maintenant détruits après leur départ", + "it": "Supporto dello sfondo della schermata di accesso personalizzato\nmostra tooltip sull'aggiornamento nella pagina delle istanze\nle schede sono ora distrutte dopo che se ne sono andati", + "es": "Soporte del fondo de pantalla de inicio de sesión personalizado\nmostrar información sobre herramientas sobre la actualización en la página de instancias\nlas pestañas ahora se destruyen después de que se fueron", + "pl": "Obsługa niestandardowego tła ekranu logowania\nwyświetl podpowiedź na temat odświeżania strony instancji\nKarty są teraz niszczone po ich odejściu" + }, + "3.4.8": { + "en": "Small GUI corrections", + "de": "Kleine GUI-Korrekturen", + "ru": "Небольшие корректировки GUI", + "pt": "Correções de GUI pequenas", + "nl": "Kleine GUI-correcties", + "fr": "Petites corrections GUI", + "it": "Piccole correzioni della GUI", + "es": "Pequeñas correcciones GUI", + "pl": "Małe poprawki GUI" + }, + "3.4.7": { + "en": "getInterfaces function was added\nScroll position for some tables is saved\nAdded \"filtered out\" information", + "de": "getInterfaces Funktion wurde hinzugefügt\nScroll-Position für einige Tabellen wird gespeichert\nHinzugefügt \"ausgefiltert\" Informationen", + "ru": "Добавлена ​​функция getInterfaces\nПоложение прокрутки для некоторых таблиц сохраняется\nДобавлена ​​информация «отфильтрована»", + "pt": "A função getInterfaces foi adicionada\nPosição de rolagem para algumas tabelas é salva\nAdicionadas informações \"filtradas\"", + "nl": "de functie getInterfaces is toegevoegd\nDe schuifpositie voor sommige tabellen is opgeslagen\nToegevoegd \"gefilterd\" informatie", + "fr": "La fonction getInterfaces a été ajoutée\nLa position de défilement de certaines tables est enregistrée\nAjout d'informations \"filtrées\"", + "it": "è stata aggiunta la funzione getInterfaces\nLa posizione di scorrimento per alcune tabelle viene salvata\nAggiunte informazioni \"filtrate\"", + "es": "Se agregó la función getInterfaces\nLa posición de desplazamiento para algunas tablas se guarda\nSe agregó información \"filtrada\"", + "pl": "Funkcja getInterfaces została dodana\nPozycja przewijania dla niektórych tabel zostaje zapisana\nDodano informacje \"przefiltrowane\"" + }, + "3.4.6": { + "en": "Minor GUI fixes", + "de": "Minor GUI Fixes", + "ru": "Незначительные исправления GUI", + "pt": "Correções de GUI menores", + "nl": "Kleine GUI-oplossingen", + "fr": "Corrections mineures de l'interface graphique", + "it": "Correzioni minori della GUI", + "es": "Correcciones menores de GUI", + "pl": "Drobne poprawki GUI" + }, + "3.4.3": { + "en": "The button in selectID was fixed\ndisk info was added\nThe filter in table mode on adapter tab was showed\nmemAvailable for RAM monitoring is used\nfix select problem in config dialog\nadded the asking about unsaved scripts", + "de": "Die Schaltfläche in selectID wurde behoben\nDatenträgerinfo wurde hinzugefügt\nDer Filter im Tabellenmodus auf der Adapterregisterkarte wurde angezeigt\nmemAvailable für die RAM-Überwachung wird verwendet\nBehebung eines Problems im Konfigurationsdialog\ndie Frage nach nicht gespeicherten Skripten hinzugefügt", + "ru": "Кнопка в selectID была исправлена\nбыла добавлена ​​информация о диске\nБыл показан фильтр в режиме таблицы на вкладке адаптера\nmemAvailable для мониторинга ОЗУ\nисправить проблему выбора в диалоговом окне конфигурации\nдобавлен запрос о несохраненных скриптах", + "pt": "O botão no selectID foi corrigido\ninformação de disco foi adicionada\nO filtro no modo de tabela na guia do adaptador foi mostrado\nmemAvailable for RAM monitoring is used\ncorrija o problema de seleção no diálogo de configuração\nadicionou a pergunta sobre scripts não salvos", + "nl": "De knop in selectID is hersteld\nschijfinfo is toegevoegd\nHet filter in de tabelmodus op het tabblad met de adapter werd getoond\nmemBeschikbaar voor RAM-bewaking wordt gebruikt\nfix select probleem in configuratiedialoog\nhet vragen over niet-opgeslagen scripts toegevoegd", + "fr": "Le bouton dans selectID a été corrigé\ninformations sur le disque a été ajouté\nLe filtre en mode table sur l'onglet de l'adaptateur a été affiché\nmemAvailable pour la surveillance de la RAM est utilisé\nréparer le problème de sélection dans la boîte de dialogue de configuration\najouté la question sur les scripts non sauvegardés", + "it": "Il pulsante in selectID è stato risolto\nsono state aggiunte informazioni sul disco\nÈ stato mostrato il filtro in modalità tabella sulla scheda dell'adattatore\nmemAvailable per il monitoraggio RAM viene utilizzato\nrisolve il problema di selezione nella finestra di configurazione\nha aggiunto la richiesta di script non salvati", + "es": "El botón en selectID fue arreglado\nse agregó información de disco\nSe mostró el filtro en el modo de tabla en la pestaña del adaptador\nmemDisponible para la supervisión de RAM se utiliza\ncorregir el problema de selección en el diálogo de configuración\nagregó la pregunta sobre los guiones no guardados", + "pl": "Przycisk w selectID został naprawiony\ninformacje o dysku zostały dodane\nPokazano filtr w trybie tabeli na karcie adaptera\nmem Dostępne dla monitorowania pamięci RAM\nnapraw wybrany problem w oknie konfiguracji\ndodano pytanie o niezapisane skrypty" + }, + "3.4.2": { + "en": "Fix: height of Select ID Dialog calculated wrong", + "de": "Fix: Höhe des Select ID Dialoges falsch berechnet", + "ru": "Исправлено: высота диалогового окна «Выбор идентификатора»", + "pt": "Correção: altura da caixa de diálogo Selecionar ID calculada errada", + "nl": "Fix: hoogte van Selecteer ID Dialoog berekend verkeerd", + "fr": "Correction: hauteur de la boîte de dialogue Select ID calculée incorrect", + "it": "Correzione: altezza della finestra di dialogo Seleziona ID calcolata erroneamente", + "es": "Solución: altura del cuadro de diálogo Seleccionar ID calculado incorrectamente", + "pl": "Napraw: wysokość okna dialogowego Wybierz identyfikator obliczono nieprawidłowo" + }, + "3.4.1": { + "en": "Fix: Unable to scroll trough Dropdown on Touchscreens\nAdded: Show current Tab in Pagetitle", + "de": "Fix: Scrollen durch Dropdown auf Touchscreens nicht möglich\nHinzugefügt: Zeige den aktuellen Tab in Pagetitle", + "ru": "Исправлено: Не удалось прокрутить прокрутку вниз по сенсорным экранам\nДобавлено: Показать текущую вкладку в Pagetitle", + "pt": "Correção: Não é possível rolar pela lista suspensa em telas sensíveis ao toque\nAdicionado: Mostrar guia atual no Pagetitle", + "nl": "Oplossing: kan niet bladeren door Dropdown op touchscreens\nToegevoegd: Toon het huidige tabblad in Pagetitle", + "fr": "Corrigé: Impossible de faire défiler la liste déroulante sur les écrans tactiles\nAjouté: Afficher l'onglet actuel dans Pagetitle", + "it": "Correzione: impossibile scorrere attraverso il menu a discesa sui touchscreen.\nAggiunta: mostra la scheda corrente in Pagetitle", + "es": "Solución: no se puede desplazar a través de la lista desplegable en pantallas táctiles.\nAgregado: muestra la pestaña actual en el título.", + "pl": "Poprawka: Nie można przewijać listy rozwijanej na ekranach dotykowych\nDodano: Pokaż bieżącą kartę w oknie Pagetitle" + }, + "2.0.11": { + "en": "Configurable event update disable threshold", + "de": "Konfigurierbar Schwellenwert für Ereignisses", + "ru": "Настраиваемое пороговое значение отключения обновления событий", + "pt": "Limite configurável para o número de eventos" + }, + "2.0.9": { + "en": "fix sorting, when using adapters with more than one instance", + "de": "Sortierung für Adapter mit mehr als einer Instanz repariert", + "pt": "Classificação para adaptadores com mais de uma instância" + } + }, + "desc": { + "en": "The configuration of yunkong2 via Web-Interface", + "de": "Die Konfiguration von yunkong2 über das Web-Interface", + "ru": "Конфигурация yunkong2 через веб-интерфейс", + "pt": "A configuração do yunkong2 via Web-Interface", + "fr": "La configuration de yunkong2 via Web-Interface", + "nl": "De configuratie van yunkong2 via de webinterface", + "it": "La configurazione di yunkong2 tramite interfaccia Web" + }, + "docs": { + "en": "docs/en/admin.md", + "ru": "docs/ru/admin.md", + "de": [ + "docs/de/admin.md", + "docs/de/admin/tab-adapters.md", + "docs/de/admin/tab-instances.md", + "docs/de/admin/tab-objects.md", + "docs/de/admin/tab-states.md", + "docs/de/admin/tab-groups.md", + "docs/de/admin/tab-users.md", + "docs/de/admin/tab-events.md", + "docs/de/admin/tab-hosts.md", + "docs/de/admin/tab-enums.md", + "docs/de/admin/tab-log.md", + "docs/de/admin/tab-system.md" + ], + "pt": "docs/pt/admin.md" + }, + "materialize": true, + "mode": "daemon", + "platform": "Javascript/Node.js", + "loglevel": "info", + "icon": "admin.png", + "messagebox": true, + "enabled": true, + "extIcon": "https://git.spacen.net/yunkong2/yunkong2.admin/raw/master/admin/admin.png", + "keywords": [ + "setup", + "config", + "update", + "upgrade", + "system", + "konfiguration", + "administration", + "einrichtung", + "wartung" + ], + "readme": "https://git.spacen.net/yunkong2/yunkong2.admin/blob/master/README.md", + "authors": [ + "bluefox ", + "hobbyquaker " + ], + "dependencies": [ + { + "js-controller": ">=1.2.0" + } + ], + "type": "general", + "license": "MIT", + "logTransporter": true, + "stopBeforeUpdate": true, + "wwwDontUpload": true, + "nogit": true, + "welcomeScreenPro": { + "link": "admin/index.html", + "name": "Admin", + "img": "admin/img/admin.png", + "color": "pink", + "order": 5, + "localLink": true + }, + "localLink": "%protocol%://%ip%:%port%" + }, + "native": { + "port": 8081, + "auth": false, + "secure": false, + "bind": "0.0.0.0", + "cache": false, + + "certPublic": "", + "certPrivate": "", + "certChained": "", + + "ttl": 3600, + "defaultUser": "admin", + "tmpPath": "/tmp", + "tmpPathAllow": false, + "thresholdValue": 200, + + "leEnabled": false, + "leUpdate": false, + "leCheckPort": 80, + + "loginBackgroundColor": "", + "loginBackgroundImage": false, + "loginHideLogo": false, + "loginMotto": "" + }, + "objects": [], + "instanceObjects": [ + { + "_id": "info", + "type": "channel", + "common": { + "name": "Information" + }, + "native": {} + }, + { + "_id": "", + "type": "meta", + "common": { + "name": "user files and images for background image", + "type": "meta.user" + }, + "native": {} + } + ] +} diff --git a/lib/passport.socketio.js b/lib/passport.socketio.js new file mode 100644 index 0000000..97acdfa --- /dev/null +++ b/lib/passport.socketio.js @@ -0,0 +1,109 @@ +var xtend = require('xtend'); + +function parseCookie(auth, cookieHeader) { + var cookieParser = auth.cookieParser(auth.secret); + var req = { + headers:{ + cookie: cookieHeader + } + }; + var result; + cookieParser(req, {}, function (err) { + if (err) throw err; + result = req.signedCookies || req.cookies; + }); + return result; +} + +function authorize(options) { + var defaults = { + passport: require('passport'), + key: 'connect.sid', + secret: null, + store: null, + success: function (data, accept) { + if (data.socketio_version_1) { + accept(); + } else { + accept(null, true); + } + }, + fail: function (data, message, critical, accept) { + if (data.socketio_version_1) { + accept(new Error(message)); + } else { + accept(null, false); + } + } + }; + + var auth = xtend(defaults, options); + + auth.userProperty = auth.passport._userProperty || 'user'; + + if (!auth.cookieParser) { + throw new Error('cookieParser is required use connect.cookieParser or express.cookieParser'); + } + + return function (data, accept) { + + // socket.io v1.0 now provides socket handshake data via `socket.request` + if (data.request) { + data = data.request; + data.socketio_version_1 = true; + } + + data.cookie = parseCookie(auth, data.headers.cookie || ''); + data.sessionID = (data.query && data.query.session_id) || data.cookie[auth.key] || ''; + data[auth.userProperty] = { + logged_in: false + }; + + if (data.xdomain && !data.sessionID) + return auth.fail(data, 'Can not read cookies from CORS-Requests. See CORS-Workaround in the readme.', false, accept); + + auth.store.get(data.sessionID, function (err, session) { + if (err) + return auth.fail(data, 'Error in session store:\n' + err.message, true, accept); + if (!session) + return auth.fail(data, 'No session found', false, accept); + if (!session[auth.passport._key]) + return auth.fail(data, 'Passport was not initialized', true, accept); + + var userKey = session[auth.passport._key][auth.userProperty]; + + if (!userKey) + return auth.fail(data, 'User not authorized through passport. (User Property not found)', false, accept); + + // Because of authentication error removed + /*auth.passport.deserializeUser(userKey, function(err, user) { + if (err) + return auth.fail(data, err, true, accept); + if (!user) + return auth.fail(data, "User not found", false, accept); + data[auth.userProperty] = user; + data[auth.userProperty].logged_in = true; + auth.success(data, accept); + });*/ + data[auth.userProperty] = userKey; + data[auth.userProperty].logged_in = true; + data.client._user = userKey; + auth.success(data, accept); + }); + + }; +} + +function filterSocketsByUser(socketIo, filter) { + var handshaken = socketIo.sockets.manager.handshaken; + return Object.keys(handshaken || {}) + .filter(function (skey) { + return filter(handshaken[skey].user); + }) + .map(function (skey) { + return socketIo.sockets.manager.sockets.sockets[skey]; + }); +} + +exports.authorize = authorize; +exports.filterSocketsByUser = filterSocketsByUser; diff --git a/lib/socket.js b/lib/socket.js new file mode 100644 index 0000000..54ffaea --- /dev/null +++ b/lib/socket.js @@ -0,0 +1,1279 @@ +/* jshint -W097 */ +/* jshint strict: false */ +/* jslint node: true */ +/* jshint -W061 */ +'use strict'; + +const socketio = require('socket.io'); +const request = require('request'); +const path = require('path'); +const fs = require('fs'); + +function IOSocket(server, settings, adapter, objects, states, store) { + if (!(this instanceof IOSocket)) return new IOSocket(server, settings, adapter, objects, states, store); + + const userKey = 'connect.sid'; // const + const cmdSessions = {}; + const that = this; + this.server = null; + this.subscribes = {}; + let cookieParser; + let passport; + + if (settings.auth) { + cookieParser = require('cookie-parser'); + passport = require('passport'); + } + + const passportSocketIo = require('passport.socketio'); + + // do not send too many state updates + const eventsThreshold = { + count: 0, + timeActivated: 0, + active: false, + accidents: 0, + repeatSeconds: 3, // how many seconds continuously must be number of events > value + value: parseInt(settings.thresholdValue, 10) || 200, // how many events allowed in one check interval + checkInterval: 1000 // duration of one check interval + }; + + // static information + const commandsPermissions = { + getObject: {type: 'object', operation: 'read'}, + getObjects: {type: 'object', operation: 'list'}, + getObjectView: {type: 'object', operation: 'list'}, + setObject: {type: 'object', operation: 'write'}, + requireLog: {type: 'object', operation: 'write'}, // just mapping to some command + delObject: {type: 'object', operation: 'delete'}, + extendObject: {type: 'object', operation: 'write'}, + getHostByIp: {type: 'object', operation: 'list'}, + subscribeObjects: {type: 'object', operation: 'read'}, + unsubscribeObjects: {type: 'object', operation: 'read'}, + + getStates: {type: 'state', operation: 'list'}, + getState: {type: 'state', operation: 'read'}, + setState: {type: 'state', operation: 'write'}, + delState: {type: 'state', operation: 'delete'}, + createState: {type: 'state', operation: 'create'}, + subscribe: {type: 'state', operation: 'read'}, + unsubscribe: {type: 'state', operation: 'read'}, + getStateHistory: {type: 'state', operation: 'read'}, + getVersion: {type: '', operation: ''}, + + addUser: {type: 'users', operation: 'create'}, + delUser: {type: 'users', operation: 'delete'}, + addGroup: {type: 'users', operation: 'create'}, + delGroup: {type: 'users', operation: 'delete'}, + changePassword: {type: 'users', operation: 'write'}, + + httpGet: {type: 'other', operation: 'http'}, + cmdExec: {type: 'other', operation: 'execute'}, + sendTo: {type: 'other', operation: 'sendto'}, + sendToHost: {type: 'other', operation: 'sendto'}, + readLogs: {type: 'other', operation: 'execute'}, + + readDir: {type: 'file', operation: 'list'}, + createFile: {type: 'file', operation: 'create'}, + writeFile: {type: 'file', operation: 'write'}, + readFile: {type: 'file', operation: 'read'}, + deleteFile: {type: 'file', operation: 'delete'}, + readFile64: {type: 'file', operation: 'read'}, + writeFile64: {type: 'file', operation: 'write'}, + unlink: {type: 'file', operation: 'delete'}, + rename: {type: 'file', operation: 'write'}, + mkdir: {type: 'file', operation: 'write'}, + chmodFile: {type: 'file', operation: 'write'}, + + authEnabled: {type: '', operation: ''}, + disconnect: {type: '', operation: ''}, + listPermissions: {type: '', operation: ''}, + getUserPermissions: {type: 'object', operation: 'read'} + }; + + function addUser(user, pw, options, callback) { + if (typeof options === 'function') { + callback = options; + options = null; + } + + if (!user.match(/^[-.A-Za-züäößÖÄÜа-яА-Я@+$§0-9=?!&# ]+$/)) { + if (typeof callback === 'function') { + callback('Invalid characters in the name. Only following special characters are allowed: -@+$§=?!&# and letters'); + } + return; + } + + adapter.getForeignObject('system.user.' + user, options, function (err, obj) { + if (obj) { + if (typeof callback === 'function') { + callback('User yet exists'); + } + } else { + adapter.setForeignObject('system.user.' + user, { + type: 'user', + common: { + name: user, + enabled: true, + groups: [] + } + }, options, function () { + adapter.setPassword(user, pw, callback); + }); + } + }); + } + + function delUser(user, options, callback) { + adapter.getForeignObject('system.user.' + user, options, function (err, obj) { + if (err || !obj) { + if (typeof callback === 'function') { + callback('User does not exist'); + } + } else { + if (obj.common.dontDelete) { + if (typeof callback === 'function') { + callback('Cannot delete user, while is system user'); + } + } else { + adapter.delForeignObject('system.user.' + user, options, function (err) { + // Remove this user from all groups in web client + if (typeof callback === 'function') { + callback(err); + } + }); + } + } + }); + } + + function addGroup(group, desc, acl, options, callback) { + let name = group; + if (typeof acl === 'function') { + callback = acl; + acl = null; + } + if (typeof desc === 'function') { + callback = desc; + desc = null; + } + if (typeof options === 'function') { + callback = options; + options = null; + } + if (name && name.substring(0, 1) !== name.substring(0, 1).toUpperCase()) { + name = name.substring(0, 1).toUpperCase() + name.substring(1); + } + group = group.substring(0, 1).toLowerCase() + group.substring(1); + + if (!group.match(/^[-.A-Za-züäößÖÄÜа-яА-Я@+$§0-9=?!&#_ ]+$/)) { + if (typeof callback === 'function') { + callback('Invalid characters in the group name. Only following special characters are allowed: -@+$§=?!&# and letters'); + } + return; + } + + adapter.getForeignObject('system.group.' + group, options, function (err, obj) { + if (obj) { + if (typeof callback === 'function') { + callback('Group yet exists'); + } + } else { + obj = { + _id: 'system.group.' + group, + type: 'group', + common: { + name: name, + desc: desc, + members: [], + acl: acl + } + }; + adapter.setForeignObject('system.group.' + group, obj, options, function (err) { + if (typeof callback === 'function') { + callback(err, obj); + } + }); + } + }); + } + + function delGroup(group, options, callback) { + adapter.getForeignObject('system.group.' + group, options, function (err, obj) { + if (err || !obj) { + if (typeof callback === 'function') { + callback('Group does not exist'); + } + } else { + if (obj.common.dontDelete) { + if (typeof callback === 'function') { + callback('Cannot delete group, while is system group'); + } + } else { + adapter.delForeignObject('system.group.' + group, options, function (err) { + // Remove this group from all users in web client + if (typeof callback === 'function') { + callback(err); + } + }); + } + } + }); + } + + // update session ID, but not ofter than 60 seconds + function updateSession(socket) { + if (socket._sessionID) { + const time = (new Date()).getTime(); + if (socket._lastActivity && time - socket._lastActivity > adapter.config.ttl * 1000) { + socket.emit('reauthenticate'); + return false; + } + socket._lastActivity = time; + if (!socket._sessionTimer) { + socket._sessionTimer = setTimeout(function () { + socket._sessionTimer = null; + adapter.getSession(socket._sessionID, function (obj) { + if (obj) { + adapter.setSession(socket._sessionID, adapter.config.ttl, obj); + } else { + socket.emit('reauthenticate'); + } + }); + }, 60000); + } + } + return true; + } + + function checkPermissions(socket, command, callback, arg) { + if (socket._acl.user !== 'system.user.admin') { + // type: file, object, state, other + // operation: create, read, write, list, delete, sendto, execute, sendToHost, readLogs + if (commandsPermissions[command]) { + // If permission required + if (commandsPermissions[command].type) { + if (socket._acl[commandsPermissions[command].type] && + socket._acl[commandsPermissions[command].type][commandsPermissions[command].operation]) { + return true; + } else { + adapter.log.warn('No permission for "' + socket._acl.user + '" to call ' + command + '. Need "' + commandsPermissions[command].type + '"."' + commandsPermissions[command].operation + '"'); + } + } else { + return true; + } + } else { + adapter.log.warn('No rule for command: ' + command); + } + + if (typeof callback === 'function') { + callback('permissionError'); + } else { + if (commandsPermissions[command]) { + socket.emit('permissionError', { + command: command, + type: commandsPermissions[command].type, + operation: commandsPermissions[command].operation, + arg: arg + }); + } else { + socket.emit('permissionError', { + command: command, + arg: arg + }); + } + } + return false; + } else { + return true; + } + } + + function checkObject(id, options, flag) { + // read rights of object + if (!objects[id] || !objects[id].common || !objects[id].acl || flag === 'list') { + return true; + } + + if (options.user !== 'system.user.admin' && + options.groups.indexOf('system.group.administrator') === -1) { + if (objects[id].acl.owner !== options.user) { + // Check if the user is in the group + if (options.groups.indexOf(objects[id].acl.ownerGroup) !== -1) { + // Check group rights + if (!(objects[id].acl.object & (flag << 4))) { + return false + } + } else { + // everybody + if (!(objects[id].acl.object & flag)) { + return false + } + } + } else { + // Check group rights + if (!(objects[id].acl.object & (flag << 8))) { + return false + } + } + } + return true; + } + + function getAllObjects(socket, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getObjects', callback)) { + if (socket._acl && + socket._acl.user !== 'system.user.admin' && + socket._acl.groups.indexOf('system.group.administrator') === -1) { + const result = {}; + for (const ob in objects) { + if (objects.hasOwnProperty(ob) && checkObject(ob, socket._acl, 4 /* 'read' */)) { + result[ob] = objects[ob]; + } + } + callback(null, result); + } else { + if (typeof callback === 'function') { + callback(null, objects); + } + } + } + } + + function pattern2RegEx(pattern) { + if (!pattern) { + return null; + } + if (pattern !== '*') { + if (pattern[0] === '*' && pattern[pattern.length - 1] !== '*') pattern += '$'; + if (pattern[0] !== '*' && pattern[pattern.length - 1] === '*') pattern = '^' + pattern; + } + pattern = pattern.replace(/\./g, '\\.'); + pattern = pattern.replace(/\*/g, '.*'); + pattern = pattern.replace(/\[/g, '\\['); + pattern = pattern.replace(/]/g, '\\]'); + pattern = pattern.replace(/\(/g, '\\('); + pattern = pattern.replace(/\)/g, '\\)'); + return pattern; + } + + this.subscribe = function (socket, type, pattern) { + //console.log((socket._name || socket.id) + ' subscribe ' + pattern); + if (socket) { + socket._subscribe = socket._subscribe || {}; + } + if (!this.subscribes[type]) this.subscribes[type] = {}; + + let s; + if (socket) { + s = socket._subscribe[type] = socket._subscribe[type] || []; + for (let i = 0; i < s.length; i++) { + if (s[i].pattern === pattern) return; + } + } + + let p = pattern2RegEx(pattern); + if (p === null) { + adapter.log.warn('Empty pattern!'); + return; + } + if (socket) { + s.push({pattern: pattern, regex: new RegExp(p)}); + } + + if (this.subscribes[type][pattern] === undefined) { + this.subscribes[type][pattern] = 1; + if (type === 'stateChange') { + adapter.log.debug('Subscribe STATES: ' + pattern); + adapter.subscribeForeignStates(pattern); + } else if (type === 'objectChange') { + adapter.log.debug('Subscribe OBJECTS: ' + pattern); + adapter.subscribeForeignObjects && adapter.subscribeForeignObjects(pattern); + } else if (type === 'log') { + adapter.log.debug('Subscribe LOGS'); + adapter.requireLog && adapter.requireLog(true); + } + } else { + this.subscribes[type][pattern]++; + } + }; + + function showSubscribes(socket, type) { + if (socket && socket._subscribe) { + const s = socket._subscribe[type] || []; + const ids = []; + for (let i = 0; i < s.length; i++) { + ids.push(s[i].pattern); + } + adapter.log.debug('Subscribes: ' + ids.join(', ')); + } else { + adapter.log.debug('Subscribes: no subscribes'); + } + } + + this.unsubscribe = function (socket, type, pattern) { + //console.log((socket._name || socket.id) + ' unsubscribe ' + pattern); + if (!this.subscribes[type]) this.subscribes[type] = {}; + + if (socket) { + if (!socket._subscribe || !socket._subscribe[type]) return; + for (let i = socket._subscribe[type].length - 1; i >= 0; i--) { + if (socket._subscribe[type][i].pattern === pattern) { + + // Remove pattern from global list + if (this.subscribes[type][pattern] !== undefined) { + this.subscribes[type][pattern]--; + if (this.subscribes[type][pattern] <= 0) { + if (type === 'stateChange') { + adapter.log.debug('Unsubscribe STATES: ' + pattern); + //console.log((socket._name || socket.id) + ' unsubscribeForeignStates ' + pattern); + adapter.unsubscribeForeignStates(pattern); + } else if (type === 'objectChange') { + adapter.log.debug('Unsubscribe OBJECTS: ' + pattern); + //console.log((socket._name || socket.id) + ' unsubscribeForeignObjects ' + pattern); + if (adapter.unsubscribeForeignObjects) adapter.unsubscribeForeignObjects(pattern); + } else if (type === 'log') { + //console.log((socket._name || socket.id) + ' requireLog false'); + adapter.log.debug('Unsubscribe LOGS'); + if (adapter.requireLog) adapter.requireLog(false); + } + delete this.subscribes[type][pattern]; + } + } + + delete socket._subscribe[type][i]; + socket._subscribe[type].splice(i, 1); + return; + } + } + } else { + // Remove pattern from global list + if (this.subscribes[type][pattern] !== undefined) { + this.subscribes[type][pattern]--; + if (this.subscribes[type][pattern] <= 0) { + if (type === 'stateChange') { + adapter.log.debug('Unsubscribe STATES: ' + pattern); + adapter.unsubscribeForeignStates(pattern); + } else if (type === 'objectChange') { + adapter.log.debug('Unsubscribe OBJECTS: ' + pattern); + if (adapter.unsubscribeForeignObjects) adapter.unsubscribeForeignObjects(pattern); + } else if (type === 'log') { + adapter.log.debug('Unsubscribe LOGS'); + if (adapter.requireLog) adapter.requireLog(false); + } + delete this.subscribes[type][pattern]; + } + } + } + }; + + this.unsubscribeAll = function () { + if (that.server && that.server.sockets) { + for (const s in that.server.sockets) { + if (that.server.sockets.hasOwnProperty(s)) { + this.unsubscribe(s, 'stateChange'); + this.unsubscribe(s, 'objectChange'); + this.unsubscribe(s, 'log'); + } + } + } + }; + + function unsubscribeSocket(socket, type) { + if (!socket._subscribe || !socket._subscribe[type]) return; + + for (let i = 0; i < socket._subscribe[type].length; i++) { + const pattern = socket._subscribe[type][i].pattern; + if (that.subscribes[type][pattern] !== undefined) { + that.subscribes[type][pattern]--; + if (that.subscribes[type][pattern] <= 0) { + if (type === 'stateChange') { + adapter.log.debug('Unsubscribe STATES: ' + pattern); + adapter.unsubscribeForeignStates(pattern); + } else if (type === 'objectChange') { + adapter.log.debug('Unsubscribe OBJECTS: ' + pattern); + adapter.unsubscribeForeignObjects && adapter.unsubscribeForeignObjects(pattern); + } else if (type === 'log') { + adapter.log.debug('Unsubscribe LOGS: ' + pattern); + adapter.requireLog && adapter.requireLog(false); + } + delete that.subscribes[type][pattern]; + } + } + } + } + + function subscribeSocket(socket, type) { + //console.log((socket._name || socket.id) + ' subscribeSocket'); + if (!socket._subscribe || !socket._subscribe[type]) return; + + for (let i = 0; i < socket._subscribe[type].length; i++) { + const pattern = socket._subscribe[type][i].pattern; + if (that.subscribes[type][pattern] === undefined){ + that.subscribes[type][pattern] = 1; + if (type === 'stateChange') { + adapter.log.debug('Subscribe STATES: ' + pattern); + adapter.subscribeForeignStates(pattern); + } else if (type === 'objectChange') { + adapter.log.debug('Subscribe OBJECTS: ' + pattern); + adapter.subscribeForeignObjects && adapter.subscribeForeignObjects(pattern); + } else if (type === 'log') { + adapter.log.debug('Subscribe LOGS'); + if (adapter.requireLog) adapter.requireLog(true); + } + } else { + that.subscribes[type][pattern]++; + } + } + } + + function socketEvents(socket) { + if (socket.conn.request.sessionID) { + socket._secure = true; + socket._sessionID = socket.conn.request.sessionID; + // Get user for session + adapter.getSession(socket.conn.request.sessionID, function (obj) { + if (!obj || !obj.passport) { + socket._acl.user = ''; + socket.emit('reauthenticate'); + } + }); + } + + // Enable logging, while some browser is connected + /*if (adapter.requireLog) { + adapter.requireLog(true); + }*/ + + if (socket.conn) { + subscribeSocket(socket, 'stateChange'); + subscribeSocket(socket, 'objectChange'); + subscribeSocket(socket, 'log'); + } + + socket.on('name', function (name) { + updateSession(socket); + if (this._name === undefined) { + this._name = name; + if (!that.infoTimeout) that.infoTimeout = setTimeout(updateConnectedInfo, 1000); + } else if (this._name !== name) { + adapter.log.warn('socket ' + this.id + ' changed socket name from ' + this._name + ' to ' + name); + this._name = name; + } + }); + /* + * objects + */ + socket.on('getObject', function (id, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getObject', callback, id)) { + adapter.getForeignObject(id, {user: this._acl.user}, callback); + } + }); + + socket.on('getObjects', function (callback) { + return getAllObjects(socket, callback); + }); + socket.on('getForeignObjects', function (pattern, type, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getObjects', callback)) { + if (typeof type === 'function') { + callback = type; + type = undefined; + } + + adapter.getForeignObjects(pattern, type, function (err, objs) { + if (typeof callback === 'function') { + callback(err, objs); + } else { + adapter.log.warn('[getObjects] Invalid callback') + } + }); + } + }); + + // Identical to getObjects + socket.on('getAllObjects', function (callback) { + return getAllObjects(socket, callback); + }); + + socket.on('getObjectView', function (design, search, params, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getObjectView', callback, search)) { + adapter.objects.getObjectView(design, search, params, {user: this._acl.user}, callback); + } + }); + + socket.on('setObject', function (id, obj, callback) { + if (updateSession(socket) && checkPermissions(socket, 'setObject', callback, id)) { + adapter.setForeignObject(id, obj, {user: this._acl.user}, callback); + } + }); + + socket.on('delObject', function (id, callback) { + if (updateSession(socket) && checkPermissions(socket, 'delObject', callback, id)) { + adapter.delForeignObject(id, {user: this._acl.user}, callback); + } + }); + + socket.on('extendObject', function (id, obj, callback) { + if (updateSession(socket) && checkPermissions(socket, 'extendObject', callback, id)) { + adapter.extendForeignObject(id, obj, {user: this._acl.user}, callback); + } + }); + + socket.on('getHostByIp', function (ip, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getHostByIp', ip)) { + adapter.objects.getObjectView('system', 'host', {}, {user: this._acl.user}, function (err, data) { + if (data.rows.length) { + for (let i = 0; i < data.rows.length; i++) { + if (data.rows[i].value.common.hostname === ip) { + if (typeof callback === 'function') { + callback(ip, data.rows[i].value); + } else { + that.adapter.log.warn('[getHostByIp] Invalid callback') + } + return; + } + if (data.rows[i].value.native.hardware && data.rows[i].value.native.hardware.networkInterfaces) { + const net = data.rows[i].value.native.hardware.networkInterfaces; + for (const eth in net) { + if (!net.hasOwnProperty(eth)) continue; + for (let j = 0; j < net[eth].length; j++) { + if (net[eth][j].address === ip) { + if (typeof callback === 'function') { + callback(ip, data.rows[i].value); + } else { + that.adapter.log.warn('[getHostByIp] Invalid callback') + } + return; + } + } + } + } + } + } + + if (typeof callback === 'function') { + callback(ip, null); + } else { + that.adapter.log.warn('[getHostByIp] Invalid callback'); + } + }); + } + }); + + /* + * states + */ + socket.on('getStates', function (callback) { + if (updateSession(socket) && checkPermissions(socket, 'getStates', callback)) { + if (typeof callback === 'function') { + callback(null, states); + } else { + adapter.log.warn('[getStates] Invalid callback') + } + } + }); + + socket.on('getState', function (id, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getState', callback, id)) { + if (typeof callback === 'function') { + callback(null, states[id]); + } else { + adapter.log.warn('[getState] Invalid callback') + } + } + }); + + socket.on('getForeignStates', function (pattern, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getStates', callback)) { + adapter.getForeignStates(pattern, function (err, objs) { + if (typeof callback === 'function') { + callback(err, objs); + } else { + adapter.log.warn('[getForeignStates] Invalid callback') + } + }); + } + }); + + socket.on('setState', function (id, state, callback) { + if (updateSession(socket) && checkPermissions(socket, 'setState', callback, id)) { + if (typeof state !== 'object') { + state = {val: state}; + } + + adapter.setForeignState(id, state, {user: this._acl.user}, function (err, res) { + if (typeof callback === 'function') { + callback(err, res); + } + }); + } + }); + + socket.on('delState', function (id, callback) { + if (updateSession(socket) && checkPermissions(socket, 'delState', callback, id)) { + adapter.delForeignState(id, {user: this._acl.user}, callback); + } + }); + + socket.on('requireLog', function (isEnabled, callback) { + if (updateSession(socket) && checkPermissions(socket, 'setObject', callback)) { + if (isEnabled) { + that.subscribe(this, 'log', 'dummy'); + } else { + that.unsubscribe(this, 'log', 'dummy'); + } + + if (adapter.log.level === 'debug') showSubscribes(socket, 'log'); + + if (typeof callback === 'function') { + setImmediate(callback, null); + } + } + }); + /* + * History + */ + socket.on('getStateHistory', function (id, options, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getStateHistory', callback)) { + options.user = this._acl.user; + options.aggregate = options.aggregate || 'none'; + adapter.getHistory(id, options, callback); + } + }); + socket.on('getHistory', function (id, options, callback) { + if (updateSession(socket) && checkPermissions(socket, 'getStateHistory', callback)) { + options.user = this._acl.user; + options.aggregate = options.aggregate || 'none'; + adapter.getHistory(id, options, callback); + } + }); + /* + * user/group + */ + socket.on('addUser', function (user, pass, callback) { + if (updateSession(socket) && checkPermissions(socket, 'addUser', callback, user)) { + addUser(user, pass, {user: this._acl.user}, callback); + } + }); + + socket.on('delUser', function (user, callback) { + if (updateSession(socket) && checkPermissions(socket, 'delUser', callback, user)) { + delUser(user, {user: this._acl.user}, callback); + } + }); + + socket.on('addGroup', function (group, desc, acl, callback) { + if (updateSession(socket) && checkPermissions(socket, 'addGroup', callback, group)) { + addGroup(group, desc, acl, {user: this._acl.user}, callback); + } + }); + + socket.on('delGroup', function (group, callback) { + if (updateSession(socket) && checkPermissions(socket, 'delGroup', callback, group)) { + delGroup(group, {user: this._acl.user}, callback); + } + }); + + socket.on('changePassword', function (user, pass, callback) { + if (updateSession(socket)) { + if (user === socket._acl.user || checkPermissions(socket, 'changePassword', callback, user)) { + adapter.setPassword(user, pass, {user: this._acl.user}, callback); + } + } + }); + + // HTTP + socket.on('httpGet', function (url, callback) { + if (updateSession(socket) && checkPermissions(socket, 'httpGet', callback, url)) { + request(url, callback); + } + }); + + // commands will be executed on host/controller + // following response commands are expected: cmdStdout, cmdStderr, cmdExit + socket.on('cmdExec', function (host, id, cmd, callback) { + if (updateSession(socket) && checkPermissions(socket, 'cmdExec', callback, cmd)) { + console.log('cmdExec on ' + host + '(' + id + '): ' + cmd); + // remember socket for this ID. + cmdSessions[id] = {socket: socket}; + adapter.sendToHost(host, 'cmdExec', {data: cmd, id: id}); + } + }); + + socket.on('readDir', function (_adapter, path, callback) { + if (updateSession(socket) && checkPermissions(socket, 'readDir', callback, path)) { + adapter.readDir(_adapter, path, {user: this._acl.user}, callback); + } + }); + + socket.on('writeFile', function (_adapter, filename, data, callback) { + if (updateSession(socket) && checkPermissions(socket, 'writeFile', callback, filename)) { + adapter.writeFile(_adapter, filename, data, {user: this._acl.user}, callback); + } + }); + + socket.on('readFile', function (_adapter, filename, callback) { + if (updateSession(socket) && checkPermissions(socket, 'readFile', callback, filename)) { + adapter.readFile(_adapter, filename, {user: this._acl.user}, callback); + } + }); + + socket.on('readFile64', function (_adapter, filename, callback) { + if (updateSession(socket) && checkPermissions(socket, 'readFile64', callback, filename)) { + adapter.readFile(_adapter, filename, {user: this._acl.user}, callback); + } + }); + + socket.on('sendTo', function (adapterInstance, command, message, callback) { + if (updateSession(socket) && checkPermissions(socket, 'sendTo', callback, command)) { + adapter.sendTo(adapterInstance, command, message, function (res) { + if (typeof callback === 'function') { + setTimeout(function () { + callback(res); + }, 0); + } + }); + } + }); + + // following commands are protected and require the extra permissions + const protectedCommands = ['cmdExec', 'getLocationOnDisk', 'getDiagData', 'getDevList', 'delLogs', 'writeDirAsZip', 'writeObjectsAsZip', 'readObjectsAsZip', 'checkLogging', 'updateMultihost']; + + socket.on('sendToHost', function (host, command, message, callback) { + // host can answer following commands: cmdExec, getRepository, getInstalled, getInstalledAdapter, getVersion, getDiagData, getLocationOnDisk, getDevList, getLogs, getHostInfo, + // delLogs, readDirAsZip, writeDirAsZip, readObjectsAsZip, writeObjectsAsZip, checkLogging, updateMultihost + if (updateSession(socket) && checkPermissions(socket, protectedCommands.indexOf(command) !== -1 ? 'cmdExec' : 'sendToHost', callback, command)) { + adapter.sendToHost(host, command, message, function (res) { + if (typeof callback === 'function') { + setTimeout(function () { + callback(res); + }, 0); + } + }); + } + }); + + socket.on('authEnabled', function (callback) { + if (typeof callback === 'function') { + callback(adapter.config.auth, socket._acl.user.replace(/^system\.user\./, '')); + } + }); + + socket.on('disconnect', function () { + unsubscribeSocket(this, 'stateChange'); + unsubscribeSocket(this, 'objectChange'); + unsubscribeSocket(this, 'log'); + // Disable logging if no one browser is connected + if (adapter.requireLog) { + adapter.log.warn('Disable logging, because no one socket connected'); + adapter.requireLog(!!that.server.engine.clientsCount); + } + }); + + socket.on('listPermissions', function (callback) { + if (updateSession(socket)) { + if (typeof callback === 'function') { + callback(commandsPermissions); + } + } + }); + + socket.on('getUserPermissions', function (callback) { + if (updateSession(socket) && checkPermissions(socket, 'getUserPermissions', callback)) { + if (typeof callback === 'function') { + callback(null, socket._acl); + } + } + }); + + socket.on('eventsThreshold', function (isActive) { + if (!isActive) { + disableEventThreshold(true); + } else { + enableEventThreshold(); + } + }); + + socket.on('eventsThreshold', function (isActive) { + if (!isActive) { + disableEventThreshold(true); + } else { + enableEventThreshold(); + } + }); + + socket.on('subscribe', function (pattern, callback) { + if (updateSession(socket) && checkPermissions(socket, 'subscribe', callback, pattern)) { + if (pattern && typeof pattern === 'object' && pattern instanceof Array) { + for (let p = 0; p < pattern.length; p++) { + that.subscribe(this, 'stateChange', pattern[p]); + } + } else { + that.subscribe(this, 'stateChange', pattern); + } + if (adapter.log.level === 'debug') showSubscribes(socket, 'stateChange'); + if (typeof callback === 'function') { + setImmediate(callback, null); + } + } + }); + // same as 'subscribe' + socket.on('subscribeStates', function (pattern, callback) { + if (updateSession(socket) && checkPermissions(socket, 'subscribe', callback, pattern)) { + if (pattern && typeof pattern === 'object' && pattern instanceof Array) { + for (let p = 0; p < pattern.length; p++) { + that.subscribe(this, 'stateChange', pattern[p]); + } + } else { + that.subscribe(this, 'stateChange', pattern); + } + if (adapter.log.level === 'debug') showSubscribes(socket, 'stateChange'); + if (typeof callback === 'function') { + setImmediate(callback, null); + } + } + }); + + + socket.on('unsubscribe', function (pattern, callback) { + if (updateSession(socket) && checkPermissions(socket, 'unsubscribe', callback, pattern)) { + if (pattern && typeof pattern === 'object' && pattern instanceof Array) { + for (let p = 0; p < pattern.length; p++) { + that.unsubscribe(this, 'stateChange', pattern[p]); + } + } else { + that.unsubscribe(this, 'stateChange', pattern); + } + if (adapter.log.level === 'debug') showSubscribes(socket, 'stateChange'); + if (typeof callback === 'function') { + setImmediate(callback, null); + } + } + }); + + // same as 'unsubscribe' + socket.on('unsubscribeStates', function (pattern, callback) { + if (updateSession(socket) && checkPermissions(socket, 'unsubscribe', callback, pattern)) { + if (pattern && typeof pattern === 'object' && pattern instanceof Array) { + for (let p = 0; p < pattern.length; p++) { + that.unsubscribe(this, 'stateChange', pattern[p]); + } + } else { + that.unsubscribe(this, 'stateChange', pattern); + } + if (adapter.log.level === 'debug') showSubscribes(socket, 'stateChange'); + if (typeof callback === 'function') { + setImmediate(callback, null); + } + } + }); + + socket.on('subscribeObjects', function (pattern, callback) { + if (updateSession(socket) && checkPermissions(socket, 'subscribeObjects', callback, pattern)) { + if (pattern && typeof pattern === 'object' && pattern instanceof Array) { + for (let p = 0; p < pattern.length; p++) { + that.subscribe(this, 'objectChange', pattern[p]); + } + } else { + that.subscribe(this, 'objectChange', pattern); + } + if (typeof callback === 'function') { + setImmediate(callback, null); + } + } + }); + + socket.on('unsubscribeObjects', function (pattern, callback) { + if (updateSession(socket) && checkPermissions(socket, 'unsubscribeObjects', callback, pattern)) { + if (pattern && typeof pattern === 'object' && pattern instanceof Array) { + for (let p = 0; p < pattern.length; p++) { + that.unsubscribe(this, 'objectChange', pattern[p]); + } + } else { + that.unsubscribe(this, 'objectChange', pattern); + } + if (typeof callback === 'function') { + setImmediate(callback, null); + } + } + }); + + socket.on('readLogs', function (callback) { + if (updateSession(socket) && checkPermissions(socket, 'readLogs', callback)) { + let result = {list: []}; + + // deliver file list + try { + const config = adapter.systemConfig; + // detect file log + if (config && config.log && config.log.transport) { + for (const transport in config.log.transport) { + if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') { + let filename = config.log.transport[transport].filename || 'log/'; + const parts = filename.replace(/\\/g, '/').split('/'); + parts.pop(); + filename = parts.join('/'); + if (filename[0] !== '/' && !filename.match(/^\W:/)) { + filename = path.normalize(__dirname + '/../../../') + filename; + } + if (fs.existsSync(filename)) { + const files = fs.readdirSync(filename); + for (let f = 0; f < files.length; f++) { + try { + if (!fs.lstatSync(filename + '/' + files[f]).isDirectory()) { + result.list.push('log/' + transport + '/' + files[f]); + } + } catch (e) { + // push unchecked + // result.list.push('log/' + transport + '/' + files[f]); + adapter.log.error('Cannot check file: ' + filename + '/' + files[f]); + } + } + } + } + } + } else { + result = {error: 'no file loggers'}; + } + } catch (e) { + adapter.log.error(e); + result = {error: e}; + } + if (typeof callback === 'function') { + callback(result.error, result.list); + } + } + }); + + socket.on('getVersion', callback => { + if (typeof callback === 'function') { + let version = ''; + try { + const pack = require('../io-package.json'); + version = pack && pack.common && pack.common.version; + } catch (e) { + version = 'unknown'; + } + callback(null, version) + } + }); + } + + function onAuthorizeSuccess(data, accept) { + adapter.log.info('successful connection to socket.io from ' + data.connection.remoteAddress); + //adapter.log.info(JSON.stringify(data)); + + accept(); + } + + function onAuthorizeFail(data, message, error, accept) { + if (error) { + adapter.log.error('failed connection to socket.io from ' + data.connection.remoteAddress + ':', message); + } + + if (error) { + accept(new Error(message)); + } else { + accept('failed connection to socket.io: ' + message);//null, false); + } + // this error will be sent to the user as a special error-package + // see: http://socket.io/docs/client-api/#socket > error-object + } + + function initSocket(socket) { + disableEventThreshold(); + + if (adapter.config.auth) { + adapter.config.ttl = parseInt(adapter.config.ttl, 10) || 3600; + getUserFromSocket(socket, function (err, user) { + if (err || !user) { + adapter.log.error('socket.io ' + err); + } else { + adapter.log.debug('socket.io client ' + user + ' connected'); + adapter.calculatePermissions(user, commandsPermissions, function (acl) { + socket._acl = acl; + socketEvents(socket); + }); + } + }); + } else { + adapter.calculatePermissions(adapter.config.defaultUser || 'system.user.admin', commandsPermissions, function (acl) { + socket._acl = acl; + socketEvents(socket); + }); + } + } + + // Extract user name from socket + function getUserFromSocket(socket, callback) { + let wait = false; + try { + if (socket.conn.request.sessionID) { + wait = true; + if (store) { + store.get(socket.conn.request.sessionID, function (err, obj) { + if (obj && obj.passport && obj.passport.user) { + if (typeof callback === 'function') { + callback(null, obj.passport.user ? 'system.user.' + obj.passport.user : ''); + } + } + }); + } + } + } catch (e) { + + } + if (!wait && callback) { + callback('Cannot detect user'); + } + } + + function disableEventThreshold(readAll) { + if (eventsThreshold.active) { + eventsThreshold.accidents = 0; + eventsThreshold.count = 0; + eventsThreshold.active = false; + eventsThreshold.timeActivated = 0; + adapter.log.info('Subscribe on all states again'); + + setTimeout(function () { + if (readAll) { + adapter.getForeignStates('*', function (err, res) { + adapter.log.info('received all states'); + for (const id in res) { + if (res.hasOwnProperty(id) && JSON.stringify(states[id]) !== JSON.stringify(res[id])) { + that.server.sockets.emit('stateChange', id, res[id]); + states[id] = res[id]; + } + } + }); + } + + that.server.sockets.emit('eventsThreshold', false); + adapter.unsubscribeForeignStates('system.adapter.*'); + adapter.subscribeForeignStates('*'); + + }, 50); + + } + } + + function enableEventThreshold() { + if (!eventsThreshold.active) { + eventsThreshold.active = true; + + setTimeout(function () { + adapter.log.info('Unsubscribe from all states, except system\'s, because over ' + eventsThreshold.repeatSeconds + ' seconds the number of events is over ' + eventsThreshold.value + ' (in last second ' + eventsThreshold.count + ')'); + eventsThreshold.timeActivated = new Date().getTime(); + + that.server.sockets.emit('eventsThreshold', true); + adapter.unsubscribeForeignStates('*'); + adapter.subscribeForeignStates('system.adapter.*'); + }, 100); + } + } + + this.repoUpdated = function () { + if (that.server && that.server.sockets) { + that.server.sockets.emit('repoUpdated'); + } + }; + + this.objectChange = function (id, obj) { + const clients = that.server.sockets.connected; + + for (const i in clients) { + if (clients.hasOwnProperty(i)) { + updateSession(clients[i]); + } + } + that.server.sockets.emit('objectChange', id, obj); + }; + + this.sendCommand = function (obj) { + if (cmdSessions[obj.message.id]) { + if (that.server) { + that.server.sockets.emit(obj.command, obj.message.id, obj.message.data); + } + // we cannot save the socket, because if it takes a bit time, the socket will be invalid + //cmdSessions[obj.message.id].socket.emit(obj.command, obj.message.id, obj.message.data); + if (obj.command === 'cmdExit') { + delete cmdSessions[obj.message.id]; + } + } + }; + + this.sendLog = function (obj) { + // TODO Build in some threshold + if (that.server && that.server.sockets) { + that.server.sockets.emit('log', obj); + } + }; + + this.stateChange = function (id, state) { + const clients = that.server.sockets.connected; + + if (!eventsThreshold.active) { + eventsThreshold.count++; + } + for (const i in clients) { + if (clients.hasOwnProperty(i)) { + updateSession(clients[i]); + } + } + that.server.sockets.emit('stateChange', id, state); + }; + + (function __constructor() { + // detect event bursts + setInterval(function () { + if (!eventsThreshold.active) { + if (eventsThreshold.count > eventsThreshold.value) { + eventsThreshold.accidents++; + + if (eventsThreshold.accidents >= eventsThreshold.repeatSeconds) { + enableEventThreshold(); + } + } else { + eventsThreshold.accidents = 0; + } + eventsThreshold.count = 0; + } else if (new Date().getTime() - eventsThreshold.timeActivated > 60000) { + disableEventThreshold(); + } + }, eventsThreshold.checkInterval); + + /*server.server.set('logger', { + debug: function(obj) {adapter.log.debug('socket.io: ' + obj)}, + info: function(obj) {adapter.log.debug('socket.io: ' + obj)} , + error: function(obj) {adapter.log.error('socket.io: ' + obj)}, + warn: function(obj) {adapter.log.warn('socket.io: ' + obj)} + });*/ + // it can be used as client too for cloud + if (!settings.clientid) { + if (!server.__inited) { + that.server = socketio.listen(server); + server.__inited = true; + } + } else { + that.server = server; + } + + that.server.on('connection', initSocket); + + if (settings.auth && that.server) { + that.server.use(passportSocketIo.authorize({ + passport: passport, + cookieParser: cookieParser, + key: userKey, // the name of the cookie where express/connect stores its session_id + secret: settings.secret, // the session_secret to parse the cookie + store: store, // we NEED to use a sessionstore. no memorystore please + success: onAuthorizeSuccess, // *optional* callback on success - read more below + fail: onAuthorizeFail // *optional* callback on fail/error - read more below + })); + } + })(); + + return this; +} +module.exports = IOSocket; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..a40fe76 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,83 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +let controllerDir; +let appName; + +/** + * returns application name + * + * The name of the application can be different and this function finds it out. + * + * @returns {string} + */ + function getAppName() { + const parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 2].split('.')[0]; +} + +/** + * looks for js-controller home folder + * + * @param {boolean} isInstall + * @returns {string} + */ +function getControllerDir(isInstall) { + // Find the js-controller location + const possibilities = [ + 'yunkong2.js-controller', + 'yunkong2.js-controller', + ]; + /** @type {string} */ + let controllerPath; + for (const pkg of possibilities) { + try { + const possiblePath = require.resolve(pkg); + if (fs.existsSync(possiblePath)) { + controllerPath = possiblePath; + break; + } + } catch (e) { /* not found */ } + } + if (!controllerPath) { + if (!isInstall) { + console.log('Cannot find js-controller'); + process.exit(10); + } else { + process.exit(); + } + } + // we found the controller + return path.dirname(controllerPath); +} + +/** + * reads controller base settings + * + * @alias getConfig + * @returns {object} + */ + function getConfig() { + let configPath; + if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', appName + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else { + throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json'); + } +} +appName = getAppName(); +controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1); +const adapter = require(path.join(controllerDir, 'lib/adapter.js')); + +exports.controllerDir = controllerDir; +exports.getConfig = getConfig; +exports.Adapter = adapter; +exports.appName = appName; diff --git a/lib/web.js b/lib/web.js new file mode 100644 index 0000000..d1ec7ac --- /dev/null +++ b/lib/web.js @@ -0,0 +1,462 @@ +/* jshint -W097 */ +/* jshint strict: false */ +/* jslint node: true */ +/* jshint -W061 */ +'use strict'; +const Stream = require('stream'); +const utils = require(__dirname + '/utils'); // Get common adapter utils +const LE = require(utils.controllerDir + '/lib/letsencrypt.js'); +const express = require('express'); +const fs = require('fs'); +let path; // will be loaded later + +let session; +let bodyParser; +let AdapterStore; +let password; +let passport; +let LocalStrategy; +let flash; +let cookieParser; +let fileUpload; + +function Web(settings, adapter, onReady) { + if (!(this instanceof Web)) return new Web(settings, adapter, onReady); + const server = { + app: null, + server: null + }; + const bruteForce = {}; + let store = null; + let loginPage; + this.server = server; + + this.close = () => server.server && server.server.close(); + + function decorateLogFile(filename) { + const prefix = '' + + '\n' + + '\n\n\n'; + const suffix = ''; + const log = fs.readFileSync(filename).toString(); + return prefix + log + suffix; + } + + function prepareLoginTemplate() { + let def = 'background: #64b5f6;\n'; + let template = fs.readFileSync(__dirname + '/../www/login/index.html').toString('utf8'); + if (adapter.config.loginBackgroundColor) { + def = 'background-color: ' + adapter.config.loginBackgroundColor + ';\n' + } + if (adapter.config.loginBackgroundImage) { + def += ' background-image: url(../' + adapter.namespace + '/login-bg.png);\n'; + } + if (adapter.config.loginHideLogo) { + template = template.replace('.logo { display: block }', '.logo { display: none }'); + } + if (adapter.config.loginMotto) { + template = template.replace('Discover awesome. yunkong2', adapter.config.loginMotto); + } + return template.replace('background: #64b5f6;', def); + } + + //settings: { + // "port": 8080, + // "auth": false, + // "secure": false, + // "bind": "0.0.0.0", // "::" + // "cache": false + //} + (function __construct () { + if (settings.port) { + server.app = express(); + if (settings.auth) { + session = require('express-session'); + cookieParser = require('cookie-parser'); + bodyParser = require('body-parser'); + AdapterStore = require(utils.controllerDir + '/lib/session.js')(session, settings.ttl); + password = require(utils.controllerDir + '/lib/password.js'); + passport = require('passport'); + LocalStrategy = require('passport-local').Strategy; + flash = require('connect-flash'); // TODO report error to user + + store = new AdapterStore({adapter: adapter}); + + passport.use(new LocalStrategy( + (username, password, done) => { + if (bruteForce[username] && bruteForce[username].errors > 4) { + let minutes = (new Date().getTime() - bruteForce[username].time); + if (bruteForce[username].errors < 7) { + if ((new Date().getTime() - bruteForce[username].time) < 60000) { + minutes = 1; + } else { + minutes = 0; + } + } else + if (bruteForce[username].errors < 10) { + if ((new Date().getTime() - bruteForce[username].time) < 180000) { + minutes = Math.ceil((180000 - minutes) / 60000); + } else { + minutes = 0; + } + } else + if (bruteForce[username].errors < 15) { + if ((new Date().getTime() - bruteForce[username].time) < 600000) { + minutes = Math.ceil((600000 - minutes) / 60000); + } else { + minutes = 0; + } + } else + if ((new Date().getTime() - bruteForce[username].time) < 3600000) { + minutes = Math.ceil((3600000 - minutes) / 60000); + } else { + minutes = 0; + } + + if (minutes) { + return done('Too many errors. Try again in ' + minutes + ' ' + (minutes === 1 ? 'minute' : 'minutes') + '.', false); + } + } + adapter.checkPassword(username, password, res => { + if (!res) { + bruteForce[username] = bruteForce[username] || {errors: 0}; + bruteForce[username].time = new Date().getTime(); + bruteForce[username].errors++; + } else if (bruteForce[username]) { + delete bruteForce[username]; + } + + if (res) { + return done(null, username); + } else { + return done(null, false); + } + }); + + } + )); + passport.serializeUser((user, done) => done(null, user)); + + passport.deserializeUser((user, done) => done(null, user)); + + server.app.use(cookieParser()); + server.app.use(bodyParser.urlencoded({ + extended: true + })); + server.app.use(bodyParser.json()); + server.app.use(session({ + secret: settings.secret, + saveUninitialized: true, + resave: true, + cookie: { + maxAge: adapter.config.ttl * 1000 + }, + store: store + })); + server.app.use(passport.initialize()); + server.app.use(passport.session()); + server.app.use(flash()); + + server.app.post('/login', (req, res, next) => { + let redirect = '/'; + if (req.body.origin) { + const parts = req.body.origin.match(/href=(.+)$/); + if (parts && parts[1]) { + redirect = decodeURIComponent(parts[1]); + } + } + passport.authenticate('local', { + successRedirect: redirect, + failureRedirect: '/login/index.html' + req.body.origin + (req.body.origin ? '&error' : '?error'), + failureFlash: 'Invalid username or password.' + })(req, res, next); + }); + + server.app.get('/logout', (req, res) => { + req.logout(); + res.redirect('/login/index.html'); + }); + + server.app.get('/login/index.html', (req, res) => { + loginPage = loginPage || prepareLoginTemplate(); + res.contentType('text/html'); + res.status(200).send(loginPage); + }); + + // route middleware to make sure a user is logged in + server.app.use((req, res, next) => { + if (!req.isAuthenticated()) { + if (/admin\.\d+\/login-bg\.png(\?.*)?$/.test(req.originalUrl)) { + // Read names of files for gong + adapter.objects.readFile(adapter.namespace, 'login-bg.png', null, (err, file) => { + if (!err && file) { + res.set('Content-Type', 'image/png'); + res.status(200).send(file); + } else { + res.status(404).send(); + } + }); + } else if (/^\/login\//.test(req.originalUrl) || + /\.ico(\?.*)?$/.test(req.originalUrl)) { + return next(); + } else { + res.redirect('/login/index.html?href=' + encodeURIComponent(req.originalUrl)); + } + } else { + return next(); + } + }); + } else { + server.app.get('/login', (req, res) => { + res.redirect('/'); + }); + server.app.get('/logout', (req, res) => { + res.redirect('/'); + }); + } + + // send log files + server.app.get('/log/*', (req, res) => { + let parts = req.url.split('/'); + parts = parts.splice(2); + const transport = parts.shift(); + let filename = parts.join('/'); + const config = adapter.systemConfig; + // detect file log + if (config && config.log && config.log.transport) { + if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') { + path = path || require('path'); + + if (config.log.transport[transport].filename) { + parts = config.log.transport[transport].filename.replace(/\\/g, '/').split('/'); + parts.pop(); + filename = path.join(parts.join('/'), filename); + } else { + filename = path.join('log/', filename) ; + } + + if (filename[0] !== '/' && !filename.match(/^\W:/)) { + filename = path.normalize(__dirname + '/../../../') + filename; + } + + if (fs.existsSync(filename)) { + const stat = fs.lstatSync(filename); + if (stat.size > 2 * 1024 * 1024) { + res.sendFile(filename); + } else { + res.send(decorateLogFile(filename)); + } + + return; + } + } + } + res.status(404).send('File ' + filename + ' not found'); + }); + const appOptions = {}; + if (settings.cache) { + appOptions.maxAge = 30758400000; + } + + if (settings.tmpPathAllow && settings.tmpPath) { + server.app.use('/tmp/', express.static(settings.tmpPath, {maxAge: 0})); + fileUpload = fileUpload || require('express-fileupload'); + server.app.use(fileUpload({ + useTempFiles: true, + tempFilePath: settings.tmpPath + })); + server.app.post('/upload', (req, res) => { + if (!req.files) { + return res.status(400).send('No files were uploaded.'); + } + + // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file + let myFile; + for (const name in req.files) { + if (req.files.hasOwnProperty(name)) { + myFile = req.files[name]; + break; + } + } + + if (myFile) { + if (myFile.data && myFile.data.length > 600 * 1024 * 1024) { + return res.status(500).send('File is too big. (Max 600MB)'); + } + // Use the mv() method to place the file somewhere on your server + myFile.mv(settings.tmpPath + '/restore.iob', err => { + if (err) { + res.status(500).send(err); + } else { + res.send('File uploaded!'); + } + }); + } else { + return res.status(500).send('File not uploaded'); + } + }); + } + + if (!fs.existsSync(__dirname + '/../www')) { + server.app.use('/', (req, res) => { + res.send('This adapter cannot be installed directly from github.
You must install it from npm.
Write for that "npm install yunkong2.admin" in according directory.'); + }); + } else { + server.app.use('/', express.static(__dirname + '/../www', appOptions)); + } + + // reverse proxy with url rewrite for couchdb attachments in .admin + server.app.use('/adapter/', (req, res) => { + + // Example: /example/?0 + let url = req.url; + + // add index.html + url = url.replace(/\/($|\?|#)/, '/index.html$1'); + + // Read config files for admin from /adapters/admin/admin/... + if (url.substring(0, '/' + adapter.name + '/'.length) === '/' + adapter.name + '/') { + url = url.replace('/' + adapter.name + '/', __dirname + '/../admin/'); + url = url.replace(/\?[0-9]*/, ''); + + try { + if (fs.existsSync(url)) { + fs.createReadStream(url).pipe(res); + } else { + const ss = new Stream(); + ss.pipe = dest => dest.write('File not found'); + + ss.pipe(res); + } + } catch (e) { + const s = new Stream(); + s.pipe = dest => dest.write('File not found: ' + e); + + s.pipe(res); + } + return; + } + url = url.split('/'); + // Skip first / + url.shift(); + // Get ID + const id = url.shift() + '.admin'; + url = url.join('/'); + const pos = url.indexOf('?'); + if (pos !== -1) { + url = url.substring(0, pos); + } + adapter.readFile(id, url, null, (err, buffer, mimeType) => { + if (!buffer || err) { + res.contentType('text/html'); + res.status(404).send('File ' + url + ' not found'); + } else { + if (mimeType) { + res.contentType(mimeType['content-type'] || mimeType); + } else { + res.contentType('text/javascript'); + } + res.send(buffer); + } + }); + }); + + server.server = LE.createServer(server.app, settings, adapter.config.certificates, adapter.config.leConfig, adapter.log); + server.server.__server = server; + } else { + adapter.log.error('port missing'); + process.exit(1); + } + + if (server.server) { + settings.port = parseInt(settings.port, 10); + + adapter.getPort(settings.port, port => { + if (port !== settings.port && !adapter.config.findNextPort) { + adapter.log.error('port ' + settings.port + ' already in use'); + process.exit(1); + } + server.server.listen(port, (!settings.bind || settings.bind === '0.0.0.0') ? undefined : settings.bind || undefined); + + adapter.log.info('http' + (settings.secure ? 's' : '') + ' server listening on port ' + port); + adapter.log.info('Use link "http' + (settings.secure ? 's' : '') + '://localhost:' + port + '" to configure.'); + + if (typeof onReady === 'function') { + onReady(server.server, store); + } + }); + } + + if (server.server) { + return server; + } else { + return null; + } + })(); + + return this; +} + +module.exports = Web; \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..0fa6b21 --- /dev/null +++ b/main.js @@ -0,0 +1,363 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +'use strict'; + +const adapterName = require(__dirname + '/package.json').name.split('.').pop(); +const utils = require(__dirname + '/lib/utils'); // Get common adapter utils +const tools = require(utils.controllerDir + '/lib/tools.js'); +const SocketIO = require(__dirname + '/lib/socket'); +const Web = require(__dirname + '/lib/web'); + +let socket = null; +let webServer = null; + +let objects = {}; +let states = {}; +let secret = 'Zgfr56gFe87jJOM'; // Will be generated by first start + +let adapter = new utils.Adapter({ + name: adapterName, // adapter name + dirname: __dirname, // say own position + logTransporter: true, // receive the logs + systemConfig: true, + install: callback => typeof callback === 'function' && callback() +}); + +adapter.on('objectChange', (id, obj) => { + if (obj) { + //console.log('objectChange: ' + id); + objects[id] = obj; + + if (id === 'system.repositories') { + writeUpdateInfo(); + } + } else { + //console.log('objectDeleted: ' + id); + if (objects[id]) { + delete objects[id]; + } + } + + // TODO Build in some threshold of messages + if (socket) { + socket.objectChange(id, obj); + } +}); + +adapter.on('stateChange', (id, state) => { + if (!state) { + if (states[id]) { + delete states[id]; + } + } else { + states[id] = state; + } + if (socket) { + socket.stateChange(id, state); + } +}); + +adapter.on('ready', () => { + adapter.getForeignObject('system.config', (err, obj) => { + if (!err && obj) { + obj.native = obj.native || {}; + if (!obj.native.secret) { + require('crypto').randomBytes(24, (ex, buf) => { + adapter.config.secret = buf.toString('hex'); + adapter.extendForeignObject('system.config', {native: {secret: adapter.config.secret}}); + main(); + }); + } else { + adapter.config.secret = obj.native.secret; + main(); + } + } else { + adapter.config.secret = secret; + adapter.logger.error('Cannot find object system.config'); + } + }); +}); + +adapter.on('message', obj => { + if (!obj || !obj.message) { + return false; + } + + if (socket) { + socket.sendCommand(obj); + } + + return true; +}); + +adapter.on('unload', callback => { + if (socket) { + // unsubscribe all + socket.unsubscribeAll(); + } + + try { + adapter.log.info('terminating http' + (adapter.config.secure ? 's' : '') + ' server on port ' + adapter.config.port); + webServer.close(); + callback(); + } catch (e) { + callback(); + } +}); + +// obj = {message: msg, severity: level, from: this.namespace, ts: (new Date()).getTime()} +adapter.on('log', obj => socket && socket.sendLog(obj)); + +function createUpdateInfo() { + // create connected object and state + let obj = objects[adapter.namespace + '.info.updatesNumber']; + + if (!obj || !obj.common || obj.common.type !== 'number') { + obj = { + _id: 'info.updatesNumber', + type: 'state', + common: { + role: 'indicator.updates', + name: 'Number of adapters to update', + type: 'number', + read: true, + write: false, + def: 0 + }, + native: {} + }; + + adapter.setObject(obj._id, obj); + } + obj = objects[adapter.namespace + '.info.updatesList']; + if (!obj || !obj.common || obj.common.type !== 'string') { + obj = { + _id: 'info.updatesList', + type: 'state', + common: { + role: 'indicator.updates', + name: 'List of adapters to update', + type: 'string', + read: true, + write: false, + def: '' + }, + native: {} + }; + + adapter.setObject(obj._id, obj); + } +} + +// Helper methods +function upToDate(a, b) { + a = a.split('.'); + b = b.split('.'); + a[0] = parseInt(a[0], 10); + b[0] = parseInt(b[0], 10); + if (a[0] > b[0]) { + return false; + } else if (a[0] < b[0]) { + return true; + } else if (a[0] === b[0]) { + a[1] = parseInt(a[1], 10); + b[1] = parseInt(b[1], 10); + if (a[1] > b[1]) { + return false; + } else if (a[1] < b[1]) { + return true; + } else if (a[1] === b[1]) { + a[2] = parseInt(a[2], 10); + b[2] = parseInt(b[2], 10); + return a[2] <= b[2]; + } + } else { + return true; + } +} + +function writeUpdateInfo(sources) { + if (!sources) { + let obj = objects['system.repositories']; + if (!objects['system.config'] || !objects['system.config'].common) { + adapter.log.warn('Repository cannot be read. Invalid "system.config" object.'); + return; + } + + const activeRepo = objects['system.config'].common.activeRepo; + + if (obj && obj.native && obj.native.repositories && obj.native.repositories[activeRepo] && + obj.native.repositories[activeRepo].json) { + sources = obj.native.repositories[activeRepo].json; + } else { + adapter.setState('info.updatesNumber', 0, true); + adapter.setState('info.updatesList', '', true); + if (obj && obj.native && obj.native.repositories && obj.native.repositories[activeRepo]) { + adapter.log.warn('Repository cannot be read'); + } else { + adapter.log.warn('No repository source configured'); + } + return; + } + } + + let installed = tools.getInstalledInfo(); + let list = []; + + for (let name in sources) { + if (!sources.hasOwnProperty(name)) continue; + if (installed[name] && installed[name].version && sources[name].version) { + if (sources[name].version !== installed[name].version && + !upToDate(sources[name].version, installed[name].version)) { + // remove first part of the name + const n = name.indexOf('.'); + list.push(n === -1 ? name : name.substring(n + 1)); + } + } + } + adapter.setState('info.updatesNumber', list.length, true); + adapter.setState('info.updatesList', list.join(', '), true); +} + +// to do => remove it later, when all repositories patched. +function patchRepos(callback) { + return callback && callback(); + // do not patch any more. Delete it later 2018.04.23 + /* + adapter.getForeignObject('system.repositories', (err, obj) => { + let changed = false; + if (obj && obj.native && obj.native.repositories) { + // default link should point to stable + if (!obj.native.repositories.default || obj.native.repositories.default.link !== 'http://download.yunkong2.net/sources-dist.json') { + changed = true; + obj.native.repositories.default = { + link: 'http://download.yunkong2.net/sources-dist.json' + }; + } + // latest link should point to latest + if (!obj.native.repositories.latest) { + obj.native.repositories.latest = { + link: 'http://download.yunkong2.net/sources-dist-latest.json' + }; + changed = true; + } + + // change URL of raw sources from yunkong2.js-controller to yunkong2.repositories + for (let r in obj.native.repositories) { + if (obj.native.repositories.hasOwnProperty(r) && + obj.native.repositories[r].link === 'https://raw.githubusercontent.com/yunkong2/yunkong2.js-controller/master/conf/sources-dist.json') { + obj.native.repositories[r].link = 'https://raw.githubusercontent.com/yunkong2/yunkong2.repositories/master/sources-dist.json'; + changed = true; + } + } + } + if (changed) { + adapter.setForeignObject(obj._id, obj, function () { + callback && callback(); + }); + } else { + callback && callback(); + } + });*/ +} +function initSocket(server, store) { + socket = new SocketIO(server, adapter.config, adapter, objects, states, store); + socket.subscribe(null, 'objectChange', '*'); +} + +function main() { + // adapter.subscribeForeignStates('*'); + // adapter.subscribeForeignObjects('*'); + + adapter.config.defaultUser = adapter.config.defaultUser || 'admin'; + if (!adapter.config.defaultUser.match(/^system\.user\./)) { + adapter.config.defaultUser = 'system.user.' + adapter.config.defaultUser; + } + + if (adapter.config.secure) { + // Load certificates + adapter.getCertificates((err, certificates, leConfig) => { + adapter.config.certificates = certificates; + adapter.config.leConfig = leConfig; + + getData(() => webServer = new Web(adapter.config, adapter, initSocket)); + }); + } else { + getData(() => webServer = new Web(adapter.config, adapter, initSocket)); + } + + patchRepos(() => { + // By default update repository every 24 hours + if (adapter.config.autoUpdate === undefined) { + adapter.config.autoUpdate = 24; + } + adapter.config.autoUpdate = parseInt(adapter.config.autoUpdate, 10) || 0; + if (adapter.config.autoUpdate) { + setInterval(() => updateRegister(), adapter.config.autoUpdate * 3600000); + updateRegister(); + } + }); +} + + +function getData(callback) { + adapter.log.info('requesting all states'); + let tasks = 0; + tasks++; + adapter.getForeignStates('*', (err, res) => { + adapter.log.info('received all states'); + states = res; + if (!--tasks && callback) callback(); + }); + adapter.log.info('requesting all objects'); + tasks++; + adapter.objects.getObjectList({include_docs: true}, (err, res) => { + adapter.log.info('received all objects'); + res = res.rows; + objects = {}; + let tmpPath = ''; + for (let i = 0; i < res.length; i++) { + objects[res[i].doc._id] = res[i].doc; + if (res[i].doc.type === 'instance' && res[i].doc.common && res[i].doc.common.tmpPath) { + if (tmpPath) { + adapter.log.warn('tmpPath has multiple definitions!!'); + } + tmpPath = res[i].doc.common.tmpPath; + } + } + + // Some adapters want access on specified tmp directory + if (tmpPath) { + adapter.config.tmpPath = tmpPath; + adapter.config.tmpPathAllow = true; + } + + createUpdateInfo(); + writeUpdateInfo(); + if (!--tasks && callback) callback(); + }); +} + +// read repository information from active repository +function updateRegister() { + adapter.log.info('Request actual repository...'); + adapter.getForeignObject('system.config', (err, data) => { + if (data && data.common) { + + adapter.sendToHost(adapter.host, 'getRepository', { + repo: data.common.activeRepo, + update: true + }, _repository => { + if (_repository === 'permissionError') { + adapter.log.error('May not read "getRepository"'); + } else { + adapter.log.info('Repository received successfully.'); + + if (socket) { + socket.repoUpdated(); + } + } + }); + } + }); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a806445 --- /dev/null +++ b/package.json @@ -0,0 +1,70 @@ +{ + "name": "yunkong2.admin", + "description": "The adapter opens a webserver for the yunkong2 admin UI.", + "version": "3.5.9", + "contributors": [ + "bluefox ", + "apollon77", + "soef ", + "hobbyquaker " + ], + "homepage": "https://git.spacen.net/yunkong2/yunkong2.admin", + "repository": { + "type": "git", + "url": "https://git.spacen.net/yunkong2/yunkong2.admin" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://git.spacen.net/yunkong2/yunkong2.admin/blob/master/LICENSE" + } + ], + "keywords": [ + "yunkong2", + "setup" + ], + "dependencies": { + "body-parser": "^1.18.2", + "connect-flash": "^0.1.1", + "cookie-parser": "^1.4.3", + "express": "^4.16.3", + "express-fileupload": "https://git.spacen.net/screenmeet/express-fileupload/tarball/8d8f5d9b7cec2d7c17e6d85654edf1c0aaa5fa82", + "express-session": "^1.15.6", + "passport": "^0.4.0", + "passport-local": "^1.0.0", + "passport.socketio": "^3.7.0", + "request": "^2.85.0", + "socket.io": "1.7.2", + "xtend": "^4.0.1" + }, + "devDependencies": { + "babel-core": "^6.26.3", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.26.0", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "chai": "^4.1.2", + "gulp": "^3.9.1", + "gulp-babel": "^7.0.1", + "gulp-clean-css": "^3.9.4", + "gulp-concat": "^2.6.1", + "gulp-htmlmin": "^4.0.0", + "gulp-less": "^4.0.0", + "gulp-sass": "^3.1.0", + "gulp-sourcemaps": "^2.6.4", + "gulp-uglify": "^3.0.0", + "gulp-util": "^3.0.8", + "materialize-css": "^1.0.0-alpha.4", + "mocha": "^5.2.0" + }, + "bugs": { + "url": "https://git.spacen.net/yunkong2/yunkong2.admin/issues" + }, + "main": "main.js", + "scripts": { + "test": "node node_modules/mocha/bin/mocha", + "prepublish": "node node_modules/gulp/bin/gulp default" + }, + "author": "bluefox ", + "license": "MIT" +} diff --git a/src/adminAdapters.html b/src/adminAdapters.html new file mode 100644 index 0000000..b075028 --- /dev/null +++ b/src/adminAdapters.html @@ -0,0 +1,168 @@ + + +
+ + + +
+ + diff --git a/src/adminConfig.html b/src/adminConfig.html new file mode 100644 index 0000000..22deb1e --- /dev/null +++ b/src/adminConfig.html @@ -0,0 +1,12 @@ + diff --git a/src/adminCron.html b/src/adminCron.html new file mode 100644 index 0000000..2512e53 --- /dev/null +++ b/src/adminCron.html @@ -0,0 +1,153 @@ +
+ + +
\ No newline at end of file diff --git a/src/adminCustoms.html b/src/adminCustoms.html new file mode 100644 index 0000000..c346c31 --- /dev/null +++ b/src/adminCustoms.html @@ -0,0 +1,102 @@ + diff --git a/src/adminEditObject.html b/src/adminEditObject.html new file mode 100644 index 0000000..616d3e3 --- /dev/null +++ b/src/adminEditObject.html @@ -0,0 +1,188 @@ + + +
+ +
\ No newline at end of file diff --git a/src/adminEnums.html b/src/adminEnums.html new file mode 100644 index 0000000..22bc314 --- /dev/null +++ b/src/adminEnums.html @@ -0,0 +1,64 @@ + diff --git a/src/adminEvents.html b/src/adminEvents.html new file mode 100644 index 0000000..533bd44 --- /dev/null +++ b/src/adminEvents.html @@ -0,0 +1,49 @@ + diff --git a/src/adminHosts.html b/src/adminHosts.html new file mode 100644 index 0000000..c3c4d36 --- /dev/null +++ b/src/adminHosts.html @@ -0,0 +1,88 @@ + + + +
+ + +
diff --git a/src/adminInstances.html b/src/adminInstances.html new file mode 100644 index 0000000..d3e4299 --- /dev/null +++ b/src/adminInstances.html @@ -0,0 +1,44 @@ + + + \ No newline at end of file diff --git a/src/adminIntro.html b/src/adminIntro.html new file mode 100644 index 0000000..e139c5f --- /dev/null +++ b/src/adminIntro.html @@ -0,0 +1,42 @@ + + + +
+
+ check + INFO +
+ +
+
+
+

+
+
+ +
+
+
+ Infocontent_copyclose +
+
+
diff --git a/src/adminIssue.html b/src/adminIssue.html new file mode 100644 index 0000000..745c50a --- /dev/null +++ b/src/adminIssue.html @@ -0,0 +1,38 @@ + + + + + diff --git a/src/adminLogs.html b/src/adminLogs.html new file mode 100644 index 0000000..4df283f --- /dev/null +++ b/src/adminLogs.html @@ -0,0 +1,48 @@ + + + diff --git a/src/adminMenuEdit.html b/src/adminMenuEdit.html new file mode 100644 index 0000000..339b61e --- /dev/null +++ b/src/adminMenuEdit.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/adminObjects.html b/src/adminObjects.html new file mode 100644 index 0000000..aea0e72 --- /dev/null +++ b/src/adminObjects.html @@ -0,0 +1,57 @@ + + +
+ +
+ diff --git a/src/adminReadme.html b/src/adminReadme.html new file mode 100644 index 0000000..d669d7c --- /dev/null +++ b/src/adminReadme.html @@ -0,0 +1,46 @@ + diff --git a/src/adminSystem.html b/src/adminSystem.html new file mode 100644 index 0000000..b00f6cf --- /dev/null +++ b/src/adminSystem.html @@ -0,0 +1,547 @@ + diff --git a/src/adminUsers.html b/src/adminUsers.html new file mode 100644 index 0000000..8f0d8c1 --- /dev/null +++ b/src/adminUsers.html @@ -0,0 +1,94 @@ + diff --git a/src/colorpicker/img/alpha-horizontal.png b/src/colorpicker/img/alpha-horizontal.png new file mode 100644 index 0000000..d0a65c0 Binary files /dev/null and b/src/colorpicker/img/alpha-horizontal.png differ diff --git a/src/colorpicker/img/alpha.png b/src/colorpicker/img/alpha.png new file mode 100644 index 0000000..38043f1 Binary files /dev/null and b/src/colorpicker/img/alpha.png differ diff --git a/src/colorpicker/img/hue-horizontal.png b/src/colorpicker/img/hue-horizontal.png new file mode 100644 index 0000000..a0d9add Binary files /dev/null and b/src/colorpicker/img/hue-horizontal.png differ diff --git a/src/colorpicker/img/hue.png b/src/colorpicker/img/hue.png new file mode 100644 index 0000000..d89560e Binary files /dev/null and b/src/colorpicker/img/hue.png differ diff --git a/src/colorpicker/img/saturation.png b/src/colorpicker/img/saturation.png new file mode 100644 index 0000000..594ae50 Binary files /dev/null and b/src/colorpicker/img/saturation.png differ diff --git a/src/colorpicker/js/materialize-colorpicker.js b/src/colorpicker/js/materialize-colorpicker.js new file mode 100644 index 0000000..1d12ee8 --- /dev/null +++ b/src/colorpicker/js/materialize-colorpicker.js @@ -0,0 +1,1086 @@ +/*! + * Bootstrap Colorpicker + * http://mjolnic.github.io/bootstrap-colorpicker/ + * + * Originally written by (c) 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * @todo Update DOCS + */ + +(function(factory) { + "use strict"; + if (typeof exports === 'object') { + module.exports = factory(window.jQuery); + } else if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (window.jQuery && !window.jQuery.fn.colorpicker) { + factory(window.jQuery); + } + } + (function($) { + 'use strict'; + + // Color object + var Color = function(val, customColors) { + this.value = { + h: 0, + s: 0, + b: 0, + a: 1 + }; + this.origFormat = null; // original string format + if (customColors) { + $.extend(this.colors, customColors); + } + if (val) { + if (val.toLowerCase !== undefined) { + // cast to string + val = val + ''; + this.setColor(val); + } else if (val.h !== undefined) { + this.value = val; + } + } + }; + + Color.prototype = { + constructor: Color, + // 140 predefined colors from the HTML Colors spec + colors: { + "aliceblue": "#f0f8ff", + "antiquewhite": "#faebd7", + "aqua": "#00ffff", + "aquamarine": "#7fffd4", + "azure": "#f0ffff", + "beige": "#f5f5dc", + "bisque": "#ffe4c4", + "black": "#000000", + "blanchedalmond": "#ffebcd", + "blue": "#0000ff", + "blueviolet": "#8a2be2", + "brown": "#a52a2a", + "burlywood": "#deb887", + "cadetblue": "#5f9ea0", + "chartreuse": "#7fff00", + "chocolate": "#d2691e", + "coral": "#ff7f50", + "cornflowerblue": "#6495ed", + "cornsilk": "#fff8dc", + "crimson": "#dc143c", + "cyan": "#00ffff", + "darkblue": "#00008b", + "darkcyan": "#008b8b", + "darkgoldenrod": "#b8860b", + "darkgray": "#a9a9a9", + "darkgreen": "#006400", + "darkkhaki": "#bdb76b", + "darkmagenta": "#8b008b", + "darkolivegreen": "#556b2f", + "darkorange": "#ff8c00", + "darkorchid": "#9932cc", + "darkred": "#8b0000", + "darksalmon": "#e9967a", + "darkseagreen": "#8fbc8f", + "darkslateblue": "#483d8b", + "darkslategray": "#2f4f4f", + "darkturquoise": "#00ced1", + "darkviolet": "#9400d3", + "deeppink": "#ff1493", + "deepskyblue": "#00bfff", + "dimgray": "#696969", + "dodgerblue": "#1e90ff", + "firebrick": "#b22222", + "floralwhite": "#fffaf0", + "forestgreen": "#228b22", + "fuchsia": "#ff00ff", + "gainsboro": "#dcdcdc", + "ghostwhite": "#f8f8ff", + "gold": "#ffd700", + "goldenrod": "#daa520", + "gray": "#808080", + "green": "#008000", + "greenyellow": "#adff2f", + "honeydew": "#f0fff0", + "hotpink": "#ff69b4", + "indianred": "#cd5c5c", + "indigo": "#4b0082", + "ivory": "#fffff0", + "khaki": "#f0e68c", + "lavender": "#e6e6fa", + "lavenderblush": "#fff0f5", + "lawngreen": "#7cfc00", + "lemonchiffon": "#fffacd", + "lightblue": "#add8e6", + "lightcoral": "#f08080", + "lightcyan": "#e0ffff", + "lightgoldenrodyellow": "#fafad2", + "lightgrey": "#d3d3d3", + "lightgreen": "#90ee90", + "lightpink": "#ffb6c1", + "lightsalmon": "#ffa07a", + "lightseagreen": "#20b2aa", + "lightskyblue": "#87cefa", + "lightslategray": "#778899", + "lightsteelblue": "#b0c4de", + "lightyellow": "#ffffe0", + "lime": "#00ff00", + "limegreen": "#32cd32", + "linen": "#faf0e6", + "magenta": "#ff00ff", + "maroon": "#800000", + "mediumaquamarine": "#66cdaa", + "mediumblue": "#0000cd", + "mediumorchid": "#ba55d3", + "mediumpurple": "#9370d8", + "mediumseagreen": "#3cb371", + "mediumslateblue": "#7b68ee", + "mediumspringgreen": "#00fa9a", + "mediumturquoise": "#48d1cc", + "mediumvioletred": "#c71585", + "midnightblue": "#191970", + "mintcream": "#f5fffa", + "mistyrose": "#ffe4e1", + "moccasin": "#ffe4b5", + "navajowhite": "#ffdead", + "navy": "#000080", + "oldlace": "#fdf5e6", + "olive": "#808000", + "olivedrab": "#6b8e23", + "orange": "#ffa500", + "orangered": "#ff4500", + "orchid": "#da70d6", + "palegoldenrod": "#eee8aa", + "palegreen": "#98fb98", + "paleturquoise": "#afeeee", + "palevioletred": "#d87093", + "papayawhip": "#ffefd5", + "peachpuff": "#ffdab9", + "peru": "#cd853f", + "pink": "#ffc0cb", + "plum": "#dda0dd", + "powderblue": "#b0e0e6", + "purple": "#800080", + "red": "#ff0000", + "rosybrown": "#bc8f8f", + "royalblue": "#4169e1", + "saddlebrown": "#8b4513", + "salmon": "#fa8072", + "sandybrown": "#f4a460", + "seagreen": "#2e8b57", + "seashell": "#fff5ee", + "sienna": "#a0522d", + "silver": "#c0c0c0", + "skyblue": "#87ceeb", + "slateblue": "#6a5acd", + "slategray": "#708090", + "snow": "#fffafa", + "springgreen": "#00ff7f", + "steelblue": "#4682b4", + "tan": "#d2b48c", + "teal": "#008080", + "thistle": "#d8bfd8", + "tomato": "#ff6347", + "turquoise": "#40e0d0", + "violet": "#ee82ee", + "wheat": "#f5deb3", + "white": "#ffffff", + "whitesmoke": "#f5f5f5", + "yellow": "#ffff00", + "yellowgreen": "#9acd32", + "transparent": "transparent" + }, + _sanitizeNumber: function(val) { + if (typeof val === 'number') { + return val; + } + if (isNaN(val) || (val === null) || (val === '') || (val === undefined)) { + return 1; + } + if (val.toLowerCase !== undefined) { + return parseFloat(val); + } + return 1; + }, + isTransparent: function(strVal) { + if (!strVal) { + return false; + } + strVal = strVal.toLowerCase().trim(); + return (strVal === 'transparent') || (strVal.match(/#?00000000/)) || (strVal.match(/(rgba|hsla)\(0,0,0,0?\.?0\)/)); + }, + rgbaIsTransparent: function(rgba) { + return ((rgba.r === 0) && (rgba.g === 0) && (rgba.b === 0) && (rgba.a === 0)); + }, + //parse a string to HSB + setColor: function(strVal) { + strVal = strVal.toLowerCase().trim(); + if (strVal) { + if (this.isTransparent(strVal)) { + this.value = { + h: 0, + s: 0, + b: 0, + a: 0 + }; + } else { + this.value = this.stringToHSB(strVal) || { + h: 0, + s: 0, + b: 0, + a: 1 + }; // if parser fails, defaults to black + } + } + }, + stringToHSB: function(strVal) { + strVal = strVal.toLowerCase(); + var alias; + if (typeof this.colors[strVal] !== 'undefined') { + strVal = this.colors[strVal]; + alias = 'alias'; + } + var that = this, + result = false; + $.each(this.stringParsers, function(i, parser) { + var match = parser.re.exec(strVal), + values = match && parser.parse.apply(that, [match]), + format = alias || parser.format || 'rgba'; + if (values) { + if (format.match(/hsla?/)) { + result = that.RGBtoHSB.apply(that, that.HSLtoRGB.apply(that, values)); + } else { + result = that.RGBtoHSB.apply(that, values); + } + that.origFormat = format; + return false; + } + return true; + }); + return result; + }, + setHue: function(h) { + this.value.h = 1 - h; + }, + setSaturation: function(s) { + this.value.s = s; + }, + setBrightness: function(b) { + this.value.b = 1 - b; + }, + setAlpha: function(a) { + this.value.a = parseInt((1 - a) * 100, 10) / 100; + }, + toRGB: function(h, s, b, a) { + if (!h) { + h = this.value.h; + s = this.value.s; + b = this.value.b; + } + h *= 360; + var R, G, B, X, C; + h = (h % 360) / 60; + C = b * s; + X = C * (1 - Math.abs(h % 2 - 1)); + R = G = B = b - C; + + h = ~~h; + R += [C, X, 0, 0, X, C][h]; + G += [X, C, C, X, 0, 0][h]; + B += [0, 0, X, C, C, X][h]; + return { + r: Math.round(R * 255), + g: Math.round(G * 255), + b: Math.round(B * 255), + a: a || this.value.a + }; + }, + toHex: function(h, s, b, a) { + var rgb = this.toRGB(h, s, b, a); + if (this.rgbaIsTransparent(rgb)) { + return 'transparent'; + } + return '#' + ((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1); + }, + toHSL: function(h, s, b, a) { + h = h || this.value.h; + s = s || this.value.s; + b = b || this.value.b; + a = a || this.value.a; + + var H = h, + L = (2 - s) * b, + S = s * b; + if (L > 0 && L <= 1) { + S /= L; + } else { + S /= 2 - L; + } + L /= 2; + if (S > 1) { + S = 1; + } + return { + h: isNaN(H) ? 0 : H, + s: isNaN(S) ? 0 : S, + l: isNaN(L) ? 0 : L, + a: isNaN(a) ? 0 : a + }; + }, + toAlias: function(r, g, b, a) { + var rgb = this.toHex(r, g, b, a); + for (var alias in this.colors) { + if (this.colors[alias] === rgb) { + return alias; + } + } + return false; + }, + RGBtoHSB: function(r, g, b, a) { + r /= 255; + g /= 255; + b /= 255; + + var H, S, V, C; + V = Math.max(r, g, b); + C = V - Math.min(r, g, b); + H = (C === 0 ? null : + V === r ? (g - b) / C : + V === g ? (b - r) / C + 2 : + (r - g) / C + 4 + ); + H = ((H + 360) % 6) * 60 / 360; + S = C === 0 ? 0 : C / V; + return { + h: this._sanitizeNumber(H), + s: S, + b: V, + a: this._sanitizeNumber(a) + }; + }, + HueToRGB: function(p, q, h) { + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + if ((h * 6) < 1) { + return p + (q - p) * h * 6; + } else if ((h * 2) < 1) { + return q; + } else if ((h * 3) < 2) { + return p + (q - p) * ((2 / 3) - h) * 6; + } else { + return p; + } + }, + HSLtoRGB: function(h, s, l, a) { + if (s < 0) { + s = 0; + } + var q; + if (l <= 0.5) { + q = l * (1 + s); + } else { + q = l + s - (l * s); + } + + var p = 2 * l - q; + + var tr = h + (1 / 3); + var tg = h; + var tb = h - (1 / 3); + + var r = Math.round(this.HueToRGB(p, q, tr) * 255); + var g = Math.round(this.HueToRGB(p, q, tg) * 255); + var b = Math.round(this.HueToRGB(p, q, tb) * 255); + return [r, g, b, this._sanitizeNumber(a)]; + }, + toString: function(format) { + format = format || 'rgba'; + var c = false; + switch (format) { + case 'rgb': + { + c = this.toRGB(); + if (this.rgbaIsTransparent(c)) { + return 'transparent'; + } + return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')'; + } + break; + case 'rgba': + { + c = this.toRGB(); + return 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + c.a + ')'; + } + break; + case 'hsl': + { + c = this.toHSL(); + return 'hsl(' + Math.round(c.h * 360) + ',' + Math.round(c.s * 100) + '%,' + Math.round(c.l * 100) + '%)'; + } + break; + case 'hsla': + { + c = this.toHSL(); + return 'hsla(' + Math.round(c.h * 360) + ',' + Math.round(c.s * 100) + '%,' + Math.round(c.l * 100) + '%,' + c.a + ')'; + } + break; + case 'hex': + { + return this.toHex(); + } + break; + case 'alias': + return this.toAlias() || this.toHex(); + default: + { + return c; + } + break; + } + }, + // a set of RE's that can match strings and generate color tuples. + // from John Resig color plugin + // https://github.com/jquery/jquery-color/ + stringParsers: [{ + re: /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/, + format: 'rgb', + parse: function(execResult) { + return [ + execResult[1], + execResult[2], + execResult[3], + 1 + ]; + } + }, { + re: /rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/, + format: 'rgb', + parse: function(execResult) { + return [ + 2.55 * execResult[1], + 2.55 * execResult[2], + 2.55 * execResult[3], + 1 + ]; + } + }, { + re: /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, + format: 'rgba', + parse: function(execResult) { + return [ + execResult[1], + execResult[2], + execResult[3], + execResult[4] + ]; + } + }, { + re: /rgba\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, + format: 'rgba', + parse: function(execResult) { + return [ + 2.55 * execResult[1], + 2.55 * execResult[2], + 2.55 * execResult[3], + execResult[4] + ]; + } + }, { + re: /hsl\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/, + format: 'hsl', + parse: function(execResult) { + return [ + execResult[1] / 360, + execResult[2] / 100, + execResult[3] / 100, + execResult[4] + ]; + } + }, { + re: /hsla\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, + format: 'hsla', + parse: function(execResult) { + return [ + execResult[1] / 360, + execResult[2] / 100, + execResult[3] / 100, + execResult[4] + ]; + } + }, { + re: /#?([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, + format: 'hex', + parse: function(execResult) { + return [ + parseInt(execResult[1], 16), + parseInt(execResult[2], 16), + parseInt(execResult[3], 16), + 1 + ]; + } + }, { + re: /#?([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, + format: 'hex', + parse: function(execResult) { + return [ + parseInt(execResult[1] + execResult[1], 16), + parseInt(execResult[2] + execResult[2], 16), + parseInt(execResult[3] + execResult[3], 16), + 1 + ]; + } + }], + colorNameToHex: function(name) { + if (typeof this.colors[name.toLowerCase()] !== 'undefined') { + return this.colors[name.toLowerCase()]; + } + return false; + } + }; + + + var defaults = { + horizontal: false, // horizontal mode layout ? + inline: false, //forces to show the colorpicker as an inline element + color: false, //forces a color + format: false, //forces a format + input: 'input', // children input selector + container: false, // container selector + component: '.add-on, .input-group-addon', // children component selector + sliders: { + saturation: { + maxLeft: 100, + maxTop: 100, + callLeft: 'setSaturation', + callTop: 'setBrightness' + }, + hue: { + maxLeft: 0, + maxTop: 100, + callLeft: false, + callTop: 'setHue' + }, + alpha: { + maxLeft: 0, + maxTop: 100, + callLeft: false, + callTop: 'setAlpha' + } + }, + slidersHorz: { + saturation: { + maxLeft: 100, + maxTop: 100, + callLeft: 'setSaturation', + callTop: 'setBrightness' + }, + hue: { + maxLeft: 100, + maxTop: 0, + callLeft: 'setHue', + callTop: false + }, + alpha: { + maxLeft: 100, + maxTop: 0, + callLeft: 'setAlpha', + callTop: false + } + }, + template: + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
', + align: 'right', + customClass: null, + colorSelectors: null + }; + + var Colorpicker = function(element, options) { + this.element = $(element).addClass('colorpicker-element'); + this.options = $.extend(true, {}, defaults, this.element.data(), options); + this.component = this.options.component; + this.component = (this.component !== false) ? this.element.find(this.component) : false; + if (this.component && (this.component.length === 0)) { + this.component = false; + } else { + this.options.color = this.component.css('background-color'); + } + + this.container = (this.options.container === true) ? this.element : this.options.container; + this.container = (this.container !== false) ? $(this.container) : false; + + // Is the element an input? Should we search inside for any input? + this.input = this.element.is('input') ? this.element : (this.options.input ? + this.element.find(this.options.input) : false); + if (this.input && (this.input.length === 0)) { + this.input = false; + } + // Set HSB color + this.color = new Color(this.options.color !== false ? this.options.color : this.getValue(), this.options.colorSelectors); + this.format = this.options.format !== false ? this.options.format : this.color.origFormat; + + // Setup picker + this.picker = $(this.options.template); + if (this.options.customClass) { + this.picker.addClass(this.options.customClass); + } + if (this.options.inline) { + this.picker.addClass('colorpicker-inline colorpicker-visible'); + } else { + this.picker.addClass('colorpicker-hidden'); + } + if (this.options.horizontal) { + this.picker.addClass('colorpicker-horizontal'); + } + if (this.format === 'rgba' || this.format === 'hsla' || this.options.format === false) { + this.picker.addClass('colorpicker-with-alpha'); + } + if (this.options.align === 'right') { + this.picker.addClass('colorpicker-right'); + } + if (this.options.inline === true) { + this.picker.addClass('colorpicker-no-arrow'); + } + if (this.options.colorSelectors) { + var colorpicker = this; + $.each(this.options.colorSelectors, function(name, color) { + var $btn = $('').css('background-color', color).data('class', name); + $btn.click(function() { + colorpicker.setValue($(this).css('background-color')); + }); + colorpicker.picker.find('.colorpicker-selectors').append($btn); + }); + this.picker.find('.colorpicker-selectors').show(); + } + this.picker.on('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.mousedown, this)); + this.picker.appendTo(this.container ? this.container : $('body')); + + // Bind events + if (this.input !== false) { + this.input.on({ + 'keyup.colorpicker': $.proxy(this.keyup, this) + }); + this.input.on({ + 'change.colorpicker': $.proxy(this.change, this) + }); + if (this.component === false) { + this.element.on({ + 'focus.colorpicker': $.proxy(this.show, this) + }); + } + if (this.options.inline === false) { + this.element.on({ + 'focusout.colorpicker': $.proxy(this.hide, this) + }); + } + } + + if (this.component !== false) { + this.component.on({ + 'click.colorpicker': $.proxy(this.show, this) + }); + } + + if ((this.input === false) && (this.component === false)) { + this.element.on({ + 'click.colorpicker': $.proxy(this.show, this) + }); + } + + // for HTML5 input[type='color'] + if ((this.input !== false) && (this.component !== false) && (this.input.attr('type') === 'color')) { + + this.input.on({ + 'click.colorpicker': $.proxy(this.show, this), + 'focus.colorpicker': $.proxy(this.show, this) + }); + } + this.update(true); + + $($.proxy(function() { + this.element.trigger('create'); + }, this)); + }; + + Colorpicker.Color = Color; + + Colorpicker.prototype = { + constructor: Colorpicker, + destroy: function() { + this.picker.remove(); + this.element.removeData('colorpicker').off('.colorpicker'); + if (this.input !== false) { + this.input.off('.colorpicker'); + } + if (this.component !== false) { + this.component.off('.colorpicker'); + } + this.element.removeClass('colorpicker-element'); + this.element.trigger({ + type: 'destroy' + }); + }, + reposition: function() { + if (this.options.inline !== false || this.options.container) { + return false; + } + var type = this.container && this.container[0] !== document.body ? 'position' : 'offset'; + var element = this.component || this.element; + var offset = element[type](); + if (this.options.align === 'right') { + offset.left -= this.picker.outerWidth() - element.outerWidth(); + } + this.picker.css({ + top: offset.top + element.outerHeight(), + left: offset.left + }); + }, + show: function(e) { + if (this.isDisabled()) { + return false; + } + this.picker.addClass('colorpicker-visible').removeClass('colorpicker-hidden'); + this.reposition(); + $(window).on('resize.colorpicker', $.proxy(this.reposition, this)); + if (e && (!this.hasInput() || this.input.attr('type') === 'color')) { + if (e.stopPropagation && e.preventDefault) { + e.stopPropagation(); + e.preventDefault(); + } + } + if (this.options.inline === false) { + $(window.document).on({ + 'mousedown.colorpicker': $.proxy(this.hide, this) + }); + } + this.element.trigger({ + type: 'showPicker', + color: this.color + }); + }, + hide: function() { + this.picker.addClass('colorpicker-hidden').removeClass('colorpicker-visible'); + $(window).off('resize.colorpicker', this.reposition); + $(document).off({ + 'mousedown.colorpicker': this.hide + }); + this.update(); + this.element.trigger({ + type: 'hidePicker', + color: this.color + }); + }, + updateData: function(val) { + val = val || this.color.toString(this.format); + this.element.data('color', val); + return val; + }, + updateInput: function(val) { + val = val || this.color.toString(this.format); + if (this.input !== false) { + if (this.options.colorSelectors) { + var color = new Color(val, this.options.colorSelectors); + var alias = color.toAlias(); + if (typeof this.options.colorSelectors[alias] !== 'undefined') { + val = alias; + } + } + this.input.prop('value', val); + } + return val; + }, + updatePicker: function(val) { + if (val !== undefined) { + this.color = new Color(val, this.options.colorSelectors); + } + var sl = (this.options.horizontal === false) ? this.options.sliders : this.options.slidersHorz; + var icns = this.picker.find('i'); + if (icns.length === 0) { + return; + } + if (this.options.horizontal === false) { + sl = this.options.sliders; + icns.eq(1).css('top', sl.hue.maxTop * (1 - this.color.value.h)).end() + .eq(2).css('top', sl.alpha.maxTop * (1 - this.color.value.a)); + } else { + sl = this.options.slidersHorz; + icns.eq(1).css('left', sl.hue.maxLeft * (1 - this.color.value.h)).end() + .eq(2).css('left', sl.alpha.maxLeft * (1 - this.color.value.a)); + } + icns.eq(0).css({ + 'top': sl.saturation.maxTop - this.color.value.b * sl.saturation.maxTop, + 'left': this.color.value.s * sl.saturation.maxLeft + }); + this.picker.find('.colorpicker-saturation').css('backgroundColor', this.color.toHex(this.color.value.h, 1, 1, 1)); + this.picker.find('.colorpicker-alpha').css('backgroundColor', this.color.toHex()); + this.picker.find('.colorpicker-color, .colorpicker-color div').css('backgroundColor', this.color.toString(this.format)); + return val; + }, + updateComponent: function(val) { + val = val || this.color.toString(this.format); + if (this.component !== false) { + var icn = this.component.find('i').eq(0); + if (icn.length > 0) { + icn.css({ + 'backgroundColor': val + }); + } else { + this.component.css({ + 'backgroundColor': val + }); + } + } + return val; + }, + update: function(force) { + var val; + if ((this.getValue(false) !== false) || (force === true)) { + // Update input/data only if the current value is not empty + val = this.updateComponent(); + this.updateInput(val); + this.updateData(val); + this.updatePicker(); // only update picker if value is not empty + } + return val; + + }, + setValue: function(val) { // set color manually + this.color = new Color(val, this.options.colorSelectors); + this.update(true); + this.element.trigger({ + type: 'changeColor', + color: this.color, + value: val + }); + }, + getValue: function(defaultValue) { + defaultValue = (defaultValue === undefined) ? '#000000' : defaultValue; + var val; + if (this.hasInput()) { + val = this.input.val(); + } else { + val = this.element.data('color'); + } + if ((val === undefined) || (val === '') || (val === null)) { + // if not defined or empty, return default + val = defaultValue; + } + return val; + }, + hasInput: function() { + return (this.input !== false); + }, + isDisabled: function() { + if (this.hasInput()) { + return (this.input.prop('disabled') === true); + } + return false; + }, + disable: function() { + if (this.hasInput()) { + this.input.prop('disabled', true); + this.element.trigger({ + type: 'disable', + color: this.color, + value: this.getValue() + }); + return true; + } + return false; + }, + enable: function() { + if (this.hasInput()) { + this.input.prop('disabled', false); + this.element.trigger({ + type: 'enable', + color: this.color, + value: this.getValue() + }); + return true; + } + return false; + }, + currentSlider: null, + mousePointer: { + left: 0, + top: 0 + }, + mousedown: function(e) { + if (!e.pageX && !e.pageY && e.originalEvent) { + e.pageX = e.originalEvent.touches[0].pageX; + e.pageY = e.originalEvent.touches[0].pageY; + } + e.stopPropagation(); + e.preventDefault(); + + var target = $(e.target); + + //detect the slider and set the limits and callbacks + var zone = target.closest('div'); + var sl = this.options.horizontal ? this.options.slidersHorz : this.options.sliders; + if (!zone.is('.colorpicker')) { + if (zone.is('.colorpicker-saturation')) { + this.currentSlider = $.extend({}, sl.saturation); + } else if (zone.is('.colorpicker-hue')) { + this.currentSlider = $.extend({}, sl.hue); + } else if (zone.is('.colorpicker-alpha')) { + this.currentSlider = $.extend({}, sl.alpha); + } else { + return false; + } + var offset = zone.offset(); + //reference to guide's style + this.currentSlider.guide = zone.find('i')[0].style; + this.currentSlider.left = e.pageX - offset.left; + this.currentSlider.top = e.pageY - offset.top; + this.mousePointer = { + left: e.pageX, + top: e.pageY + }; + //trigger mousemove to move the guide to the current position + $(document).on({ + 'mousemove.colorpicker': $.proxy(this.mousemove, this), + 'touchmove.colorpicker': $.proxy(this.mousemove, this), + 'mouseup.colorpicker': $.proxy(this.mouseup, this), + 'touchend.colorpicker': $.proxy(this.mouseup, this) + }).trigger('mousemove'); + } + return false; + }, + mousemove: function(e) { + if (!e.pageX && !e.pageY && e.originalEvent) { + e.pageX = e.originalEvent.touches[0].pageX; + e.pageY = e.originalEvent.touches[0].pageY; + } + e.stopPropagation(); + e.preventDefault(); + var left = Math.max( + 0, + Math.min( + this.currentSlider.maxLeft, + this.currentSlider.left + ((e.pageX || this.mousePointer.left) - this.mousePointer.left) + ) + ); + var top = Math.max( + 0, + Math.min( + this.currentSlider.maxTop, + this.currentSlider.top + ((e.pageY || this.mousePointer.top) - this.mousePointer.top) + ) + ); + this.currentSlider.guide.left = left + 'px'; + this.currentSlider.guide.top = top + 'px'; + if (this.currentSlider.callLeft) { + this.color[this.currentSlider.callLeft].call(this.color, left / this.currentSlider.maxLeft); + } + if (this.currentSlider.callTop) { + this.color[this.currentSlider.callTop].call(this.color, top / this.currentSlider.maxTop); + } + // Change format dynamically + // Only occurs if user choose the dynamic format by + // setting option format to false + if (this.currentSlider.callTop === 'setAlpha' && this.options.format === false) { + + // Converting from hex / rgb to rgba + if (this.color.value.a !== 1) { + this.format = 'rgba'; + this.color.origFormat = 'rgba'; + } + + // Converting from rgba to hex + else { + this.format = 'hex'; + this.color.origFormat = 'hex'; + } + } + this.update(true); + + this.element.trigger({ + type: 'changeColor', + color: this.color + }); + return false; + }, + mouseup: function(e) { + e.stopPropagation(); + e.preventDefault(); + $(document).off({ + 'mousemove.colorpicker': this.mousemove, + 'touchmove.colorpicker': this.mousemove, + 'mouseup.colorpicker': this.mouseup, + 'touchend.colorpicker': this.mouseup + }); + return false; + }, + change: function(e) { + this.keyup(e); + }, + keyup: function(e) { + if ((e.keyCode === 38)) { + if (this.color.value.a < 1) { + this.color.value.a = Math.round((this.color.value.a + 0.01) * 100) / 100; + } + this.update(true); + } else if ((e.keyCode === 40)) { + if (this.color.value.a > 0) { + this.color.value.a = Math.round((this.color.value.a - 0.01) * 100) / 100; + } + this.update(true); + } else { + this.color = new Color(this.input.val(), this.options.colorSelectors); + // Change format dynamically + // Only occurs if user choose the dynamic format by + // setting option format to false + if (this.color.origFormat && this.options.format === false) { + this.format = this.color.origFormat; + } + if (this.getValue(false) !== false) { + this.updateData(); + this.updateComponent(); + this.updatePicker(); + } + } + this.element.trigger({ + type: 'changeColor', + color: this.color, + value: this.input.val() + }); + } + }; + + $.colorpicker = Colorpicker; + + $.fn.colorpicker = function(option) { + var pickerArgs = arguments, + rv; + + var $returnValue = this.each(function() { + var $this = $(this), + inst = $this.data('colorpicker'), + options = ((typeof option === 'object') ? option : {}); + if ((!inst) && (typeof option !== 'string')) { + $this.data('colorpicker', new Colorpicker(this, options)); + } else { + if (typeof option === 'string') { + rv = inst[option].apply(inst, Array.prototype.slice.call(pickerArgs, 1)); + } + } + }); + if (option === 'getValue') { + return rv; + } + return $returnValue; + }; + + $.fn.colorpicker.constructor = Colorpicker; + + })); diff --git a/src/colorpicker/less/colorpicker.less b/src/colorpicker/less/colorpicker.less new file mode 100644 index 0000000..75e9487 --- /dev/null +++ b/src/colorpicker/less/colorpicker.less @@ -0,0 +1,225 @@ +/*! + * Bootstrap Colorpicker + * http://mjolnic.github.io/bootstrap-colorpicker/ + * + * Originally written by (c) 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + */ +@imgPath: "../img/"; + +.bgImg(@imgFilename){ + background-image: url("@{imgPath}@{imgFilename}"); +} +.borderRadius(@size){ + -webkit-border-radius: @size; + -moz-border-radius: @size; + border-radius: @size; +} + +.colorpicker-saturation { + width: 100px; + height: 100px; + .bgImg('saturation.png'); + cursor: crosshair; + float: left; + i { + display: block; + height: 5px; + width: 5px; + border: 1px solid #000; + .borderRadius(5px); + position: absolute; + top: 0; + left: 0; + margin: -4px 0 0 -4px; + b { + display: block; + height: 5px; + width: 5px; + border: 1px solid #fff; + .borderRadius(5px); + } + } +} + +.colorpicker-hue, +.colorpicker-alpha { + width: 15px; + height: 100px; + float: left; + cursor: row-resize; + margin-left: 4px; + margin-bottom: 4px; +} +.colorpicker-hue i, +.colorpicker-alpha i { + display: block; + height: 1px; + background: #000; + border-top: 1px solid #fff; + position: absolute; + top: 0; + left: 0; + width: 100%; + margin-top: -1px; +} +.colorpicker-hue { + .bgImg('hue.png'); +} +.colorpicker-alpha { + .bgImg('alpha.png'); + display: none; +} +.colorpicker-saturation, +.colorpicker-hue, +.colorpicker-alpha{ + background-size: contain; +} +.colorpicker { + *zoom: 1; + top: 0; + left: 0; + padding: 4px; + min-width: 130px; + margin-top: 1px; + .borderRadius(4px); + z-index:2500; +} +.colorpicker:before, +.colorpicker:after { + display: table; + content: ""; + line-height: 0; +} +.colorpicker:after { + clear: both; +} +.colorpicker:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; + top: -7px; + left: 6px; +} +.colorpicker:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + position: absolute; + top: -6px; + left: 7px; +} +.colorpicker div { + position: relative; +} +.colorpicker.colorpicker-with-alpha { + min-width: 140px; +} +.colorpicker.colorpicker-with-alpha .colorpicker-alpha { + display: block; +} +.colorpicker-color { + height: 10px; + margin-top: 5px; + clear: both; + .bgImg('alpha.png'); + background-position: 0 100%; +} +.colorpicker-color div { + height: 10px; +} +.colorpicker-selectors { + display: none; + height: 10px; + margin-top: 5px; + clear: both; +} +.colorpicker-selectors i { + cursor: pointer; + float: left; + height: 10px; + width: 10px; +} +.colorpicker-selectors i + i { + margin-left: 3px; +} +.colorpicker-element .input-group-addon i, +.colorpicker-element .add-on i { + display: inline-block; + cursor: pointer; + height: 16px; + vertical-align: text-top; + width: 16px; +} +.colorpicker.colorpicker-inline { + position: relative; + display: inline-block; + float: none; + z-index: auto; +} +.colorpicker.colorpicker-horizontal { + width: 110px; + min-width: 110px; + height: auto; +} +.colorpicker.colorpicker-horizontal .colorpicker-saturation{ + margin-bottom: 4px; +} +.colorpicker.colorpicker-horizontal .colorpicker-color { + width: 100px; +} +.colorpicker.colorpicker-horizontal .colorpicker-hue, +.colorpicker.colorpicker-horizontal .colorpicker-alpha { + width: 100px; + height: 15px; + float: left; + cursor: col-resize; + margin-left: 0px; + margin-bottom: 4px; +} +.colorpicker.colorpicker-horizontal .colorpicker-hue i, +.colorpicker.colorpicker-horizontal .colorpicker-alpha i { + display: block; + height: 15px; + background: #ffffff; + position: absolute; + top: 0; + left: 0; + width: 1px; + border: none; + margin-top: 0px; +} +.colorpicker.colorpicker-horizontal .colorpicker-hue { + .bgImg('hue-horizontal.png'); +} +.colorpicker.colorpicker-horizontal .colorpicker-alpha { + .bgImg('alpha-horizontal.png'); +} + +.colorpicker.colorpicker-hidden{ + display:none; +} + +.colorpicker.colorpicker-visible{ + display:block; +} +.colorpicker-inline.colorpicker-visible{ + display:inline-block; +} + +.colorpicker-right:before { + left: auto; + right: 6px; +} +.colorpicker-right:after { + left: auto; + right: 7px; +} diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000..72714fa Binary files /dev/null and b/src/favicon.ico differ diff --git a/src/i18n/de/translations.json b/src/i18n/de/translations.json new file mode 100644 index 0000000..d549b4e --- /dev/null +++ b/src/i18n/de/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " für %s", + "%s added to %s": "%s wurde zu %s hinzugefügt", + "%s object(s) processed": "%s Objekte wurden bearbeitet", + "%s processes": "%s Prozesse", + "%s was imported": "%s wurde importiert", + "(without prefix)": "(ohne Präfix)", + "1 %d days ago": "vor %d Tagen", + "2 %d days ago": "vor %d Tagen", + "5 %d days ago": "vor %d Tagen", + "A-Z": "A-Z", + "Access control": "Zugriffskontrolle", + "Access control list": "Liste der Zugriffsrechte", + "Acknowledged": "Bestätigt", + "Activated. Click to stop.": "Aktiviert. Klicken zum Stoppen.", + "Active instances": "Aktivierte Instanzen", + "Active repository:": "Aktiver Verwahrungsort", + "Adapter configuration": "Adapterkonfiguration", + "Adapter settings for %s states": "Adaptereinstellungen für %s-Status", + "Adapters": "Adapter", + "Adapters from this Group installed": "Adaptern aus dieser Gruppe installiert", + "Add": "Hinzufügen", + "Add Objecttree from JSON File": "Eine Objektstruktur mittels JSON Datei hochladen", + "Add certificate from file": "Zertifikat aus der Datei hinzufügen", + "Add instance...": "Instanz wird hinzugefügt ...", + "Add member": "Mitglied hinzufügen", + "Add new child object to selected parent": "Ein Tochterobjekt zu ausgewähltem Objekt hinzufügen", + "Add new field": "Neues Attribut hinzufügen", + "Add new issue": "Bug melden", + "Add new object: ": "Neues Objekt hinzufügen: ", + "Add new object: %s": "Neues Objekt hinzufügen: %s", + "Address:": "Adresse", + "Admin is not enabled in cloud settings!": "Admin ist in Cloud-Einstellungen nicht aktiviert!", + "Administrator": "Administrator", + "Afghanistan": "Afghanistan", + "Albania": "Albanien", + "Algeria": "Algerien", + "All": "Alle", + "American Samoa": "American Samoa", + "Andorra": "Andorra", + "Angola": "Angola", + "Anguilla": "Anguilla", + "Antarctica": "Antarctica", + "Antigua and Barbuda": "Antigua und Barbuda", + "Apr": "apr", + "April": "April", + "Architecture": "Die Architektur", + "Are you sure to delete %s?": "Wollen Sie wirklich \"%s\" löschen?", + "Are you sure to delete all children of %s?": "Wollen Sie wirklich alle unterliegende Objekte von \"%s\" löschen?", + "Are you sure to delete all children of %s?": "Wollen Sie wirklich \"%s\" und alle unterliegende Objekte löschen?", + "Are you sure to delete script %s?": "Wollen Sie das Skript '%s' wirklich löschen?", + "Are you sure you want to delete adapter %s?": "Möchten Sie den Adapter %s wirklich löschen?", + "Are you sure you want to delete the instance %s?": "Möchten Sie die Instanz %s wirklich löschen?", + "Are you sure?": "Sind sie sicher?", + "Are you sure? Changes are not saved.": "Sind Sie sicher? Die Änderungen sind nicht gespeichert.", + "Argentina": "Argentinien", + "Armenia": "Armenien", + "Aruba": "Aruba", + "Aug": "Aug", + "August": "August", + "Australia": "Australien", + "Austria": "Österreich", + "Authentication was deactivated": "Die Authentifizierung wurde deaktiviert", + "Available": "Verfügbar", + "Available version:": "Verfügbare Version:", + "Azerbaijan": "Azerbaijan", + "Background": "Hintergrund", + "Background color of the login screen": "Hintergrundfarbe des Anmeldebildschirms", + "Background image": "Hintergrundbild", + "Bahamas": "Bahamas", + "Bahrain": "Bahrain", + "Bangladesh": "Bangladesh", + "Barbados": "Barbados", + "Belarus": "Weissrussland", + "Belgium": "Belgien", + "Belize": "Belize", + "Benin": "Benin", + "Bermuda": "Bermuda", + "Bhutan": "Bhutan", + "Bolivia": "Bolivien", + "Bosnia and Herzegovina": "Bosnien und Herzegovina", + "Botswana": "Botswana", + "Bouvet Island": "Bouvet Island", + "Brazil": "Brasilien", + "British Indian Ocean Territory": "British Indian Ocean Territory", + "Brunei Darussalam": "Brunei Darussalam", + "Bulgaria": "Bulgarien", + "Burkina Faso": "Burkina Faso", + "Burundi": "Burundi", + "CPUs": "CPUs", + "Calendar": "Kalender", + "Cambodia": "Kambodscha", + "Cameroon": "Kamerun", + "Canada": "Kanada", + "Cancel": "Abbrechen", + "Cannot create user: ": "Benutzer kann nicht erstellt werden: ", + "Cannot delete user: ": "Benutzer kann nicht gelöscht werden: ", + "Cannot disable admin!": "Admin kann nicht deaktiviert werden!", + "Cannot read file!": "Kann die Datei nicht lesen!", + "Cannot read version from NPM": "Die Version aus NPM konnte nicht gelesen werden", + "Cannot set password: ": "Kann Passwort nicht setzen: ", + "Cape Verde": "Cap Verde", + "Cayman Islands": "Cayman Islands", + "Central African Republic": "Zentralafrikanische Republik", + "Certificates": "Zertifikate", + "Chad": "Tschad", + "Change": "Ändern", + "Changelog": "Änderungsprotokoll", + "Channel": "Kanal", + "Chart": "Grafik", + "Chart for %s": "Chart für %s", + "Check all": "Alle auswählen", + "Chile": "Chile", + "China": "China", + "Christmas Island": "Christmas Island", + "City:": "Stadt", + "Clear": "Löschen", + "Clear list": "Anzeige löschen", + "Clear log": "Log löschen", + "Clear on disk permanent": "Lösche Log auf dem Host", + "Click do activate events again, or just wait one minute": "Eine Minute warten oder hier klicken, um die Ereignisse erneut zu aktivieren", + "Click on icon": "Auf das Icon klicken", + "Close": "Schließen", + "Cocos Islands": "Kokosinseln", + "Collapse all nodes": "Alle Knoten ausblenden", + "Colombia": "Kolumbien", + "Color": "Farbe", + "Comoros": "Komoren", + "Configuration not saved.": "Die Konfiguration ist nicht gespeichert.", + "Congo": "Congo", + "Connected to %s: ": "Verbunden mit %s: ", + "Connected to host: ": "Verbunden mit Host: ", + "Cook Islands": "Cook Islands", + "Copy log": "Log kopieren", + "Copy to clipboard": "In die Zwischenablage kopieren", + "Costa Rica": "Costa Rica", + "Country:": "Land", + "Create": "Erzeugen", + "Create new category": "Eine neue Kategorie erstellen", + "Create new category, like %s": "Eine neue Kategorie erstellen, wie %s", + "Create new enum": "Eine neue Aufzählung erstellen", + "Create new enum, like %s": "Eine neue Aufzählung erstellen, wie %s", + "Create new group": "Neue Gruppe erstellen", + "Create new user": "Erstelle einen neuen Benutzer", + "Created": "Erstellt", + "Croatia": "Kroatien", + "Cron expression": "Cron-Ausdruck", + "Cuba": "Kuba", + "Currency:": "Währung", + "Custom": "Beliebig", + "Cyprus": "Zypern", + "Czech Republic": "Tschechische Republik", + "D$ecember": "Dezember", + "DD.MM.YY": "DD.MM.YY", + "DD.MM.YYYY": "DD.MM.YYYY", + "DD/MM/YYYY": "DD/MM/YYYY", + "Date From": "Datum von", + "Date To": "Datum bis", + "Date format:": "Datumsformat", + "Deactivated. Click to start.": "Deaktiviert. Klicken zum Starten.", + "Debug outputs:": "Debug-Ausgabe", + "Dec": "Dez", + "December": "Dezember", + "Default ACL": "Standard ACL", + "Default history instance:": "Standard Historyinstanz", + "Delete attribute": "Attribut löschen", + "Delete category": "Kategorie löschen", + "Delete enum": "Aufzählung löschen", + "Delete member": "Mitglied löschen", + "Delete object": "Objekt löschen", + "Denmark": "Dänemark", + "Description": "Beschreibung", + "Device": "Gerät", + "Device discovery": "Gerätesuche", + "Disable authentication": "Authentifizierung deaktivieren", + "Disk free": "Festplatten frei", + "Disk free:": "Datenträger verfügbar:", + "Disk size": "Festplattengröße", + "Djibouti": "Dschibuti", + "Do you want to delete just one object or all children of %s too?": "Wollen Sie nur ein Objekt löschen oder auch alle Unter-Objekte von %s?", + "Do you want to upgrade all adapters?": "Alle Adapter upgraden?", + "Domains:": "Domäne", + "Dominica": "Dominica", + "Dominican Republic": "Dominikanische Republik", + "Done with error: %s": "Fertig mit Fehler: %s", + "Download log": "Log herunterladen", + "Drop the files here": "Die Dateien hier ablegen", + "Drop the icons here": "Die Icons hier ablegen", + "East Timor": "Ost Timor", + "Ecuador": "Equador", + "Edit": "Editieren", + "Edit category": "Kategorie bearbeiten", + "Edit enum": "Aufzählung bearbeiten", + "Edit in dialog": "Im Dialog bearbeiten", + "Edit object": "Objekt bearbeiten", + "Egypt": "Ägypten", + "El Salvador": "El Salvador", + "Email for account:": "Emailadresse des Kontos", + "Enabled:": "Aktiviert", + "Enums": "Aufzählungen", + "Equatorial Guinea": "Äquatorial-Guinea", + "Eritrea": "Eriträa", + "Error": "Fehler", + "Estonia": "Estland", + "Ethiopia": "Äthiopien", + "Event": "Typ", + "Events": "Ereignisse", + "Everyone": "Jeder", + "Expand all nodes": "Erweitern Sie alle Knoten", + "Failed to open JSON File": "Diese Datei ist keine gültige JSON-Datei", + "Falkland Islands (Malvinas)": "Falklandinseln (Malvinas)", + "Faroe Islands": "Faröer Inseln", + "Feb": "Feb", + "February": "Februar", + "Fiji": "Fidschi", + "File is too big!": "Die Datei ist zu groß!", + "File rights": "Zugriff auf Dateien", + "Filter": "Filter", + "Filter:": "Filter", + "Filtered out": "Alles ist herausgefiltert", + "Find coordinates...": "Koordinaten suchen...", + "Finland": "Finnland", + "Float divider:": "Dezimaltrennzeichen", + "France": "Frankreich", + "Free RAM:": "Frei:", + "French Guiana": "Französisch Guiana", + "French Polynesia": "Französisch Polynesien", + "French Southern Territories": "French Southern Territories", + "Fri": "Fr", + "From": "Quelle", + "From github": "Von GitHub", + "Function": "Funktion", + "Gabon": "Gabun", + "Gambia": "Gambia", + "Generated ID:": "Erzeugte ID", + "Georgia": "Georgia", + "Germany": "Deutschland", + "Ghana": "Ghana", + "Gibraltar": "Gibraltar", + "Go to Github...": "Gehe zu Github...", + "Greece": "Griechenland", + "Greenland": "Grönland", + "Grenada": "Granada", + "Group": "Gruppe", + "Groups": "Gruppen", + "Guadeloupe": "Guadeloupe", + "Guam": "Guam", + "Guatemala": "Guatemala", + "Guernsey": "Guernsey", + "Guinea": "Guinea", + "Guinea-Bissau": "Guinea-Bissau", + "Guyana": "Guyana", + "Haiti": "Haiti", + "Has no permission to %s %s %s": "Kein Zugriff '%s' für %s %s", + "Heard and Mc Donald Islands": "Heard and Mc Donald Islands", + "Heartbeat: ": "Lebenszeichen: ", + "Honduras": "Honduras", + "Hong Kong": "Hong Kong", + "Host": "Host", + "Host %s is offline": "Host %s ist offline", + "Host:": "Host", + "Hosts": "Hosts", + "Hungary": "Ungarn", + "ID": "ID", + "Iceland": "Island", + "Icon upload": "Bild hochladen", + "Ignore warning": "Warnung ignorieren", + "In background": "Im Hintergrund", + "India": "Indien", + "Indonesia": "Indonesien", + "Info": "Info", + "Insert": "Einfügen", + "Install": "Installieren", + "Install adapter from URL": "Adapter aus beliebiger Quelle installieren oder aktualisieren", + "Install adapter from github": "Adapter von GitHub installieren oder aktualisieren", + "Install from custom URL": "Installieren aus eigener URL", + "Install or update from URL...": "Installieren oder aktualisieren von URL ...", + "Installation counter": "Installationszähler", + "Installations counter": "Installationszähler", + "Installed": "Eingerichtet", + "Installed from group": "Installiert aus der Gruppe", + "Installed instances": "Installierte Instanzen", + "Installed version": "Installierte Version", + "Instances": "Instanzen", + "Instructions": "Anleitung", + "Intro": "Übersicht", + "Invalid version of %s": "Falsche Version von %s", + "Invalid version of %s. Required %s": "Nicht kompatible Version von %s. Es wird %s erwartet", + "Iran": "Iran", + "Iraq": "Irak", + "Ireland": "Irland", + "Is yet in the list": "Ist schon in der Liste", + "Isle of Man": "Isle of Man", + "Israel": "Israel", + "Italy": "Italien", + "Ivory Coast": "Elfenbeinküste", + "Jamaica": "Jamaika", + "Jan": "Jan", + "January": "Januar", + "Japan": "Japan", + "Jersey": "Jersey", + "Jordan": "Jordanien", + "Jul": "Jul", + "July": "Juli", + "Jun": "Juni", + "June": "Juni", + "Kazakhstan": "Kasachstan", + "Kenya": "Kenia", + "Kiribati": "Kiribati", + "Known bugs for": "Bekannte Bugs für", + "Korea": "Korea", + "Kosovo": "Kosovo", + "Kuwait": "Kuwait", + "Kyrgyzstan": "Kirgisistan", + "Lao People's Democratic Republic": "Demokratische Volksrepublik Laos", + "Last changed": "Letzte Änderung", + "Last update": "Letztes Update", + "Latitude:": "Breitengrad", + "Latvia": "Lettland", + "Lebanon": "Libanon", + "Lesotho": "Lesotho", + "Let's Encrypt settings": "Let's Encrypt Einstellungen", + "Let's encrypt SSL": "Let's Encrypt SSL", + "Liberia": "Liberia", + "Libyan Arab Jamahiriya": "Libyan Arab Jamahiriya", + "License": "Lizenz", + "License terms": "Lizenzbedingungen", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Auf allen IP Adressen hören", + "Lithuania": "Litauen", + "Loading...": "Lade...", + "Log": "Log", + "Log file will be deleted. Are you sure?": "Die Logdatei wird gelöscht. Sind Sie sicher?", + "Log size:": "Log-Größe:", + "Login timeout(sec):": "Login Timeout (Sek)", + "Logout": "Abmelden", + "Longitude:": "Längengrad", + "Luxembourg": "Luxemburg", + "MB": "MB", + "Macau": "Macau", + "Macedonia": "Mazedonien", + "Madagascar": "Madagaskar", + "Mai": "Mai", + "Main": "Allgemein", + "Main settings": "Haupteinstellungen", + "Malawi": "Malawi", + "Malaysia": "Malaysia", + "Maldives": "Malediven", + "Mali": "Mali", + "Malta": "Malta", + "Manually created": "Manuell erzeugt", + "Mar": "Mär", + "March": "März", + "Marshall Islands": "Marshall Inseln", + "Martinique": "Martinique", + "Mauritania": "Mauritania", + "Mauritius": "Mauritius", + "Mayotte": "Mayotte", + "Members": "Mitglieder", + "Message": "Meldung", + "Message buffer overflow. Losing oldest": "Zu viele Meldungen. Älteste werden gelöscht.", + "Mexico": "Mexiko", + "Micronesia": "Mikronesien", + "Model": "Modell", + "Moldova": "Moldawien", + "Mon": "Mo", + "Monaco": "Monaco", + "Mongolia": "Mongolei", + "Montenegro": "Montenegro", + "Montserrat": "Montserrat", + "Morocco": "Marokko", + "Mozambique": "Mosambik", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Name", + "Name:": "Name", + "Namibia": "Namibia", + "Nauru": "Nauru", + "Nepal": "Nepal", + "Netherlands": "Niederlande", + "Netherlands Antilles": "Netherlands Antilles", + "New": "Hinzufügen", + "New Caledonia": "Neukaledonien", + "New Zealand": "Neuseeland", + "New category": "Neue Aufzählung", + "New enum": "Neue Aufzählung", + "New group": "Neue Gruppe", + "New object": "Neues Objekt", + "New objekt": "Neues Objekt", + "New user": "Neuer Benutzer", + "Nicaragua": "Nicaragua", + "Niger": "Niger", + "Nigeria": "Nigeria", + "Niue": "Niue", + "No data": "Keine Daten", + "No states selected!": "Keine Zustände ausgewählt!", + "No version of %s": "Keine Versionsinfo für %s", + "Node.js": "Node.js", + "Norfolk Island": "Norfolk Island", + "Northern Mariana Islands": "Northern Mariana Islands", + "Norway": "Norway", + "Not exists": "Existiert nicht", + "Note:": "Hinweis", + "Nov": "Nov", + "November": "November", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "Das Objekt \"%s\" existiert nicht. Seite aktualisieren.", + "Object may not be deleted": "Das Objekt darf nicht gelöscht werden", + "Object rights": "Zugriff auf Objekte", + "Objects": "Objekte", + "Oct": "Okt", + "October": "Oktober", + "Ok": "Ok", + "Oman": "Oman", + "Only one": "Nur eins", + "Open original": "In neuem Tab öffnen", + "Owner": "Besitzer", + "Owner group": "Besitzer-Gruppe", + "Owner user": "Besitzer-Anwender", + "Pakistan": "Pakistan", + "Palau": "Palau", + "Palestine": "Palästina", + "Panama": "Panama", + "Papua New Guinea": "Papua Neuguinea", + "Paraguay": "Paraguay", + "Parent": "Parent", + "Parse error": "Fehler beim Parsen des JSON-Quelltexts", + "Password": "Passwort", + "Password and confirmation are not equal!": "Passwort und Bestätigung sind unterschiedlich!", + "Password cannot be empty!": "Passwort darf nicht leer sein!", + "Password repeat": "Passwort wiederholen", + "Path to storage:": "Pfad zum speichern", + "Pause output": "Ausgabe pausieren", + "Peru": "Peru", + "Philippines": "Philippinen", + "Pitcairn": "Pitcairn", + "Platform": "Plattform", + "Please confirm": "Bitte bestätigen", + "Poland": "Poland", + "Popular": "oft benuzt", + "Popular first": "Beliebte zuerst", + "Port to check the domain:": "Port um die Erreichbarkeit der Domäne zu prüfen", + "Portugal": "Portugal", + "Preserve ID": "ID beibehalten", + "Preview": "Vorschau", + "Processing...": "Bearbeite...", + "Puerto Rico": "Puerto Rico", + "Qatar": "Qatar", + "RAM": "RAM", + "RAM total usage:": "gesamte RAM-Nutzung:", + "RAM usage": "RAM-Nutzung", + "Rebuild tree": "Baum neu erstellen", + "Recently updated": "Kürzlich aktualisiert", + "Refresh log": "Log aktualisieren", + "Removed": "Entfernt", + "Removing of adapter...": "Entfernen des Adapters...", + "Removing of instance...": "Intanz wird entfernt...", + "Rename": "Umbenennen", + "Repositories": "Verwahrungsorte", + "Reunion": "Reunion", + "Rights": "Zugriffsrechte", + "Role": "Rolle", + "Romania": "Rumänien", + "Room": "Raum", + "Running: ": "Ausgeführt:", + "Russian Federation": "Russland", + "Rwanda": "Ruanda", + "Saint Kitts and Nevis": "Saint Kitts and Nevis", + "Saint Lucia": "Saint Lucia", + "Saint Vincent and the Grenadines": "Saint Vincent and the Grenadines", + "Samoa": "Samoa", + "San Marino": "San Marino", + "Sao Tome and Principe": "Sao Tome and Principe", + "Sat": "Sa", + "Saudi Arabia": "Saudi Arabien", + "Save": "Speichern", + "Save Objecttree as JSON File": "Die ausgewählte Objektstruktur als JSON-Datei speichern", + "Save Objecttree is not possible": "Speichern der Objektstruktur ist nicht möglich", + "Save configuration": "Konfiguration speichern", + "Script": "Skript", + "Scripts": "Skripte", + "Select": "Auswählen", + "Select ID": "ID Auswählen", + "Select adapter:": "Adapter auswählen", + "Select language": "Sprache", + "Select options": "Optionen auswählen", + "Senegal": "Senegal", + "Sent data:": "Gesendete Daten", + "Sep": "Sep", + "September": "September", + "Serbia": "Serbien", + "Set": "Setzen", + "Set CRON": "Zeitplan übernehmen", + "Set CRON schedule for restarts": "Legen Sie den CRON-Zeitplan für Neustarts fest", + "Settings": "Einstellungen", + "Settings for %s": "Einstellungen für %s", + "Seychelles": "Seychellen", + "Show instances only for current host": "Instanzen nur für den aktuellen Host anzeigen", + "Show values of instance": "Zeige Werte aus", + "Show...": "Zeige...", + "Sierra Leone": "Sierra Leone", + "Singapore": "Singapur", + "Size: %s, Free: %s": "Größe: %s, Verfügbar: %s", + "Slovakia": "Slowakei", + "Slovenia": "Slowenien", + "Solomon Islands": "Solomon Islands", + "Somalia": "Somalia", + "Some data are not stored. Discard?": "Einige Daten sind nicht gespeichert. Verwerfen?", + "Sort alphabetically": "Alphabetisch nach Namen sortieren", + "South Africa": "Südafrika", + "South Georgia South Sandwich Islands": "South Georgia South Sandwich Islands", + "Spain": "Spanien", + "Speed": "Geschwindigkeit", + "Sri Lanka": "Sri Lanka", + "St. Helena": "St. Helena", + "St. Pierre and Miquelon": "St. Pierre und Miquelon", + "Started...": "Gestartet...", + "State": "Datenpunkt", + "State type": "Datenpunkttyp", + "States": "Zustände", + "States rights": "Zugriff auf Zustände", + "Statistics": "Statistik", + "Statistics:": "Statistik", + "Storage of %s": "Einstellungen von %s", + "Storage of %s states": "Einstellungen von %s Zuständen", + "Success!": "Erfolgreich!", + "Sudan": "Sudan", + "Suggestion": "Empfehlung", + "Sun": "So", + "Suriname": "Surinam", + "Svalbard and Jan Mayen Islands": "Svalbard and Jan Mayen Islands", + "Swaziland": "Swaziland", + "Sweden": "Sweden", + "Switzerland": "Schweiz", + "Syrian Arab Republic": "Syrian Arab Republic", + "System": "System", + "System language:": "Systemsprache", + "System settings": "Systemeinstellungen", + "System uptime": "System uptime", + "Table": "Tabelle", + "Taiwan": "Taiwan", + "Tajikistan": "Tadschikistan", + "Tanzania": "Tansania", + "Temperature units:": "Temperatureinheit", + "Thailand": "Thailand", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 unterstützt die LTS Versionen 6, 8, 10 oder neuer von Node.js. Bitte aktualisieren Sie die verwendete Node.js Version (\"%s\") auf dem Rechner \"%s\" auf eine unterstützte Version. Wir empfehlen die Verwendung von Node.js 6.", + "Thu": "Do", + "Time": "Zeit", + "Time From": "Zeit von", + "Time To": "Zeit zum", + "Time stamp": "Zeitstempel", + "Title": "Titel", + "To": "Bis", + "Today": "heute", + "Toggle expert mode": "Expertenmodus umschalten", + "Toggle states view": "Wechseln Sie in die Statusansicht", + "Togo": "Togo", + "Tokelau": "Tokelau", + "Tonga": "Tonga", + "Too many events": "Zu viele Ereignisse", + "Total count in group": "Insgesamt in der Gruppe", + "Trigger event": "Simuliere Tastendruck", + "Trinidad and Tobago": "Trinidad und Tobago", + "Tue": "Di", + "Tunisia": "Tunesien", + "Turkey": "Türkei", + "Turkmenistan": "Turkmenistan", + "Turks and Caicos Islands": "Turks und Caicos Islands", + "Tuvalu": "Tuvalu", + "Type": "Typ", + "URL or file path:": "URL oder Dateipfad", + "Uganda": "Uganda", + "Ukraine": "Ukraine", + "Uncheck All": "Alle abwählen", + "United Arab Emirates": "Vereinigte Arabische Emirate", + "United Kingdom": "United Kingdom", + "United States": "United States", + "United States minor outlying islands": "United States Minor Outlying Islands", + "Unknown file format!": "Unbekanntes Dateiformat!", + "Unsecure_Auth": "Das Passwort wird über unsichere Verbindung gesendet. Um Ihre Passwörter zu schützen, aktivieren Sie die sichere Verbindung (HTTPS)!", + "Unsupported image format": "Nicht unterstütztes Bildformat", + "Update": "Aktualisieren", + "Update objects": "Objekte aktualisieren", + "Update states": "Zustände aktualisieren", + "Updated": "Updated", + "Upgrade all adapters": "Alle Adapter upgraden", + "Upload": "Dateiupload", + "Upload admin started": "Upload von Konfiguraton wurde gestartet", + "Upload started...": "Upload gestartet ...", + "Uptime": "Uptime", + "Uruguay": "Uruguay", + "Use Lets Encrypt certificates:": "Let's Encrypt Zertifikate benutzen", + "Use this instance for automatic update:": "Diese Instanz für automatische Zertifikat-Updates benutzen:", + "User": "Benutzer", + "User deleted": "Benutzer gelöscht", + "User does not exist": "Benutzer existiert nicht", + "User yet exists": "Benutzer bereits vorhanden", + "Users": "Benutzer", + "Uzbekistan": "Usbekistan", + "Value": "Wert", + "Values of %s": "Werte für %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "Vatikanstaat", + "Venezuela": "Venezuela", + "Vietnam": "Vietnam", + "Virgin Islands (British)": "Britische Jungferninseln", + "Virgin Islands (U.S.)": "Amerikanische Jungferninseln", + "Wallis and Futuna Islands": "Wallis and Futuna Islands", + "Warning!": "Warnung!", + "Wed": "Mi", + "Western Sahara": "Western Sahara", + "With": "mit", + "Without": "ohne", + "Yemen": "Jemen", + "You are going to add new instance: ": "Sie werden eine neue Instanz hinzufügen:", + "You can check changelog here": "Änderungen können hier angeschaut werden", + "You can drag&drop the devices, channels and states to enums": "Sie können Geräte, Kanäle und Zustände per Drag & Drop in Auflistungen ziehen", + "You can drag&drop users to groups": "Sie können Benutzer per Drag & Drop in Gruppen verschieben", + "You can't see events via cloud": "Sie können keine Ereignisse über die Cloud sehen", + "Your home": "Dein Haus", + "Zaire": "Zaire", + "Zambia": "Sambia", + "Zimbabwe": "Simbabwe", + "_All": "Alle", + "_Toggle expert mode": "Expertenmodus", + "__different__": "unterschiedlich", + "a-z": "a-z", + "ack": "Bestätigt", + "actions": "Aktionen", + "active": "aktiv", + "adapter with updates": "Adapter mit Aktualisierungen", + "adapters count": "Adapter zählen", + "add": "Hinzufügen", + "add children": "Tochterobjekt erzeugen", + "add instance": "Instanz hinzufügen", + "add repository": "Verwahrungsort hinzufügen", + "agree": "Einverstanden", + "alarm_group": "Alarm", + "alive": "Läuft", + "all": "Alle", + "alpha": "alpha", + "array": "Feld", + "auto": "Auto", + "available": "Verfügbar", + "beta": "beta", + "boolean": "Logikwert", + "bug": "Bugtracker", + "cancel": "Abbrechen", + "cert_path_note": "Sie können den absoluten Pfad zum Zertifikat verwenden, wie '/opt/certs/cert.pem', oder einfach hochladen", + "certificate": "Zertifikate", + "change view mode": "Ansicht ändern", + "channel": "Kanal", + "clear": "löschen", + "climate-control_group": "Klimakontrolle", + "close on ready": "Schließen wenn fertig", + "collapse": "zuklappen", + "collapse all": "Alle zuklappen", + "comma": "Komma", + "command execution": "Kommando-Ausführung", + "common": "Allgemein", + "common adapters_group": "Allgemein", + "common_color": "Farbe", + "common_def": "Standardwert", + "common_desc": "Beschreibung", + "common_icon": "Symbol", + "common_max": "maximaler Wert", + "common_min": "minimaler Wert", + "common_read": "Lesen erlaubt", + "common_role": "Rolle", + "common_states": "vordefinierte Werte", + "common_type": "Art", + "common_unit": "Maßeinheit", + "common_write": "Schreiben erlaubt", + "communication_group": "Kommunikation", + "config": "Konfiguration", + "config instance": "Instanz konfigurieren", + "confirm password": "Bestätigung", + "connected": "Verbunden", + "copy": "Kopieren", + "copy note": "Strg+A gefolgt von Strg+C drücken, um den Inhalt in die Zwischenablage zu kopieren. Irgendwo klicken, um das Fenster zu schliessen.", + "create operation": "erzeugen", + "custom enum": "Benutzerdefiniert", + "custom group": "Benutzerdefinierte Gruppe", + "daemon": "daemon", + "date-and-time_group": "Datum und Uhrzeit", + "daysShortText": "T.", + "debug": "Debug", + "delete": "Löschen", + "delete adapter": "Adapter löschen", + "delete group": "Gruppe löschen", + "delete instance": "Instanz löschen", + "delete operation": "löschen", + "delete script": "Script löschen", + "delete user": "Benutzer löschen", + "desc": "Beschreibung", + "description": "Beschreibung", + "device": "Gerät", + "diag-note": "Wir haben hart daran gearbeitet, dieses Projekt auf die Beine zu stellen. Als Gegenleistung bitten wir Sie, uns die Nutzungs-Statistik zu senden.
Es werden keine privaten Informationen zu yunkong2.org gesendet. Jedes Mal wenn die Adapterliste aktualisiert wird, wird auch die anonymisierte Statistik verschickt.
Vielen Dank!", + "edit": "Ändern", + "edit enum": "Aufzählung ändern", + "edit enums": "Aufzählungen für bearbeiten", + "edit file": "Datei bearbeiten", + "edit group": "Gruppe bearbeiten", + "edit instance": "Instanz bearbeiten", + "edit script": "Script bearbeiten", + "edit user": "Benutzer bearbeiten", + "edit value": "Wert bearbeiten", + "enabled": "Aktiviert", + "energy_group": "Energie", + "engine": "Ausführen mit", + "engine type": "Enginetyp", + "error": "Fehler", + "events": "Ereignisse", + "execute operation": "ausführen", + "expand": "aufklappen", + "expand all": "Alle aufklappen", + "extended": "erweitert", + "false": "falsch", + "file permissions": "Datei-Rechte", + "from": "Quelle", + "garden_group": "Garten", + "general_group": "Allgemein", + "geoposition_group": "Geoposition", + "groups": "Gruppen", + "hardware_group": "Hardware", + "history": "Historie", + "history data": "Historische Daten", + "host": "Server", + "household_group": "Haushalt", + "http operation": "http", + "id": "ID", + "info": "Info", + "infrastructure_group": "Infrastruktur", + "install": "Installieren", + "install specific version": "Bestimmte Version installieren", + "installed": "Installiert", + "installed adapters": "Adapter mit Instanzen", + "instance": "Instanz", + "instance number": "Gewünschte Instanznummer", + "yunkong2 Enums": "yunkong2 Aufzählungen", + "yunkong2 States": "yunkong2 Zustände", + "yunkong2 adapter instances": "yunkong2 Adapter-Instanzen", + "yunkong2 adapter scripts": "yunkong2 Adapter Skripte", + "yunkong2 adapters": "yunkong2 Adapter", + "yunkong2 certificates": "yunkong2 Zertifikate", + "yunkong2 groups": "yunkong2 Gruppen", + "yunkong2 hosts": "yunkong2 Hosts", + "yunkong2 repositories": "yunkong2 Verwahrungsorte", + "yunkong2 users": "yunkong2 Benutzer", + "iot-system_group": "IoT Systeme", + "iot-systems_group": "IoT Systeme", + "keywords": "Schlüsselworte", + "lc": "Geändert", + "less": "Weniger", + "letsnecrypt_help": "Einstellungen für das Let's Encrypt Konto. Um ein gratis Zertifikat für Ihre Domain zu erhalten, folgen sie den Anweisungen hier.", + "letsnecrypt_help_domains": "z.B: 'example.com, www.example.com'", + "letsnecrypt_help_email": "Bitte immer nur eine Email-Adresse benutzen.", + "letsnecrypt_help_path": "Verzeichnis, in dem die Zertifikate gespeichert werden. Der Pfad ist relativ zum Konfigurationsverzeichnis", + "license": "Lizenz", + "license agreement": "Lizenzvertrag", + "license not agree": "Mit der Lizenz nicht einverstanden!", + "license_checkbox": "Ich bin mit der Sammlung von anonymen Statistiken einverstanden.
(kann auch später in den Einstellungen deaktiviert werden)", + "lighting_group": "Beleuchtung", + "link": "Link", + "list": "Liste", + "list operation": "auflisten", + "logic_group": "Logik", + "loglevel": "Log-Stufe", + "media_group": "Medien", + "members": "Mitglieder", + "memlimit": "RAM-Limit", + "message": "Nachricht", + "messaging_group": "Messaging", + "misc-data_group": "Sonstige", + "mixed": "gemischt", + "mode": "Modus", + "more": "Mehr", + "multi": "Werteliste", + "multimedia_group": "Multimedia", + "name": "Name", + "native": "Nativ", + "network_group": "Netzwerk", + "new certificate": "neues Zertifikat", + "new group": "Neue Gruppe", + "new script": "Neues Script", + "new user": "Neuer Benutzer", + "newObject": "Neues Objekt", + "no-city": "ohne Stadt", + "node-red": "node-red", + "none": "nichts", + "normal": "normal", + "not ack": "Nicht best.", + "not agree": "Nicht einverstanden", + "npm error": "Fehler", + "number": "Zahl", + "object": "Objekt", + "object permissions": "Objekt-Rechte", + "of": "von", + "ok": "Ok", + "open web page": "Adapter-Webseite öffnen", + "os": "Betriebssystem", + "other permissions": "Andere Rechte", + "parent name": "Eltern Name", + "password": "Passwort", + "permissionError": "Zugriffsfehler", + "place here": "Platziere die Dateien hier", + "planned": "geplant", + "platform": "Plattform", + "point": "Punkt", + "popular": "Beliebheit", + "process": "Prozess", + "protocols_group": "Protokolle", + "raw": "Raw (nur Experten)", + "read": "lesen", + "read operation": "lesen", + "readme": "Lies mich", + "reload": "Neu laden", + "reload instance": "Instanz neu starten", + "rest": "Weitere (nur lesend)", + "restart": "Neu starten", + "restart script": "Skript neu starten", + "role": "Rolle", + "save": "Speichern", + "schedule_group": "Zeitplanung", + "script_group": "Skripte und Logik", + "select member by double click": "Mitglied durch Doppelklick auswählen", + "sendto operation": "Senden an", + "service_group": "Instandhaltung", + "severity": "Schweregrad", + "silly": "Alles", + "stable": "stabil", + "state": "Datenpunkt/Zustand", + "state permissions": "Zustands-Rechte", + "storage_group": "Aufbewahrung", + "string": "Zeichenkette", + "subscribe": "abonnieren", + "switch": "Schalter", + "terminal": "Terminal", + "third-party_group": "Andere", + "this adapter does not allow multiple instances": "Dieser Adapter lässt mehrere Instanzen nicht zu", + "title": "Titel", + "today": "heute", + "true": "wahr", + "ts": "Zeit", + "type": "Typ", + "unit": "Einheit", + "update": "Aktualisieren", + "update adapter information": "Adapterinformationen aktualisieren", + "update-part1": "Aufgrund der Vielzahl an Hardware und Plattformen, auf denen yunkong2 läuft, muss der js-controller manuell aktualisiert werden. Dazu auf der Konsole des Hosts folgende Kommandos ausführen:", + "updated": "aktualisiert", + "updates": "Aktualisierung", + "upload": "Upload", + "user permissions": "Zugriffsrechte", + "users": "Benutzer", + "users permissions": "Gruppen-Rechte", + "utility_group": "Dienstprogramm", + "val": "Wert", + "value": "Wert", + "value.from": "Geändert von", + "value.lc": "Letzte Änderung", + "value.q": "Qualitätscode", + "value.ts": "Zeitstempel", + "value.val": "Wert", + "version": "Version", + "vis_group": "yunkong2.vis", + "visualisation_group": "Visualisierung", + "visualization-icons_group": "Visualisierungs-Icons", + "visualization-widgets_group": "Visualisierung Widgets", + "visualization_group": "Visualisierung", + "warn": "Warnung", + "weather_group": "Wetter", + "wetty": "Wetty", + "write": "schreiben", + "write operation": "schreiben", + "yesterday": "gestern" +} diff --git a/src/i18n/en/translations.json b/src/i18n/en/translations.json new file mode 100644 index 0000000..cc6e0eb --- /dev/null +++ b/src/i18n/en/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " for %s", + "%s added to %s": "%s added to %s", + "%s object(s) processed": "%s objects were processed", + "%s processes": "%s processes", + "%s was imported": "%s was imported", + "(without prefix)": "(without prefix)", + "1 %d days ago": "%d days ago", + "2 %d days ago": "%d days ago", + "5 %d days ago": "%d days ago", + "A-Z": "A-Z", + "Access control": "Access control", + "Access control list": "Access control list", + "Acknowledged": "Acknowledged", + "Activated. Click to stop.": "Activated. Click to stop.", + "Active instances": "Active instances", + "Active repository:": "Active repository", + "Adapter configuration": "Adapter configuration", + "Adapter settings for %s states": "Adapter settings for %s states", + "Adapters": "Adapters", + "Adapters from this Group installed": "Adapters from this group installed", + "Add": "Add", + "Add Objecttree from JSON File": "Add objects tree from JSON file", + "Add certificate from file": "Add certificate from file", + "Add instance...": "Adding instance...", + "Add member": "Add member", + "Add new child object to selected parent": "Add new child object to selected parent", + "Add new field": "Add new field", + "Add new issue": "Report a bug", + "Add new object: ": "Add new object: ", + "Add new object: %s": "Add new object: %s", + "Address:": "Address", + "Admin is not enabled in cloud settings!": "Admin is not enabled in cloud settings!", + "Administrator": "Administrator", + "Afghanistan": "Afghanistan", + "Albania": "Albania", + "Algeria": "Algeria", + "All": "All", + "American Samoa": "American Samoa", + "Andorra": "Andorra", + "Angola": "Angola", + "Anguilla": "Anguilla", + "Antarctica": "Antarctica", + "Antigua and Barbuda": "Antigua and Barbuda", + "Apr": "Apr", + "April": "April", + "Architecture": "Architecture", + "Are you sure to delete %s?": "Are you sure to delete \"%s\"?", + "Are you sure to delete all children of %s?": "Are you sure to delete all children of %s?", + "Are you sure to delete all children of %s?": "Are you sure to delete \"%s\" and all children?", + "Are you sure to delete script %s?": "Are you sure to delete script '%s'?", + "Are you sure you want to delete adapter %s?": "Are you sure you want to delete adapter %s?", + "Are you sure you want to delete the instance %s?": "Are you sure you want to delete the instance %s?", + "Are you sure?": "Are you sure?", + "Are you sure? Changes are not saved.": "Are you sure? Changes are not saved.", + "Argentina": "Argentina", + "Armenia": "Armenia", + "Aruba": "Aruba", + "Aug": "Aug", + "August": "August", + "Australia": "Australia", + "Austria": "Austria", + "Authentication was deactivated": "Authentication was deactivated", + "Available": "Available", + "Available version:": "Available version", + "Azerbaijan": "Azerbaijan", + "Background": "Background", + "Background color of the login screen": "Background color of the login screen", + "Background image": "Background image", + "Bahamas": "Bahamas", + "Bahrain": "Bahrain", + "Bangladesh": "Bangladesh", + "Barbados": "Barbados", + "Belarus": "Belarus", + "Belgium": "Belgium", + "Belize": "Belize", + "Benin": "Benin", + "Bermuda": "Bermuda", + "Bhutan": "Bhutan", + "Bolivia": "Bolivia", + "Bosnia and Herzegovina": "Bosnia and Herzegovina", + "Botswana": "Botswana", + "Bouvet Island": "Bouvet Island", + "Brazil": "Brazil", + "British Indian Ocean Territory": "British Indian Ocean Territory", + "Brunei Darussalam": "Brunei Darussalam", + "Bulgaria": "Bulgaria", + "Burkina Faso": "Burkina Faso", + "Burundi": "Burundi", + "CPUs": "CPUs", + "Calendar": "Calendar", + "Cambodia": "Cambodia", + "Cameroon": "Cameroon", + "Canada": "Canada", + "Cancel": "Cancel", + "Cannot create user: ": "Cannot create user: ", + "Cannot delete user: ": "Cannot delete user: ", + "Cannot disable admin!": "Cannot disable admin!", + "Cannot read file!": "Cannot read file!", + "Cannot read version from NPM": "Could not read version from NPM", + "Cannot set password: ": "Cannot set password: ", + "Cape Verde": "Cape Verde", + "Cayman Islands": "Cayman Islands", + "Central African Republic": "Central African Republic", + "Certificates": "Certificates", + "Chad": "Chad", + "Change": "Change", + "Changelog": "Change log", + "Channel": "Channel", + "Chart": "Chart", + "Chart for %s": "Chart for %s", + "Check all": "Check all", + "Chile": "Chile", + "China": "China", + "Christmas Island": "Christmas Island", + "City:": "City", + "Clear": "Clear", + "Clear list": "Clear list", + "Clear log": "Clear log", + "Clear on disk permanent": "Clear on disk permanent", + "Click do activate events again, or just wait one minute": "Click do activate events again, or just wait one minute", + "Click on icon": "Click on icon to open a link", + "Close": "close", + "Cocos Islands": "Cocos Islands", + "Collapse all nodes": "Collapse all nodes", + "Colombia": "Colombia", + "Color": "Color", + "Comoros": "Comoros", + "Configuration not saved.": "Configuration not saved.", + "Congo": "Congo", + "Connected to %s: ": "Connected to %s: ", + "Connected to host: ": "Connected to host: ", + "Cook Islands": "Cook Islands", + "Copy log": "Copy log", + "Copy to clipboard": "Copy to clipboard", + "Costa Rica": "Costa Rica", + "Country:": "Country", + "Create": "Create", + "Create new category": "Create new category", + "Create new category, like %s": "Create new category, like %s", + "Create new enum": "Create new enum", + "Create new enum, like %s": "Create new enum, like %s", + "Create new group": "Create new group", + "Create new user": "Create new user", + "Created": "Created", + "Croatia": "Croatia", + "Cron expression": "Cron expression", + "Cuba": "Cuba", + "Currency:": "Currency", + "Custom": "Custom", + "Cyprus": "Cyprus", + "Czech Republic": "Czech Republic", + "D$ecember": "D$ecember", + "DD.MM.YY": "DD.MM.YY", + "DD.MM.YYYY": "DD.MM.YYYY", + "DD/MM/YYYY": "DD/MM/YYYY", + "Date From": "Date From", + "Date To": "Date To", + "Date format:": "Date format", + "Deactivated. Click to start.": "Deactivated. Click to start.", + "Debug outputs:": "Debug outputs", + "Dec": "Dec", + "December": "December", + "Default ACL": "Default ACL", + "Default history instance:": "Default history instance", + "Delete attribute": "Delete attribute", + "Delete category": "Delete category", + "Delete enum": "Delete enum", + "Delete member": "Delete member", + "Delete object": "Delete object", + "Denmark": "Denmark", + "Description": "Description", + "Device": "Device", + "Device discovery": "Device discovery", + "Disable authentication": "Disable authentication", + "Disk free": "Disk free", + "Disk free:": "Disk free:", + "Disk size": "Disk size", + "Djibouti": "Djibouti", + "Do you want to delete just one object or all children of %s too?": "Do you want to delete just one object or all children of %s too?", + "Do you want to upgrade all adapters?": "Do you want to upgrade all adapters?", + "Domains:": "Domains", + "Dominica": "Dominica", + "Dominican Republic": "Dominican Republic", + "Done with error: %s": "Done with error: %s", + "Download log": "Download log", + "Drop the files here": "Drop the files here", + "Drop the icons here": "Drop the icons here", + "East Timor": "East Timor", + "Ecuador": "Ecuador", + "Edit": "Edit", + "Edit category": "Edit category", + "Edit enum": "Edit enum", + "Edit in dialog": "Edit in dialog", + "Edit object": "Edit object", + "Egypt": "Egypt", + "El Salvador": "El Salvador", + "Email for account:": "Email for account", + "Enabled:": "Enabled", + "Enums": "Enums", + "Equatorial Guinea": "Equatorial Guinea", + "Eritrea": "Eritrea", + "Error": "Error", + "Estonia": "Estonia", + "Ethiopia": "Ethiopia", + "Event": "Type", + "Events": "Events", + "Everyone": "Everyone", + "Expand all nodes": "Expand all nodes", + "Failed to open JSON File": "Failed to open JSON file", + "Falkland Islands (Malvinas)": "Falkland Islands (Malvinas)", + "Faroe Islands": "Faroe Islands", + "Feb": "Feb", + "February": "February", + "Fiji": "Fiji", + "File is too big!": "File is too big!", + "File rights": "File rights", + "Filter": "Filter", + "Filter:": "Filter", + "Filtered out": "Everything is filtered out", + "Find coordinates...": "Find coordinates...", + "Finland": "Finland", + "Float divider:": "Float divider", + "France": "France", + "Free RAM:": "Free:", + "French Guiana": "French Guiana", + "French Polynesia": "French Polynesia", + "French Southern Territories": "French Southern Territories", + "Fri": "Fri", + "From": "From: ", + "From github": "From github", + "Function": "Function", + "Gabon": "Gabon", + "Gambia": "Gambia", + "Generated ID:": "Generated ID", + "Georgia": "Georgia", + "Germany": "Germany", + "Ghana": "Ghana", + "Gibraltar": "Gibraltar", + "Go to Github...": "Go to Github...", + "Greece": "Greece", + "Greenland": "Greenland", + "Grenada": "Grenada", + "Group": "Group", + "Groups": "Groups", + "Guadeloupe": "Guadeloupe", + "Guam": "Guam", + "Guatemala": "Guatemala", + "Guernsey": "Guernsey", + "Guinea": "Guinea", + "Guinea-Bissau": "Guinea-Bissau", + "Guyana": "Guyana", + "Haiti": "Haiti", + "Has no permission to %s %s %s": "Has no permission to %s %s %s", + "Heard and Mc Donald Islands": "Heard and Mc Donald Islands", + "Heartbeat: ": "Heartbeat: ", + "Honduras": "Honduras", + "Hong Kong": "Hong Kong", + "Host": "Host", + "Host %s is offline": "Host %s is offline", + "Host:": "Host", + "Hosts": "Hosts", + "Hungary": "Hungary", + "ID": "ID", + "Iceland": "Iceland", + "Icon upload": "Icon upload", + "Ignore warning": "Ignore warning", + "In background": "In background", + "India": "India", + "Indonesia": "Indonesia", + "Info": "Info", + "Insert": "Insert", + "Install": "Install", + "Install adapter from URL": "Install or update the adapter from URL", + "Install adapter from github": "Install or update the adapter from Github", + "Install from custom URL": "Install from custom URL", + "Install or update from URL...": "Install or update from URL...", + "Installation counter": "Installations counter", + "Installations counter": "Installations counter", + "Installed": "Installed", + "Installed from group": "Installed from group", + "Installed instances": "Installed instances", + "Installed version": "Installed version", + "Instances": "Instances", + "Instructions": "Instructions", + "Intro": "Overview", + "Invalid version of %s": "Invalid version of %s", + "Invalid version of %s. Required %s": "Invalid version of %s. Required %s", + "Iran": "Iran", + "Iraq": "Iraq", + "Ireland": "Ireland", + "Is yet in the list": "It's already in the list", + "Isle of Man": "Isle of Man", + "Israel": "Israel", + "Italy": "Italy", + "Ivory Coast": "Ivory Coast", + "Jamaica": "Jamaica", + "Jan": "Jan", + "January": "January", + "Japan": "Japan", + "Jersey": "Jersey", + "Jordan": "Jordan", + "Jul": "Jul", + "July": "July", + "Jun": "Jun", + "June": "June", + "Kazakhstan": "Kazakhstan", + "Kenya": "Kenya", + "Kiribati": "Kiribati", + "Known bugs for": "Known bugs for", + "Korea": "Korea", + "Kosovo": "Kosovo", + "Kuwait": "Kuwait", + "Kyrgyzstan": "Kyrgyzstan", + "Lao People's Democratic Republic": "Lao People's Democratic Republic", + "Last changed": "Last changed", + "Last update": "Last update", + "Latitude:": "Latitude", + "Latvia": "Latvia", + "Lebanon": "Lebanon", + "Lesotho": "Lesotho", + "Let's Encrypt settings": "Let's Encrypt settings", + "Let's encrypt SSL": "Let's encrypt SSL", + "Liberia": "Liberia", + "Libyan Arab Jamahiriya": "Libyan Arab Jamahiriya", + "License": "License", + "License terms": "License terms", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Listen on all IPs", + "Lithuania": "Lithuania", + "Loading...": "Loading...", + "Log": "Log", + "Log file will be deleted. Are you sure?": "Log file will be deleted. Are you sure?", + "Log size:": "Log size", + "Login timeout(sec):": "Login timeout (sec)", + "Logout": "Logout", + "Longitude:": "Longitude", + "Luxembourg": "Luxembourg", + "MB": "Mb", + "Macau": "Macau", + "Macedonia": "Macedonia", + "Madagascar": "Madagascar", + "Mai": "Mai", + "Main": "Main", + "Main settings": "Main settings", + "Malawi": "Malawi", + "Malaysia": "Malaysia", + "Maldives": "Maldives", + "Mali": "Mali", + "Malta": "Malta", + "Manually created": "Manually created", + "Mar": "Mar", + "March": "March", + "Marshall Islands": "Marshall Islands", + "Martinique": "Martinique", + "Mauritania": "Mauritania", + "Mauritius": "Mauritius", + "Mayotte": "Mayotte", + "Members": "Members", + "Message": "Message", + "Message buffer overflow. Losing oldest": "Message buffer overflow. Losing oldest.", + "Mexico": "Mexico", + "Micronesia": "Micronesia", + "Model": "Model", + "Moldova": "Moldova", + "Mon": "Mon", + "Monaco": "Monaco", + "Mongolia": "Mongolia", + "Montenegro": "Montenegro", + "Montserrat": "Montserrat", + "Morocco": "Morocco", + "Mozambique": "Mozambique", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Name", + "Name:": "Name", + "Namibia": "Namibia", + "Nauru": "Nauru", + "Nepal": "Nepal", + "Netherlands": "Netherlands", + "Netherlands Antilles": "Netherlands Antilles", + "New": "New", + "New Caledonia": "New Caledonia", + "New Zealand": "New Zealand", + "New category": "New category", + "New enum": "New enum", + "New group": "New group", + "New object": "New object", + "New objekt": "New object", + "New user": "New user", + "Nicaragua": "Nicaragua", + "Niger": "Niger", + "Nigeria": "Nigeria", + "Niue": "Niue", + "No data": "No data", + "No states selected!": "No states selected!", + "No version of %s": "No version of %s", + "Node.js": "Node.js", + "Norfolk Island": "Norfolk Island", + "Northern Mariana Islands": "Northern Mariana Islands", + "Norway": "Norway", + "Not exists": "Does not exist", + "Note:": "Note", + "Nov": "Nov", + "November": "November", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "Object \"%s\" does not exist. Update the page.", + "Object may not be deleted": "Object may not be deleted", + "Object rights": "Object rights", + "Objects": "Objects", + "Oct": "Oct", + "October": "October", + "Ok": "Ok", + "Oman": "Oman", + "Only one": "Only one", + "Open original": "Open on new tab", + "Owner": "Owner", + "Owner group": "Owner group", + "Owner user": "Owner user", + "Pakistan": "Pakistan", + "Palau": "Palau", + "Palestine": "Palestine", + "Panama": "Panama", + "Papua New Guinea": "Papua New Guinea", + "Paraguay": "Paraguay", + "Parent": "Parent", + "Parse error": "Parse error", + "Password": "Password", + "Password and confirmation are not equal!": "Password and confirmation are not equal!", + "Password cannot be empty!": "Password cannot be empty!", + "Password repeat": "Password repeat", + "Path to storage:": "Path to storage", + "Pause output": "Pause output", + "Peru": "Peru", + "Philippines": "Philippines", + "Pitcairn": "Pitcairn", + "Platform": "Platform", + "Please confirm": "Please confirm", + "Poland": "Poland", + "Popular": "Popular", + "Popular first": "Popular first", + "Port to check the domain:": "Port to check the domain", + "Portugal": "Portugal", + "Preserve ID": "Preserve ID", + "Preview": "Preview", + "Processing...": "Processing...", + "Puerto Rico": "Puerto Rico", + "Qatar": "Qatar", + "RAM": "RAM", + "RAM total usage:": "Total RAM usage:", + "RAM usage": "RAM usage", + "Rebuild tree": "Rebuild tree", + "Recently updated": "Recently updated", + "Refresh log": "Refresh log", + "Removed": "Removed", + "Removing of adapter...": "Removing of adapter...", + "Removing of instance...": "Removing of instance...", + "Rename": "Rename", + "Repositories": "Repositories", + "Reunion": "Reunion", + "Rights": "Access rights", + "Role": "Role", + "Romania": "Romania", + "Room": "Room", + "Running: ": "Running: ", + "Russian Federation": "Russian Federation", + "Rwanda": "Rwanda", + "Saint Kitts and Nevis": "Saint Kitts and Nevis", + "Saint Lucia": "Saint Lucia", + "Saint Vincent and the Grenadines": "Saint Vincent and the Grenadines", + "Samoa": "Samoa", + "San Marino": "San Marino", + "Sao Tome and Principe": "Sao Tome and Principe", + "Sat": "Sat", + "Saudi Arabia": "Saudi Arabia", + "Save": "Save", + "Save Objecttree as JSON File": "Save objects tree as JSON file", + "Save Objecttree is not possible": "Saving the objects tree is not possible", + "Save configuration": "Save configuration", + "Script": "Script", + "Scripts": "Scripts", + "Select": "Select", + "Select ID": "Select ID", + "Select adapter:": "Select adapter", + "Select language": "Select language", + "Select options": "Select options", + "Senegal": "Senegal", + "Sent data:": "Sent data", + "Sep": "Sep", + "September": "September", + "Serbia": "Serbia", + "Set": "Set", + "Set CRON": "Set", + "Set CRON schedule for restarts": "Set CRON schedule for restarts", + "Settings": "Settings", + "Settings for %s": "Settings for %s", + "Seychelles": "Seychelles", + "Show instances only for current host": "Show instances only for current host", + "Show values of instance": "Show values of instance", + "Show...": "Show...", + "Sierra Leone": "Sierra Leone", + "Singapore": "Singapore", + "Size: %s, Free: %s": "Size: %s, Available: %s", + "Slovakia": "Slovakia", + "Slovenia": "Slovenia", + "Solomon Islands": "Solomon Islands", + "Somalia": "Somalia", + "Some data are not stored. Discard?": "Some data are not stored. Discard?", + "Sort alphabetically": "Sort alphabetically by name", + "South Africa": "South Africa", + "South Georgia South Sandwich Islands": "South Georgia South Sandwich Islands", + "Spain": "Spain", + "Speed": "Speed", + "Sri Lanka": "Sri Lanka", + "St. Helena": "St. Helena", + "St. Pierre and Miquelon": "St. Pierre and Miquelon", + "Started...": "Started...", + "State": "Datapoint", + "State type": "State type", + "States": "States", + "States rights": "States rights", + "Statistics": "Statistics", + "Statistics:": "Statistics", + "Storage of %s": "Storage of %s", + "Storage of %s states": "Storage of %s states", + "Success!": "Success!", + "Sudan": "Sudan", + "Suggestion": "Recommendation", + "Sun": "Sun", + "Suriname": "Suriname", + "Svalbard and Jan Mayen Islands": "Svalbard and Jan Mayen Islands", + "Swaziland": "Swaziland", + "Sweden": "Sweden", + "Switzerland": "Switzerland", + "Syrian Arab Republic": "Syrian Arab Republic", + "System": "System", + "System language:": "System language", + "System settings": "System settings", + "System uptime": "System uptime", + "Table": "Table", + "Taiwan": "Taiwan", + "Tajikistan": "Tajikistan", + "Tanzania": "Tanzania", + "Temperature units:": "Temperature units", + "Thailand": "Thailand", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 supports the LTS versions 6, 8, 10 or newer of node.js. Please update your version (\"%s\") on host \"%s\" to one of the supported versions. We recommend to use Node.js 6.", + "Thu": "Thu", + "Time": "Time", + "Time From": "Time From", + "Time To": "Time To", + "Time stamp": "Time stamp", + "Title": "Title", + "To": "To", + "Today": "Today", + "Toggle expert mode": "Toggle expert mode", + "Toggle states view": "Toggle the states view", + "Togo": "Togo", + "Tokelau": "Tokelau", + "Tonga": "Tonga", + "Too many events": "Too many events", + "Total count in group": "Total count in group", + "Trigger event": "Trigger event", + "Trinidad and Tobago": "Trinidad and Tobago", + "Tue": "Tue", + "Tunisia": "Tunisia", + "Turkey": "Turkey", + "Turkmenistan": "Turkmenistan", + "Turks and Caicos Islands": "Turks and Caicos Islands", + "Tuvalu": "Tuvalu", + "Type": "Type", + "URL or file path:": "URL or file path", + "Uganda": "Uganda", + "Ukraine": "Ukraine", + "Uncheck All": "Uncheck All", + "United Arab Emirates": "United Arab Emirates", + "United Kingdom": "United Kingdom", + "United States": "United States", + "United States minor outlying islands": "United States minor outlying islands", + "Unknown file format!": "Unknown file format!", + "Unsecure_Auth": "The password will be sent via unsecure connection. To protect your passwords enable the secure connection (HTTPS)!", + "Unsupported image format": "Unsupported image format", + "Update": "Update", + "Update objects": "Update objects", + "Update states": "Update states", + "Updated": "Updated", + "Upgrade all adapters": "Upgrade all adapters", + "Upload": "File upload", + "Upload admin started": "Upload of configuration is started", + "Upload started...": "Upload started...", + "Uptime": "Uptime", + "Uruguay": "Uruguay", + "Use Lets Encrypt certificates:": "Use Let's Encrypt certificates", + "Use this instance for automatic update:": "Use this instance for automatic update", + "User": "User", + "User deleted": "User deleted", + "User does not exist": "User does not exist", + "User yet exists": "User already exists", + "Users": "Users", + "Uzbekistan": "Uzbekistan", + "Value": "Value", + "Values of %s": "Values of %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "Vatican City State", + "Venezuela": "Venezuela", + "Vietnam": "Vietnam", + "Virgin Islands (British)": "Virgin Islands (British)", + "Virgin Islands (U.S.)": "Virgin Islands (U.S.)", + "Wallis and Futuna Islands": "Wallis and Futuna Islands", + "Warning!": "Warning!", + "Wed": "Wed", + "Western Sahara": "Western Sahara", + "With": "With", + "Without": "Without", + "Yemen": "Yemen", + "You are going to add new instance: ": "You are going to add new instance: ", + "You can check changelog here": "You can check the changelog here", + "You can drag&drop the devices, channels and states to enums": "You can drag&drop the devices, channels and states to enums", + "You can drag&drop users to groups": "You can drag&drop users to groups", + "You can't see events via cloud": "You can't see events via cloud", + "Your home": "Your home", + "Zaire": "Zaire", + "Zambia": "Zambia", + "Zimbabwe": "Zimbabwe", + "_All": "All", + "_Toggle expert mode": "Toggle expert mode", + "__different__": "different", + "a-z": "a-z", + "ack": "ack", + "actions": "actions", + "active": "active", + "adapter with updates": "adapters with updates", + "adapters count": "adapters count", + "add": "Add", + "add children": "add children", + "add instance": "add instance", + "add repository": "add repository", + "agree": "agree", + "alarm_group": "Alarm", + "alive": "alive", + "all": "all", + "alpha": "alpha", + "array": "array", + "auto": "auto", + "available": "available", + "beta": "beta", + "boolean": "boolean", + "bug": "bugtracker", + "cancel": "Cancel", + "cert_path_note": "You can use an absolute path to the certificate, like '/opt/certs/cert.pem', or just upload it per drag&drop", + "certificate": "certificate", + "change view mode": "change view mode", + "channel": "channel", + "clear": "clear", + "climate-control_group": "Climate Control", + "close on ready": "close on ready", + "collapse": "collapse", + "collapse all": "collapse all", + "comma": "comma", + "command execution": "Command execution", + "common": "common", + "common adapters_group": "Common", + "common_color": "color", + "common_def": "default value", + "common_desc": "description", + "common_icon": "icon", + "common_max": "max value", + "common_min": "minimal value", + "common_read": "read allowed", + "common_role": "role", + "common_states": "predefined values", + "common_type": "type", + "common_unit": "measure unit", + "common_write": "write allowed", + "communication_group": "Communication", + "config": "Settings", + "config instance": "config instance", + "confirm password": "Confirm password", + "connected": "connected", + "copy": "copy", + "copy note": "Press Ctrl+A and Ctrl+C to copy the log to the clipboard and click with the mouse anywhere to close.", + "create operation": "create", + "custom enum": "?ustom enum", + "custom group": "Custom group", + "daemon": "daemon", + "date-and-time_group": "Date and Time", + "daysShortText": "d.", + "debug": "debug", + "delete": "delete", + "delete adapter": "delete adapter", + "delete group": "delete group", + "delete instance": "delete instance", + "delete operation": "delete", + "delete script": "delete script", + "delete user": "delete user", + "desc": "desc", + "description": "Description", + "device": "device", + "diag-note": "We worked hard to create this project. In return we expect from you some usage statistics.
Every time the adapter list is updated, the anonymized statistics are sent. We respect your privacy, so no private information will be transmitted.
Thank you!", + "edit": "edit", + "edit enum": "edit enum", + "edit enums": "Edit enumerations for", + "edit file": "edit file", + "edit group": "edit group", + "edit instance": "edit instance", + "edit script": "edit script", + "edit user": "edit user", + "edit value": "Edit value", + "enabled": "enabled", + "energy_group": "Energy", + "engine": "engine", + "engine type": "engine type", + "error": "error", + "events": "events", + "execute operation": "Execute operation", + "expand": "expand", + "expand all": "expand all", + "extended": "extended", + "false": "false", + "file permissions": "File permissions", + "from": "from", + "garden_group": "Garden", + "general_group": "General", + "geoposition_group": "Geo position", + "groups": "groups", + "hardware_group": "Hardware", + "history": "history", + "history data": "history data", + "host": "host", + "household_group": "Household", + "http operation": "http", + "id": "ID", + "info": "info", + "infrastructure_group": "Infrastructure", + "install": "install", + "install specific version": "Install a specific version", + "installed": "installed", + "installed adapters": "Filter adapters with existing instances", + "instance": "instance", + "instance number": "Desired instance number", + "yunkong2 Enums": "yunkong2 enums", + "yunkong2 States": "yunkong2 states", + "yunkong2 adapter instances": "yunkong2 adapter instances", + "yunkong2 adapter scripts": "yunkong2 adapter scripts", + "yunkong2 adapters": "yunkong2 adapters", + "yunkong2 certificates": "yunkong2 certificates", + "yunkong2 groups": "yunkong2 groups", + "yunkong2 hosts": "yunkong2 hosts", + "yunkong2 repositories": "yunkong2 repositories", + "yunkong2 users": "yunkong2 users", + "iot-system_group": "IoT systems", + "iot-systems_group": "IoT Systems", + "keywords": "keywords", + "lc": "Last change", + "less": "less", + "letsnecrypt_help": "This are settings for Let's Encrypt account. To get the free certificates for your domain. You can read more here.", + "letsnecrypt_help_domains": "E.g: 'example.com, www.example.com'", + "letsnecrypt_help_email": "Please use your email address. It will be used for your account.", + "letsnecrypt_help_path": "Directory name where the certificates will be stored. This is always relative to configuration directory", + "license": "license", + "license agreement": "license agreement", + "license not agree": "Do not agree with the license!", + "license_checkbox": "I agree with the collection of anonymous statistics.
(This can be disabled in settings)", + "lighting_group": "Lighting", + "link": "link", + "list": "list", + "list operation": "list elements", + "logic_group": "Logic", + "loglevel": "loglevel", + "media_group": "Media", + "members": "members", + "memlimit": "RAM limit", + "message": "message", + "messaging_group": "Messaging", + "misc-data_group": "Misc. data", + "mixed": "mixed", + "mode": "mode", + "more": "more", + "multi": "multistate", + "multimedia_group": "Multimedia", + "name": "name", + "native": "native", + "network_group": "Network", + "new certificate": "new certificate", + "new group": "new group", + "new script": "new script", + "new user": "new user", + "newObject": "New object", + "no-city": "no city", + "node-red": "node-red", + "none": "none", + "normal": "normal", + "not ack": "not ack", + "not agree": "not agree", + "npm error": "npm error", + "number": "number", + "object": "object", + "object permissions": "Object permissions", + "of": "of", + "ok": "Ok", + "open web page": "Open web page of adapter", + "os": "operating system", + "other permissions": "Other permissions", + "parent name": "parent name", + "password": "Password", + "permissionError": "Permission error", + "place here": "place the files here", + "planned": "planned", + "platform": "platform", + "point": "point", + "popular": "popular", + "process": "process", + "protocols_group": "Protocols", + "raw": "Raw (experts only)", + "read": "read", + "read operation": "read", + "readme": "readme", + "reload": "reload", + "reload instance": "reload instance", + "rest": "rest (read only)", + "restart": "auto restart", + "restart script": "restart script", + "role": "role", + "save": "save", + "schedule_group": "Schedule", + "script_group": "Scripts and Logic", + "select member by double click": "select member by double clicking", + "sendto operation": "Send-to operation", + "service_group": "Maintenance", + "severity": "severity", + "silly": "silly", + "stable": "stable", + "state": "state", + "state permissions": "State permissions", + "storage_group": "Storage", + "string": "string", + "subscribe": "subscribe", + "switch": "switch", + "terminal": "Terminal", + "third-party_group": "Third party", + "this adapter does not allow multiple instances": "This adapter does not allow multiple instances", + "title": "title", + "today": "today", + "true": "true", + "ts": "Timestamp", + "type": "type", + "unit": "unit", + "update": "update", + "update adapter information": "update adapter information", + "update-part1": "Because yunkong2 runs on many very different platforms, only manual updating is possible at the moment. To start the manual update, please go to your controller via console and execute the following:", + "updated": "updated", + "updates": "updates", + "upload": "Upload", + "user permissions": "user permissions", + "users": "users", + "users permissions": "User permissions", + "utility_group": "Utility", + "val": "val", + "value": "value", + "value.from": "Changed from", + "value.lc": "Last change", + "value.q": "Quality code", + "value.ts": "Timestamp", + "value.val": "value", + "version": "version", + "vis_group": "yunkong2.vis", + "visualisation_group": "Visualisation", + "visualization-icons_group": "Visualization Icons", + "visualization-widgets_group": "Visualization Widgets", + "visualization_group": "Visualisation", + "warn": "warn", + "weather_group": "Weather", + "wetty": "Wetty", + "write": "write", + "write operation": "write", + "yesterday": "yesterday" +} diff --git a/src/i18n/es/translations.json b/src/i18n/es/translations.json new file mode 100644 index 0000000..2a65a74 --- /dev/null +++ b/src/i18n/es/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " para %s", + "%s added to %s": "%s se ha agregado a %s", + "%s object(s) processed": "%s objetos fueron procesados", + "%s processes": "%s procesos", + "%s was imported": "%s fue importado", + "(without prefix)": "(sin prefijo)", + "1 %d days ago": "hace %d días", + "2 %d days ago": "hace %d días", + "5 %d days ago": "hace %d días", + "A-Z": "A-Z", + "Access control": "Controle de acceso", + "Access control list": "Lista de control de acceso", + "Acknowledged": "Confirmado", + "Activated. Click to stop.": "Activado. Haga clic para detener.", + "Active instances": "Instancias activas", + "Active repository:": "Repositorio activo:", + "Adapter configuration": "Configuración del adaptador", + "Adapter settings for %s states": "Configuración del adaptador para estados de %s", + "Adapters": "Adaptadores", + "Adapters from this Group installed": "Adaptadores de este grupo instalados", + "Add": "Añadir", + "Add Objecttree from JSON File": "Agregar árbol de objetos de archivo JSON", + "Add certificate from file": "Agregar certificado de un archivo", + "Add instance...": "Agregar instancia ...", + "Add member": "Añadir miembro", + "Add new child object to selected parent": "Agregar nuevo objeto dentro del seleccionado", + "Add new field": "Agregar nuevo atributo", + "Add new issue": "Reportar un error", + "Add new object: ": "Agregar nuevo objeto:", + "Add new object: %s": "Agregar nuevo objeto: %s", + "Address:": "Dirección:", + "Admin is not enabled in cloud settings!": "¡La Admin no está habilitada en la configuración de la nube!", + "Administrator": "Administrador", + "Afghanistan": "Afganistán", + "Albania": "Albania", + "Algeria": "Argelia", + "All": "Todos", + "American Samoa": "Samoa Americana", + "Andorra": "Andorra", + "Angola": "Angola", + "Anguilla": "Anguila", + "Antarctica": "Antártica", + "Antigua and Barbuda": "Antigua y Barbuda", + "Apr": "Apr", + "April": "Abril", + "Architecture": "Arquitectura", + "Are you sure to delete %s?": "¿Está seguro de que desea borrar \"%s\"?", + "Are you sure to delete all children of %s?": "¿Es seguro que quiere borrar todos los objetos subyacentes de %s?", + "Are you sure to delete all children of %s?": "¿Usted está seguro de que desea eliminar \"%s\" y todos los objetos subyacentes?", + "Are you sure to delete script %s?": "¿Está seguro de que quiere borrar el script '%s'?", + "Are you sure you want to delete adapter %s?": "¿Está seguro de que desea eliminar el adaptador %s?", + "Are you sure you want to delete the instance %s?": "¿Seguro que quieres eliminar la instancia %s ?", + "Are you sure?": "¿Estás seguro?", + "Are you sure? Changes are not saved.": "¿Estás seguro? Los cambios no se guardan.", + "Argentina": "Argentina", + "Armenia": "Armenia", + "Aruba": "Aruba", + "Aug": "Ago", + "August": "Augusto", + "Australia": "Australia", + "Austria": "Austria", + "Authentication was deactivated": "La autenticación fue desactivada", + "Available": "Disponible", + "Available version:": "Versión disponible:", + "Azerbaijan": "Azerbaiyán", + "Background": "Fondo", + "Background color of the login screen": "Color de fondo de la pantalla de inicio de sesión", + "Background image": "Imagen de fondo", + "Bahamas": "Bahamas", + "Bahrain": "Bahrein", + "Bangladesh": "Bangladesh", + "Barbados": "Barbados", + "Belarus": "Bielorrusia", + "Belgium": "Bélgica", + "Belize": "Belize", + "Benin": "Benin", + "Bermuda": "Bermudas", + "Bhutan": "Bután", + "Bolivia": "Bolivia", + "Bosnia and Herzegovina": "Bosnia y Herzegovina", + "Botswana": "Botswana", + "Bouvet Island": "Isla de Bouvet", + "Brazil": "Brasil", + "British Indian Ocean Territory": "Territorio británico del Océano Índico", + "Brunei Darussalam": "Brunéi Darussalam", + "Bulgaria": "Bulgaria", + "Burkina Faso": "Burkina Faso", + "Burundi": "Burundi", + "CPUs": "CPUs", + "Calendar": "Calendario", + "Cambodia": "Camboya", + "Cameroon": "Camerún", + "Canada": "Canadá", + "Cancel": "Cancelar", + "Cannot create user: ": "No se puede crear usuario:", + "Cannot delete user: ": "No se puede suprimir el usuario:", + "Cannot disable admin!": "¡No se puede deshabilitar el administrador!", + "Cannot read file!": "¡No se puede leer el archivo!", + "Cannot read version from NPM": "No se pudo leer la versión de NPM", + "Cannot set password: ": "No se pudo cambiar la contraseña:", + "Cape Verde": "Capo Verde", + "Cayman Islands": "Islas Caimán", + "Central African Republic": "República Centroafricana", + "Certificates": "Certificados", + "Chad": "Chad", + "Change": "Cambiar", + "Changelog": "Cambiar registro", + "Channel": "Canal", + "Chart": "Gráfico", + "Chart for %s": "Gráfico para %s", + "Check all": "Seleccionar todos", + "Chile": "Chile", + "China": "China", + "Christmas Island": "Isla de Navidad", + "City:": "Ciudad:", + "Clear": "Borrar", + "Clear list": "Borrar la lista", + "Clear log": "Borrar el log", + "Clear on disk permanent": "Delatar el log permanente del disco", + "Click do activate events again, or just wait one minute": "Haga clic en activar eventos nuevamente, o simplemente espere un minuto", + "Click on icon": "Haga clic en el icono para abrir un link", + "Close": "cerrar", + "Cocos Islands": "Islas Cocos", + "Collapse all nodes": "Contraer todos los nodos", + "Colombia": "Colombia", + "Color": "Color", + "Comoros": "Comores", + "Configuration not saved.": "La configuración no está guardada.", + "Congo": "Congo", + "Connected to %s: ": "Conectado con %s:", + "Connected to host: ": "Conectado con el host:", + "Cook Islands": "Islas Cook", + "Copy log": "Copiar el log", + "Copy to clipboard": "Copiar al portapapeles", + "Costa Rica": "Costa Rica", + "Country:": "País:", + "Create": "Crear", + "Create new category": "Crear nueva categoría", + "Create new category, like %s": "Crear una nueva categoría, como %s", + "Create new enum": "Crear nueva clasificación", + "Create new enum, like %s": "Crear nueva clasificación, como %s", + "Create new group": "Crear nuevo grupo", + "Create new user": "Crear nuevo usuario", + "Created": "Creado", + "Croatia": "Croacia", + "Cron expression": "Expresión de Cron", + "Cuba": "Cuba", + "Currency:": "Moneda:", + "Custom": "Cualquier", + "Cyprus": "Chipre", + "Czech Republic": "República Checa", + "D$ecember": "Diciembre", + "DD.MM.YY": "DD.MM.AA", + "DD.MM.YYYY": "DD.MM.AAAA", + "DD/MM/YYYY": "DD/MM/AAAA", + "Date From": "Fecha de", + "Date To": "Fecha a", + "Date format:": "Formato de fecha:", + "Deactivated. Click to start.": "Desactivado. Haga clic para empezar.", + "Debug outputs:": "Salidas de depuración", + "Dec": "Dic", + "December": "Diciembre", + "Default ACL": "ACL predeterminado", + "Default history instance:": "Instancia histórica predeterminada:", + "Delete attribute": "Eliminar atributos", + "Delete category": "Eliminar categoría", + "Delete enum": "Eliminar la clasificación", + "Delete member": "Eliminar miembro", + "Delete object": "Eliminar objeto", + "Denmark": "Danimarca", + "Description": "Descripción", + "Device": "Aparato", + "Device discovery": "Detección de aparato", + "Disable authentication": "Deshabilitar autenticación", + "Disk free": "Tamaño libre de disco", + "Disk free:": "Disco disponible:", + "Disk size": "Tamaño de disco", + "Djibouti": "Djibouti", + "Do you want to delete just one object or all children of %s too?": "¿Usted desea borrar sólo un objeto o todos los objetos subyacentes %s también?", + "Do you want to upgrade all adapters?": "¿Desea actualizar todos los adaptadores?", + "Domains:": "Dominios:", + "Dominica": "Dominica", + "Dominican Republic": "República Dominicana", + "Done with error: %s": "Terminado con error: %s", + "Download log": "Descargar registro", + "Drop the files here": "Suelte los archivos aquí", + "Drop the icons here": "Suelte los iconos aquí", + "East Timor": "Timor Oriental", + "Ecuador": "Ecuador", + "Edit": "Editar", + "Edit category": "Editar categoria", + "Edit enum": "Editar calificación", + "Edit in dialog": "Editar en diálogo", + "Edit object": "Editar objeto", + "Egypt": "Egipto", + "El Salvador": "El Salvador", + "Email for account:": "Email para la cuenta:", + "Enabled:": "Habilitado", + "Enums": "Clasificaciones", + "Equatorial Guinea": "Guinea Ecuatorial", + "Eritrea": "Eritrea", + "Error": "Error", + "Estonia": "Estonia", + "Ethiopia": "Etiopía", + "Event": "Tipo", + "Events": "Eventos", + "Everyone": "Todos", + "Expand all nodes": "Expandir todos los nodos", + "Failed to open JSON File": "Error al abrir el archivo JSON", + "Falkland Islands (Malvinas)": "Islas Falkland (Malvinas)", + "Faroe Islands": "Islas Feroe", + "Feb": "feb", + "February": "Febrero", + "Fiji": "Fiji", + "File is too big!": "¡El archivo es muy grande!", + "File rights": "Derechos de archivo", + "Filter": "Filtro", + "Filter:": "Filtro", + "Filtered out": "Todo está filtrado", + "Find coordinates...": "Encuentre coordenadas ...", + "Finland": "Finlandia", + "Float divider:": "Separador decimal:", + "France": "Francia", + "Free RAM:": "RAM libre:", + "French Guiana": "Guiana Francesa", + "French Polynesia": "Polinesia Francesa", + "French Southern Territories": "Territorios Franceses del Sur", + "Fri": "Vie", + "From": "Fuente", + "From github": "Hacer Github", + "Function": "Función", + "Gabon": "Gabón", + "Gambia": "Gambia", + "Generated ID:": "ID generado:", + "Georgia": "Georgia", + "Germany": "Alemania", + "Ghana": "Ghana", + "Gibraltar": "Gibraltar", + "Go to Github...": "Ve a Github...", + "Greece": "Grecia", + "Greenland": "Groenlandia", + "Grenada": "Grenada", + "Group": "Grupo", + "Groups": "Grupos", + "Guadeloupe": "Guadalupe", + "Guam": "Guam", + "Guatemala": "Guatemala", + "Guernsey": "Guernesex", + "Guinea": "Guiné", + "Guinea-Bissau": "Guiné-Bissau", + "Guyana": "Guayana", + "Haiti": "Haití", + "Has no permission to %s %s %s": "No tiene permiso para %s %s %s", + "Heard and Mc Donald Islands": "Heard e Islas McDonald", + "Heartbeat: ": "Señal de vida:", + "Honduras": "Honduras", + "Hong Kong": "Hong Kong", + "Host": "Host", + "Host %s is offline": "Host %s está desconectado", + "Host:": "Host:", + "Hosts": "Hosts", + "Hungary": "Hungría", + "ID": "ID", + "Iceland": "Islandia", + "Icon upload": "Upload de imagem", + "Ignore warning": "Ignorar advertencia", + "In background": "Atrás", + "India": "India", + "Indonesia": "Indonesia", + "Info": "Información", + "Insert": "Colocar", + "Install": "Instalar", + "Install adapter from URL": "Instale o atualize el adaptador a un uma URL", + "Install adapter from github": "Instalar o actualizar el adaptador por Github", + "Install from custom URL": "Instalar desde una URL cualquiera", + "Install or update from URL...": "Instalar o actualizar desde URL ...", + "Installation counter": "Contador de instalación", + "Installations counter": "Contador de instalaciones", + "Installed": "Instalado", + "Installed from group": "Instalado por el grupo", + "Installed instances": "Instancias instaladas", + "Installed version": "Versión instalada", + "Instances": "Instancias", + "Instructions": "Instrucciones", + "Intro": "comienzo", + "Invalid version of %s": "Versión no válida de %s", + "Invalid version of %s. Required %s": "Versión no válida de %s. Necesita ser %s", + "Iran": "Irán", + "Iraq": "Irak", + "Ireland": "Irlanda", + "Is yet in the list": "Ya está en la lista", + "Isle of Man": "Isle of Man", + "Israel": "Israel", + "Italy": "Italia", + "Ivory Coast": "Costa de Marfil", + "Jamaica": "Jamaica", + "Jan": "Jan", + "January": "Enero", + "Japan": "Japón", + "Jersey": "Jersey", + "Jordan": "Jordania", + "Jul": "Jul", + "July": "Julio", + "Jun": "Jun", + "June": "Junio", + "Kazakhstan": "Kazajstán", + "Kenya": "Kenia", + "Kiribati": "Kiribati", + "Known bugs for": "Errores conocidos para", + "Korea": "Corea", + "Kosovo": "Kosovo", + "Kuwait": "Kuwait", + "Kyrgyzstan": "Kirguistán", + "Lao People's Democratic Republic": "República Democrática Popular Lao", + "Last changed": "Última modificación", + "Last update": "Última actualización", + "Latitude:": "Latitud:", + "Latvia": "Letonia", + "Lebanon": "Líbano", + "Lesotho": "Lesotho", + "Let's Encrypt settings": "Configuración de Let's Encrypt", + "Let's encrypt SSL": "Let's Encrypt SSL", + "Liberia": "Liberia", + "Libyan Arab Jamahiriya": "Jamahiriya Árabe Libia", + "License": "Licencia", + "License terms": "Condiciones de licencia", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Escucha todos los IPs", + "Lithuania": "Lituania", + "Loading...": "Cargando...", + "Log": "Log", + "Log file will be deleted. Are you sure?": "Se borrará el archivo log. ¿Estás seguro?", + "Log size:": "Tamaño del log:", + "Login timeout(sec):": "Límite de tiempo para el acceso (sec):", + "Logout": "Cerrar sesión", + "Longitude:": "Longitud:", + "Luxembourg": "Luxemburgo", + "MB": "MB", + "Macau": "Macau", + "Macedonia": "Macedonia", + "Madagascar": "Madagascar", + "Mai": "May", + "Main": "General", + "Main settings": "Configuración general", + "Malawi": "Malawi", + "Malaysia": "Malasia", + "Maldives": "Maldivas", + "Mali": "Mali", + "Malta": "Malta", + "Manually created": "Creación manual", + "Mar": "Mar", + "March": "Marzo", + "Marshall Islands": "Islas Marshall", + "Martinique": "Martinica", + "Mauritania": "Mauritania", + "Mauritius": "Mauricio", + "Mayotte": "Mayotte", + "Members": "Miembros", + "Message": "Mensaje", + "Message buffer overflow. Losing oldest": "Mensajes de más. Los más viejos serán borrados.", + "Mexico": "México", + "Micronesia": "Micronesia", + "Model": "Modelo", + "Moldova": "Moldavia", + "Mon": "Lun", + "Monaco": "Mónaco", + "Mongolia": "Mongolia", + "Montenegro": "Montenegro", + "Montserrat": "Montserrat", + "Morocco": "Marruecos", + "Mozambique": "Mozambique", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Nombre", + "Name:": "Nombre:", + "Namibia": "Namibia", + "Nauru": "Nauru", + "Nepal": "Nepal", + "Netherlands": "Holanda", + "Netherlands Antilles": "Antillas Holandesas", + "New": "Nuevo", + "New Caledonia": "Nueva Caledonia", + "New Zealand": "Nueva Zelanda", + "New category": "Nueva categoría", + "New enum": "Nueva clasificación", + "New group": "Nuevo grupo", + "New object": "Nuevo objeto", + "New objekt": "Nuevo objeto", + "New user": "Nuevo usuario", + "Nicaragua": "Nicaragua", + "Niger": "Niger", + "Nigeria": "Nigeria", + "Niue": "Niue", + "No data": "Sin datos", + "No states selected!": "¡Ningún estado seleccionado!", + "No version of %s": "No hay información de la versión %s", + "Node.js": "Node.js", + "Norfolk Island": "Isla de Norfolk", + "Northern Mariana Islands": "Islas Marianas del Norte", + "Norway": "Noruega", + "Not exists": "No existe", + "Note:": "Nota:", + "Nov": "Nov", + "November": "Noviembre", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "El objeto \"%s\" no existe. Actualice la página.", + "Object may not be deleted": "El objeto no se puede eliminar", + "Object rights": "Derechos del objeto", + "Objects": "Objetos", + "Oct": "Oct", + "October": "Octubre", + "Ok": "Ok", + "Oman": "Omán", + "Only one": "Apenas uno", + "Open original": "Abrir en una pestaña nueva", + "Owner": "Propietario", + "Owner group": "Grupo de propietarios", + "Owner user": "Usuario propietario", + "Pakistan": "Pakistán", + "Palau": "Palau", + "Palestine": "Palestina", + "Panama": "Panamá", + "Papua New Guinea": "Papúa Nueva Guinea", + "Paraguay": "Paraguay", + "Parent": "Padre", + "Parse error": "Error de análisis", + "Password": "Contraseña", + "Password and confirmation are not equal!": "¡La contraseña y la confirmación no son iguales!", + "Password cannot be empty!": "¡La contraseña no puede estar vacía!", + "Password repeat": "Repetición de la contraseña", + "Path to storage:": "Ruta para grabar:", + "Pause output": "Pausar la salida", + "Peru": "Perú", + "Philippines": "Filipinas", + "Pitcairn": "Pitcairn", + "Platform": "Plataforma", + "Please confirm": "Por favor, confirme", + "Poland": "Polonia", + "Popular": "Popular", + "Popular first": "Popular primero", + "Port to check the domain:": "Puerto para comprobar el dominio:", + "Portugal": "Portugal", + "Preserve ID": "Conservar ID", + "Preview": "Vista previa", + "Processing...": "En proceso...", + "Puerto Rico": "Puerto Rico", + "Qatar": "Qatar", + "RAM": "RAM", + "RAM total usage:": "Uso total de RAM:", + "RAM usage": "Uso de RAM", + "Rebuild tree": "Reconstruir el árbol", + "Recently updated": "Recientemente actualizado", + "Refresh log": "Actualizar el log", + "Removed": "Removido", + "Removing of adapter...": "Quitar el adaptador...", + "Removing of instance...": "Eliminando de instancia...", + "Rename": "Rebautizar", + "Repositories": "Repositorios", + "Reunion": "Reunión", + "Rights": "Derechos de acceso", + "Role": "Cargo", + "Romania": "Rumania", + "Room": "Habitación", + "Running: ": "Concluido:", + "Russian Federation": "Federación Rusa", + "Rwanda": "Ruanda", + "Saint Kitts and Nevis": "San Cristóbal y Nieves", + "Saint Lucia": "Santa Lucía", + "Saint Vincent and the Grenadines": "San Vicente y las Granadinas", + "Samoa": "Samoa", + "San Marino": "San Marino", + "Sao Tome and Principe": "San Tome y Principe", + "Sat": "Sáb", + "Saudi Arabia": "Arabia Saudita", + "Save": "Grabar", + "Save Objecttree as JSON File": "Grabe la estructura de objetos seleccionados como un archivo JSON", + "Save Objecttree is not possible": "La grabación no es posible", + "Save configuration": "Grabar la configuración", + "Script": "Script", + "Scripts": "Scripts", + "Select": "Seleccione", + "Select ID": "Seleccione ID", + "Select adapter:": "Seleccione el adaptador", + "Select language": "Seleccione el idioma", + "Select options": "Seleccione las opciones", + "Senegal": "Senegal", + "Sent data:": "Datos enviados:", + "Sep": "Sep", + "September": "Septiembre", + "Serbia": "Serbia", + "Set": "Conjunto", + "Set CRON": "Acepta el cronograma", + "Set CRON schedule for restarts": "Establecer el cronograma CRON para reinicios", + "Settings": "Configuración", + "Settings for %s": "Configuraciones para %s", + "Seychelles": "Seychelles", + "Show instances only for current host": "Mostrar instancias solo para el host actual", + "Show values of instance": "Mostrar valores de instancia", + "Show...": "Muestre...", + "Sierra Leone": "Sierra Leona", + "Singapore": "Singapur", + "Size: %s, Free: %s": "Tamaño: %s, disponible: %s", + "Slovakia": "Eslovaquia", + "Slovenia": "Eslovenia", + "Solomon Islands": "Islas Salomón", + "Somalia": "Somalia", + "Some data are not stored. Discard?": "Algunos datos no se graban. ¿Descartar?", + "Sort alphabetically": "Orden alfabetica", + "South Africa": "Sudáfrica", + "South Georgia South Sandwich Islands": "Islas Sandwich del sur de Georgia", + "Spain": "España", + "Speed": "Velocidad", + "Sri Lanka": "Sri Lanka", + "St. Helena": "Santa Elena", + "St. Pierre and Miquelon": "San Pedro y Miquelón", + "Started...": "Comenzado...", + "State": "Punto de datos", + "State type": "Tipo de punto de datos", + "States": "Estados", + "States rights": "Derechos de los estados", + "Statistics": "Estadísticas", + "Statistics:": "Estadísticas:", + "Storage of %s": "Configuraciones de %s", + "Storage of %s states": "Configuración de los estados de %s", + "Success!": "¡Éxito!", + "Sudan": "Sudán", + "Suggestion": "Recomendaciones", + "Sun": "Sol", + "Suriname": "Surinam", + "Svalbard and Jan Mayen Islands": "Islas Svalbard y Jan Mayen", + "Swaziland": "Suazilandia", + "Sweden": "Suecia", + "Switzerland": "Suiza", + "Syrian Arab Republic": "República Árabe de Siria", + "System": "Sistema", + "System language:": "Idioma del sistema:", + "System settings": "Configuración del sistema", + "System uptime": "System uptime", + "Table": "Tabla", + "Taiwan": "Taiwan", + "Tajikistan": "Tayikistán", + "Tanzania": "Tanzania", + "Temperature units:": "Unidades de temperatura:", + "Thailand": "Tailandia", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 soporta las versiones LTS 6, 8 y 10 de Node.js. Actualice su versión (\"%s\") en el host \"%s\" para una de las versiones soportadas. Recomendamos usar Node.js 6.", + "Thu": "Jue", + "Time": "Tiempo", + "Time From": "Tiempo desde", + "Time To": "Hora de", + "Time stamp": "Timestamp", + "Title": "Título", + "To": "Para", + "Today": "hoy", + "Toggle expert mode": "El modo experto", + "Toggle states view": "Alternar la vista de estados", + "Togo": "Togo", + "Tokelau": "Tokelau", + "Tonga": "Tonga", + "Too many events": "Muchos eventos", + "Total count in group": "Cuenta total del grupo", + "Trigger event": "Simule un evento", + "Trinidad and Tobago": "Trinidad y Tobago", + "Tue": "Mar", + "Tunisia": "Túnez", + "Turkey": "Turquía", + "Turkmenistan": "Turkmenistán", + "Turks and Caicos Islands": "Islas Turcas y Caicos", + "Tuvalu": "Tuvalu", + "Type": "Tipo", + "URL or file path:": "URL o ruta de acceso del archivo", + "Uganda": "Uganda", + "Ukraine": "Ucrania", + "Uncheck All": "Desmarcar todos", + "United Arab Emirates": "Emiratos Árabes Unidos", + "United Kingdom": "Reino Unido", + "United States": "Estados Unidos", + "United States minor outlying islands": "Islas periféricas menores de los Estados Unidos", + "Unknown file format!": "¡Formato de archivo desconocido!", + "Unsecure_Auth": "La contraseña se enviará a través de una conexión no segura. Para proteger sus contraseñas, ¡habilite la conexión segura (HTTPS)!", + "Unsupported image format": "Formato de imagen no compatible", + "Update": "Actualización", + "Update objects": "Actualizar objetos", + "Update states": "Actualizar estados", + "Updated": "Actualizado", + "Upgrade all adapters": "Actualice todos los adaptadores", + "Upload": "Upload de arquivo", + "Upload admin started": "Cargar el administrador iniciado", + "Upload started...": "La carga comenzó ...", + "Uptime": "Uptime", + "Uruguay": "Uruguay", + "Use Lets Encrypt certificates:": "Utilice los certificados de Let's Encrypt:", + "Use this instance for automatic update:": "Utilice esta instancia para actualizar los certificados:", + "User": "Usuario", + "User deleted": "Usuario eliminado", + "User does not exist": "Usuario no existe", + "User yet exists": "El usuario ya existe", + "Users": "Usuarios", + "Uzbekistan": "Uzbekistán", + "Value": "Valor", + "Values of %s": "Valores para %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "Estado de la Ciudad del Vaticano", + "Venezuela": "Venezuela", + "Vietnam": "Vietnam", + "Virgin Islands (British)": "Islas Vírgenes (Británicas)", + "Virgin Islands (U.S.)": "Islas Vírgenes (EE.UU.)", + "Wallis and Futuna Islands": "Islas Wallis y Futuna", + "Warning!": "¡Advertencia!", + "Wed": "Wed", + "Western Sahara": "Sahara Occidental", + "With": "con", + "Without": "sin", + "Yemen": "Yemen", + "You are going to add new instance: ": "Vas a agregar una nueva instancia:", + "You can check changelog here": "Usted puede comprobar el changelog aquí", + "You can drag&drop the devices, channels and states to enums": "Usted puede usar drag & drop para clasificar los aparatos, canales y estados en una clasificación", + "You can drag&drop users to groups": "Puede utilizar drag & drop para clasificar usuarios en grupos", + "You can't see events via cloud": "No puedes ver eventos a través de la nube", + "Your home": "Su casa", + "Zaire": "Zaire", + "Zambia": "Zambia", + "Zimbabwe": "Zimbabwe", + "_All": "Todos", + "_Toggle expert mode": "Modo de experto", + "__different__": "diferente", + "a-z": "a-z", + "ack": "confirmado", + "actions": "acciones", + "active": "activo", + "adapter with updates": "adaptador con actualizaciones", + "adapters count": "conteo de adaptadores", + "add": "Añadir", + "add children": "insertar objeto secundario", + "add instance": "añadir instancia", + "add repository": "añadir repositorio", + "agree": "aceptado", + "alarm_group": "Alarma", + "alive": "funcionando", + "all": "todos", + "alpha": "alfa", + "array": "campo", + "auto": "auto", + "available": "disponible", + "beta": "beta", + "boolean": "booleano", + "bug": "bugtracker", + "cancel": "Cancelar", + "cert_path_note": "Puede utilizar la ruta absoluta para el certificado, como '/opt/certs/cert.pem', o simplemente cargar un archivo", + "certificate": "certificado", + "change view mode": "cambiar el modo de vista", + "channel": "canal", + "clear": "borrar", + "climate-control_group": "Controle climático", + "close on ready": "Cierre cuando esté listo", + "collapse": "colapso", + "collapse all": "colapsar todos", + "comma": "coma", + "command execution": "Ejecución de comandos", + "common": "general", + "common adapters_group": "General", + "common_color": "color", + "common_def": "valor predeterminado", + "common_desc": "descripción", + "common_icon": "ico", + "common_max": "valor máximo", + "common_min": "valor mínimo", + "common_read": "permitido leer", + "common_role": "papel", + "common_states": "estados", + "common_type": "tipo", + "common_unit": "Unidad de medida", + "common_write": "escribir", + "communication_group": "Comunicación", + "config": "Configuración", + "config instance": "configurar la instancia", + "confirm password": "Confirme la contraseña", + "connected": "conectado", + "copy": "copiar", + "copy note": "Presione Ctrl + A y luego Ctrl + C para copiar el contenido al portapapeles. Haga clic en algún lugar para cerrar la ventana.", + "create operation": "producir", + "custom enum": "Enum personalizado", + "custom group": "Grupo personalizado", + "daemon": "daemon", + "date-and-time_group": "Fecha y hora", + "daysShortText": "d.", + "debug": "depurar", + "delete": "borrar", + "delete adapter": "borrar adaptador", + "delete group": "borrar grupo", + "delete instance": "borrar instancia", + "delete operation": "borrar", + "delete script": "borrar el script", + "delete user": "borrar usuario", + "desc": "desc.", + "description": "Descripción", + "device": "aparato", + "diag-note": "Trabajamos duro para crear este proyecto. En cambio, pedimos que nos envíe las estadísticas de uso.
Ninguna información privada será enviada a yunkong2.net. Cada vez que se actualiza la lista del adaptador, también se enviarán las estadísticas anónimas. -
¡Gracias!", + "edit": "editar", + "edit enum": "editar la clasificación", + "edit enums": "Editar enumeraciones para", + "edit file": "editar archivo", + "edit group": "editar grupo", + "edit instance": "editar instancia", + "edit script": "editar script", + "edit user": "editar usuario", + "edit value": "Editar valor", + "enabled": "activado", + "energy_group": "Energía", + "engine": "ejecutar con", + "engine type": "tipo de mecanismo", + "error": "error", + "events": "eventos", + "execute operation": "ejecutar", + "expand": "expandir", + "expand all": "expandir todo", + "extended": "expandido", + "false": "falso", + "file permissions": "Permisos de archivo", + "from": "fuente", + "garden_group": "Jardín", + "general_group": "General", + "geoposition_group": "Posición geográfica", + "groups": "grupos", + "hardware_group": "Equipamiento", + "history": "historia", + "history data": "datos históricos", + "host": "host", + "household_group": "Casa", + "http operation": "http", + "id": "ID", + "info": "información", + "infrastructure_group": "Infraestructura", + "install": "instalar", + "install specific version": "Instale una versión específica", + "installed": "instalado", + "installed adapters": "Filtro adaptadores con instancias existentes", + "instance": "instancia", + "instance number": "Número de instancia deseado", + "yunkong2 Enums": "Clasificaciones de yunkong2", + "yunkong2 States": "Estados del yunkong2", + "yunkong2 adapter instances": "Instancias de adaptador de yunkong2", + "yunkong2 adapter scripts": "Scripts para el adaptador de yunkong2", + "yunkong2 adapters": "Adaptadores del yunkong2", + "yunkong2 certificates": "Certificados del yunkong2", + "yunkong2 groups": "Grupos de yunkong2", + "yunkong2 hosts": "Hosts do yunkong2", + "yunkong2 repositories": "Repositorios de yunkong2", + "yunkong2 users": "Usuarios de yunkong2", + "iot-system_group": "Sistemas IoT", + "iot-systems_group": "Sistemas IoT", + "keywords": "palabras clave", + "lc": "mod.", + "less": "menos", + "letsnecrypt_help": "Configuraciones para la cuenta Let's Encrypt. Para obtener un certificado gratuito para su dominio, siga las instrucciones aquí.", + "letsnecrypt_help_domains": "por ejemplo: 'example.com, www.example.com'", + "letsnecrypt_help_email": "Utilice siempre sólo una e-mail.", + "letsnecrypt_help_path": "Directorio donde se escriben los certificados. La ruta de acceso se refiere al directorio de configuración", + "license": "licencia", + "license agreement": "Contrato de licencia", + "license not agree": "¡No estoy de acuerdo con la licencia!", + "license_checkbox": "Estoy de acuerdo con la colección de estadísticas anónimas.
(también se puede desactivar más tarde en la configuración)", + "lighting_group": "Iluminación", + "link": "link", + "list": "lista", + "list operation": "lista", + "logic_group": "Lógica", + "loglevel": "Nivel de log", + "media_group": "Medios", + "members": "miembros", + "memlimit": "Límite de RAM", + "message": "mensaje", + "messaging_group": "Mensajes", + "misc-data_group": "Datos mezclados", + "mixed": "misturado", + "mode": "modo", + "more": "más", + "multi": "lista de valores", + "multimedia_group": "Multimedia", + "name": "nome", + "native": "nativo", + "network_group": "Red", + "new certificate": "nuevo certificado", + "new group": "nuevo grupo", + "new script": "nuevo script", + "new user": "nuevo usuario", + "newObject": "Nuevo objeto", + "no-city": "sin ciudad", + "node-red": "node-red", + "none": "ninguno", + "normal": "normal", + "not ack": "no conf.", + "not agree": "No estoy de acuerdo", + "npm error": "error en NPM", + "number": "numero", + "object": "oggetto", + "object permissions": "Permessi sugli oggetti", + "of": "de", + "ok": "Ok", + "open web page": "Abrir un sitio web do adaptador", + "os": "Sistema operativo", + "other permissions": "Otros permisos", + "parent name": "nombre de los padres", + "password": "contraseña", + "permissionError": "Error de permiso", + "place here": "coloca los archivos aquí", + "planned": "planificado", + "platform": "plataforma", + "point": "punto", + "popular": "popular", + "process": "proceso", + "protocols_group": "Protocolos", + "raw": "Raw (sólo expertos)", + "read": "leer", + "read operation": "leer", + "readme": "leerme", + "reload": "recargar", + "reload instance": "reiniciar la instancia", + "rest": "además (sólo lectura)", + "restart": "reiniciar", + "restart script": "reiniciar el script", + "role": "función (tipo)", + "save": "guardar", + "schedule_group": "Programación de tiempo", + "script_group": "Scripts y lógica", + "select member by double click": "seleccione el miembro haciendo doble clic", + "sendto operation": "Operación enviar a", + "service_group": "Mantenimiento", + "severity": "severidad", + "silly": "todo", + "stable": "estable", + "state": "estado", + "state permissions": "Derechos de los estados", + "storage_group": "Almacenamiento", + "string": "cadena de caracteres", + "subscribe": "inscribirse", + "switch": "interruptor", + "terminal": "Terminal", + "third-party_group": "Outros", + "this adapter does not allow multiple instances": "Este adaptador no permite varias instancias", + "title": "título", + "today": "hoy", + "true": "verdade", + "ts": "marca de tiempo", + "type": "tipo", + "unit": "unidad", + "update": "actualizar", + "update adapter information": "actualizar la información del adaptador", + "update-part1": "Debido al gran número de plataformas de hardware que el yunkong2 se puede ejecutar, es necesario actualizar el JS-Controller manualmente. Para ello, ejecute los siguientes comandos en la consola de host:", + "updated": "actualizado", + "updates": "actualizaciones", + "upload": "Cargar", + "user permissions": "permisos de usuario", + "users": "usuarios", + "users permissions": "permisos del usuario", + "utility_group": "Utilidad", + "val": "val", + "value": "valor", + "value.from": "Cambiaron desde", + "value.lc": "Ultimo cambio", + "value.q": "Código de calidad", + "value.ts": "Marca de tiempo", + "value.val": "valor", + "version": "versión", + "vis_group": "yunkong2.vis", + "visualisation_group": "Visualización", + "visualization-icons_group": "Iconos de visualización", + "visualization-widgets_group": "Widgets de visualización", + "visualization_group": "Visualización", + "warn": "aviso", + "weather_group": "Tiempo", + "wetty": "Wetty", + "write": "escribir", + "write operation": "escribir", + "yesterday": "ayer" +} diff --git a/src/i18n/fr/translations.json b/src/i18n/fr/translations.json new file mode 100644 index 0000000..9b3a15c --- /dev/null +++ b/src/i18n/fr/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " pour %s", + "%s added to %s": "%s ajouté à %s", + "%s object(s) processed": "%s objets ont été traités", + "%s processes": "%s processus", + "%s was imported": "%s a été importé", + "(without prefix)": "(sans préfixe)", + "1 %d days ago": "il y a %d jours", + "2 %d days ago": "il y a %d jours", + "5 %d days ago": "il y a %d jours", + "A-Z": "A-Z", + "Access control": "Contrôle d'accès", + "Access control list": "Liste des droits d'accès", + "Acknowledged": "Confirmé", + "Activated. Click to stop.": "Activé. Cliquez pour arrêter.", + "Active instances": "Instances actives", + "Active repository:": "Dépôt actif", + "Adapter configuration": "Configuration de l'adaptateur", + "Adapter settings for %s states": "Paramètres de l'adaptateur pour les états %s", + "Adapters": "Adaptateurs", + "Adapters from this Group installed": "Les adaptateurs de ce groupe sont installés", + "Add": "Ajouter", + "Add Objecttree from JSON File": "Ajouter une arborescence d'objets à partir du fichier JSON", + "Add certificate from file": "Ajouter un certificat à partir du fichier", + "Add instance...": "Ajouter une instance ...", + "Add member": "Ajouter un membre", + "Add new child object to selected parent": "Ajouter un nouvel objet enfant au parent sélectionné", + "Add new field": "Ajouter un nouveau champ", + "Add new issue": "Signaler un bug", + "Add new object: ": "Ajouter un nouvel objet", + "Add new object: %s": "Ajouter un nouvel objet: %s", + "Address:": "Adresse", + "Admin is not enabled in cloud settings!": "L'Admin n'est pas activé dans les paramètres du cloud!", + "Administrator": "Administrateur", + "Afghanistan": "Afghanistan", + "Albania": "Albanie", + "Algeria": "Algérie", + "All": "Tout", + "American Samoa": "Samoa américaines", + "Andorra": "Andorre", + "Angola": "Angola", + "Anguilla": "Anguilla", + "Antarctica": "Antarctique", + "Antigua and Barbuda": "Antigua-et-Barbuda", + "Apr": "avr.", + "April": "avril", + "Architecture": "Architecture", + "Are you sure to delete %s?": "Êtes-vous sûr de vouloir supprimer \"%s\"?", + "Are you sure to delete all children of %s?": "Êtes-vous sûr de vouloir supprimer tous les descendants de %s ?", + "Are you sure to delete all children of %s?": "Êtes-vous sûr de vouloir supprimer tous les descendants de %s ?", + "Are you sure to delete script %s?": "Etes-vous sûr de vouloir supprimer le script '%s'?", + "Are you sure you want to delete adapter %s?": "Êtes-vous sûr de vouloir supprimer l'adaptateur %s?", + "Are you sure you want to delete the instance %s?": "Êtes-vous sûr de vouloir supprimer l'instance %s?", + "Are you sure?": "Êtes-vous sûr?", + "Are you sure? Changes are not saved.": "Êtes-vous sûr? Les modifications ne sont pas enregistrées.", + "Argentina": "Argentine", + "Armenia": "Arménie", + "Aruba": "Aruba", + "Aug": "août", + "August": "août", + "Australia": "Australie", + "Austria": "Autriche", + "Authentication was deactivated": "L'authentification a été désactivée", + "Available": "Disponible", + "Available version:": "Version disponible:", + "Azerbaijan": "Azerbaïdjan", + "Background": "Contexte", + "Background color of the login screen": "Couleur d'arrière-plan de l'écran de connexion", + "Background image": "Image de fond", + "Bahamas": "Bahamas", + "Bahrain": "Bahreïn", + "Bangladesh": "Bangladesh", + "Barbados": "Barbade", + "Belarus": "Bélarus", + "Belgium": "Belgique", + "Belize": "Belize", + "Benin": "Bénin", + "Bermuda": "Bermudes", + "Bhutan": "Bhoutan", + "Bolivia": "Bolivie", + "Bosnia and Herzegovina": "Bosnie Herzégovine", + "Botswana": "Botswana", + "Bouvet Island": "Île Bouvet", + "Brazil": "Brésil", + "British Indian Ocean Territory": "Territoire britannique de l'océan Indien", + "Brunei Darussalam": "Brunei Darussalam", + "Bulgaria": "Bulgarie", + "Burkina Faso": "Burkina Faso", + "Burundi": "Burundi", + "CPUs": "CPUs", + "Calendar": "Calendrier", + "Cambodia": "Cambodge", + "Cameroon": "Cameroun", + "Canada": "Canada", + "Cancel": "Annuler", + "Cannot create user: ": "Impossible de créer l'utilisateur:", + "Cannot delete user: ": "Impossible de supprimer l'utilisateur:", + "Cannot disable admin!": "Impossible de désactiver l'administrateur!", + "Cannot read file!": "Impossible de lire le fichier!", + "Cannot read version from NPM": "Impossible de lire la version de NPM", + "Cannot set password: ": "Impossible de définir le mot de passe:", + "Cape Verde": "Cap-Vert", + "Cayman Islands": "Îles Caïmans", + "Central African Republic": "République centrafricaine", + "Certificates": "Certificats", + "Chad": "Chad", + "Change": "Changement", + "Changelog": "Modifier le journal", + "Channel": "Canal", + "Chart": "Graphique", + "Chart for %s": "Graphique pour %s", + "Check all": "Vérifie tout", + "Chile": "Chili", + "China": "Chine", + "Christmas Island": "L'île de noël", + "City:": "Ville", + "Clear": "Effacer", + "Clear list": "Effacer la liste", + "Clear log": "Effacer le journal", + "Clear on disk permanent": "Effacer sur le disque permanent", + "Click do activate events again, or just wait one minute": "Cliquez pour activer à nouveau les événements, ou attendez une minute", + "Click on icon": "Cliquez sur l'icône", + "Close": "Fermer", + "Cocos Islands": "Îles Cocos", + "Collapse all nodes": "Réduire tous les nœuds", + "Colombia": "Colombie", + "Color": "Couleur", + "Comoros": "Comores", + "Configuration not saved.": "Configuration non enregistrée", + "Congo": "Congo", + "Connected to %s: ": "Connecté à %s:", + "Connected to host: ": "Connecté à l'hôte:", + "Cook Islands": "Les Îles Cook", + "Copy log": "Copier le journal", + "Copy to clipboard": "Copier dans le presse-papier", + "Costa Rica": "Costa Rica", + "Country:": "Pays", + "Create": "Créer", + "Create new category": "Créer une nouvelle catégorie", + "Create new category, like %s": "Créer une nouvelle catégorie, comme %s", + "Create new enum": "Créer une nouvelle énumération", + "Create new enum, like %s": "Créer une nouvelle énumération, comme %s", + "Create new group": "Créer un nouveau groupe", + "Create new user": "Créer un nouvel utilisateur", + "Created": "Créé", + "Croatia": "Croatie", + "Cron expression": "Expression Cron", + "Cuba": "Cuba", + "Currency:": "Devise", + "Custom": "Au choix", + "Cyprus": "Chypre", + "Czech Republic": "République Tchèque", + "D$ecember": "Décembre", + "DD.MM.YY": "JJ.MM.AA", + "DD.MM.YYYY": "JJ.MM.AAAA", + "DD/MM/YYYY": "JJ/MM/AAAA", + "Date From": "Dater de", + "Date To": "Date à", + "Date format:": "Format des dates", + "Deactivated. Click to start.": "Désactivé. Cliquez pour commencer", + "Debug outputs:": "Sorties de débogage", + "Dec": "déc.", + "December": "Décembre", + "Default ACL": "ACL par défaut", + "Default history instance:": "Instance d'historique par défaut", + "Delete attribute": "Supprimer l'attribut", + "Delete category": "Supprimer la catégorie", + "Delete enum": "Supprimer l'énumération", + "Delete member": "Supprimer le membre", + "Delete object": "Supprimer l'objet", + "Denmark": "Danemark", + "Description": "Description", + "Device": "Dispositif", + "Device discovery": "Découverte du périphérique", + "Disable authentication": "Désactiver l'authentification", + "Disk free": "Taille sans disque", + "Disk free:": "Disque disponible:", + "Disk size": "Taille du disque", + "Djibouti": "Djibouti", + "Do you want to delete just one object or all children of %s too?": "Voulez-vous supprimer seulement un objet ou tous les descendants de %s aussi?", + "Do you want to upgrade all adapters?": "Voulez-vous actualiser tous les adaptateurs?", + "Domains:": "Domaines", + "Dominica": "Dominique", + "Dominican Republic": "République Dominicaine", + "Done with error: %s": "Terminé avec erreur: %s", + "Download log": "Télécharger le journal", + "Drop the files here": "Déposez les fichiers ici", + "Drop the icons here": "Déposez les icônes ici", + "East Timor": "Timor oriental", + "Ecuador": "Equateur", + "Edit": "Modifier", + "Edit category": "Modifier la catégorie", + "Edit enum": "Modifier l'énumération", + "Edit in dialog": "Modifier dans le dialogue", + "Edit object": "Modifier l'objet", + "Egypt": "Egypte", + "El Salvador": "Le Salvador", + "Email for account:": "Email pour compte", + "Enabled:": "Activée", + "Enums": "Énumérations", + "Equatorial Guinea": "Guinée Équatoriale", + "Eritrea": "Érythrée", + "Error": "Erreur", + "Estonia": "Estonie", + "Ethiopia": "Ethiopie", + "Event": "Événement", + "Events": "Événements", + "Everyone": "Toutes les personnes", + "Expand all nodes": "Développer tous les nœuds", + "Failed to open JSON File": "Échec de l'ouverture du fichier JSON", + "Falkland Islands (Malvinas)": "Îles Falkland (Malvinas)", + "Faroe Islands": "Îles Féroé", + "Feb": "févr.", + "February": "février", + "Fiji": "Fidji", + "File is too big!": "Le fichier est trop grand!", + "File rights": "Droits du fichier", + "Filter": "Filtre", + "Filter:": "Filtre", + "Filtered out": "Tout est filtré", + "Find coordinates...": "Trouver les coordonnées ...", + "Finland": "Finlande", + "Float divider:": "Caractère pour point décimal", + "France": "France", + "Free RAM:": "Mémoire libre:", + "French Guiana": "Guinée Française", + "French Polynesia": "Polynésie française", + "French Southern Territories": "Terres australes françaises", + "Fri": "ven", + "From": "De:", + "From github": "Depuis github", + "Function": "Fonction", + "Gabon": "Gabon", + "Gambia": "Gambie", + "Generated ID:": "Identifiant généré", + "Georgia": "Géorgie", + "Germany": "Allemagne", + "Ghana": "Ghana", + "Gibraltar": "Gibraltar", + "Go to Github...": "Aller à Github...", + "Greece": "Grèce", + "Greenland": "Groenland", + "Grenada": "Grenade", + "Group": "Groupe", + "Groups": "Groupes", + "Guadeloupe": "Guadeloupe", + "Guam": "Guam", + "Guatemala": "Guatemala", + "Guernsey": "Guernesey", + "Guinea": "Guinée", + "Guinea-Bissau": "Guinée-Bissau", + "Guyana": "Guyane", + "Haiti": "Haïti", + "Has no permission to %s %s %s": "N'a pas d'autorisation pour %s %s %s", + "Heard and Mc Donald Islands": "Heard et Mc Donald Islands", + "Heartbeat: ": "Signe de vie", + "Honduras": "Honduras", + "Hong Kong": "Hong Kong", + "Host": "Hôte", + "Host %s is offline": "L'hôte %s est déconnecté", + "Host:": "Hôte:", + "Hosts": "Hôtes", + "Hungary": "Hongrie", + "ID": "ID", + "Iceland": "Islande", + "Icon upload": "Téléchargement d'icône", + "Ignore warning": "Ignorer l'avertissement", + "In background": "En arrière-plan", + "India": "Inde", + "Indonesia": "Indonésie", + "Info": "Info", + "Insert": "Insérer", + "Install": "Installer", + "Install adapter from URL": "Installer ou mettre à jour l'adaptateur depuis l'URL", + "Install adapter from github": "Installer ou mettre à jour l'adaptateur depuis github", + "Install from custom URL": "Installer à partir d'une URL personnalisée", + "Install or update from URL...": "Installer ou mettre à jour à partir de l'URL ...", + "Installation counter": "Compteur d'installation", + "Installations counter": "Compteur d'installations", + "Installed": "Installée", + "Installed from group": "Installé à partir du groupe", + "Installed instances": "Instances installées", + "Installed version": "Version installée", + "Instances": "Instances", + "Instructions": "Instructions", + "Intro": "Début", + "Invalid version of %s": "Version non valide de %s", + "Invalid version of %s. Required %s": "Version non valide de %s. Requis %s", + "Iran": "Iran", + "Iraq": "Irak", + "Ireland": "Irlande", + "Is yet in the list": "Est déjà dans la liste", + "Isle of Man": "Île de Man", + "Israel": "Israël", + "Italy": "Italie", + "Ivory Coast": "Côte d'Ivoire", + "Jamaica": "Jamaïque", + "Jan": "janv.", + "January": "janvier", + "Japan": "Japon", + "Jersey": "Jersey", + "Jordan": "Jordan", + "Jul": "juill.", + "July": "juillet", + "Jun": "juin", + "June": "juin", + "Kazakhstan": "Kazakhstan", + "Kenya": "Kenya", + "Kiribati": "Kiribati", + "Known bugs for": "Bugs connus pour", + "Korea": "Corée", + "Kosovo": "Kosovo", + "Kuwait": "Koweit", + "Kyrgyzstan": "Kirghizistan", + "Lao People's Democratic Republic": "République démocratique populaire lao", + "Last changed": "Dernière modification", + "Last update": "Dernière mise à jour", + "Latitude:": "Latitude", + "Latvia": "Lettonie", + "Lebanon": "Liban", + "Lesotho": "Lesotho", + "Let's Encrypt settings": "Paramètres Let's encrypt", + "Let's encrypt SSL": "Let's encrypt SSL", + "Liberia": "Libéria", + "Libyan Arab Jamahiriya": "Jamahiriya arabe libyenne", + "License": "Licence", + "License terms": "Termes de la licence", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Écouter sur toutes les adresses IP", + "Lithuania": "Lituanie", + "Loading...": "Chargement...", + "Log": "Journal", + "Log file will be deleted. Are you sure?": "Le fichier journal sera supprimé. Êtes-vous sûr?", + "Log size:": "Taille du journal", + "Login timeout(sec):": "Délai d'attente de connexion (s):", + "Logout": "Déconnection", + "Longitude:": "Longitude", + "Luxembourg": "Luxembourg", + "MB": "MB", + "Macau": "Macao", + "Macedonia": "Macédoine", + "Madagascar": "Madagascar", + "Mai": "mai", + "Main": "Principale", + "Main settings": "Réglages principaux", + "Malawi": "Malawi", + "Malaysia": "Malaisie", + "Maldives": "Maldives", + "Mali": "Mali", + "Malta": "Malte", + "Manually created": "Créé manuellement", + "Mar": "mars", + "March": "mars", + "Marshall Islands": "Iles Marshall", + "Martinique": "Martinique", + "Mauritania": "Mauritanie", + "Mauritius": "Maurice", + "Mayotte": "Mayotte", + "Members": "Membres", + "Message": "Message", + "Message buffer overflow. Losing oldest": "Dépassement de tampon de message. Perdre plus vieux.", + "Mexico": "Mexique", + "Micronesia": "Micronésie", + "Model": "Modèle", + "Moldova": "Moldavie", + "Mon": "lun", + "Monaco": "Monaco", + "Mongolia": "Mongolie", + "Montenegro": "Monténégro", + "Montserrat": "Montserrat", + "Morocco": "Maroc", + "Mozambique": "Mozambique", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Nom", + "Name:": "Nom", + "Namibia": "Namibie", + "Nauru": "Nauru", + "Nepal": "Népal", + "Netherlands": "Pays-Bas", + "Netherlands Antilles": "Antilles néerlandaises", + "New": "Nouveau", + "New Caledonia": "Nouvelle Calédonie", + "New Zealand": "Nouvelle-Zélande", + "New category": "Nouvelle catégorie", + "New enum": "Nouvelle énumération", + "New group": "Nouveau groupe", + "New object": "Nouvel object", + "New objekt": "Nouvel object", + "New user": "Nouvel utilisateur", + "Nicaragua": "Nicaragua", + "Niger": "Niger", + "Nigeria": "Nigeria", + "Niue": "Nioué", + "No data": "Pas de données", + "No states selected!": "Aucun état sélectionné!", + "No version of %s": "Aucune version de %s", + "Node.js": "Node.js", + "Norfolk Island": "L'ile de Norfolk", + "Northern Mariana Islands": "Îles Mariannes du Nord", + "Norway": "Norvège", + "Not exists": "N'existe pas", + "Note:": "Remarque", + "Nov": "nov.", + "November": "novembre", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "L'objet \"%s\" n'existe pas. Actualisez la page", + "Object may not be deleted": "L'objet ne peut pas être supprimé", + "Object rights": "Droits d'objet", + "Objects": "Objets", + "Oct": "oct.", + "October": "octobre", + "Ok": "D'accord", + "Oman": "Oman", + "Only one": "Seulement un", + "Open original": "Ouvrir sur un nouvel onglet", + "Owner": "Propriétaire", + "Owner group": "Groupe de propriétaires", + "Owner user": "Utilisateur propriétaire", + "Pakistan": "Pakistan", + "Palau": "Palau", + "Palestine": "Palestine", + "Panama": "Panama", + "Papua New Guinea": "Papouasie Nouvelle Guinée", + "Paraguay": "Paraguay", + "Parent": "Parent", + "Parse error": "Erreur d'analyse", + "Password": "Mot de passe", + "Password and confirmation are not equal!": "Le mot de passe et la confirmation ne sont pas égaux!", + "Password cannot be empty!": "Le mot de passe ne peut pas être vide!", + "Password repeat": "Mot de passe", + "Path to storage:": "Chemin d'accès pour le stockage", + "Pause output": "Mettre en pause la sortie", + "Peru": "Pérou", + "Philippines": "Philippines", + "Pitcairn": "Pitcairn", + "Platform": "Plate-forme", + "Please confirm": "Veuillez confirmer", + "Poland": "Pologne", + "Popular": "Populaire", + "Popular first": "Populaire d'abord", + "Port to check the domain:": "Port pour vérifier le domaine", + "Portugal": "Portugal", + "Preserve ID": "Conserver l'ID", + "Preview": "Aperçu", + "Processing...": "En traitement...", + "Puerto Rico": "Porto Rico", + "Qatar": "Qatar", + "RAM": "RAM", + "RAM total usage:": "Utilisation totale de RAM:", + "RAM usage": "Utilisation de la RAM", + "Rebuild tree": "Reconstruire l'arbre", + "Recently updated": "Récemment mis à jour", + "Refresh log": "Actualiser le journal", + "Removed": "Supprimé", + "Removing of adapter...": "Retrait de l'adaptateur...", + "Removing of instance...": "Suppression de l'instance...", + "Rename": "Renommer", + "Repositories": "Dépôts", + "Reunion": "Réunion", + "Rights": "Droits d'accès", + "Role": "Rôle", + "Romania": "Roumanie", + "Room": "Chambre", + "Running: ": "Fonctionnement:", + "Russian Federation": "Fédération Russe", + "Rwanda": "Rwanda", + "Saint Kitts and Nevis": "Saint-Christophe-et-Niévès", + "Saint Lucia": "Sainte-Lucie", + "Saint Vincent and the Grenadines": "Saint-Vincent-et-les-Grenadines", + "Samoa": "Samoa", + "San Marino": "Saint Marin", + "Sao Tome and Principe": "Sao Tomé et Principe", + "Sat": "sam", + "Saudi Arabia": "Arabie Saoudite", + "Save": "sauvegarder", + "Save Objecttree as JSON File": "Enregistrer l'arborescence d'objets en tant que fichier JSON", + "Save Objecttree is not possible": "Enregistrer l'arborescence d'objets n'est pas possible", + "Save configuration": "Enregistrer la configuration", + "Script": "Scénario", + "Scripts": "Scripts", + "Select": "Sélectionner", + "Select ID": "Sélectionner un identifiant", + "Select adapter:": "Sélectionnez l'adaptateur", + "Select language": "Choisir la langue", + "Select options": "Sélectionnez les options", + "Senegal": "Sénégal", + "Sent data:": "Données envoyées", + "Sep": "sept.", + "September": "septembre", + "Serbia": "Serbie", + "Set": "Accepter", + "Set CRON": "Accepter l'horaire", + "Set CRON schedule for restarts": "Définir la planification CRON pour les redémarrages", + "Settings": "Paramètres", + "Settings for %s": "Paramètres pour %s", + "Seychelles": "les Seychelles", + "Show instances only for current host": "Afficher les instances uniquement pour l'hôte actuel", + "Show values of instance": "Afficher les valeurs de l'instance", + "Show...": "Montrer...", + "Sierra Leone": "Sierra Leone", + "Singapore": "Singapour", + "Size: %s, Free: %s": "Taille: %s, Disponible: %s", + "Slovakia": "Slovaquie", + "Slovenia": "Slovénie", + "Solomon Islands": "Les îles Salomon", + "Somalia": "Somalie", + "Some data are not stored. Discard?": "Certaines données ne sont pas stockées. Abandonner?", + "Sort alphabetically": "Trier par ordre alphabétique", + "South Africa": "Afrique du Sud", + "South Georgia South Sandwich Islands": "Géorgie du Sud Îles Sandwich du Sud", + "Spain": "Espagne", + "Speed": "La vitesse", + "Sri Lanka": "Sri Lanka", + "St. Helena": "Sainte-Hélène", + "St. Pierre and Miquelon": "Saint-Pierre et Miquelon", + "Started...": "Commencé...", + "State": "Ètat", + "State type": "État type", + "States": "États", + "States rights": "Droits des états", + "Statistics": "Statistiques", + "Statistics:": "Statistiques", + "Storage of %s": "Stockage de %s", + "Storage of %s states": "Stockage des états %s", + "Success!": "Succès!", + "Sudan": "Soudan", + "Suggestion": "Recommandation", + "Sun": "dim", + "Suriname": "Suriname", + "Svalbard and Jan Mayen Islands": "Îles Svalbard et Jan Mayen", + "Swaziland": "Swaziland", + "Sweden": "Suède", + "Switzerland": "Suisse", + "Syrian Arab Republic": "République arabe syrienne", + "System": "Système", + "System language:": "Langue du système", + "System settings": "Paramètres du système", + "System uptime": "System uptime", + "Table": "Table", + "Taiwan": "Taïwan", + "Tajikistan": "Tadjikistan", + "Tanzania": "Tanzanie", + "Temperature units:": "Unités de température", + "Thailand": "Thaïlande", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 supporte les versions LTS 6, 8 et 10 de node.js. Veuillez mettre à jour votre version (\"%s\") sur l'hôte \"%s\" avec une des versions prises en charge. Nous vous recommandons d'utiliser node.js 6.", + "Thu": "jeu", + "Time": "Temps", + "Time From": "Temps de", + "Time To": "Temps de", + "Time stamp": "Horodatage", + "Title": "Titre", + "To": "À", + "Today": "aujourd'hui", + "Toggle expert mode": "Basculer en mode expert", + "Toggle states view": "Basculer la vue des états", + "Togo": "Aller", + "Tokelau": "Tokelau", + "Tonga": "Tonga", + "Too many events": "Trop d'événements", + "Total count in group": "Nombre total dans le groupe", + "Trigger event": "Événement déclencheur", + "Trinidad and Tobago": "Trinité-et-Tobago", + "Tue": "mar", + "Tunisia": "Tunisie", + "Turkey": "Turquie", + "Turkmenistan": "Turkménistan", + "Turks and Caicos Islands": "îles Turques-et-Caïques", + "Tuvalu": "Tuvalu", + "Type": "Type", + "URL or file path:": "URL ou chemin du fichier", + "Uganda": "Ouganda", + "Ukraine": "Ukraine", + "Uncheck All": "Tout décocher", + "United Arab Emirates": "Emirats Arabes Unis", + "United Kingdom": "Royaume-Uni", + "United States": "États Unis", + "United States minor outlying islands": "Îles mineures éloignées des États-Unis", + "Unknown file format!": "Format de fichier inconnu", + "Unsecure_Auth": "Le mot de passe sera envoyé via une connexion non sécurisée. Pour protéger vos mots de passe, activez la connexion sécurisée (HTTPS)!", + "Unsupported image format": "Format d'image non pris en charge", + "Update": "Mettre à jour", + "Update objects": "Actualiser les objets", + "Update states": "Actualiser les états", + "Updated": "Actualisé", + "Upgrade all adapters": "Actualiser tous les adaptateurs", + "Upload": "Téléchargement de fichiers", + "Upload admin started": "Chargement de la configuration commencé", + "Upload started...": "Chargement commencé ...", + "Uptime": "Uptime", + "Uruguay": "Uruguay", + "Use Lets Encrypt certificates:": "Utiliser les certificats Let's Encrypt", + "Use this instance for automatic update:": "Utiliser cette instance pour la mise à jour automatique", + "User": "Utilisateur", + "User deleted": "Utilisateur supprimé", + "User does not exist": "L'utilisateur n'existe pas", + "User yet exists": "L'utilisateur existe encore", + "Users": "Utilisateurs", + "Uzbekistan": "Ouzbékistan", + "Value": "Valeur", + "Values of %s": "Valeurs pour %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "État de la Cité du Vatican", + "Venezuela": "Venezuela", + "Vietnam": "Vietnam", + "Virgin Islands (British)": "Îles Vierges britanniques", + "Virgin Islands (U.S.)": "Îles Vierges (États-Unis)", + "Wallis and Futuna Islands": "Wallis et Futuna", + "Warning!": "Attention!", + "Wed": "mer", + "Western Sahara": "Sahara occidental", + "With": "avec", + "Without": "sans", + "Yemen": "Yémen", + "You are going to add new instance: ": "Vous allez ajouter une nouvelle instance:", + "You can check changelog here": "Vous pouvez vérifier le changelog ici", + "You can drag&drop the devices, channels and states to enums": "Vous pouvez faire glisser et déposer les périphériques, les canaux et les états vers les énumérations", + "You can drag&drop users to groups": "Vous pouvez faire glisser et déposer les utilisateurs vers les groupes", + "You can't see events via cloud": "Vous ne pouvez pas voir les événements via le cloud", + "Your home": "Le chez-soi", + "Zaire": "Zaïre", + "Zambia": "Zambie", + "Zimbabwe": "Zimbabwe", + "_All": "Tout", + "_Toggle expert mode": "Choisir le mode expert", + "__different__": "différent", + "a-z": "a-z", + "ack": "confirmé", + "actions": "actes", + "active": "actif", + "adapter with updates": "adaptateur avec mises à jour", + "adapters count": "nombre d'adaptateurs", + "add": "Ajouter", + "add children": "ajouter des descendants", + "add instance": "ajouter une instance", + "add repository": "ajouter un dépôt", + "agree": "se mettre d'accord", + "alarm_group": "Alarme", + "alive": "vivant", + "all": "tout", + "alpha": "alpha", + "array": "tableau", + "auto": "auto", + "available": "disponible", + "beta": "bêta", + "boolean": "booléen", + "bug": "bugtracker", + "cancel": "Annuler", + "cert_path_note": "Vous pouvez utiliser le chemin absolu du certificat, comme '/opt/certs/cert.pem', ou simplement le télécharger par glisser-déposer", + "certificate": "certificat", + "change view mode": "changer le mode d'affichage", + "channel": "canal", + "clear": "annuler", + "climate-control_group": "Gestion du climat", + "close on ready": "Fermer lorsque terminé", + "collapse": "réduire", + "collapse all": "tout réduire", + "comma": "virgule", + "command execution": "Exécution de commande", + "common": "commun", + "common adapters_group": "Général", + "common_color": "Couleur", + "common_def": "valeur par défaut", + "common_desc": "la description", + "common_icon": "icône", + "common_max": "Valeur maximale", + "common_min": "Valeur minimale", + "common_read": "lecture autorisé", + "common_role": "rôle", + "common_states": "valeurs prédéfinies", + "common_type": "type", + "common_unit": "unité de mesure", + "common_write": "écriture autorisé", + "communication_group": "Communication", + "config": "config", + "config instance": "instance de configuration", + "confirm password": "Confirmez le mot de passe", + "connected": "connecté", + "copy": "copie", + "copy note": "Appuyez sur Ctrl + A puis sur Ctrl + C pour copier le contenu dans le presse-papiers. Cliquez quelque part pour fermer la fenêtre.", + "create operation": "créer", + "custom enum": "Enum personnalisé", + "custom group": "Groupe personnalisé", + "daemon": "service", + "date-and-time_group": "Date et heure", + "daysShortText": "j.", + "debug": "déboguer", + "delete": "effacer", + "delete adapter": "supprimer l'adaptateur", + "delete group": "supprimer le groupe", + "delete instance": "supprimer l'instance", + "delete operation": "supprimer l'opération", + "delete script": "supprimer le script", + "delete user": "Supprimer l'utilisateur", + "desc": "description", + "description": "description", + "device": "dispositif", + "diag-note": "Nous avons travaillé dur pour créer ce projet. A charge de revanche, nous attendons de vous des statistiques sur l'utilisation.
Aucune information privée ne sera envoyée à yunkong2.net. Chaque fois que vous appuyez sur Mettre à jour la liste des adaptateurs, les statistiques anonymisées seront envoyées.
Merci!", + "edit": "modifier", + "edit enum": "éditer enum", + "edit enums": "Modifier les énumérations pour", + "edit file": "modifier le fichier", + "edit group": "modifier le groupe", + "edit instance": "instance de modification", + "edit script": "modifier le script", + "edit user": "Modifier l'utilisateur", + "edit value": "Modifier la valeur", + "enabled": "activée", + "energy_group": "Énergie", + "engine": "executer avec", + "engine type": "type d'execution", + "error": "Erreur", + "events": "événements", + "execute operation": "exécuter l'opération", + "expand": "développer", + "expand all": "développer tout", + "extended": "élargi", + "false": "faux", + "file permissions": "Autorisations de fichier", + "from": "de", + "garden_group": "Jardin", + "general_group": "Général", + "geoposition_group": "Position géographique", + "groups": "groupes", + "hardware_group": "Matériel", + "history": "histoire", + "history data": "données historiques", + "host": "hôte", + "household_group": "Ménage", + "http operation": "http", + "id": "ID", + "info": "Info", + "infrastructure_group": "Infrastructure", + "install": "installer", + "install specific version": "installer une version spécifique", + "installed": "installée", + "installed adapters": "adaptateurs installés", + "instance": "exemple", + "instance number": "Numéro d'instance souhaité", + "yunkong2 Enums": "yunkong2 Enums", + "yunkong2 States": "yunkong2 States", + "yunkong2 adapter instances": "Instances d'adaptateur yunkong2", + "yunkong2 adapter scripts": "scripts de l'adaptateur yunkong2", + "yunkong2 adapters": "Adaptateurs yunkong2", + "yunkong2 certificates": "Certificats yunkong2", + "yunkong2 groups": "Groupes yunkong2", + "yunkong2 hosts": "Hôtes yunkong2", + "yunkong2 repositories": "Dépôts yunkong2", + "yunkong2 users": "Utilisateurs de yunkong2", + "iot-system_group": "Les systèmes IoT", + "iot-systems_group": "Les systèmes IoT", + "keywords": "mots clés", + "lc": "mod.", + "less": "moins", + "letsnecrypt_help": "Paramètres du compte Let's Encrypt. Pour obtenir un certificat gratuit pour votre domaine, suivez les instructions ici.", + "letsnecrypt_help_domains": "par exemple: 'example.com, www.example.com'", + "letsnecrypt_help_email": "Veuillez toujours utiliser une seule adresse e-mail.", + "letsnecrypt_help_path": "Répertoire où les certificats sont stockés. Le chemin est relatif au répertoire de configuration", + "license": "Licence", + "license agreement": "Contrat de licence", + "license not agree": "En désaccord avec la licence!", + "license_checkbox": "Je suis d'accord avec la collecte de statistiques anonymes.
(peut être désactivé dans les paramètres)", + "lighting_group": "Éclairage", + "link": "lien", + "list": "liste", + "list operation": "afficher liste", + "logic_group": "Logique", + "loglevel": "loglevel", + "media_group": "Media", + "members": "membres", + "memlimit": "RAM limit", + "message": "message", + "messaging_group": "Messagerie", + "misc-data_group": "Autre", + "mixed": "mixte", + "mode": "mode", + "more": "plus", + "multi": "liste de valeurs", + "multimedia_group": "Multimédia", + "name": "prénom", + "native": "originaire de", + "network_group": "Réseau", + "new certificate": "nouveau certificat", + "new group": "nouveau groupe", + "new script": "nouveau script", + "new user": "nouvel utilisateur", + "newObject": "Nouvel objet", + "no-city": "sans ville", + "node-red": "node-red", + "none": "aucun", + "normal": "Ordinaire", + "not ack": "pas confirmé", + "not agree": "pas d'accord", + "npm error": "erreur npm", + "number": "nombre", + "object": "objet", + "object permissions": "autorisations d'objet", + "of": "de", + "ok": "D'accord", + "open web page": "Accéder à la page Web de l'adaptateur", + "os": "système d'exploitation", + "other permissions": "autres autorisations", + "parent name": "nom du parent", + "password": "mot de passe", + "permissionError": "Erreur d'autorisation", + "place here": "Placez les fichiers ici", + "planned": "prévu", + "platform": "Plate-forme", + "point": "point", + "popular": "populaire", + "process": "processus", + "protocols_group": "Protocoles", + "raw": "Raw (experts seulement)", + "read": "lire", + "read operation": "lecture", + "readme": "readme", + "reload": "recharger", + "reload instance": "reload instance", + "rest": "autres (lecture seulement)", + "restart": "redémarrer", + "restart script": "redémarrer le script", + "role": "rôle", + "save": "enregistrer", + "schedule_group": "Planification", + "script_group": "Scripts et logique", + "select member by double click": "sélectionner un membre en double-cliquant", + "sendto operation": "opération sendto", + "service_group": "Entretien", + "severity": "gravité", + "silly": "silly", + "stable": "stable", + "state": "Etat", + "state permissions": "autorisations d'état", + "storage_group": "Espace de rangement", + "string": "chaîne", + "subscribe": "souscrire", + "switch": "commutateur", + "terminal": "Terminal", + "third-party_group": "Autre", + "this adapter does not allow multiple instances": "Cet adaptateur ne permet pas plusieurs instances", + "title": "titre", + "today": "aujourd'hui", + "true": "vrai", + "ts": "ts", + "type": "type", + "unit": "unité", + "update": "mettre à jour", + "update adapter information": "mettre à jour les informations d'adaptateur", + "update-part1": "En raison des plates-formes très différentes, où yunkong2 peut fonctionner, uniquement la mise à jour manuelle est possible. Pour exécuter la mise à jour manuelle, veuillez exécuter les instructions suivantes via la console de l'hôte:", + "updated": "actualisé", + "updates": "mises à jour", + "upload": "télécharger", + "user permissions": "autorisations des utilisateurs", + "users": "utilisateurs", + "users permissions": "autorisations d'utilisateur", + "utility_group": "Utilitaire", + "val": "val", + "value": "valeur", + "value.from": "Changé de", + "value.lc": "Dernier changement", + "value.q": "Code de qualité", + "value.ts": "Horodatage", + "value.val": "valeur", + "version": "version", + "vis_group": "yunkong2.vis", + "visualisation_group": "Visualisation", + "visualization-icons_group": "Icônes de visualisation", + "visualization-widgets_group": "Widgets de visualisation", + "visualization_group": "Visualisation", + "warn": "prévenir", + "weather_group": "Météo", + "wetty": "Wetty", + "write": "écriture", + "write operation": "écrire", + "yesterday": "hier" +} diff --git a/src/i18n/it/translations.json b/src/i18n/it/translations.json new file mode 100644 index 0000000..6b9eecf --- /dev/null +++ b/src/i18n/it/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " per %s", + "%s added to %s": "%s aggiunto a %s", + "%s object(s) processed": "%s oggetti sono stati elaborati", + "%s processes": "%s processi", + "%s was imported": "L'%s è stato importato", + "(without prefix)": "(senza prefisso)", + "1 %d days ago": "%d giorni fa", + "2 %d days ago": "%d giorni fa", + "5 %d days ago": "%d giorni fa", + "A-Z": "A-Z", + "Access control": "Controllo di accesso", + "Access control list": "Lista di controllo di accesso", + "Acknowledged": "Riconosciuto", + "Activated. Click to stop.": "Attivato. Clicca per interrompere.", + "Active instances": "Istanze attive", + "Active repository:": "Repository attivo:", + "Adapter configuration": "Configurazione dell'adattatore", + "Adapter settings for %s states": "Impostazioni dell'adattatore per stati %s", + "Adapters": "Adattatori", + "Adapters from this Group installed": "Gli adattatori di questo gruppo sono stati installati", + "Add": "Inserisci", + "Add Objecttree from JSON File": "Aggiungi albero degli oggetti dal file JSON", + "Add certificate from file": "Aggiungi certificato dal file", + "Add instance...": "Aggiungere istanza ...", + "Add member": "Aggiungi membro", + "Add new child object to selected parent": "Aggiungi un nuovo oggetto figlio al genitore selezionato", + "Add new field": "Aggiungi un nuovo campo", + "Add new issue": "Segnala un bug", + "Add new object: ": "Aggiungi un nuovo oggetto:", + "Add new object: %s": "Aggiungi nuovo oggetto: %s", + "Address:": "Indirizzo:", + "Admin is not enabled in cloud settings!": "L'Admin non è abilitato nelle impostazioni cloud!", + "Administrator": "Amministratore", + "Afghanistan": "afghanistan", + "Albania": "Albania", + "Algeria": "algeria", + "All": "Tutti", + "American Samoa": "Samoa americane", + "Andorra": "Andorra", + "Angola": "angola", + "Anguilla": "Anguilla", + "Antarctica": "Antartide", + "Antigua and Barbuda": "Antigua e Barbuda", + "Apr": "aprile", + "April": "aprile", + "Architecture": "Architettura", + "Are you sure to delete %s?": "Eliminare \"%s\" ?", + "Are you sure to delete all children of %s?": "Eliminare tutti figli di %s ?", + "Are you sure to delete all children of %s?": "Eliminare \"%s\" e tutti figli?", + "Are you sure to delete script %s?": "Sei sicuro di voler eliminare lo script '%s'?", + "Are you sure you want to delete adapter %s?": "Sei sicuro di voler eliminare l'adattatore %s?", + "Are you sure you want to delete the instance %s?": "Sei sicuro di voler eliminare l'istanza %s ?", + "Are you sure?": "Sei sicuro?", + "Are you sure? Changes are not saved.": "Sei sicuro? Le modifiche non vengono salvate.", + "Argentina": "Argentina", + "Armenia": "Armenia", + "Aruba": "aruba", + "Aug": "agosto", + "August": "agosto", + "Australia": "Australia", + "Austria": "Austria", + "Authentication was deactivated": "L'autenticazione è stata disattivata", + "Available": "A disposizione", + "Available version:": "Versione disponibile:", + "Azerbaijan": "Azerbaijan", + "Background": "Sfondo", + "Background color of the login screen": "Colore di sfondo della schermata di accesso", + "Background image": "Immagine di sfondo", + "Bahamas": "Bahamas", + "Bahrain": "Bahrain", + "Bangladesh": "Bangladesh", + "Barbados": "Barbados", + "Belarus": "Bielorussia", + "Belgium": "Belgio", + "Belize": "Belize", + "Benin": "Benin", + "Bermuda": "Bermuda", + "Bhutan": "Bhutan", + "Bolivia": "Bolivia", + "Bosnia and Herzegovina": "Bosnia Erzegovina", + "Botswana": "Botswana", + "Bouvet Island": "Isola Bouvet", + "Brazil": "Brasile", + "British Indian Ocean Territory": "Territorio britannico dell'Oceano Indiano", + "Brunei Darussalam": "Brunei Darussalam", + "Bulgaria": "Bulgaria", + "Burkina Faso": "Burkina Faso", + "Burundi": "Burundi", + "CPUs": "CPUs", + "Calendar": "Calendario", + "Cambodia": "Cambogia", + "Cameroon": "Camerun", + "Canada": "Canada", + "Cancel": "Annulla", + "Cannot create user: ": "Impossibile creare un utente:", + "Cannot delete user: ": "Impossibile eliminare l'utente:", + "Cannot disable admin!": "Impossibile disabilitare l'amministratore!", + "Cannot read file!": "Impossibile leggere il file!", + "Cannot read version from NPM": "Impossibile leggere la versione da NPM", + "Cannot set password: ": "Impossibile impostare la password:", + "Cape Verde": "capo Verde", + "Cayman Islands": "Isole Cayman", + "Central African Republic": "Repubblica Centrafricana", + "Certificates": "certificati", + "Chad": "Chad", + "Change": "Modificare", + "Changelog": "Registro delle modifiche", + "Channel": "Canale", + "Chart": "Grafico", + "Chart for %s": "Grafico per %s", + "Check all": "Seleziona tutto", + "Chile": "Chile", + "China": "Cina", + "Christmas Island": "Isola di Natale", + "City:": "Città:", + "Clear": "Chiaro", + "Clear list": "Elenco chiaro", + "Clear log": "Pulisci il registro", + "Clear on disk permanent": "Cancella su disco permanente", + "Click do activate events again, or just wait one minute": "Fai clic su attiva nuovamente gli eventi o aspetta solo un minuto", + "Click on icon": "Clicca sull'icona per aprire un link", + "Close": "vicino", + "Cocos Islands": "Isole Cocos", + "Collapse all nodes": "Comprimi tutti i nodi", + "Colombia": "Colombia", + "Color": "Colore", + "Comoros": "Comoros", + "Configuration not saved.": "Configurazione non salvata.", + "Congo": "Congo", + "Connected to %s: ": "Connesso a %s:", + "Connected to host: ": "Connesso all'host:", + "Cook Islands": "Isole Cook", + "Copy log": "Copia registro", + "Copy to clipboard": "Copia negli appunti", + "Costa Rica": "Costa Rica", + "Country:": "Nazione:", + "Create": "Creare", + "Create new category": "Crea una nuova categoria", + "Create new category, like %s": "Crea una nuova categoria, come %s", + "Create new enum": "Crea nuovo enum", + "Create new enum, like %s": "Crea nuovo enum, come %s", + "Create new group": "Crea nuovo gruppo", + "Create new user": "Crea un nuovo utente", + "Created": "Creato", + "Croatia": "Croazia", + "Cron expression": "Espressione Cron", + "Cuba": "Cuba", + "Currency:": "Moneta:", + "Custom": "costume", + "Cyprus": "Cipro", + "Czech Republic": "Repubblica Ceca", + "D$ecember": "dicembre", + "DD.MM.YY": "GG.MM.AA", + "DD.MM.YYYY": "GG.MM.AAAA", + "DD/MM/YYYY": "GG / MM / AAAA", + "Date From": "Data da", + "Date To": "Data a", + "Date format:": "Formato data:", + "Deactivated. Click to start.": "Disattivato. Clicca per iniziare.", + "Debug outputs:": "Uscite di debug", + "Dec": "Dic", + "December": "Dicembre", + "Default ACL": "ACL predefinito", + "Default history instance:": "Istanza cronologia predefinita:", + "Delete attribute": "Elimina attributo", + "Delete category": "Elimina categoria", + "Delete enum": "Elimina enum", + "Delete member": "Elimina membro", + "Delete object": "Elimina oggetto", + "Denmark": "Danimarca", + "Description": "Descrizione", + "Device": "Dispositivo", + "Device discovery": "Scoperta del dispositivo", + "Disable authentication": "Disabilitare l'autenticazione", + "Disk free": "Dimensione libera del disco", + "Disk free:": "Disco disponibile:", + "Disk size": "Dimensione del disco", + "Djibouti": "Gibuti", + "Do you want to delete just one object or all children of %s too?": "Vuoi eliminare solo un oggetto o tutti figli di %s anche tu?", + "Do you want to upgrade all adapters?": "Vuoi aggiornare tutti gli adattatori?", + "Domains:": "domini:", + "Dominica": "Dominica", + "Dominican Republic": "Repubblica Dominicana", + "Done with error: %s": "Fatto con errore: %s", + "Download log": "Scarica il registro", + "Drop the files here": "Rilascia i file qui", + "Drop the icons here": "Lascia le icone qui", + "East Timor": "Timor Est", + "Ecuador": "Ecuador", + "Edit": "modificare", + "Edit category": "Modifica categoria", + "Edit enum": "Modifica enum", + "Edit in dialog": "Modifica nella finestra di dialogo", + "Edit object": "Modifica oggetto", + "Egypt": "Egitto", + "El Salvador": "El Salvador", + "Email for account:": "Email per account:", + "Enabled:": "Abilitato", + "Enums": "Enums", + "Equatorial Guinea": "Guinea Equatoriale", + "Eritrea": "l'Eritrea", + "Error": "Errore", + "Estonia": "Estonia", + "Ethiopia": "Etiopia", + "Event": "genere", + "Events": "eventi", + "Everyone": "Tutti", + "Expand all nodes": "Espandi tutti i nodi", + "Failed to open JSON File": "Impossibile aprire il file JSON", + "Falkland Islands (Malvinas)": "Isole Falkland (Malvinas)", + "Faroe Islands": "Isole Faroe", + "Feb": "febbraio", + "February": "febbraio", + "Fiji": "Fiji", + "File is too big!": "Il file è troppo grande!", + "File rights": "Diritti di file", + "Filter": "Filtro", + "Filter:": "Filtro", + "Filtered out": "Tutto è filtrato", + "Find coordinates...": "Trova coordinate ...", + "Finland": "Finlandia", + "Float divider:": "Separatore galleggiante:", + "France": "Francia", + "Free RAM:": "Gratuito:", + "French Guiana": "Guiana francese", + "French Polynesia": "Polinesia francese", + "French Southern Territories": "Territori della Francia del sud", + "Fri": "Fri", + "From": "A partire dal:", + "From github": "Da github", + "Function": "Funzione", + "Gabon": "Gabon", + "Gambia": "Gambia", + "Generated ID:": "Generato & nbsp; ID:", + "Georgia": "Georgia", + "Germany": "Germania", + "Ghana": "Ghana", + "Gibraltar": "Gibilterra", + "Go to Github...": "Vai a Github...", + "Greece": "Grecia", + "Greenland": "Groenlandia", + "Grenada": "Grenada", + "Group": "Gruppo", + "Groups": "gruppi", + "Guadeloupe": "Guadeloupe", + "Guam": "Guam", + "Guatemala": "Guatemala", + "Guernsey": "maglione", + "Guinea": "Guinea", + "Guinea-Bissau": "Guinea-Bissau", + "Guyana": "Guyana", + "Haiti": "Haiti", + "Has no permission to %s %s %s": "Non ha il permesso di %s %s %s", + "Heard and Mc Donald Islands": "Heard e Mc Donald Islands", + "Heartbeat: ": "Battito cardiaco:", + "Honduras": "Honduras", + "Hong Kong": "Hong Kong", + "Host": "Ospite", + "Host %s is offline": "L'host %s non è in linea", + "Host:": "Ospite:", + "Hosts": "host", + "Hungary": "Ungheria", + "ID": "ID", + "Iceland": "Islanda", + "Icon upload": "Caricamento dell'icona", + "Ignore warning": "Ignora l'avviso", + "In background": "Sullo sfondo", + "India": "India", + "Indonesia": "Indonesia", + "Info": "Informazioni", + "Insert": "Inserire", + "Install": "Installare", + "Install adapter from URL": "Installa o aggiorna l'adattatore dall'URL", + "Install adapter from github": "Installa o aggiorna l'adattatore da github", + "Install from custom URL": "Installa dall'URL personalizzato", + "Install or update from URL...": "Installa o aggiorna dall'URL ...", + "Installation counter": "Contatore di installazione", + "Installations counter": "Contatore installazioni", + "Installed": "Installato", + "Installed from group": "Installato dal gruppo", + "Installed instances": "Istanze installate", + "Installed version": "Versione installata", + "Instances": "istanze", + "Instructions": "Istruzioni", + "Intro": "Inizio", + "Invalid version of %s": "Versione non valida di %s", + "Invalid version of %s. Required %s": "Versione non valida di %s. Richiesto %s", + "Iran": "Ho corso", + "Iraq": "Iraq", + "Ireland": "Irlanda", + "Is yet in the list": "È già nella lista", + "Isle of Man": "Isola di Man", + "Israel": "Israele", + "Italy": "Italia", + "Ivory Coast": "Costa d'Avorio", + "Jamaica": "Giamaica", + "Jan": "gennaio", + "January": "gennaio", + "Japan": "Giappone", + "Jersey": "maglia", + "Jordan": "Giordania", + "Jul": "luglio", + "July": "luglio", + "Jun": "giugno", + "June": "giugno", + "Kazakhstan": "Kazakistan", + "Kenya": "Kenia", + "Kiribati": "Kiribati", + "Known bugs for": "Bug noti per", + "Korea": "Corea", + "Kosovo": "Kosovo", + "Kuwait": "Kuwait", + "Kyrgyzstan": "Kyrgyzstan", + "Lao People's Democratic Republic": "Repubblica democratica popolare del Laos", + "Last changed": "Ultima modifica", + "Last update": "Ultimo aggiornamento", + "Latitude:": "Latitudine:", + "Latvia": "Lettonia", + "Lebanon": "Libano", + "Lesotho": "Lesoto", + "Let's Encrypt settings": "Let's Encrypt settings", + "Let's encrypt SSL": "Let's encrypt SSL", + "Liberia": "Liberia", + "Libyan Arab Jamahiriya": "Jamahiriya arabo libico", + "License": "Licenza", + "License terms": "Termini di licenza", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Ascolta su tutti gli IP", + "Lithuania": "Lituania", + "Loading...": "Caricamento in corso...", + "Log": "Login", + "Log file will be deleted. Are you sure?": "Il file di registro verrà eliminato. Sei sicuro?", + "Log size:": "Dimensione del registro:", + "Login timeout(sec):": "Timeout di accesso (sec):", + "Logout": "Disconnettersi", + "Longitude:": "Longitudine:", + "Luxembourg": "Lussemburgo", + "MB": "mb", + "Macau": "Macau", + "Macedonia": "Macedonia", + "Madagascar": "Madagascar", + "Mai": "maggio", + "Main": "Principale", + "Main settings": "Impostazioni principali", + "Malawi": "Malawi", + "Malaysia": "Malaysia", + "Maldives": "Maldive", + "Mali": "Mali", + "Malta": "Malta", + "Manually created": "Creato manualmente", + "Mar": "marzo", + "March": "marzo", + "Marshall Islands": "Isole Marshall", + "Martinique": "Martinique", + "Mauritania": "Mauritania", + "Mauritius": "Mauritius", + "Mayotte": "Mayotte", + "Members": "Utenti", + "Message": "Messaggio", + "Message buffer overflow. Losing oldest": "Overflow del buffer dei messaggi. Perdere più vecchio.", + "Mexico": "Messico", + "Micronesia": "Micronesia", + "Model": "Modello", + "Moldova": "Moldova", + "Mon": "Mon", + "Monaco": "Monaco", + "Mongolia": "Mongolia", + "Montenegro": "Montenegro", + "Montserrat": "Montserrat", + "Morocco": "Marocco", + "Mozambique": "Mozambico", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Nome", + "Name:": "Nome:", + "Namibia": "Namibia", + "Nauru": "Nauru", + "Nepal": "Nepal", + "Netherlands": "Olanda", + "Netherlands Antilles": "Antille Olandesi", + "New": "Nuovo", + "New Caledonia": "Nuova Caledonia", + "New Zealand": "Nuova Zelanda", + "New category": "Nuova categoria", + "New enum": "Nuovo enum", + "New group": "Nuovo gruppo", + "New object": "Nuovo oggetto", + "New objekt": "Nuovo oggetto", + "New user": "Nuovo utente", + "Nicaragua": "Nicaragua", + "Niger": "Niger", + "Nigeria": "Nigeria", + "Niue": "Niue", + "No data": "Nessun dato", + "No states selected!": "Nessun stato selezionato!", + "No version of %s": "Nessuna versione di %s", + "Node.js": "Node.js", + "Norfolk Island": "Isola Norfolk", + "Northern Mariana Islands": "Isole Marianne settentrionali", + "Norway": "Norvegia", + "Not exists": "Non esiste", + "Note:": "Nota:", + "Nov": "novembre", + "November": "novembre", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "L'oggetto \"%s\" non esiste. Aggiorna la pagina.", + "Object may not be deleted": "L'oggetto non può essere cancellato", + "Object rights": "Diritti dell'oggetto", + "Objects": "Oggetti", + "Oct": "ottobre", + "October": "ottobre", + "Ok": "Ok", + "Oman": "Oman", + "Only one": "Solo uno", + "Open original": "Apri in una nuova scheda", + "Owner": "Proprietario", + "Owner group": "Gruppo di proprietari", + "Owner user": "Utente proprietario", + "Pakistan": "Pakistan", + "Palau": "Palau", + "Palestine": "Palestina", + "Panama": "Panama", + "Papua New Guinea": "Papua Nuova Guinea", + "Paraguay": "Paraguay", + "Parent": "Genitore", + "Parse error": "Errore", + "Password": "Parola d'ordine", + "Password and confirmation are not equal!": "Password e conferma non sono uguali!", + "Password cannot be empty!": "La password non può essere vuota!", + "Password repeat": "Ripetizione della password", + "Path to storage:": "Percorso di archiviazione:", + "Pause output": "Metti in pausa l'output", + "Peru": "Perù", + "Philippines": "Filippine", + "Pitcairn": "Pitcairn", + "Platform": "piattaforma", + "Please confirm": "Per favore conferma", + "Poland": "Polonia", + "Popular": "Popolare", + "Popular first": "Popolare prima", + "Port to check the domain:": "Porta per controllare il dominio:", + "Portugal": "Portogallo", + "Preserve ID": "Preservare l'ID", + "Preview": "Anteprima", + "Processing...": "In lavorazione...", + "Puerto Rico": "Porto Rico", + "Qatar": "Qatar", + "RAM": "RAM", + "RAM total usage:": "Utilizzo totale della RAM:", + "RAM usage": "Utilizzo della RAM", + "Rebuild tree": "Ricostruisci l'albero", + "Recently updated": "Aggiornato di recente", + "Refresh log": "Aggiorna registro", + "Removed": "Rimosso", + "Removing of adapter...": "Rimozione dell'adattatore...", + "Removing of instance...": "Rimozione dell'istanza...", + "Rename": "Rinominare", + "Repositories": "repository", + "Reunion": "Riunione", + "Rights": "Diritti di accesso", + "Role": "Ruolo", + "Romania": "Romania", + "Room": "Camera", + "Running: ": "In esecuzione:", + "Russian Federation": "Federazione Russa", + "Rwanda": "Ruanda", + "Saint Kitts and Nevis": "Saint Kitts e Nevis", + "Saint Lucia": "Santa Lucia", + "Saint Vincent and the Grenadines": "Saint Vincent e Grenadine", + "Samoa": "Samoa", + "San Marino": "San Marino", + "Sao Tome and Principe": "Sao Tome e Principe", + "Sat": "Sat.", + "Saudi Arabia": "Arabia Saudita", + "Save": "Salvare", + "Save Objecttree as JSON File": "Salva albero degli oggetti come file JSON", + "Save Objecttree is not possible": "Salvare l'albero degli oggetti non è possibile", + "Save configuration": "Salva configurazione", + "Script": "copione", + "Scripts": "Script", + "Select": "Selezionare", + "Select ID": "Seleziona ID", + "Select adapter:": "Seleziona adattatore", + "Select language": "Seleziona la lingua", + "Select options": "Selezionare le opzioni", + "Senegal": "Senegal", + "Sent data:": "Dati inviati:", + "Sep": "sen", + "September": "settembre", + "Serbia": "Serbia", + "Set": "Impostato", + "Set CRON": "Impostato", + "Set CRON schedule for restarts": "Imposta la pianificazione CRON per il riavvio", + "Settings": "impostazioni", + "Settings for %s": "Impostazioni per %s", + "Seychelles": "Seychelles", + "Show instances only for current host": "Mostra le istanze solo per l'host corrente", + "Show values of instance": "Mostra valori di istanza", + "Show...": "Mostrare...", + "Sierra Leone": "Sierra Leone", + "Singapore": "Singapore", + "Size: %s, Free: %s": "Dimensione: %s, disponibile: %s", + "Slovakia": "Slovacchia", + "Slovenia": "Slovenia", + "Solomon Islands": "Isole Salomone", + "Somalia": "Somalia", + "Some data are not stored. Discard?": "Alcuni dati non sono memorizzati. Scartare?", + "Sort alphabetically": "Ordina alfabeticamente per nome", + "South Africa": "Sud Africa", + "South Georgia South Sandwich Islands": "Isole Sandwich South Georgia South", + "Spain": "Spagna", + "Speed": "Velocità", + "Sri Lanka": "Sri Lanka", + "St. Helena": "Sant'Elena", + "St. Pierre and Miquelon": "St. Pierre e Miquelon", + "Started...": "Iniziato...", + "State": "datapoint", + "State type": "Stato & nbsp; Tipo", + "States": "stati", + "States rights": "Diritti degli Stati", + "Statistics": "statistica", + "Statistics:": "Statistiche:", + "Storage of %s": "Memoria di %s", + "Storage of %s states": "Memorizzazione di stati %s", + "Success!": "Successo!", + "Sudan": "Sudan", + "Suggestion": "Raccomandazione", + "Sun": "sole", + "Suriname": "Suriname", + "Svalbard and Jan Mayen Islands": "Isole Svalbard e Jan Mayen", + "Swaziland": "Swaziland", + "Sweden": "Svezia", + "Switzerland": "Svizzera", + "Syrian Arab Republic": "Repubblica Araba Siriana", + "System": "Sistema", + "System language:": "Linguaggio di sistema:", + "System settings": "Impostazioni di sistema", + "System uptime": "System uptime", + "Table": "tavolo", + "Taiwan": "Taiwan", + "Tajikistan": "Tajikistan", + "Tanzania": "Tanzania", + "Temperature units:": "Unità di temperatura:", + "Thailand": "Tailandia", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 supporta le versioni LTS 6, 8 e 10 di node.js. Aggiorna la tua versione (\"%s\") sull'host \"%s\" a una delle versioni supportate. Si consiglia di utilizzare node.js 6.", + "Thu": "Gio", + "Time": "Tempo", + "Time From": "Tempo da", + "Time To": "Tempo di", + "Time stamp": "Data e ora", + "Title": "Titolo", + "To": "A", + "Today": "oggi", + "Toggle expert mode": "Attiva / disattiva la modalità esperto", + "Toggle states view": "Attiva / disattiva la visualizzazione degli stati", + "Togo": "Andare", + "Tokelau": "Tokelau", + "Tonga": "tonga", + "Too many events": "Troppi eventi", + "Total count in group": "Conteggio totale nel gruppo", + "Trigger event": "Evento scatenante", + "Trinidad and Tobago": "Trinidad e Tobago", + "Tue": "mar", + "Tunisia": "Tunisia", + "Turkey": "tacchino", + "Turkmenistan": "Turkmenistan", + "Turks and Caicos Islands": "Isole Turks e Caicos", + "Tuvalu": "Tuvalu", + "Type": "genere", + "URL or file path:": "URL o percorso del file:", + "Uganda": "Uganda", + "Ukraine": "Ucraina", + "Uncheck All": "Deseleziona tutto", + "United Arab Emirates": "Emirati Arabi Uniti", + "United Kingdom": "Regno Unito", + "United States": "stati Uniti", + "United States minor outlying islands": "Isole minori periferiche degli Stati Uniti", + "Unknown file format!": "Formato di file sconosciuto!", + "Unsecure_Auth": "La password verrà inviata tramite connessione non protetta. Per proteggere le tue password abilita la connessione sicura (HTTPS)!", + "Unsupported image format": "Formato immagine non supportato", + "Update": "Aggiornare", + "Update objects": "Aggiorna oggetti", + "Update states": "Stati di aggiornamento", + "Updated": "aggiornato", + "Upgrade all adapters": "Aggiorna tutti gli adattatori", + "Upload": "Upload di file", + "Upload admin started": "Upload admin avviato", + "Upload started...": "Caricamento avviato ...", + "Uptime": "Uptime", + "Uruguay": "Uruguay", + "Use Lets Encrypt certificates:": "Utilizza i certificati Let's Encrypt:", + "Use this instance for automatic update:": "Utilizza questa istanza per l'aggiornamento automatico:", + "User": "Utente", + "User deleted": "Utente eliminato", + "User does not exist": "l'utente non esiste", + "User yet exists": "L'utente esiste già", + "Users": "utenti", + "Uzbekistan": "Uzbekistan", + "Value": "Valore", + "Values of %s": "Valori per %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "Stato della Città del Vaticano", + "Venezuela": "Venezuela", + "Vietnam": "Vietnam", + "Virgin Islands (British)": "Isole Vergini (britanniche)", + "Virgin Islands (U.S.)": "Isole Vergini (U.S.)", + "Wallis and Futuna Islands": "Isole Wallis e Futuna", + "Warning!": "Avvertimento!", + "Wed": "cf.", + "Western Sahara": "Sahara occidentale", + "With": "Con", + "Without": "Senza", + "Yemen": "yemen", + "You are going to add new instance: ": "Stai per aggiungere una nuova istanza:", + "You can check changelog here": "Puoi controllare il log delle modifiche qui ", + "You can drag&drop the devices, channels and states to enums": "È possibile trascinare e rilasciare dispositivi, canali e stati in enumerazioni", + "You can drag&drop users to groups": "È possibile trascinare e rilasciare gli utenti in gruppi", + "You can't see events via cloud": "Non puoi vedere eventi tramite cloud", + "Your home": "La tua casa", + "Zaire": "Zaire", + "Zambia": "Zambia", + "Zimbabwe": "Zimbabwe", + "_All": "Tutti", + "_Toggle expert mode": "Attiva / disattiva la modalità esperto", + "__different__": "diverso", + "a-z": "a-z", + "ack": "confermato", + "actions": "Azioni", + "active": "attivo", + "adapter with updates": "adattatori con aggiornamenti", + "adapters count": "gli adattatori contano", + "add": "Inserisci", + "add children": "aggiungi bambini", + "add instance": "aggiungi istanza", + "add repository": "aggiungi repository", + "agree": "essere d'accordo", + "alarm_group": "Allarme", + "alive": "vivo", + "all": "tutti", + "alpha": "alfa", + "array": "schieramento", + "auto": "auto", + "available": "a disposizione", + "beta": "beta", + "boolean": "booleano", + "bug": "bugtracker", + "cancel": "Annulla", + "cert_path_note": "Puoi utilizzare un percorso assoluto per il certificato, come \"/opt/certs/cert.pem\", o semplicemente caricarlo per drag & drop", + "certificate": "certificato", + "change view mode": "cambia modalità di visualizzazione", + "channel": "canale", + "clear": "chiaro", + "climate-control_group": "Controllo climatico", + "close on ready": "vicino pronto", + "collapse": "crollo", + "collapse all": "collassa tutto", + "comma": "virgola", + "command execution": "Esecuzione del comando", + "common": "Comune", + "common adapters_group": "Comune", + "common_color": "colore", + "common_def": "valore di default", + "common_desc": "descrizione", + "common_icon": "icona", + "common_max": "valore massimo", + "common_min": "valore minimo", + "common_read": "leggere permesso", + "common_role": "ruolo", + "common_states": "valori predefiniti", + "common_type": "genere", + "common_unit": "unità di misura", + "common_write": "scrivere permesso", + "communication_group": "Comunicazione", + "config": "impostazioni", + "config instance": "istanza di configurazione", + "confirm password": "Conferma password", + "connected": "collegato", + "copy": "copia", + "copy note": "Premi Ctrl + A e Ctrl + C per copiare il registro negli appunti e fai clic con il mouse ovunque per chiuderlo.", + "create operation": "creare", + "custom enum": "Enum personalizzato", + "custom group": "Gruppo personalizzato", + "daemon": "demone", + "date-and-time_group": "Data e ora", + "daysShortText": "d.", + "debug": "mettere a punto", + "delete": "Elimina", + "delete adapter": "elimina l'adattatore", + "delete group": "cancella il gruppo", + "delete instance": "cancella istanza", + "delete operation": "Elimina", + "delete script": "cancella script", + "delete user": "cancella utente", + "desc": "disc", + "description": "Descrizione", + "device": "dispositivo", + "diag-note": "Abbiamo lavorato sodo per creare questo progetto. In cambio ci aspettiamo da voi alcune statistiche di utilizzo.
Ogni volta che la lista dell'adattatore viene aggiornata, vengono inviate le statistiche anonime. Rispettiamo la tua privacy, quindi non verranno trasmesse informazioni private.
Grazie!", + "edit": "modificare", + "edit enum": "modifica enum", + "edit enums": "Modifica enumerazioni per", + "edit file": "modifica file", + "edit group": "modifica gruppo", + "edit instance": "modifica istanza", + "edit script": "modifica script", + "edit user": "modifica utente", + "edit value": "Modifica valore", + "enabled": "abilitato", + "energy_group": "Energia", + "engine": "motore", + "engine type": "tipo di motore", + "error": "errore", + "events": "eventi", + "execute operation": "Esegui operazione", + "expand": "espandere", + "expand all": "Espandi tutto", + "extended": "esteso", + "false": "falso", + "file permissions": "Permessi di file", + "from": "a partire dal", + "garden_group": "Giardino", + "general_group": "Generale", + "geoposition_group": "Posizione geografica", + "groups": "gruppi", + "hardware_group": "Hardware", + "history": "storia", + "history data": "dati storici", + "host": "ospite", + "household_group": "Domestico", + "http operation": "http", + "id": "ID", + "info": "Informazioni", + "infrastructure_group": "Infrastruttura", + "install": "installare", + "install specific version": "Installa una versione specifica", + "installed": "installato", + "installed adapters": "Filtro adattatori con istanze esistenti", + "instance": "esempio", + "instance number": "Numero di istanza desiderato", + "yunkong2 Enums": "enumerazioni di yunkong2", + "yunkong2 States": "afferma yunkong2", + "yunkong2 adapter instances": "istanze dell'adattatore yunkong2", + "yunkong2 adapter scripts": "Script dell'adattatore yunkong2", + "yunkong2 adapters": "adattatori yunkong2", + "yunkong2 certificates": "certificati yunkong2", + "yunkong2 groups": "gruppi di yunkong2", + "yunkong2 hosts": "host yunkong2", + "yunkong2 repositories": "repository yunkong2", + "yunkong2 users": "utenti di yunkong2", + "iot-system_group": "Sistemi IoT", + "iot-systems_group": "Sistemi IoT", + "keywords": "parole chiave", + "lc": "cam.", + "less": "Di meno", + "letsnecrypt_help": "Queste sono le impostazioni per l'account Let's Encrypt. Per ottenere i certificati gratuiti per il tuo dominio. Puoi leggere più qui .", + "letsnecrypt_help_domains": "E.g: \"example.com, www.example.com\"", + "letsnecrypt_help_email": "Si prega di utilizzare il tuo indirizzo email. Sarà usato per il tuo account.", + "letsnecrypt_help_path": "Nome della directory in cui verranno archiviati i certificati. Questo è sempre relativo alla directory di configurazione", + "license": "licenza", + "license agreement": "Contratto di licenza", + "license not agree": "Non sono d'accordo con la licenza!", + "license_checkbox": "Sono d'accordo con la raccolta di statistiche anonime.
(Questo può essere disabilitato nelle impostazioni)", + "lighting_group": "Illuminazione", + "link": "collegamento", + "list": "elenco", + "list operation": "elenca gli elementi", + "logic_group": "Logica", + "loglevel": "loglevel", + "media_group": "Media", + "members": "membri", + "memlimit": "Limite di RAM", + "message": "Messaggio", + "messaging_group": "messaggistica", + "misc-data_group": "Varie. dati", + "mixed": "misto", + "mode": "modalità", + "more": "Di Più", + "multi": "Multistate", + "multimedia_group": "Multimedia", + "name": "nome", + "native": "nativo", + "network_group": "Rete", + "new certificate": "nuovo certificato", + "new group": "nuovo gruppo", + "new script": "nuovo script", + "new user": "nuovo utente", + "newObject": "Nuovo oggetto", + "no-city": "nessuna città", + "node-red": "node-rosso", + "none": "nessuna", + "normal": "normale", + "not ack": "non dire", + "not agree": "non sono d'accordo", + "npm error": "errore di npm", + "number": "numero", + "object": "oggetto", + "object permissions": "Permessi sugli oggetti", + "of": "di", + "ok": "Ok", + "open web page": "Apri la pagina web dell'adattatore", + "os": "sistema operativo", + "other permissions": "Altre autorizzazioni", + "parent name": "nome del genitore", + "password": "Parola d'ordine", + "permissionError": "Errore di autorizzazione", + "place here": "posiziona i file qui", + "planned": "pianificato", + "platform": "piattaforma", + "point": "punto", + "popular": "popolare", + "process": "processi", + "protocols_group": "Protocolli", + "raw": "Raw (solo per esperti)", + "read": "leggere", + "read operation": "leggere", + "readme": "readme", + "reload": "ricaricare", + "reload instance": "ricarica istanza", + "rest": "riposo (sola lettura)", + "restart": "riavvio automatico", + "restart script": "riavviare lo script", + "role": "ruolo", + "save": "salvare", + "schedule_group": "Programma", + "script_group": "Script e logica", + "select member by double click": "seleziona membro facendo doppio clic", + "sendto operation": "Operazione di invio", + "service_group": "Manutenzione", + "severity": "gravità", + "silly": "sciocco", + "stable": "stabile", + "state": "stato", + "state permissions": "Permessi di stato", + "storage_group": "Conservazione", + "string": "stringa", + "subscribe": "sottoscrivi", + "switch": "interruttore", + "terminal": "Terminale", + "third-party_group": "Terzo", + "this adapter does not allow multiple instances": "Questo adattatore non consente più istanze", + "title": "titolo", + "today": "oggi", + "true": "vero", + "ts": "timestamp", + "type": "genere", + "unit": "unità", + "update": "aggiornare", + "update adapter information": "aggiorna le informazioni dell'adattatore", + "update-part1": "Poiché yunkong2 funziona su molte piattaforme molto diverse, al momento è possibile solo l'aggiornamento manuale. Per avviare l'aggiornamento manuale, andare al controller tramite console ed eseguire quanto segue:", + "updated": "aggiornato", + "updates": "aggiornamenti", + "upload": "Caricare", + "user permissions": "permessi dell'utente", + "users": "utenti", + "users permissions": "Permessi utente", + "utility_group": "Utilità", + "val": "val", + "value": "valore", + "value.from": "Modificato da", + "value.lc": "Ultima modifica", + "value.q": "Codice di qualità", + "value.ts": "timestamp", + "value.val": "valore", + "version": "versione", + "vis_group": "yunkong2.vis", + "visualisation_group": "visualizzazione", + "visualization-icons_group": "Icone di visualizzazione", + "visualization-widgets_group": "Widget di visualizzazione", + "visualization_group": "visualizzazione", + "warn": "avvisare", + "weather_group": "Tempo metereologico", + "wetty": "Wetty", + "write": "Scrivi", + "write operation": "Scrivi", + "yesterday": "ieri" +} diff --git a/src/i18n/nl/translations.json b/src/i18n/nl/translations.json new file mode 100644 index 0000000..cf56d1e --- /dev/null +++ b/src/i18n/nl/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " voor %s", + "%s added to %s": "%s toegevoegd aan %s", + "%s object(s) processed": "%s objecten werden verwerkt", + "%s processes": "%s processen", + "%s was imported": "%s is geïmporteerd", + "(without prefix)": "(zonder voorvoegsel)", + "1 %d days ago": "%d dagen geleden", + "2 %d days ago": "%d dagen geleden", + "5 %d days ago": "%d dagen geleden", + "A-Z": "A-Z", + "Access control": "Toegangscontrole", + "Access control list": "Toegangscontrole lijst", + "Acknowledged": "Erkend", + "Activated. Click to stop.": "Geactiveerd. Klik om te stoppen.", + "Active instances": "Actieve instanties", + "Active repository:": "Actieve repository:", + "Adapter configuration": "Adapter configuratie", + "Adapter settings for %s states": "Adapterinstellingen voor% toestanden", + "Adapters": "Adapters", + "Adapters from this Group installed": "Adapters van deze groep geïnstalleerd", + "Add": "Toevoegen", + "Add Objecttree from JSON File": "Object-boom toevoegen vanuit JSON-bestand", + "Add certificate from file": "Certificaat uit bestand toevoegen", + "Add instance...": "Een exemplaar toevoegen ...", + "Add member": "Lid toevoegen", + "Add new child object to selected parent": "Voeg een nieuw object toe", + "Add new field": "Nieuw veld toevoegen", + "Add new issue": "Meld een bug", + "Add new object: ": "Nieuw object toevoegen:", + "Add new object: %s": "Nieuw object toevoegen: %s", + "Address:": "Adres:", + "Admin is not enabled in cloud settings!": "Beheerder is niet ingeschakeld in cloud-instellingen!", + "Administrator": "Beheerder", + "Afghanistan": "Afghanistan", + "Albania": "Albanië", + "Algeria": "Algerije", + "All": "Alle", + "American Samoa": "Amerikaans Samoa", + "Andorra": "Andorra", + "Angola": "Angola", + "Anguilla": "Anguilla", + "Antarctica": "Antarctica", + "Antigua and Barbuda": "Antigua en Barbuda", + "Apr": "april", + "April": "april", + "Architecture": "architectuur", + "Are you sure to delete %s?": "Weet u zeker dat u \"%s\" wilt verwijderen?", + "Are you sure to delete all children of %s?": "Weet u zeker dat u alle onderliggende van %s wilt verwijderen?", + "Are you sure to delete all children of %s?": "Weet u zeker dat u \"%s\" en alle onderliggende wilt verwijderen?", + "Are you sure to delete script %s?": "Weet je zeker dat je script ' %s' wilt verwijderen?", + "Are you sure you want to delete adapter %s?": "Weet je zeker dat je adapter %s wilt verwijderen?", + "Are you sure you want to delete the instance %s?": "Weet u zeker dat u het exemplaar %s wilt verwijderen?", + "Are you sure?": "Weet je het zeker?", + "Are you sure? Changes are not saved.": "Weet je het zeker? Wijzigingen worden niet opgeslagen.", + "Argentina": "Argentinië", + "Armenia": "Armenië", + "Aruba": "Aruba", + "Aug": "augustus", + "August": "augustus", + "Australia": "Australië", + "Austria": "Oostenrijk", + "Authentication was deactivated": "Verificatie was gedeactiveerd", + "Available": "Beschikbaar", + "Available version:": "Beschikbare versie:", + "Azerbaijan": "Azerbeidzjan", + "Background": "Achtergrond", + "Background color of the login screen": "Achtergrondkleur van het inlogscherm", + "Background image": "Achtergrond afbeelding", + "Bahamas": "Bahamas", + "Bahrain": "Bahrein", + "Bangladesh": "Bangladesh", + "Barbados": "Barbados", + "Belarus": "Wit-Rusland", + "Belgium": "België", + "Belize": "Belize", + "Benin": "Benin", + "Bermuda": "Bermuda", + "Bhutan": "Bhutan", + "Bolivia": "Bolivia", + "Bosnia and Herzegovina": "Bosnië-Herzegovina", + "Botswana": "Botswana", + "Bouvet Island": "Bouvet Island", + "Brazil": "Brazilië", + "British Indian Ocean Territory": "Brits-Indisch oceaan gebied", + "Brunei Darussalam": "Brunei Darussalam", + "Bulgaria": "Bulgarije", + "Burkina Faso": "Burkina Faso", + "Burundi": "Boeroendi", + "CPUs": "CPUs", + "Calendar": "Kalender", + "Cambodia": "Cambodja", + "Cameroon": "Kameroen", + "Canada": "Canada", + "Cancel": "Annuleren", + "Cannot create user: ": "Kan gebruiker niet aanmaken:", + "Cannot delete user: ": "Kan gebruiker niet verwijderen:", + "Cannot disable admin!": "Kan admin niet uitschakelen!", + "Cannot read file!": "Kan bestand niet lezen!", + "Cannot read version from NPM": "Kon de versie van NPM niet lezen", + "Cannot set password: ": "Kan het wachtwoord niet instellen:", + "Cape Verde": "Kaapverdië", + "Cayman Islands": "Kaaiman Eilanden", + "Central African Republic": "Centraal Afrikaanse Republiek", + "Certificates": "certificaten", + "Chad": "Tsjaad", + "Change": "Wijzig", + "Changelog": "Wijzig log", + "Channel": "Kanaal", + "Chart": "tabel", + "Chart for %s": "Grafiek voor %s", + "Check all": "Controleer alles", + "Chile": "Chili", + "China": "China", + "Christmas Island": "Kersteiland", + "City:": "Stad:", + "Clear": "Verwijderen", + "Clear list": "Verwijder lijst", + "Clear log": "Logboek opschonen", + "Clear on disk permanent": "Definitief van schijf wissen", + "Click do activate events again, or just wait one minute": "Klik op activeer gebeurtenissen opnieuw of wacht een minuut", + "Click on icon": "Klik op het pictogram om de ​​link te openen", + "Close": "sluiten", + "Cocos Islands": "Cocoseilanden", + "Collapse all nodes": "Knip alle knooppunten samen", + "Colombia": "Colombia", + "Color": "Kleur", + "Comoros": "Comoren", + "Configuration not saved.": "Configuratie niet opgeslagen.", + "Congo": "Congo", + "Connected to %s: ": "Verbonden met %s:", + "Connected to host: ": "Verbonden met host:", + "Cook Islands": "Cook Eilanden", + "Copy log": "Log kopiëren", + "Copy to clipboard": "Kopieer naar klembord", + "Costa Rica": "Costa Rica", + "Country:": "Land:", + "Create": "Aanmaken", + "Create new category": "Maak een nieuwe categorie", + "Create new category, like %s": "Maak een nieuwe categorie, zoals %s", + "Create new enum": "Maak een nieuwe opsomming", + "Create new enum, like %s": "Maak een nieuwe enum, zoals %s", + "Create new group": "Maak een nieuwe groep", + "Create new user": "Maak een nieuwe gebruiker", + "Created": "Gemaakt", + "Croatia": "Kroatië", + "Cron expression": "Tijdschema", + "Cuba": "Cuba", + "Currency:": "Valuta:", + "Custom": "speciaal", + "Cyprus": "Cyprus", + "Czech Republic": "Tsjechische Republiek", + "D$ecember": "december", + "DD.MM.YY": "DD.MM.YY", + "DD.MM.YYYY": "DD.MM.JJJJ", + "DD/MM/YYYY": "DD / MM / YYYY", + "Date From": "Datum vanaf", + "Date To": "Datum tot", + "Date format:": "Datumnotatie:", + "Deactivated. Click to start.": "Gedeactiveerd. Klik om te starten.", + "Debug outputs:": "Debug-uitgangen", + "Dec": "Dec", + "December": "December", + "Default ACL": "Standaard ACL", + "Default history instance:": "Standaard geschiedenisinstantie:", + "Delete attribute": "Attribuut verwijderen", + "Delete category": "Verwijder categorie", + "Delete enum": "enum verwijderen", + "Delete member": "Lid verwijderen", + "Delete object": "Object verwijderen", + "Denmark": "Denemarken", + "Description": "Beschrijving", + "Device": "Apparaat", + "Device discovery": "Zoek naar apparaten", + "Disable authentication": "Schakel verificatie uit", + "Disk free": "Schijfvrije grootte", + "Disk free:": "Schijf beschikbaar:", + "Disk size": "Schijfgrootte", + "Djibouti": "Djibouti", + "Do you want to delete just one object or all children of %s too?": "Wilt u alleen één object of alle onderliggende objecten van %s verwijderen?", + "Do you want to upgrade all adapters?": "Wilt u alle adapters bijwerken ?", + "Domains:": "domeinen:", + "Dominica": "Dominica", + "Dominican Republic": "Dominicaanse Republiek", + "Done with error: %s": "Gedaan met fout: %s", + "Download log": "Log downloaden", + "Drop the files here": "Zet de bestanden hier neer", + "Drop the icons here": "Zet de pictogrammen hier neer", + "East Timor": "Oost Timor", + "Ecuador": "Ecuador", + "Edit": "Bewerken", + "Edit category": "Bewerk categorie", + "Edit enum": "Bewerk enum", + "Edit in dialog": "Bewerken in dialoogvenster", + "Edit object": "Bewerk object", + "Egypt": "Egypte", + "El Salvador": "El Salvador", + "Email for account:": "E-mail voor account:", + "Enabled:": "Ingeschakeld", + "Enums": "enums", + "Equatorial Guinea": "Equatoriaal-Guinea", + "Eritrea": "Eritrea", + "Error": "Fout", + "Estonia": "Estland", + "Ethiopia": "Ethiopië", + "Event": "Type", + "Events": "Gebeurtenissen", + "Everyone": "Iedereen", + "Expand all nodes": "Vouw alle knooppunten uit", + "Failed to open JSON File": "Openen van JSON-bestand mislukt", + "Falkland Islands (Malvinas)": "Falkland Eilanden (Malvinas)", + "Faroe Islands": "Faeröer", + "Feb": "februari", + "February": "februari", + "Fiji": "Fiji", + "File is too big!": "Bestand is te groot!", + "File rights": "Bestandsrechten", + "Filter": "Filter", + "Filter:": "Filter", + "Filtered out": "Alles is uitgefilterd", + "Find coordinates...": "Vind coördinaten ...", + "Finland": "Finland", + "Float divider:": "Teken voor decimale waardes:", + "France": "Frankrijk", + "Free RAM:": "Vrij geheugen:", + "French Guiana": "Frans Guyana", + "French Polynesia": "Frans-Polynesië", + "French Southern Territories": "Franse zuidelijke gebieden", + "Fri": "vr", + "From": "Oorsprong:", + "From github": "Van github", + "Function": "Functie", + "Gabon": "Gabon", + "Gambia": "Gambia", + "Generated ID:": "Gegenereerde ID:", + "Georgia": "Georgië", + "Germany": "Duitsland", + "Ghana": "Ghana", + "Gibraltar": "Gibraltar", + "Go to Github...": "Ga naar Github...", + "Greece": "Griekenland", + "Greenland": "Groenland", + "Grenada": "Grenada", + "Group": "Groep", + "Groups": "Groepen", + "Guadeloupe": "Guadeloupe", + "Guam": "Guam", + "Guatemala": "Guatemala", + "Guernsey": "Guernsey", + "Guinea": "Guinea", + "Guinea-Bissau": "Guinee-Bissau", + "Guyana": "Guyana", + "Haiti": "Haïti", + "Has no permission to %s %s %s": "Heeft geen toestemming voor %s %s %s", + "Heard and Mc Donald Islands": "Gehoord en Mc Donald Islands", + "Heartbeat: ": "Levensteken:", + "Honduras": "Honduras", + "Hong Kong": "Hong Kong", + "Host": "Host", + "Host %s is offline": "Host %s is offline", + "Host:": "Host:", + "Hosts": "Systemen", + "Hungary": "Hongarije", + "ID": "ID", + "Iceland": "IJsland", + "Icon upload": "Icon upload", + "Ignore warning": "Negeer waarschuwing", + "In background": "Op de achtergrond", + "India": "Indië", + "Indonesia": "Indonesië", + "Info": "info", + "Insert": "invoegen", + "Install": "Installeren", + "Install adapter from URL": "Installeer of update de adapter via de URL", + "Install adapter from github": "Installeer of update de adapter vanuit github", + "Install from custom URL": "Installeer vanaf aangepaste URL", + "Install or update from URL...": "Installeren of updaten vanaf URL ...", + "Installation counter": "Installatieteller", + "Installations counter": "Installaties teller", + "Installed": "Geïnstalleerd", + "Installed from group": "Installeer uit groep", + "Installed instances": "Geïnstalleerde instanties", + "Installed version": "Geïnstalleerde versie", + "Instances": "Instanties", + "Instructions": "Instructies", + "Intro": "Begin", + "Invalid version of %s": "Ongeldige versie van %s", + "Invalid version of %s. Required %s": "Ongeldige versie van %s. Benodigd %s", + "Iran": "Ik rende", + "Iraq": "Irak", + "Ireland": "Ierland", + "Is yet in the list": "Staat al in de lijst", + "Isle of Man": "Isle of Man", + "Israel": "Israël", + "Italy": "Italië", + "Ivory Coast": "Ivoorkust", + "Jamaica": "Jamaica", + "Jan": "januari", + "January": "januari", + "Japan": "Japan", + "Jersey": "Jersey", + "Jordan": "Jordanië", + "Jul": "juli", + "July": "juli", + "Jun": "juni", + "June": "juni", + "Kazakhstan": "Kazachstan", + "Kenya": "Kenia", + "Kiribati": "Kiribati", + "Known bugs for": "Bekende bugs voor", + "Korea": "Korea", + "Kosovo": "Kosovo", + "Kuwait": "Koeweit", + "Kyrgyzstan": "Kirgizië", + "Lao People's Democratic Republic": "Lao Democratische Volksrepubliek", + "Last changed": "Laatst gewijzigd", + "Last update": "Laatste update", + "Latitude:": "Breedtegraad:", + "Latvia": "Letland", + "Lebanon": "Libanon", + "Lesotho": "Lesotho", + "Let's Encrypt settings": "Let's Encrypt instellingen", + "Let's encrypt SSL": "Let's encrypt SSL", + "Liberia": "Liberia", + "Libyan Arab Jamahiriya": "Libië", + "License": "Licentie", + "License terms": "Licentievoorwaarden", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Luister op alle IP's", + "Lithuania": "Litouwen", + "Loading...": "Bezig met laden...", + "Log": "Log", + "Log file will be deleted. Are you sure?": "Logbestand wordt verwijderd. Weet je het zeker?", + "Log size:": "Log bestandsgrootte:", + "Login timeout(sec):": "Time-out bij aanmelding (sec):", + "Logout": "Uitloggen", + "Longitude:": "Lengtegraad:", + "Luxembourg": "Luxemburg", + "MB": "Mb", + "Macau": "Macau", + "Macedonia": "Macedonië", + "Madagascar": "Madagascar", + "Mai": "mei", + "Main": "Basis", + "Main settings": "Basisinstellingen", + "Malawi": "Malawi", + "Malaysia": "Maleisië", + "Maldives": "Maldiven", + "Mali": "Mali", + "Malta": "Malta", + "Manually created": "Handmatig aangemaakt", + "Mar": "maart", + "March": "maart", + "Marshall Islands": "Marshall eilanden", + "Martinique": "Martinique", + "Mauritania": "Mauritanië", + "Mauritius": "Mauritius", + "Mayotte": "Mayotte", + "Members": "leden", + "Message": "Bericht", + "Message buffer overflow. Losing oldest": "Berichtbuffer volgelopen. Oudste worden verwijderd.", + "Mexico": "Mexico", + "Micronesia": "Micronesië", + "Model": "Model", + "Moldova": "Moldavië", + "Mon": "ma", + "Monaco": "Monaco", + "Mongolia": "Mongolië", + "Montenegro": "Montenegro", + "Montserrat": "Montserrat", + "Morocco": "Marokko", + "Mozambique": "Mozambique", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Naam", + "Name:": "Naam:", + "Namibia": "Namibië", + "Nauru": "Nauru", + "Nepal": "Nepal", + "Netherlands": "Nederland", + "Netherlands Antilles": "Nederlandse Antillen", + "New": "nieuwe", + "New Caledonia": "Nieuw-Caledonië", + "New Zealand": "Nieuw Zeeland", + "New category": "Nieuwe categorie", + "New enum": "Nieuwe enum", + "New group": "Nieuwe groep", + "New object": "Nieuwe objekt", + "New objekt": "Nieuwe objekt", + "New user": "Nieuwe gebruiker", + "Nicaragua": "Nicaragua", + "Niger": "Niger", + "Nigeria": "Nigeria", + "Niue": "Niue", + "No data": "Geen gegevens", + "No states selected!": "Geen gegevens geselecteerd!", + "No version of %s": "Geen versie van %s", + "Node.js": "Node.js", + "Norfolk Island": "Norfolk Island", + "Northern Mariana Islands": "noordelijke Mariana eilanden", + "Norway": "Noorwegen", + "Not exists": "Bestaat niet", + "Note:": "Notitie:", + "Nov": "november", + "November": "november", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "Objekt \"%s\" bestaat niet. Werk de pagina bij.", + "Object may not be deleted": "Object mag niet worden verwijderd", + "Object rights": "Objektrechten", + "Objects": "Objekten", + "Oct": "oktober", + "October": "oktober", + "Ok": "OK", + "Oman": "Oman", + "Only one": "éénmalig", + "Open original": "Open op nieuw tabblad", + "Owner": "Eigenaar", + "Owner group": "Eigenaar groep", + "Owner user": "Eigenaar gebruiker", + "Pakistan": "Pakistan", + "Palau": "Palau", + "Palestine": "Palestina", + "Panama": "Panama", + "Papua New Guinea": "Papoea-Nieuw-Guinea", + "Paraguay": "Paraguay", + "Parent": "Bovenliggende", + "Parse error": "Verwerkings fout", + "Password": "Wachtwoord", + "Password and confirmation are not equal!": "Wachtwoord en bevestiging zijn niet hetzelfde!", + "Password cannot be empty!": "Wachtwoord mag niet leeg zijn!", + "Password repeat": "Wachtwoord herhalen", + "Path to storage:": "Pad naar opslag:", + "Pause output": "Pauzeer de uitvoer", + "Peru": "Peru", + "Philippines": "Filippijnen", + "Pitcairn": "Pitcairn", + "Platform": "Platform", + "Please confirm": "Bevestig alstublieft", + "Poland": "Polen", + "Popular": "Populair", + "Popular first": "Populair eerst", + "Port to check the domain:": "Poort om het domein te controleren:", + "Portugal": "Portugal", + "Preserve ID": "Bewaar ID", + "Preview": "Voorbeeld", + "Processing...": "Verwerken...", + "Puerto Rico": "Puerto Rico", + "Qatar": "Katar", + "RAM": "RAM", + "RAM total usage:": "Geheugen gebruik totaal:", + "RAM usage": "Geheugen-gebruik", + "Rebuild tree": "Bouw de boom opnieuw op", + "Recently updated": "Recent geüpdatet", + "Refresh log": "Log vernieuwen", + "Removed": "verwijderd", + "Removing of adapter...": "Adapter verwijderen...", + "Removing of instance...": "Verwijdering van instantie...", + "Rename": "andere naam geven", + "Repositories": "Repositories", + "Reunion": "Bijeenkomst", + "Rights": "Toegangsrechten", + "Role": "Rol", + "Romania": "Roemenië", + "Room": "Ruimte", + "Running: ": "Bezig:", + "Russian Federation": "Russische Federatie", + "Rwanda": "Rwanda", + "Saint Kitts and Nevis": "Saint Kitts en Nevis", + "Saint Lucia": "Saint Lucia", + "Saint Vincent and the Grenadines": "Saint Vincent en de Grenadines", + "Samoa": "Samoa", + "San Marino": "San Marino", + "Sao Tome and Principe": "Sao Tomé en Principe", + "Sat": "Sat.", + "Saudi Arabia": "Saoedi-Arabië", + "Save": "Opslaan", + "Save Objecttree as JSON File": "Bewaar objekten-boom als JSON-bestand", + "Save Objecttree is not possible": "Objectboom opslaan is niet mogelijk", + "Save configuration": "Configuratie opslaan", + "Script": "Script", + "Scripts": "Scripts", + "Select": "kiezen", + "Select ID": "Selecteer ID", + "Select adapter:": "Selecteer adapter", + "Select language": "Selecteer taal", + "Select options": "selecteer opties", + "Senegal": "Senegal", + "Sent data:": "Verzonden gegevens:", + "Sep": "sen", + "September": "september", + "Serbia": "Servië", + "Set": "Set", + "Set CRON": "instellen", + "Set CRON schedule for restarts": "Stel CRON-schema in voor herstarts", + "Settings": "instellingen", + "Settings for %s": "Instellingen voor %s", + "Seychelles": "Seychellen", + "Show instances only for current host": "Toon alleen instanties voor de huidige host", + "Show values of instance": "Toon waarden van instantie", + "Show...": "Weergeven...", + "Sierra Leone": "Sierra Leone", + "Singapore": "Singapore", + "Size: %s, Free: %s": "Grootte: %s, beschikbaar: %s", + "Slovakia": "Slowakije", + "Slovenia": "Slovenië", + "Solomon Islands": "Solomon eilanden", + "Somalia": "Somalië", + "Some data are not stored. Discard?": "Sommige gegevens zijn niet opgeslagen. Wijzigingen verwerpen ?", + "Sort alphabetically": "Sorteer alfabetisch op naam", + "South Africa": "Zuid-Afrika", + "South Georgia South Sandwich Islands": "South Georgia South Sandwich Islands", + "Spain": "Spanje", + "Speed": "Snelheid", + "Sri Lanka": "Sri Lanka", + "St. Helena": "St. Helena", + "St. Pierre and Miquelon": "St. Pierre en Miquelon", + "Started...": "Begonnen...", + "State": "Data punt", + "State type": "Datapunt type", + "States": "Toestanden", + "States rights": "Toestandsrechten", + "Statistics": "Statistieken", + "Statistics:": "Statistieken:", + "Storage of %s": "Opslag van %s", + "Storage of %s states": "Opslag van %s toestanden", + "Success!": "Succes!", + "Sudan": "Soedan", + "Suggestion": "Suggestie", + "Sun": "zon", + "Suriname": "Suriname", + "Svalbard and Jan Mayen Islands": "Spitsbergen en Jan Mayen-eilanden", + "Swaziland": "Swaziland", + "Sweden": "Zweden", + "Switzerland": "Zwitserland", + "Syrian Arab Republic": "Syrische Arabische Republiek", + "System": "Systeem", + "System language:": "Systeem taal:", + "System settings": "Systeem instellingen", + "System uptime": "System uptime", + "Table": "Tabel", + "Taiwan": "Taiwan", + "Tajikistan": "Tadzjikistan", + "Tanzania": "Tanzania", + "Temperature units:": "Temperatuur eenheden:", + "Thailand": "Thailand", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 ondersteunt de LTS-versies 6, 8 en 10 van node.js. Update uw versie (\"%s\") op host \"%s\" naar een van de ondersteunde versies. We raden aan om node.js 6 te gebruiken.", + "Thu": "Don", + "Time": "Tijd", + "Time From": "Tijd vanaf", + "Time To": "Tijd om", + "Time stamp": "Tijdstempel", + "Title": "Titel", + "To": "Naar", + "Today": "vandaag", + "Toggle expert mode": "Expertenmodus", + "Toggle states view": "Wissel de statenweergave", + "Togo": "Togo", + "Tokelau": "Tokelau", + "Tonga": "Tonga", + "Too many events": "Te veel evenementen", + "Total count in group": "Totaal in groep", + "Trigger event": "Evenement simuleren", + "Trinidad and Tobago": "Trinidad en Tobago", + "Tue": "Din", + "Tunisia": "Tunesië", + "Turkey": "Turkije", + "Turkmenistan": "Turkmenistan", + "Turks and Caicos Islands": "Turks- en Caicoseilanden", + "Tuvalu": "Tuvalu", + "Type": "Type", + "URL or file path:": "URL of bestandspad", + "Uganda": "Oeganda", + "Ukraine": "Oekraïne", + "Uncheck All": "Deselecteer alles", + "United Arab Emirates": "Verenigde Arabische Emiraten", + "United Kingdom": "Verenigd Koningkrijk", + "United States": "Verenigde Staten", + "United States minor outlying islands": "Kleine afgelegen eilanden van de Verenigde Staten", + "Unknown file format!": "Onbekend bestandsformaat!", + "Unsecure_Auth": "Het wachtwoord wordt verzonden via onbeveiligde verbinding. Ter beveiliging van uw wachtwoorden schakelt u de beveiligde verbinding (HTTPS) in!", + "Unsupported image format": "Niet-ondersteunde afbeeldingsindeling", + "Update": "Bijwerken", + "Update objects": "Objecten bijwerken", + "Update states": "Toestanden bijwerken", + "Updated": "bijgewerkt", + "Upgrade all adapters": "Alle adapters bijwerken", + "Upload": "Bestand upload", + "Upload admin started": "Upload-beheerder gestart", + "Upload started...": "Upload gestart ...", + "Uptime": "Uptime", + "Uruguay": "Uruguay", + "Use Lets Encrypt certificates:": "Gebruik Let's Encrypt-certificaten:", + "Use this instance for automatic update:": "Gebruik deze instantie voor automatische update:", + "User": "Gebruiker", + "User deleted": "Gebruiker verwijderd", + "User does not exist": "Gebruiker bestaat niet", + "User yet exists": "Gebruiker bestaat al", + "Users": "Gebruikers", + "Uzbekistan": "Oezbekistan", + "Value": "Waarde", + "Values of %s": "Waarden voor %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "Vaticaanstad", + "Venezuela": "Venezuela", + "Vietnam": "Vietnam", + "Virgin Islands (British)": "Maagdeneilanden (Brits)", + "Virgin Islands (U.S.)": "Amerikaanse Maagdeneilanden", + "Wallis and Futuna Islands": "Wallis en Futuna Eilanden", + "Warning!": "Waarschuwing!", + "Wed": "cf.", + "Western Sahara": "Westelijke Sahara", + "With": "met", + "Without": "zonder", + "Yemen": "Jemen", + "You are going to add new instance: ": "U gaat een nieuw exemplaar toevoegen:", + "You can check changelog here": "U kunt de wijzigingen hier bekijken", + "You can drag&drop the devices, channels and states to enums": "U kunt de apparaten, kanalen en toestanden naar enums verslepen", + "You can drag&drop users to groups": "U kunt gebruikers naar groepen slepen en neerzetten", + "You can't see events via cloud": "U kunt gebeurtenissen niet via de cloud bekijken", + "Your home": "Uw huis", + "Zaire": "Zaïre", + "Zambia": "Zambia", + "Zimbabwe": "Zimbabwe", + "_All": "Alle", + "_Toggle expert mode": "Expertenmodus", + "__different__": "verschillend", + "a-z": "a-z", + "ack": "bevestigt", + "actions": "Acties", + "active": "actief", + "adapter with updates": "adapters met updates", + "adapters count": "adapters tellen", + "add": "Toevoegen", + "add children": "voeg onderliggende toe", + "add instance": "Instantie toevoegen", + "add repository": "repository toevoegen", + "agree": "Akkoord", + "alarm_group": "Alarm", + "alive": "actief", + "all": "alle", + "alpha": "alpha", + "array": "veld", + "auto": "auto", + "available": "Beschikbaar", + "beta": "beta", + "boolean": "boolean", + "bug": "bugtracker", + "cancel": "Annuleer", + "cert_path_note": "U kunt een absoluut pad gebruiken voor de certificaten, zoals '/opt/certs/cert.pem', of u kunt het uploaden door het hierheen te slepen", + "certificate": "certificaat", + "change view mode": "verander weergavemodus", + "channel": "kanaal", + "clear": "verwijderen", + "climate-control_group": "Klimaatcontrole", + "close on ready": "dichtbij klaar", + "collapse": "inklappen", + "collapse all": "alles inklappen", + "comma": "komma", + "command execution": "opdrachtuitvoering", + "common": "Algemeen", + "common adapters_group": "Algemeen", + "common_color": "kleur", + "common_def": "standaardwaarde", + "common_desc": "Beschrijving", + "common_icon": "icoon", + "common_max": "maximum waarde", + "common_min": "minimale waarde", + "common_read": "lezen toegestaan", + "common_role": "rol", + "common_states": "voorgedefinieerde waarden", + "common_type": "type", + "common_unit": "meeteenheid", + "common_write": "schrijf toegestaan", + "communication_group": "Communicatie", + "config": "configuratie", + "config instance": "Instantie configureren", + "confirm password": "wachtwoord bevestigen", + "connected": "verbonden", + "copy": "kopiëren", + "copy note": "Druk op Ctrl + A en vervolgens op Ctrl + C om de inhoud naar het klembord te kopiëren. Klik ergens om het venster te sluiten.", + "create operation": "maken", + "custom enum": "Aangepaste enum", + "custom group": "Aangepaste groep", + "daemon": "demon", + "date-and-time_group": "Datum en tijd", + "daysShortText": "d.", + "debug": "debug", + "delete": "verwijderen", + "delete adapter": "verwijder adapter", + "delete group": "verwijder groep", + "delete instance": "verwijder instantie", + "delete operation": "verwijderen", + "delete script": "verwijder script", + "delete user": "Verwijder gebruiker", + "desc": "Beschrijving", + "description": "Beschrijving", + "device": "apparaat", + "diag-note": "We hebben hard gewerkt om dit project mogelijk te maken. Als equivalent verwachten we van u enkele statistieken over het gebruik.
Er zal geen persoonlijke informatie naar yunkong2.net worden verzonden. Telkens wanneer u op de update-adapterlijst klikt, worden de geanonimiseerde statistieken verzonden.
Dankjewel!", + "edit": "bewerken", + "edit enum": "bewerk enum", + "edit enums": "Bewerk opsommingen voor", + "edit file": "bestand bewerken", + "edit group": "groep bewerken", + "edit instance": "bewerk instantie", + "edit script": "bewerk script", + "edit user": "bewerk gebruiker", + "edit value": "Waarde bewerken", + "enabled": "ingeschakeld", + "energy_group": "Energie", + "engine": "uitvoeren met", + "engine type": "Enginetyp", + "error": "fout", + "events": "Gebeurtenissen", + "execute operation": "uitvoeren", + "expand": "uitvouwen", + "expand all": "alles uitvouwen", + "extended": "uitgebreid", + "false": "fout", + "file permissions": "bestandsrechten", + "from": "van", + "garden_group": "Tuin", + "general_group": "Algemeen", + "geoposition_group": "Geografische positie", + "groups": "groepen", + "hardware_group": "Hardware", + "history": "geschiedenis", + "history data": "geschiedenis gegevens", + "host": "Server", + "household_group": "Huishouden", + "http operation": "http", + "id": "ID", + "info": "info", + "infrastructure_group": "Infrastructuur", + "install": "Installatie", + "install specific version": "installeer specifieke versie", + "installed": "Geïnstalleerd", + "installed adapters": "geïnstalleerde adapters", + "instance": "Instantie", + "instance number": "Gewenst exemplarennummer", + "yunkong2 Enums": "yunkong2 Enums", + "yunkong2 States": "yunkong2 Toestanden", + "yunkong2 adapter instances": "yunkong2-adapterinstanties", + "yunkong2 adapter scripts": "yunkong2-adapterscripts", + "yunkong2 adapters": "yunkong2-adapters", + "yunkong2 certificates": "yunkong2-certificaten", + "yunkong2 groups": "yunkong2-groepen", + "yunkong2 hosts": "yunkong2 servers", + "yunkong2 repositories": "yunkong2-bibliotheken", + "yunkong2 users": "yunkong2-gebruikers", + "iot-system_group": "IoT-systemen", + "iot-systems_group": "IoT-systemen", + "keywords": "Trefwoorden", + "lc": "gewijzigd", + "less": "minder", + "letsnecrypt_help": "Instellingen voor het Let's Encrypt-account. Als u een gratis certificaat voor uw domein wilt ontvangen, volgt u deze instructie.", + "letsnecrypt_help_domains": "bijvoorbeeld: 'example.com, www.example.com'", + "letsnecrypt_help_email": "Gebruik altijd slechts één e-mailadres.", + "letsnecrypt_help_path": "Locatie waar de certificaten worden opgeslagen. Pad is relatief ten opzichte van de configuratiemap", + "license": "Licentie", + "license agreement": "licentieovereenkomst", + "license not agree": "Niet akkoord met de licentie!", + "license_checkbox": "Ik ben het eens met het verzamelen van anonieme statistieken.
(kan worden uitgeschakeld in de instellingen)", + "lighting_group": "Verlichting", + "link": "link", + "list": "lijst", + "list operation": "geef weer", + "logic_group": "Logica", + "loglevel": "Log niveau", + "media_group": "Media", + "members": "leden", + "memlimit": "Geheugen limiet", + "message": "bericht", + "messaging_group": "Messaging", + "misc-data_group": "Andere", + "mixed": "gemengd", + "mode": "modus", + "more": "meer", + "multi": "waardenlijst", + "multimedia_group": "Multimedia", + "name": "Naam", + "native": "inheems", + "network_group": "Netwerk", + "new certificate": "nieuw certificaat", + "new group": "nieuwe groep", + "new script": "nieuw script", + "new user": "nieuwe gebruiker", + "newObject": "Nieuw objekt", + "no-city": "zonder stad", + "node-red": "node-red", + "none": "geen", + "normal": "normaal", + "not ack": "niet bev.", + "not agree": "Niet akkoord", + "npm error": "npm fout", + "number": "getal", + "object": "Objekt", + "object permissions": "objektrechten", + "of": "van", + "ok": "OK", + "open web page": "Adapter pagina openen", + "os": "Bestuuringssysteem", + "other permissions": "andere rechten", + "parent name": "bovenliggende naam", + "password": "wachtwoord", + "permissionError": "Toegangssfout", + "place here": "plaats de bestanden hier", + "planned": "gepland", + "platform": "Platform", + "point": "punt", + "popular": "populair", + "process": "Proces", + "protocols_group": "Protocollen", + "raw": "Ruwe data (alleen Experten)", + "read": "lezen", + "read operation": "lezen", + "readme": "Leesmij", + "reload": "herladen", + "reload instance": "herlaad instantie", + "rest": "overige (alleen lezen)", + "restart": "herstarten", + "restart script": "herstart script", + "role": "rol", + "save": "opslaan", + "schedule_group": "Tijdplanning", + "script_group": "Scripts en logica", + "select member by double click": "selecteer lid door dubbelklikken", + "sendto operation": "Versturen aan", + "service_group": "Onderhoud", + "severity": "Moeilijkheidsgraad", + "silly": "silly", + "stable": "stabiel", + "state": "Datapunt/Waarde", + "state permissions": "rechten voor waardes", + "storage_group": "Opslag", + "string": "Tekenreeks", + "subscribe": "abonneren", + "switch": "schakelaar", + "terminal": "Terminal", + "third-party_group": "Anders", + "this adapter does not allow multiple instances": "Deze adapter staat meerdere instanties niet toe", + "title": "Titel", + "today": "vandaag", + "true": "waar", + "ts": "Tijd", + "type": "Type", + "unit": "eenheid", + "update": "bijwerken", + "update adapter information": "adapter informatie bijwerken", + "update-part1": "Vanwege het veelvoud aan Platformen waarop yunkong2 beschikbaar is kan een systeemupdate alleen handmatig uitgevoerd worden. Ga voor handmatige update naar je systeem via de console en voer het volgende uit:", + "updated": "bijgewerkt", + "updates": "updates", + "upload": "uploaden", + "user permissions": "gebruikersrechten", + "users": "gebruikers", + "users permissions": "gebruikersrechten", + "utility_group": "Utility", + "val": "waarde", + "value": "waarde", + "value.from": "Gewijzigd van", + "value.lc": "Laatste wijziging", + "value.q": "Kwaliteitscode", + "value.ts": "tijdstempel", + "value.val": "waarde", + "version": "versie", + "vis_group": "yunkong2.vis", + "visualisation_group": "Visualisatie", + "visualization-icons_group": "Visualisatie pictogrammen", + "visualization-widgets_group": "Visualisatie Widgets", + "visualization_group": "Visualisatie", + "warn": "waarschuwing", + "weather_group": "Weer", + "wetty": "Wetty", + "write": "schrijven", + "write operation": "schrijven", + "yesterday": "gisteren" +} diff --git a/src/i18n/pl/translations.json b/src/i18n/pl/translations.json new file mode 100644 index 0000000..553eb36 --- /dev/null +++ b/src/i18n/pl/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " dla %s", + "%s added to %s": "%s dodano do %s", + "%s object(s) processed": "Przetworzono %s obiektów", + "%s processes": "%s procesów", + "%s was imported": "%s został zaimportowany", + "(without prefix)": "(bez prefiksu)", + "1 %d days ago": "%d dni temu", + "2 %d days ago": "%d dni temu", + "5 %d days ago": "%d dni temu", + "A-Z": "A-Z", + "Access control": "Kontrola dostępu", + "Access control list": "Lista kontroli dostępu", + "Acknowledged": "Przyznane", + "Activated. Click to stop.": "Aktywowany. Kliknij, aby zatrzymać.", + "Active instances": "Aktywne instancje", + "Active repository:": "Aktywne repozytorium:", + "Adapter configuration": "Konfiguracja adaptera", + "Adapter settings for %s states": "Ustawienia adaptera dla stanów %s", + "Adapters": "Adaptery", + "Adapters from this Group installed": "Instalowane są adaptery z tej grupy", + "Add": "Dodaj", + "Add Objecttree from JSON File": "Dodaj drzewo obiektów z pliku JSON", + "Add certificate from file": "Dodaj certyfikat z pliku", + "Add instance...": "Dodawanie wystąpienia ...", + "Add member": "Dodaj członka", + "Add new child object to selected parent": "Dodaj nowy obiekt potomny do wybranego rodzica", + "Add new field": "Dodaj nowe pole", + "Add new issue": "Zgłoś błąd", + "Add new object: ": "Dodaj nowy obiekt:", + "Add new object: %s": "Dodaj nowy obiekt: %s", + "Address:": "Adres:", + "Admin is not enabled in cloud settings!": "Admin nie jest włączony w ustawieniach chmury!", + "Administrator": "Administrator", + "Afghanistan": "Afganistan", + "Albania": "Albania", + "Algeria": "Algieria", + "All": "Wszystko", + "American Samoa": "Samoa Amerykańskie", + "Andorra": "Andora", + "Angola": "Angola", + "Anguilla": "Anguilla", + "Antarctica": "Antarktyda", + "Antigua and Barbuda": "Antigua i Barbuda", + "Apr": "Apr", + "April": "kwiecień", + "Architecture": "Architektura", + "Are you sure to delete %s?": "Czy na pewno chcesz usunąć \" %s\" ?", + "Are you sure to delete all children of %s?": "Czy na pewno chcesz usunąć all children z %s ?", + "Are you sure to delete all children of %s?": "Czy na pewno usunąć \" %s\" i all children?", + "Are you sure to delete script %s?": "Czy na pewno chcesz usunąć skrypt \" %s\"?", + "Are you sure you want to delete adapter %s?": "Czy na pewno chcesz usunąć adapter %s?", + "Are you sure you want to delete the instance %s?": "Czy na pewno chcesz usunąć instancję %s ?", + "Are you sure?": "Jesteś pewny?", + "Are you sure? Changes are not saved.": "Jesteś pewny? Zmiany nie są zapisywane.", + "Argentina": "Argentyna", + "Armenia": "Armenia", + "Aruba": "Aruba", + "Aug": "Aug", + "August": "sierpień", + "Australia": "Australia", + "Austria": "Austria", + "Authentication was deactivated": "Uwierzytelnianie zostało dezaktywowane", + "Available": "Dostępny", + "Available version:": "Dostępna wersja:", + "Azerbaijan": "Azerbejdżan", + "Background": "Tło", + "Background color of the login screen": "Kolor tła ekranu logowania", + "Background image": "Zdjęcie w tle", + "Bahamas": "Bahamy", + "Bahrain": "Bahrajn", + "Bangladesh": "Bangladesz", + "Barbados": "Barbados", + "Belarus": "Białoruś", + "Belgium": "Belgia", + "Belize": "Belize", + "Benin": "Benin", + "Bermuda": "Bermudy", + "Bhutan": "Bhutan", + "Bolivia": "Boliwia", + "Bosnia and Herzegovina": "Bośnia i Hercegowina", + "Botswana": "Botswana", + "Bouvet Island": "Wyspa Bouvet", + "Brazil": "Brazylia", + "British Indian Ocean Territory": "Brytyjskie Terytorium Oceanu Indyjskiego", + "Brunei Darussalam": "Brunei Darussalam", + "Bulgaria": "Bułgaria", + "Burkina Faso": "Burkina Faso", + "Burundi": "Burundi", + "CPUs": "CPUs", + "Calendar": "Kalendarz", + "Cambodia": "Kambodża", + "Cameroon": "Kamerun", + "Canada": "Kanada", + "Cancel": "Anuluj", + "Cannot create user: ": "Nie można utworzyć użytkownika:", + "Cannot delete user: ": "Nie można usunąć użytkownika:", + "Cannot disable admin!": "Nie można wyłączyć administratora!", + "Cannot read file!": "Nie można odczytać pliku!", + "Cannot read version from NPM": "Nie można odczytać wersji z NPM", + "Cannot set password: ": "Nie można ustawić hasła:", + "Cape Verde": "Wyspy Zielonego Przylądka", + "Cayman Islands": "Kajmany", + "Central African Republic": "Republika Środkowoafrykańska", + "Certificates": "Certyfikaty", + "Chad": "Chad", + "Change": "Zmiana", + "Changelog": "Zmień dziennik", + "Channel": "Kanał", + "Chart": "Wykres", + "Chart for %s": "Wykres dla %s", + "Check all": "Zaznacz wszystkie", + "Chile": "Chile", + "China": "Chiny", + "Christmas Island": "Wyspa Bożego Narodzenia", + "City:": "Miasto:", + "Clear": "Jasny", + "Clear list": "Czysta lista", + "Clear log": "Wyczyść log", + "Clear on disk permanent": "Wyczyść na dysku na stałe", + "Click do activate events again, or just wait one minute": "Kliknij ponownie, aby aktywować zdarzenia, lub po prostu poczekaj minutę", + "Click on icon": "Kliknij ikonę, aby otworzyć łącze", + "Close": "blisko", + "Cocos Islands": "Wyspy Kokosowe", + "Collapse all nodes": "Zwiń wszystkie węzły", + "Colombia": "Kolumbia", + "Color": "Kolor", + "Comoros": "Komory", + "Configuration not saved.": "Konfiguracja nie została zapisana.", + "Congo": "Kongo", + "Connected to %s: ": "Połączono z %s:", + "Connected to host: ": "Połączony z hostem:", + "Cook Islands": "Wyspy Cooka", + "Copy log": "Skopiuj dziennik", + "Copy to clipboard": "Skopiuj do schowka", + "Costa Rica": "Kostaryka", + "Country:": "Kraj:", + "Create": "Stwórz", + "Create new category": "Utwórz nową kategorię", + "Create new category, like %s": "Utwórz nową kategorię, np. %s", + "Create new enum": "Utwórz nowe wyliczenie", + "Create new enum, like %s": "Utwórz nowe wyliczenie, np. %s", + "Create new group": "Stwórz Nową Grupę", + "Create new user": "Utwórz nowego użytkownika", + "Created": "Stworzony", + "Croatia": "Chorwacja", + "Cron expression": "Ekspresja Cron", + "Cuba": "Kuba", + "Currency:": "Waluta:", + "Custom": "Zwyczaj", + "Cyprus": "Cypr", + "Czech Republic": "Republika Czeska", + "D$ecember": "grudzień", + "DD.MM.YY": "DD.MM.YY", + "DD.MM.YYYY": "DD / MM / RRRR", + "DD/MM/YYYY": "DD / MM / RRRR", + "Date From": "Data, od", + "Date To": "Data To", + "Date format:": "Format daty:", + "Deactivated. Click to start.": "Dezaktywowane. Kliknij, aby rozpocząć.", + "Debug outputs:": "Wyjścia debugowania", + "Dec": "Gru", + "December": "Grudzień", + "Default ACL": "Domyślna lista ACL", + "Default history instance:": "Domyślna instancja historii:", + "Delete attribute": "Usuń atrybut", + "Delete category": "Usuń kategorię", + "Delete enum": "Usuń wyliczenie", + "Delete member": "Usuń członka", + "Delete object": "Usuń obiekt", + "Denmark": "Dania", + "Description": "Opis", + "Device": "Urządzenie", + "Device discovery": "Wykrywanie urządzenia", + "Disable authentication": "Wyłącz uwierzytelnianie", + "Disk free": "Rozmiar wolnego dysku", + "Disk free:": "Dostępny dysk:", + "Disk size": "Rozmiar dysku", + "Djibouti": "Dżibuti", + "Do you want to delete just one object or all children of %s too?": "Czy chcesz usunąć tylko jeden obiekt lub all children %s też?", + "Do you want to upgrade all adapters?": "Czy chcesz uaktualnić wszystkie adaptery?", + "Domains:": "Domeny:", + "Dominica": "Dominika", + "Dominican Republic": "Republika Dominikany", + "Done with error: %s": "Zrobione z błędem: %s", + "Download log": "Pobierz dziennik", + "Drop the files here": "Upuść pliki tutaj", + "Drop the icons here": "Upuść ikony tutaj", + "East Timor": "Wschodni Timor", + "Ecuador": "Ekwador", + "Edit": "Edytować", + "Edit category": "Edytuj kategorię", + "Edit enum": "Edytuj wyliczenie", + "Edit in dialog": "Edytuj w oknie dialogowym", + "Edit object": "Edytuj obiekt", + "Egypt": "Egipt", + "El Salvador": "Salwador", + "Email for account:": "E-mail do konta:", + "Enabled:": "Włączone", + "Enums": "Wylicza", + "Equatorial Guinea": "Gwinea Równikowa", + "Eritrea": "Erytrea", + "Error": "Błąd", + "Estonia": "Estonia", + "Ethiopia": "Etiopia", + "Event": "Rodzaj", + "Events": "Wydarzenia", + "Everyone": "Każdy", + "Expand all nodes": "Rozwiń wszystkie węzły", + "Failed to open JSON File": "Nie można otworzyć pliku JSON", + "Falkland Islands (Malvinas)": "Falkland Islands (Malvinas)", + "Faroe Islands": "Wyspy Owcze", + "Feb": "Lut", + "February": "luty", + "Fiji": "Fidżi", + "File is too big!": "Plik jest za duży!", + "File rights": "Prawa do plików", + "Filter": "Filtr", + "Filter:": "Filtr", + "Filtered out": "Wszystko jest odfiltrowane", + "Find coordinates...": "Znajdź współrzędne ...", + "Finland": "Finlandia", + "Float divider:": "Float divider:", + "France": "Francja", + "Free RAM:": "Darmowy:", + "French Guiana": "Gujana Francuska", + "French Polynesia": "Polinezja Francuska", + "French Southern Territories": "Francuskie Terytoria Południowe", + "Fri": "Pt", + "From": "Od:", + "From github": "Od github", + "Function": "Funkcjonować", + "Gabon": "Gabon", + "Gambia": "Gambia", + "Generated ID:": "Wygenerowany identyfikator:", + "Georgia": "Gruzja", + "Germany": "Niemcy", + "Ghana": "Ghana", + "Gibraltar": "Gibraltar", + "Go to Github...": "Idź do Github ...", + "Greece": "Grecja", + "Greenland": "Grenlandia", + "Grenada": "Grenada", + "Group": "Grupa", + "Groups": "Grupy", + "Guadeloupe": "Gwadelupa", + "Guam": "Guam", + "Guatemala": "Gwatemala", + "Guernsey": "Guernsey", + "Guinea": "Gwinea", + "Guinea-Bissau": "Gwinea Bissau", + "Guyana": "Gujana", + "Haiti": "Haiti", + "Has no permission to %s %s %s": "Nie ma uprawnień do %s %s %s", + "Heard and Mc Donald Islands": "Wyspy Hearda i Mc Donalda", + "Heartbeat: ": "Bicie serca:", + "Honduras": "Honduras", + "Hong Kong": "Hongkong", + "Host": "Gospodarz", + "Host %s is offline": "Host %s jest w trybie offline", + "Host:": "Gospodarz:", + "Hosts": "Zastępy niebieskie", + "Hungary": "Węgry", + "ID": "ID", + "Iceland": "Islandia", + "Icon upload": "Przesyłanie ikon", + "Ignore warning": "Zignoruj ​​ostrzeżenie", + "In background": "W tle", + "India": "Indie", + "Indonesia": "Indonezja", + "Info": "Informacje", + "Insert": "Wstawić", + "Install": "zainstalować", + "Install adapter from URL": "Zainstaluj lub zaktualizuj adapter z adresu URL", + "Install adapter from github": "Zainstaluj lub zaktualizuj adapter z Github", + "Install from custom URL": "Zainstaluj z niestandardowego adresu URL", + "Install or update from URL...": "Zainstaluj lub zaktualizuj z adresu URL ...", + "Installation counter": "Licznik instalacji", + "Installations counter": "Licznik instalacji", + "Installed": "Zainstalowany", + "Installed from group": "Zainstalowany z grupy", + "Installed instances": "Zainstalowane instancje", + "Installed version": "Zainstalowana wersja", + "Instances": "Instancje", + "Instructions": "Instrukcje", + "Intro": "Początek", + "Invalid version of %s": "Nieprawidłowa wersja %s", + "Invalid version of %s. Required %s": "Nieprawidłowa wersja %s. Wymagane %s", + "Iran": "Iran", + "Iraq": "Irak", + "Ireland": "Irlandia", + "Is yet in the list": "Jest już na liście", + "Isle of Man": "Wyspa Man", + "Israel": "Izrael", + "Italy": "Włochy", + "Ivory Coast": "Wybrzeże Kości Słoniowej", + "Jamaica": "Jamajka", + "Jan": "Jan", + "January": "styczeń", + "Japan": "Japonia", + "Jersey": "Golf", + "Jordan": "Jordania", + "Jul": "Jul", + "July": "lipiec", + "Jun": "Jun", + "June": "czerwiec", + "Kazakhstan": "Kazachstan", + "Kenya": "Kenia", + "Kiribati": "Kiribati", + "Known bugs for": "Znane błędy dla", + "Korea": "Korea", + "Kosovo": "Kosowo", + "Kuwait": "Kuwejt", + "Kyrgyzstan": "Kirgistan", + "Lao People's Democratic Republic": "Laotańska Republika Ludowo-Demokratyczna", + "Last changed": "Ostatnia zmiana", + "Last update": "Ostatnia aktualizacja", + "Latitude:": "Szerokość:", + "Latvia": "Łotwa", + "Lebanon": "Liban", + "Lesotho": "Lesotho", + "Let's Encrypt settings": "Zakodujmy ustawienia", + "Let's encrypt SSL": "Zaszyfruj SSL", + "Liberia": "Liberia", + "Libyan Arab Jamahiriya": "Libijska Arabska Dżamahirija", + "License": "Licencja", + "License terms": "Warunki licencji", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Posłuchaj na wszystkich IP", + "Lithuania": "Litwa", + "Loading...": "Ładuję...", + "Log": "Log", + "Log file will be deleted. Are you sure?": "Plik dziennika zostanie usunięty. Jesteś pewny?", + "Log size:": "Rozmiar dziennika:", + "Login timeout(sec):": "Limit czasu logowania (s):", + "Logout": "Wyloguj", + "Longitude:": "Długość geograficzna:", + "Luxembourg": "Luksemburg", + "MB": "Mb", + "Macau": "Makau", + "Macedonia": "Macedonia", + "Madagascar": "Madagaskar", + "Mai": "Mai", + "Main": "Główny", + "Main settings": "Ustawienia główne", + "Malawi": "Malawi", + "Malaysia": "Malezja", + "Maldives": "Malediwy", + "Mali": "Mali", + "Malta": "Malta", + "Manually created": "Ręcznie utworzone", + "Mar": "Zniszczyć", + "March": "Marsz", + "Marshall Islands": "Wyspy Marshalla", + "Martinique": "Martynika", + "Mauritania": "Mauretania", + "Mauritius": "Mauritius", + "Mayotte": "Majotta", + "Members": "Członkowie", + "Message": "Wiadomość", + "Message buffer overflow. Losing oldest": "Przepełnienie bufora wiadomości. Utrata najstarszych.", + "Mexico": "Meksyk", + "Micronesia": "Mikronezja", + "Model": "Model", + "Moldova": "Moldova", + "Mon": "Pon", + "Monaco": "Monaco", + "Mongolia": "Mongolia", + "Montenegro": "Czarnogóra", + "Montserrat": "Montserrat", + "Morocco": "Maroko", + "Mozambique": "Mozambik", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Nazwa", + "Name:": "Nazwa:", + "Namibia": "Namibia", + "Nauru": "Nauru", + "Nepal": "Nepal", + "Netherlands": "Holandia", + "Netherlands Antilles": "Antyle Holenderskie", + "New": "Nowy", + "New Caledonia": "Nowa Kaledonia", + "New Zealand": "Nowa Zelandia", + "New category": "Nowa kategoria", + "New enum": "Nowe wyliczenie", + "New group": "Nowa grupa", + "New object": "Nowy obiekt", + "New objekt": "Nowy obiekt", + "New user": "Nowy użytkownik", + "Nicaragua": "Nikaragua", + "Niger": "Niger", + "Nigeria": "Nigeria", + "Niue": "Niue", + "No data": "Brak danych", + "No states selected!": "Nie wybrano stanów!", + "No version of %s": "Brak wersji %s", + "Node.js": "Node.js", + "Norfolk Island": "Wyspa Norfolk", + "Northern Mariana Islands": "Mariany Północne", + "Norway": "Norwegia", + "Not exists": "Nie istnieje", + "Note:": "Uwaga:", + "Nov": "Nov", + "November": "listopad", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "Obiekt \" %s \" nie istnieje. Zaktualizuj stronę.", + "Object may not be deleted": "Obiekt nie może zostać usunięty", + "Object rights": "Prawa obiektu", + "Objects": "Obiekty", + "Oct": "Paź", + "October": "październik", + "Ok": "Ok", + "Oman": "Oman", + "Only one": "Tylko jeden", + "Open original": "Otwórz na nowej karcie", + "Owner": "Właściciel", + "Owner group": "Grupa właścicieli", + "Owner user": "Użytkownik właściciela", + "Pakistan": "Pakistan", + "Palau": "Palau", + "Palestine": "Palestyna", + "Panama": "Panama", + "Papua New Guinea": "Papua Nowa Gwinea", + "Paraguay": "Paragwaj", + "Parent": "Rodzic", + "Parse error": "Błąd przetwarzania", + "Password": "Hasło", + "Password and confirmation are not equal!": "Hasło i potwierdzenie nie są równe!", + "Password cannot be empty!": "Hasło nie może być puste!", + "Password repeat": "Powtórz hasło", + "Path to storage:": "Ścieżka do przechowywania:", + "Pause output": "Wstrzymaj wyjście", + "Peru": "Peru", + "Philippines": "Filipiny", + "Pitcairn": "Pitcairn", + "Platform": "Platforma", + "Please confirm": "Proszę potwierdzić", + "Poland": "Polska", + "Popular": "Popularny", + "Popular first": "Najpopularniejszy na początku", + "Port to check the domain:": "Port do sprawdzenia domeny:", + "Portugal": "Portugalia", + "Preserve ID": "Zachowaj ID", + "Preview": "Zapowiedź", + "Processing...": "Przetwarzanie...", + "Puerto Rico": "Portoryko", + "Qatar": "Katar", + "RAM": "RAM", + "RAM total usage:": "Całkowite wykorzystanie pamięci RAM:", + "RAM usage": "Wykorzystanie pamięci RAM", + "Rebuild tree": "Przebuduj drzewo", + "Recently updated": "Ostatnio zaktualizowane", + "Refresh log": "Odśwież dziennik", + "Removed": "Oddalony", + "Removing of adapter...": "Usuwanie adaptera ...", + "Removing of instance...": "Usuwanie instancji ...", + "Rename": "Przemianować", + "Repositories": "Repozytoria", + "Reunion": "Reunion", + "Rights": "Prawa dostępu", + "Role": "Rola", + "Romania": "Rumunia", + "Room": "Pokój", + "Running: ": "Bieganie:", + "Russian Federation": "Federacja Rosyjska", + "Rwanda": "Rwanda", + "Saint Kitts and Nevis": "Saint Kitts i Nevis", + "Saint Lucia": "święta Lucia", + "Saint Vincent and the Grenadines": "Saint Vincent i Grenadyny", + "Samoa": "Samoa", + "San Marino": "San Marino", + "Sao Tome and Principe": "Sao Tome and Principe", + "Sat": "Sob", + "Saudi Arabia": "Arabia Saudyjska", + "Save": "Zapisać", + "Save Objecttree as JSON File": "Zapisz drzewo obiektów jako plik JSON", + "Save Objecttree is not possible": "Zapisanie drzewa obiektów nie jest możliwe", + "Save configuration": "Zapisz konfigurację", + "Script": "Scenariusz", + "Scripts": "Skrypty", + "Select": "Wybierz", + "Select ID": "Wybierz ID", + "Select adapter:": "Wybierz adapter", + "Select language": "Wybierz język", + "Select options": "Wybierz opcje", + "Senegal": "Senegal", + "Sent data:": "Wysłane dane:", + "Sep": "Sep", + "September": "wrzesień", + "Serbia": "Serbia", + "Set": "Zestaw", + "Set CRON": "Zestaw", + "Set CRON schedule for restarts": "Ustaw harmonogram CRON na ponowne uruchomienie", + "Settings": "Ustawienia", + "Settings for %s": "Ustawienia dla %s", + "Seychelles": "Seszele", + "Show instances only for current host": "Pokaż wystąpienia tylko dla bieżącego hosta", + "Show values of instance": "Pokaż wartości instancji", + "Show...": "Pokazać...", + "Sierra Leone": "Sierra Leone", + "Singapore": "Singapur", + "Size: %s, Free: %s": "Rozmiar: %s, Dostępny: %s", + "Slovakia": "Słowacja", + "Slovenia": "Słowenia", + "Solomon Islands": "Wyspy Salomona", + "Somalia": "Somali", + "Some data are not stored. Discard?": "Niektóre dane nie są przechowywane. Odrzucać?", + "Sort alphabetically": "Sortuj alfabetycznie według nazwy", + "South Africa": "Afryka Południowa", + "South Georgia South Sandwich Islands": "South Georgia South Sandwich Islands", + "Spain": "Hiszpania", + "Speed": "Prędkość", + "Sri Lanka": "Sri Lanka", + "St. Helena": "St. Helena", + "St. Pierre and Miquelon": "St. Pierre i Miquelon", + "Started...": "Rozpoczęty...", + "State": "Punkt danych", + "State type": "Rodzaj państwa", + "States": "Stany Zjednoczone", + "States rights": "Prawa państw", + "Statistics": "Statystyka", + "Statistics:": "Statystyka:", + "Storage of %s": "Przechowywanie %s ", + "Storage of %s states": "Przechowywanie stanów %s ", + "Success!": "Powodzenie!", + "Sudan": "Sudan", + "Suggestion": "Zalecenie", + "Sun": "Słońce", + "Suriname": "Surinam", + "Svalbard and Jan Mayen Islands": "Wyspy Svalbard i Jan Mayen", + "Swaziland": "Suazi", + "Sweden": "Szwecja", + "Switzerland": "Szwajcaria", + "Syrian Arab Republic": "Republika Syryjsko-Arabska", + "System": "System", + "System language:": "Język systemu:", + "System settings": "Ustawienia systemowe", + "System uptime": "System uptime", + "Table": "Stół", + "Taiwan": "Tajwan", + "Tajikistan": "Tadżykistan", + "Tanzania": "Tanzania", + "Temperature units:": "Jednostki temperatury:", + "Thailand": "Tajlandia", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 obsługuje wersje LTS 6, 8 i 10 pliku node.js. Zaktualizuj swoją wersję (\" %s\") na hoście \" %s\" do jednej z obsługiwanych wersji. Zalecamy korzystanie z Node.js 6.", + "Thu": "Czw", + "Time": "Czas", + "Time From": "Czas od", + "Time To": "Czas na", + "Time stamp": "Znak czasu", + "Title": "Tytuł", + "To": "Do", + "Today": "Dzisiaj", + "Toggle expert mode": "Przełącz tryb ekspercki", + "Toggle states view": "Przełącz widok stanów", + "Togo": "Iść", + "Tokelau": "Tokelau", + "Tonga": "Tonga", + "Too many events": "Za dużo zdarzeń", + "Total count in group": "Łączna liczba w grupie", + "Trigger event": "Zdarzenie wyzwalające", + "Trinidad and Tobago": "Trynidad i Tobago", + "Tue": "Wt", + "Tunisia": "Tunezja", + "Turkey": "indyk", + "Turkmenistan": "Turkmenia", + "Turks and Caicos Islands": "Wyspy Turks i Caicos", + "Tuvalu": "Tuvalu", + "Type": "Rodzaj", + "URL or file path:": "Adres URL lub ścieżka do pliku", + "Uganda": "Uganda", + "Ukraine": "Ukraina", + "Uncheck All": "Odznacz wszystkie", + "United Arab Emirates": "Zjednoczone Emiraty Arabskie", + "United Kingdom": "Zjednoczone Królestwo", + "United States": "Stany Zjednoczone", + "United States minor outlying islands": "Stany Zjednoczone Dalekie Wyspy Mniejsze", + "Unknown file format!": "Nieznany format pliku!", + "Unsecure_Auth": "Hasło zostanie wysłane przez połączenie bez zabezpieczeń. Aby chronić swoje hasła, włącz bezpieczne połączenie (HTTPS)!", + "Unsupported image format": "Nieobsługiwany format obrazu", + "Update": "Aktualizacja", + "Update objects": "Zaktualizuj obiekty", + "Update states": "Zaktualizuj stany", + "Updated": "Zaktualizowano", + "Upgrade all adapters": "Uaktualnij wszystkie adaptery", + "Upload": "Udostępnianie pliku", + "Upload admin started": "Załadowanie konfiguracji jest rozpoczęte", + "Upload started...": "Przesłanie skonte ...", + "Uptime": "Uptime", + "Uruguay": "Urugwaj", + "Use Lets Encrypt certificates:": "Użyj szyfrowania Let's Encrypt:", + "Use this instance for automatic update:": "Użyj tej instancji do automatycznej aktualizacji:", + "User": "Użytkownik", + "User deleted": "Użytkownik został usunięty", + "User does not exist": "użytkownik nie istnieje", + "User yet exists": "Użytkownik już istnieje", + "Users": "Użytkownicy", + "Uzbekistan": "Uzbekistan", + "Value": "Wartość", + "Values of %s": "Wartości %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "Państwo Watykańskie", + "Venezuela": "Wenezuela", + "Vietnam": "Wietnam", + "Virgin Islands (British)": "Wyspy Dziewicze (brytyjskie)", + "Virgin Islands (U.S.)": "Wyspy Dziewicze (USA)", + "Wallis and Futuna Islands": "Wyspy Wallis i Futuna", + "Warning!": "Ostrzeżenie!", + "Wed": "Poślubić", + "Western Sahara": "Sahara Zachodnia", + "With": "Z", + "Without": "Bez", + "Yemen": "Jemen", + "You are going to add new instance: ": "Zamierzasz dodać nową instancję:", + "You can check changelog here": "Możesz sprawdzić dziennik zmian tutaj ", + "You can drag&drop the devices, channels and states to enums": "Możesz przeciągać i upuszczać urządzenia, kanały i stany do wyliczenia", + "You can drag&drop users to groups": "Możesz przeciągać i upuszczać użytkowników do grup", + "You can't see events via cloud": "Nie możesz zobaczyć wydarzeń za pośrednictwem chmury", + "Your home": "Twój dom", + "Zaire": "Zair", + "Zambia": "Zambia", + "Zimbabwe": "Zimbabwe", + "_All": "Wszystko", + "_Toggle expert mode": "Przełącz tryb ekspercki", + "__different__": "różne", + "a-z": "a-z", + "ack": "ack", + "actions": "działania", + "active": "aktywny", + "adapter with updates": "adaptery z aktualizacjami", + "adapters count": "liczba adapterów", + "add": "Dodaj", + "add children": "dodaj dzieci", + "add instance": "dodaj instancję", + "add repository": "dodaj repozytorium", + "agree": "Zgodzić się", + "alarm_group": "Alarm", + "alive": "żywy", + "all": "wszystko", + "alpha": "alfa", + "array": "szyk", + "auto": "automatyczny", + "available": "dostępny", + "beta": "beta", + "boolean": "boolean", + "bug": "bugtracker", + "cancel": "Anuluj", + "cert_path_note": "Możesz użyć bezwzględnej ścieżki do certyfikatu, takiej jak \"/opt/certs/cert.pem\", lub po prostu przesłać ją na przeciągnij i upuść", + "certificate": "certyfikat", + "change view mode": "zmień tryb widoku", + "channel": "kanał", + "clear": "jasny", + "climate-control_group": "Kontrola klimatu", + "close on ready": "gotowe do użycia", + "collapse": "zawalić się", + "collapse all": "zwinąć wszystkie", + "comma": "przecinek", + "command execution": "Wykonanie polecenia", + "common": "pospolity", + "common adapters_group": "Pospolity", + "common_color": "kolor", + "common_def": "domyślna wartość", + "common_desc": "opis", + "common_icon": "Ikona", + "common_max": "maksymalna wartość", + "common_min": "minimalna wartość", + "common_read": "odczyt dozwolony", + "common_role": "rola", + "common_states": "wstępnie zdefiniowane wartości", + "common_type": "rodzaj", + "common_unit": "jednostka miary", + "common_write": "pisz wolno", + "communication_group": "Komunikacja", + "config": "Ustawienia", + "config instance": "instancja konfiguracji", + "confirm password": "Potwierdź hasło", + "connected": "połączony", + "copy": "Kopiuj", + "copy note": "Naciśnij Ctrl + A i Ctrl + C, aby skopiować dziennik do schowka i kliknij myszką w dowolnym miejscu, aby zamknąć.", + "create operation": "Stwórz", + "custom enum": "? ustom enum", + "custom group": "Grupa niestandardowa", + "daemon": "demon", + "date-and-time_group": "Data i godzina", + "daysShortText": "d.", + "debug": "odpluskwić", + "delete": "kasować", + "delete adapter": "usuń adapter", + "delete group": "usuń grupę", + "delete instance": "usuń instancję", + "delete operation": "kasować", + "delete script": "usuń skrypt", + "delete user": "Usuń użytkownika", + "desc": "desc", + "description": "Opis", + "device": "urządzenie", + "diag-note": "Ciężko pracowaliśmy, aby stworzyć ten projekt. W zamian spodziewamy się pewnych statystyk użytkowania.
Za każdym razem, gdy lista adapterów jest aktualizowana, wysyłane są anonimowe statystyki. Szanujemy twoją prywatność, więc żadne prywatne informacje nie będą przekazywane.
Dziękuję!", + "edit": "edytować", + "edit enum": "edytuj wyliczenie", + "edit enums": "Edytuj wyliczenia dla", + "edit file": "edytuj plik", + "edit group": "edytuj grupę", + "edit instance": "edytuj instancję", + "edit script": "edytuj skrypt", + "edit user": "Edytuj użytkownika", + "edit value": "Edytuj wartość", + "enabled": "włączony", + "energy_group": "Energia", + "engine": "silnik", + "engine type": "typ silnika", + "error": "błąd", + "events": "wydarzenia", + "execute operation": "Wykonaj operację", + "expand": "rozszerzać", + "expand all": "rozwiń wszystkie", + "extended": "rozszerzony", + "false": "fałszywy", + "file permissions": "Uprawnienia do plików", + "from": "od", + "garden_group": "Ogród", + "general_group": "Generał", + "geoposition_group": "Pozycja geograficzna", + "groups": "grupy", + "hardware_group": "Sprzęt komputerowy", + "history": "historia", + "history data": "dane historyczne", + "host": "gospodarz", + "household_group": "Gospodarstwo domowe", + "http operation": "http", + "id": "ID", + "info": "info", + "infrastructure_group": "Infrastruktura", + "install": "zainstalować", + "install specific version": "Zainstaluj konkretną wersję", + "installed": "zainstalowany", + "installed adapters": "Filtruj adaptery z istniejącymi instancjami", + "instance": "instancja", + "instance number": "Żądany numer instancji", + "yunkong2 Enums": "yunkong2 wylicza", + "yunkong2 States": "stany yunkong2", + "yunkong2 adapter instances": "Instancje adaptera yunkong2", + "yunkong2 adapter scripts": "Skrypty adaptera yunkong2", + "yunkong2 adapters": "adaptery yunkong2", + "yunkong2 certificates": "Certyfikaty yunkong2", + "yunkong2 groups": "Grupy yunkong2", + "yunkong2 hosts": "Hosty yunkong2", + "yunkong2 repositories": "repozytoria yunkong2", + "yunkong2 users": "Użytkownicy yunkong2", + "iot-system_group": "Systemy IoT", + "iot-systems_group": "Systemy IoT", + "keywords": "słowa kluczowe", + "lc": "lc", + "less": "mniej", + "letsnecrypt_help": "To są ustawienia dla konta Let's Encrypt. Aby uzyskać bezpłatne certyfikaty dla swojej domeny. Możesz przeczytać więcej tutaj .", + "letsnecrypt_help_domains": "Np .: \"example.com, www.example.com\"", + "letsnecrypt_help_email": "Użyj swojego adresu e-mail. Będzie on używany na Twoim koncie.", + "letsnecrypt_help_path": "Nazwa katalogu, w którym będą przechowywane certyfikaty. Jest to zawsze zależne od katalogu konfiguracji", + "license": "licencja", + "license agreement": "umowa licencyjna", + "license not agree": "Nie zgadzam się z licencją!", + "license_checkbox": "Zgadzam się na zbieranie anonimowych statystyk.
(Można to wyłączyć w ustawieniach)", + "lighting_group": "Oświetlenie", + "link": "połączyć", + "list": "lista", + "list operation": "lista elementów", + "logic_group": "Logika", + "loglevel": "loglevel", + "media_group": "Głoska bezdźwięczna", + "members": "członków", + "memlimit": "Limit pamięci RAM", + "message": "wiadomość", + "messaging_group": "Wiadomości", + "misc-data_group": "Różne dane", + "mixed": "mieszany", + "mode": "tryb", + "more": "jeszcze", + "multi": "multistate", + "multimedia_group": "Multimedia", + "name": "Nazwa", + "native": "ojczysty", + "network_group": "Sieć", + "new certificate": "nowy certyfikat", + "new group": "Nowa grupa", + "new script": "nowy skrypt", + "new user": "nowy użytkownik", + "newObject": "Nowy obiekt", + "no-city": "nie ma miasta", + "node-red": "węzeł-czerwony", + "none": "Żaden", + "normal": "normalna", + "not ack": "nie potwierdzenia", + "not agree": "nie zgadzam się", + "npm error": "błąd npm", + "number": "numer", + "object": "obiekt", + "object permissions": "Uprawnienia obiektu", + "of": "z", + "ok": "Ok", + "open web page": "Otwórz stronę adaptera", + "os": "system operacyjny", + "other permissions": "Inne uprawnienia", + "parent name": "imię rodzica", + "password": "Hasło", + "permissionError": "Błąd uprawnień", + "place here": "umieść pliki tutaj", + "planned": "zaplanowany", + "platform": "Platforma", + "point": "punkt", + "popular": "popularny", + "process": "proces", + "protocols_group": "Protokoły", + "raw": "Surowy (tylko eksperci)", + "read": "czytać", + "read operation": "czytać", + "readme": "przeczytaj", + "reload": "przeładować", + "reload instance": "przeładuj instancję", + "rest": "reszta (tylko do odczytu)", + "restart": "auto restart", + "restart script": "zrestartuj skrypt", + "role": "rola", + "save": "zapisać", + "schedule_group": "Harmonogram", + "script_group": "Skrypty i logika", + "select member by double click": "wybierz członka, klikając dwukrotnie", + "sendto operation": "Wyślij do operacji", + "service_group": "Konserwacja", + "severity": "surowość", + "silly": "głupi", + "stable": "stabilny", + "state": "stan", + "state permissions": "Uprawnienia państwowe", + "storage_group": "Przechowywanie", + "string": "strunowy", + "subscribe": "subskrybować", + "switch": "przełącznik", + "terminal": "Terminal", + "third-party_group": "Strona trzecia", + "this adapter does not allow multiple instances": "Ten adapter nie zezwala na wiele instancji", + "title": "tytuł", + "today": "dzisiaj", + "true": "prawdziwe", + "ts": "Znak czasu", + "type": "rodzaj", + "unit": "jednostka", + "update": "aktualizacja", + "update adapter information": "aktualizuj informacje o adapterze", + "update-part1": "Ponieważ yunkong2 działa na wielu bardzo różnych platformach, w tej chwili możliwe jest tylko ręczne aktualizowanie. Aby rozpocząć aktualizację ręczną, przejdź do kontrolera za pomocą konsoli i wykonaj następujące czynności:", + "updated": "zaktualizowany", + "updates": "aktualizacje", + "upload": "Przekazać plik", + "user permissions": "uprawnienia użytkownika", + "users": "użytkowników", + "users permissions": "Uprawnienia użytkowników", + "utility_group": "Użyteczność", + "val": "val", + "value": "wartość", + "value.from": "Zmieniony z", + "value.lc": "Ostatnia zmiana", + "value.q": "Kod jakości", + "value.ts": "Znak czasu", + "value.val": "wartość", + "version": "wersja", + "vis_group": "yunkong2.vis", + "visualisation_group": "Wyobrażanie sobie", + "visualization-icons_group": "Ikony wizualizacji", + "visualization-widgets_group": "Widżetowe widżety", + "visualization_group": "Wyobrażanie sobie", + "warn": "ostrzec", + "weather_group": "Pogoda", + "wetty": "Wetty", + "write": "pisać", + "write operation": "pisać", + "yesterday": "wczoraj" +} diff --git a/src/i18n/pt/translations.json b/src/i18n/pt/translations.json new file mode 100644 index 0000000..221084d --- /dev/null +++ b/src/i18n/pt/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " para %s", + "%s added to %s": "%s foi adicionado a %s", + "%s object(s) processed": "%s objetos foram processados", + "%s processes": "%s processos", + "%s was imported": "%s foi importado", + "(without prefix)": "(sem prefixo)", + "1 %d days ago": "%d dias atrás", + "2 %d days ago": "%d dias atrás", + "5 %d days ago": "%d dias atrás", + "A-Z": "A-Z", + "Access control": "Controle de acesso", + "Access control list": "Lista de controle de acesso", + "Acknowledged": "Confirmado", + "Activated. Click to stop.": "Ativado. Clique para parar.", + "Active instances": "Instâncias ativas", + "Active repository:": "Repositório ativo:", + "Adapter configuration": "Configuração do adaptador", + "Adapter settings for %s states": "Configurações do adaptador para estados de %s", + "Adapters": "Adaptadores", + "Adapters from this Group installed": "Adaptadores deste grupo instalados", + "Add": "Adicionar", + "Add Objecttree from JSON File": "Adicionar árvore de objetos do arquivo JSON", + "Add certificate from file": "Adicionar certificado de um arquivo", + "Add instance...": "Adicionando instância ...", + "Add member": "Adicionar membro", + "Add new child object to selected parent": "Adicionar novo objeto dentro do selecionado", + "Add new field": "Adicionar novo atributo", + "Add new issue": "Reportar um erro", + "Add new object: ": "Adicionar novo objeto:", + "Add new object: %s": "Adicionar novo objeto: %s", + "Address:": "Endereço:", + "Admin is not enabled in cloud settings!": "O Admin não está ativado nas configurações de nuvem!", + "Administrator": "Administrador", + "Afghanistan": "Afeganistão", + "Albania": "Albânia", + "Algeria": "Argélia", + "All": "Todos", + "American Samoa": "Samoa Americana", + "Andorra": "Andorra", + "Angola": "Angola", + "Anguilla": "Anguila", + "Antarctica": "Antártica", + "Antigua and Barbuda": "Antígua e Barbuda", + "Apr": "abr", + "April": "abril", + "Architecture": "Arquitetura", + "Are you sure to delete %s?": "Tem certeza que quer deletar \"%s\" ?", + "Are you sure to delete all children of %s?": "Tem certeza que quer deletar todos os objetos subjacentes de %s ?", + "Are you sure to delete all children of %s?": "Você tem certeza que quer deletar \"%s\" e todos os objetos subjacentes?", + "Are you sure to delete script %s?": "Tem certeza que quer deletar o script ' %s'?", + "Are you sure you want to delete adapter %s?": "Tem certeza de que deseja excluir o adaptador %s?", + "Are you sure you want to delete the instance %s?": "Tem certeza de que deseja excluir a instância %s ?", + "Are you sure?": "Você tem certeza?", + "Are you sure? Changes are not saved.": "Você tem certeza? As alterações não estão gravadas.", + "Argentina": "Argentina", + "Armenia": "Armênia", + "Aruba": "Aruba", + "Aug": "ago", + "August": "agosto", + "Australia": "Austrália", + "Austria": "Áustria", + "Authentication was deactivated": "A autenticação foi desativada", + "Available": "Acessível", + "Available version:": "Versão disponível:", + "Azerbaijan": "Azerbaijão", + "Background": "Fundo", + "Background color of the login screen": "Cor de fundo da tela de login", + "Background image": "Imagem de fundo", + "Bahamas": "Bahamas", + "Bahrain": "Bahrein", + "Bangladesh": "Bangladesh", + "Barbados": "Barbados", + "Belarus": "Bielorrússia", + "Belgium": "Bélgica", + "Belize": "Belize", + "Benin": "Benin", + "Bermuda": "Bermudas", + "Bhutan": "Butão", + "Bolivia": "Bolívia", + "Bosnia and Herzegovina": "Bósnia e Herzegovina", + "Botswana": "Botswana", + "Bouvet Island": "Ilha de Bouvet", + "Brazil": "Brasil", + "British Indian Ocean Territory": "Território britânico do Oceano Índico", + "Brunei Darussalam": "Brunei Darussalam", + "Bulgaria": "Bulgária", + "Burkina Faso": "Burkina Faso", + "Burundi": "Burundi", + "CPUs": "CPUs", + "Calendar": "Calendário", + "Cambodia": "Camboja", + "Cameroon": "Camarões", + "Canada": "Canadá", + "Cancel": "Cancelar", + "Cannot create user: ": "Não é possível criar usuário:", + "Cannot delete user: ": "Não é possível deletar o usuário:", + "Cannot disable admin!": "Não é possível desativar o administrador!", + "Cannot read file!": "Não é possível ler o arquivo!", + "Cannot read version from NPM": "Não foi possível ler a versão do NPM", + "Cannot set password: ": "Não é possível configurar a senha:", + "Cape Verde": "Cabo Verde", + "Cayman Islands": "Ilhas Cayman", + "Central African Republic": "República Centro-Africana", + "Certificates": "Certificados", + "Chad": "Chade", + "Change": "Mudar", + "Changelog": "Registro de mudanças", + "Channel": "Canal", + "Chart": "Gráfico", + "Chart for %s": "Gráfico para %s", + "Check all": "Selecionar todos", + "Chile": "Chile", + "China": "China", + "Christmas Island": "Ilha do Natal", + "City:": "Cidade:", + "Clear": "Apagar", + "Clear list": "Apagar a lista", + "Clear log": "Apagar o log", + "Clear on disk permanent": "Delatar o log permanente do disco", + "Click do activate events again, or just wait one minute": "Clique em ativar eventos novamente ou espere um minuto", + "Click on icon": "Clique no ícone para abrir um link", + "Close": "fechar", + "Cocos Islands": "Ilhas Cocos", + "Collapse all nodes": "Recolher todos os nós", + "Colombia": "Colômbia", + "Color": "Cor", + "Comoros": "Comores", + "Configuration not saved.": "Configuração não está gravada.", + "Congo": "Congo", + "Connected to %s: ": "Conectado com %s:", + "Connected to host: ": "Conectado com o host:", + "Cook Islands": "Ilhas Cook", + "Copy log": "Cópiar o log", + "Copy to clipboard": "Copiar para o clipboard", + "Costa Rica": "Costa Rica", + "Country:": "País:", + "Create": "Criar", + "Create new category": "Criar nova categoria", + "Create new category, like %s": "Crie nova categoria, como %s", + "Create new enum": "Criar novo enum", + "Create new enum, like %s": "Crie novo enum, como %s", + "Create new group": "Criar novo grupo", + "Create new user": "Criar novo usuário", + "Created": "Criado", + "Croatia": "Croácia", + "Cron expression": "Expressão de Cron", + "Cuba": "Cuba", + "Currency:": "Moeda:", + "Custom": "Qualquer", + "Cyprus": "Chipre", + "Czech Republic": "República Checa", + "D$ecember": "dezembro", + "DD.MM.YY": "DD.MM.YY", + "DD.MM.YYYY": "DD.MM.YYYY", + "DD/MM/YYYY": "DD / MM / AAAA", + "Date From": "Data de", + "Date To": "Data para", + "Date format:": "Formato de data:", + "Deactivated. Click to start.": "Desativado. Clique para começar.", + "Debug outputs:": "Saídas de depuração", + "Dec": "Dez", + "December": "Dezembro", + "Default ACL": "ACL default", + "Default history instance:": "Default instância histórica:", + "Delete attribute": "Deletar atributo", + "Delete category": "Eliminar categoria", + "Delete enum": "Deletar enumeração", + "Delete member": "Deletar membro", + "Delete object": "Deletar objeto", + "Denmark": "Dinamarca", + "Description": "Descrição", + "Device": "Aparelho", + "Device discovery": "Descoberta de aparelhos", + "Disable authentication": "Desativar autenticação", + "Disk free": "Tamanho livre de disco", + "Disk free:": "Disco disponível:", + "Disk size": "Tamanho do disco", + "Djibouti": "Djibouti", + "Do you want to delete just one object or all children of %s too?": "Você deseja deletar apenas um objeto ou todos os objetos subjacentes %s também?", + "Do you want to upgrade all adapters?": "Deseja atualizar todos os adaptadores?", + "Domains:": "Domínios:", + "Dominica": "Dominica", + "Dominican Republic": "República Dominicana", + "Done with error: %s": "Feito com erro: %s", + "Download log": "Fazer o download do log", + "Drop the files here": "Solte os arquivos aqui", + "Drop the icons here": "Solte os ícones aqui", + "East Timor": "Timor Leste", + "Ecuador": "Equador", + "Edit": "Editar", + "Edit category": "Editar categoria", + "Edit enum": "Editar enumeração", + "Edit in dialog": "Editar no diálogo", + "Edit object": "Editar objeto", + "Egypt": "Egito", + "El Salvador": "El Salvador", + "Email for account:": "Email para conta:", + "Enabled:": "Ativado", + "Enums": "Enumerações", + "Equatorial Guinea": "Guiné Equatorial", + "Eritrea": "Eritréia", + "Error": "Erro", + "Estonia": "Estônia", + "Ethiopia": "Etiópia", + "Event": "Tipo", + "Events": "Eventos", + "Everyone": "Todos", + "Expand all nodes": "Expandir todos os nós", + "Failed to open JSON File": "Falha ao abrir o arquivo JSON", + "Falkland Islands (Malvinas)": "Ilhas Falkland (Malvinas)", + "Faroe Islands": "ilhas Faroe", + "Feb": "fev", + "February": "fevereiro", + "Fiji": "Fiji", + "File is too big!": "O arquivo é muito grande!", + "File rights": "Direitos de arquivo", + "Filter": "Filtro", + "Filter:": "Filtro", + "Filtered out": "Tudo é filtrado", + "Find coordinates...": "Encontre coordenadas ...", + "Finland": "Finlândia", + "Float divider:": "Separador decimal:", + "France": "França", + "Free RAM:": "Livre:", + "French Guiana": "Guiana Francesa", + "French Polynesia": "Polinésia Francesa", + "French Southern Territories": "Territórios Franceses do Sul", + "Fri": "Sex", + "From": "A partir de:", + "From github": "Do Github", + "Function": "Função", + "Gabon": "Gabão", + "Gambia": "Gâmbia", + "Generated ID:": "Número de identificação gerada:", + "Georgia": "Geórgia", + "Germany": "Alemanha", + "Ghana": "Gana", + "Gibraltar": "Gibraltar", + "Go to Github...": "Vá para o Github...", + "Greece": "Grécia", + "Greenland": "Gronelândia", + "Grenada": "Grenada", + "Group": "Grupo", + "Groups": "Grupos", + "Guadeloupe": "Guadalupe", + "Guam": "Guam", + "Guatemala": "Guatemala", + "Guernsey": "Guernsey", + "Guinea": "Guiné", + "Guinea-Bissau": "Guiné-Bissau", + "Guyana": "Guiana", + "Haiti": "Haiti", + "Has no permission to %s %s %s": "Não tem permissão para %s %s %s", + "Heard and Mc Donald Islands": "Heard e Ilha Mc Donald", + "Heartbeat: ": "Sinal de vida:", + "Honduras": "Honduras", + "Hong Kong": "Hong Kong", + "Host": "Hospedeiro", + "Host %s is offline": "Host %s está desconectado", + "Host:": "Host:", + "Hosts": "Hosts", + "Hungary": "Hungria", + "ID": "ID", + "Iceland": "Islândia", + "Icon upload": "Upload de imagem", + "Ignore warning": "Ignorar aviso", + "In background": "No fundo", + "India": "Índia", + "Indonesia": "Indonésia", + "Info": "Info", + "Insert": "Inserir", + "Install": "Instalar", + "Install adapter from URL": "Instale ou atualize o adaptador a partir do URL", + "Install adapter from github": "Instale ou atualize o adaptador do github", + "Install from custom URL": "Instalar de uma URL própria", + "Install or update from URL...": "Instale ou atualize a partir de URL ...", + "Installation counter": "Contador de instalação", + "Installations counter": "Contador de instalações", + "Installed": "Instalado", + "Installed from group": "Instalado de um grupo", + "Installed instances": "Instâncias instaladas", + "Installed version": "Versão instalada", + "Instances": "Instâncias", + "Instructions": "Instruções", + "Intro": "Começar", + "Invalid version of %s": "Versão inválida de %s", + "Invalid version of %s. Required %s": "Versão inválida de %s. Precisa ser %s", + "Iran": "Irã", + "Iraq": "Iraque", + "Ireland": "Irlanda", + "Is yet in the list": "Já está na lista", + "Isle of Man": "Isle of Man", + "Israel": "Israel", + "Italy": "Itália", + "Ivory Coast": "Costa do Marfim", + "Jamaica": "Jamaica", + "Jan": "jan", + "January": "janeiro", + "Japan": "Japão", + "Jersey": "Jersey", + "Jordan": "Jordânia", + "Jul": "Jul", + "July": "julho", + "Jun": "jun", + "June": "junho", + "Kazakhstan": "Cazaquistão", + "Kenya": "Quênia", + "Kiribati": "Kiribati", + "Known bugs for": "Bugs conhecidos para", + "Korea": "Coréia", + "Kosovo": "Kosovo", + "Kuwait": "Kuwait", + "Kyrgyzstan": "Quirguistão", + "Lao People's Democratic Republic": "República Democrática Popular do Lao", + "Last changed": "Última modificação", + "Last update": "Última atualização", + "Latitude:": "Latitude:", + "Latvia": "Letônia", + "Lebanon": "Líbano", + "Lesotho": "Lesoto", + "Let's Encrypt settings": "Configurações do Let's Encrypt", + "Let's encrypt SSL": "Let's Encrypt SSL", + "Liberia": "Libéria", + "Libyan Arab Jamahiriya": "Jamahiriya Árabe da Líbia", + "License": "Licença", + "License terms": "Condições de licença", + "Liechtenstein": "Liechtenstein", + "Listen on all IPs": "Ouça todos os IPs", + "Lithuania": "Lituânia", + "Loading...": "Carregando...", + "Log": "Log", + "Log file will be deleted. Are you sure?": "O arquivo de log será deletado. Você tem certeza?", + "Log size:": "Tamanho do log:", + "Login timeout(sec):": "Tempo limite de login (seg):", + "Logout": "Logoff", + "Longitude:": "Longitude:", + "Luxembourg": "Luxemburgo", + "MB": "MB", + "Macau": "Macau", + "Macedonia": "Macedônia", + "Madagascar": "Madagáscar", + "Mai": "mai", + "Main": "Geral", + "Main settings": "Configurações gerais", + "Malawi": "Malawi", + "Malaysia": "Malásia", + "Maldives": "Maldivas", + "Mali": "Mali", + "Malta": "Malta", + "Manually created": "Criação manual", + "Mar": "mar", + "March": "março", + "Marshall Islands": "Ilhas Marshall", + "Martinique": "Martinica", + "Mauritania": "Mauritânia", + "Mauritius": "Maurícia", + "Mayotte": "Mayotte", + "Members": "Membros", + "Message": "Mensagem", + "Message buffer overflow. Losing oldest": "Mensagens de mais. Os mais velhos vão ser deletados.", + "Mexico": "México", + "Micronesia": "Micronésia", + "Model": "Modelo", + "Moldova": "Moldávia", + "Mon": "Seg", + "Monaco": "Mônaco", + "Mongolia": "Mongólia", + "Montenegro": "Montenegro", + "Montserrat": "Montserrat", + "Morocco": "Marrocos", + "Mozambique": "Moçambique", + "Myanmar": "Myanmar", + "NPM": "NPM", + "Name": "Nome", + "Name:": "Nome:", + "Namibia": "Namíbia", + "Nauru": "Nauru", + "Nepal": "Nepal", + "Netherlands": "Holanda", + "Netherlands Antilles": "Antilhas holandesas", + "New": "Novo", + "New Caledonia": "Nova Caledônia", + "New Zealand": "Nova Zelândia", + "New category": "Nova categoria", + "New enum": "Novo enumeração", + "New group": "Novo grupo", + "New object": "Novo objeto", + "New objekt": "Novo objeto", + "New user": "Novo usuário", + "Nicaragua": "Nicarágua", + "Niger": "Níger", + "Nigeria": "Nigéria", + "Niue": "Niue", + "No data": "Sem dados", + "No states selected!": "Nenhum estado selecionado!", + "No version of %s": "Nenhuma informação da versão %s", + "Node.js": "Node.js", + "Norfolk Island": "Ilha de Norfolk", + "Northern Mariana Islands": "Ilhas Marianas do Norte", + "Norway": "Noruega", + "Not exists": "Não existe", + "Note:": "Nota:", + "Nov": "nov", + "November": "novembro", + "OS": "OS", + "Object \"%s\" does not exists. Update the page.": "O objeto \" %s \" não existe. Atualize a página.", + "Object may not be deleted": "O objeto não pode ser excluído", + "Object rights": "Direitos do objeto", + "Objects": "Objetos", + "Oct": "out", + "October": "outubro", + "Ok": "Ok", + "Oman": "Omã", + "Only one": "Apenas um", + "Open original": "Abrir na nova guia", + "Owner": "Proprietário", + "Owner group": "Grupo de proprietários", + "Owner user": "Usuário proprietário", + "Pakistan": "Paquistão", + "Palau": "Palau", + "Palestine": "Palestina", + "Panama": "Panamá", + "Papua New Guinea": "Papua Nova Guiné", + "Paraguay": "Paraguai", + "Parent": "Pai", + "Parse error": "Erro de análise", + "Password": "Senha", + "Password and confirmation are not equal!": "A senha e a confirmação não são iguais!", + "Password cannot be empty!": "A senha não pode estar vazia!", + "Password repeat": "Repetição da senha", + "Path to storage:": "Caminho para gravar:", + "Pause output": "Pausar o output", + "Peru": "Peru", + "Philippines": "Filipinas", + "Pitcairn": "Pitcairn", + "Platform": "Plataforma", + "Please confirm": "Por favor confirme", + "Poland": "Polônia", + "Popular": "Popular", + "Popular first": "Popular primeiro", + "Port to check the domain:": "Porta para verificar o domínio:", + "Portugal": "Portugal", + "Preserve ID": "Preservar ID", + "Preview": "Antevisão", + "Processing...": "Em processamento...", + "Puerto Rico": "Porto Rico", + "Qatar": "Catar", + "RAM": "RAM", + "RAM total usage:": "Uso total de RAM:", + "RAM usage": "Uso de RAM", + "Rebuild tree": "Reconstruir árvore", + "Recently updated": "Atualizado recentemente", + "Refresh log": "Atualizar o log", + "Removed": "Removido", + "Removing of adapter...": "Removendo o adaptador...", + "Removing of instance...": "Removendo a instância...", + "Rename": "Renomear", + "Repositories": "Repositórios", + "Reunion": "Reunião", + "Rights": "Direitos de acesso", + "Role": "Função", + "Romania": "Roménia", + "Room": "Quarto", + "Running: ": "Concluído:", + "Russian Federation": "Federação Russa", + "Rwanda": "Ruanda", + "Saint Kitts and Nevis": "São Cristóvão e Nevis", + "Saint Lucia": "Santa Lúcia", + "Saint Vincent and the Grenadines": "São Vicente e Granadinas", + "Samoa": "Samoa", + "San Marino": "São Marinho", + "Sao Tome and Principe": "São Tomé e Príncipe", + "Sat": "Sáb", + "Saudi Arabia": "Arábia Saudita", + "Save": "Gravar", + "Save Objecttree as JSON File": "Grave a estrutura de objetos selecionados como um arquivo JSON", + "Save Objecttree is not possible": "A gravação não é possível", + "Save configuration": "Salvar configuração", + "Script": "Script", + "Scripts": "Scripts", + "Select": "Selecione", + "Select ID": "Selecione ID", + "Select adapter:": "Selecione o adaptador", + "Select language": "Selecione o idioma", + "Select options": "Selecione as opções", + "Senegal": "Senegal", + "Sent data:": "Dados enviados:", + "Sep": "set", + "September": "setembro", + "Serbia": "Sérvia", + "Set": "Conjunto", + "Set CRON": "Usar", + "Set CRON schedule for restarts": "Definir cronograma do CRON para reiniciar", + "Settings": "Configurações", + "Settings for %s": "Configurações para %s", + "Seychelles": "Seychelles", + "Show instances only for current host": "Mostrar instâncias apenas para o host atual", + "Show values of instance": "Mostrar valores da instância", + "Show...": "Mostre...", + "Sierra Leone": "Serra Leoa", + "Singapore": "Cingapura", + "Size: %s, Free: %s": "Tamanho: %s, disponível: %s", + "Slovakia": "Eslováquia", + "Slovenia": "Eslovênia", + "Solomon Islands": "Ilhas Salomão", + "Somalia": "Somália", + "Some data are not stored. Discard?": "Alguns dados não são armazenados. Descartar?", + "Sort alphabetically": "Classifique alfabeticamente pelo nome", + "South Africa": "África do Sul", + "South Georgia South Sandwich Islands": "Ilhas Sandwich do Sul da Geórgia", + "Spain": "Espanha", + "Speed": "Rapidez", + "Sri Lanka": "Sri Lanka", + "St. Helena": "Santa Helena", + "St. Pierre and Miquelon": "São Pedro e Miquelon", + "Started...": "Começado...", + "State": "Ponto de dados", + "State type": "Tipo de ponto de dados", + "States": "Estados", + "States rights": "Direitos dos estados", + "Statistics": "Estatisticas", + "Statistics:": "Estatisticas:", + "Storage of %s": "História de %s", + "Storage of %s states": "História dos estados de %s", + "Success!": "Sucesso!", + "Sudan": "Sudão", + "Suggestion": "Recomendação", + "Sun": "Sol", + "Suriname": "Suriname", + "Svalbard and Jan Mayen Islands": "Ilhas Svalbard e Jan Mayen", + "Swaziland": "Suazilândia", + "Sweden": "Suécia", + "Switzerland": "Suíça", + "Syrian Arab Republic": "República Árabe da Síria", + "System": "Sistema", + "System language:": "Idioma do sistema:", + "System settings": "Configurações do sistema", + "System uptime": "System uptime", + "Table": "Tabela", + "Taiwan": "Taiwan", + "Tajikistan": "Tajiquistão", + "Tanzania": "Tanzânia", + "Temperature units:": "Unidades de temperatura:", + "Thailand": "Tailândia", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "yunkong2 suporta as versões LTS 6, 8 e 10 do Node.js. Atualize sua versão (\"%s\") no host \"%s\" para uma das versões suportadas. Recomendamos usar Node.js 6.", + "Thu": "Thu", + "Time": "Tempo", + "Time From": "Tempo de", + "Time To": "Hora de", + "Time stamp": "Timestamp", + "Title": "Título", + "To": "Para", + "Today": "hoje", + "Toggle expert mode": "Mostrar objetos do sistema", + "Toggle states view": "Alternar a exibição de estados", + "Togo": "Togo", + "Tokelau": "Tokelau", + "Tonga": "Tonga", + "Too many events": "Muitos eventos", + "Total count in group": "Contagem total no grupo", + "Trigger event": "Simule um evento", + "Trinidad and Tobago": "Trinidad e Tobago", + "Tue": "Tue", + "Tunisia": "Tunísia", + "Turkey": "Peru", + "Turkmenistan": "Turquemenistão", + "Turks and Caicos Islands": "Ilhas Turcas e Caicos", + "Tuvalu": "Tuvalu", + "Type": "Tipo", + "URL or file path:": "URL ou caminho do arquivo", + "Uganda": "Uganda", + "Ukraine": "Ucrânia", + "Uncheck All": "Desmarcar todos", + "United Arab Emirates": "Emirados Árabes Unidos", + "United Kingdom": "Reino Unido", + "United States": "Estados Unidos", + "United States minor outlying islands": "Ilhas periféricas menores dos Estados Unidos", + "Unknown file format!": "Formato de arquivo desconhecido!", + "Unsecure_Auth": "A senha será enviada por meio de conexão não segura. Para proteger suas senhas, ative a conexão segura (HTTPS)!", + "Unsupported image format": "Formato de imagem não suportado", + "Update": "Atualizar", + "Update objects": "Atualizar objetos", + "Update states": "Atualizar estados", + "Updated": "Atualizado", + "Upgrade all adapters": "Atualize todos os adaptadores", + "Upload": "Upload de arquivo", + "Upload admin started": "Carregar o administrador iniciado", + "Upload started...": "Upload iniciado ...", + "Uptime": "Uptime", + "Uruguay": "Uruguai", + "Use Lets Encrypt certificates:": "Use certificados do Let's Encrypt:", + "Use this instance for automatic update:": "Use esta instância para atualizar os certificados:", + "User": "Usuário", + "User deleted": "Usuário deletado", + "User does not exist": "Usuário não existe", + "User yet exists": "O usuário ja existe", + "Users": "Usuários", + "Uzbekistan": "Uzbequistão", + "Value": "Valor", + "Values of %s": "Valores para %s", + "Vanuatu": "Vanuatu", + "Vatican City State": "Estado da Cidade do Vaticano", + "Venezuela": "Venezuela", + "Vietnam": "Vietnã", + "Virgin Islands (British)": "Ilhas Virgens (britânicas)", + "Virgin Islands (U.S.)": "Ilhas Virgens (EUA)", + "Wallis and Futuna Islands": "Ilhas Wallis e Futuna", + "Warning!": "Atenção!", + "Wed": "Qua", + "Western Sahara": "Saara Ocidental", + "With": "com", + "Without": "sem", + "Yemen": "Iémen", + "You are going to add new instance: ": "Você vai adicionar nova instância:", + "You can check changelog here": "Você pode verificar o changelog aqui", + "You can drag&drop the devices, channels and states to enums": "Você pode usar drag & drop para classificar os aparelhos, canais e estados em uma enumeração.", + "You can drag&drop users to groups": "Você pode usar drag & drop para classificar usuários em grupos", + "You can't see events via cloud": "Você não pode ver eventos via nuvem", + "Your home": "Sua casa", + "Zaire": "Zaire", + "Zambia": "Zâmbia", + "Zimbabwe": "Zimbábue", + "_All": "Todos", + "_Toggle expert mode": "Modo de especialista", + "__different__": "diferente", + "a-z": "a-z", + "ack": "confirmado", + "actions": "ações", + "active": "ativo", + "adapter with updates": "adaptador com atualizações", + "adapters count": "contagem de adaptadores", + "add": "Adicionar", + "add children": "inserir em enumeração", + "add instance": "adicionar instância", + "add repository": "adicionar repositório", + "agree": "aceito", + "alarm_group": "Alarme", + "alive": "funcionando", + "all": "todos", + "alpha": "alfa", + "array": "campo", + "auto": "auto", + "available": "acessível", + "beta": "beta", + "boolean": "valor lógico", + "bug": "bugtracker", + "cancel": "Cancelar", + "cert_path_note": "Você pode usar o caminho absoluto para o certificado, como '/opt/certs/cert.pem', ou simplesmente fazer um upload de um arquivo", + "certificate": "certificado", + "change view mode": "alterar o modo de vista", + "channel": "canal", + "clear": "delatar", + "climate-control_group": "Controle climático", + "close on ready": "feche pronto", + "collapse": "colapso", + "collapse all": "colapsar todos", + "comma": "vírgula", + "command execution": "Execução do comando", + "common": "geral", + "common adapters_group": "Geral", + "common_color": "cor", + "common_def": "valor padrão", + "common_desc": "descrição", + "common_icon": "ícone", + "common_max": "valor máximo", + "common_min": "valor mínimo", + "common_read": "leitura permitida", + "common_role": "função", + "common_states": "valores predefinidos", + "common_type": "tipo", + "common_unit": "unidade de medida", + "common_write": "escrever permitido", + "communication_group": "Comunicação", + "config": "Configuração", + "config instance": "configurar a instância", + "confirm password": "Confirme a senha", + "connected": "conectado", + "copy": "copiar", + "copy note": "Pressione Ctrl + A e depois Ctrl + C para copiar o conteúdo para a o clipboard. Clique em algum lugar para fechar a janela.", + "create operation": "produzir", + "custom enum": "Enum personalizado", + "custom group": "Grupo personalizado", + "daemon": "daemon", + "date-and-time_group": "Data e hora", + "daysShortText": "d.", + "debug": "depurar", + "delete": "deletar", + "delete adapter": "deletar adaptador", + "delete group": "deletar grupo", + "delete instance": "deletar instância", + "delete operation": "deletar operação", + "delete script": "deletar o script", + "delete user": "deletar usuário", + "desc": "desc.", + "description": "Descrição", + "device": "aparelho", + "diag-note": "Trabalhamos duro para criar este projeto. Em troca, pedimos que nos envie as estatísticas de uso.
Nenhuma informação privada será enviada para yunkong2.net. Cada vez que a lista do adaptador é atualizada, as estatísticas anônimas também serão enviadas.
Obrigado!", + "edit": "editar", + "edit enum": "editar enumeração", + "edit enums": "Edite enumerações para", + "edit file": "editar arquivo", + "edit group": "editar grupo", + "edit instance": "editar instância", + "edit script": "editar script", + "edit user": "editar usuário", + "edit value": "Editar valor", + "enabled": "ativado", + "energy_group": "Energia", + "engine": "executar com", + "engine type": "Tipo de mecanismo", + "error": "erro", + "events": "eventos", + "execute operation": "executar", + "expand": "expandir", + "expand all": "expandir todos", + "extended": "expandido", + "false": "falso", + "file permissions": "Permissões de arquivo", + "from": "a partir de", + "garden_group": "Jardim", + "general_group": "Geral", + "geoposition_group": "Posição geográfica", + "groups": "grupos", + "hardware_group": "Equipamento", + "history": "história", + "history data": "dados históricos", + "host": "host", + "household_group": "Casa", + "http operation": "http", + "id": "ID", + "info": "informação", + "infrastructure_group": "Infraestrutura", + "install": "instalar", + "install specific version": "instale uma versão específica", + "installed": "instalado", + "installed adapters": "adaptadores instalados", + "instance": "instância", + "instance number": "Número da instância desejada", + "yunkong2 Enums": "Enumerações do yunkong2", + "yunkong2 States": "Estados do yunkong2", + "yunkong2 adapter instances": "Instâncias de adaptador do yunkong2", + "yunkong2 adapter scripts": "Scripts para adaptador do yunkong2", + "yunkong2 adapters": "Adaptadores do yunkong2", + "yunkong2 certificates": "Certificados do yunkong2", + "yunkong2 groups": "Grupos do yunkong2", + "yunkong2 hosts": "Hosts do yunkong2", + "yunkong2 repositories": "Repositórios do yunkong2", + "yunkong2 users": "Usuários do yunkong2", + "iot-system_group": "Sistemas IoT", + "iot-systems_group": "Sistemas IoT", + "keywords": "palavras-chave", + "lc": "mod.", + "less": "menos", + "letsnecrypt_help": "Configurações para a conta Let's Encrypt. Para obter um certificado gratuito para o seu domínio, siga as instruções aqui.", + "letsnecrypt_help_domains": "por exemplo: 'example.com, www.example.com'", + "letsnecrypt_help_email": "Use sempre apenas um endereço de e-mail.", + "letsnecrypt_help_path": "Diretório onde os certificados são gravados. O caminho é relativo ao diretório de configuração", + "license": "licença", + "license agreement": "Contrato de licença", + "license not agree": "Não concordo com a licença!", + "license_checkbox": "Eu concordo com a coleção de estatísticas anônimas.
(também pode ser desativado mais tarde nas configurações)", + "lighting_group": "Iluminação", + "link": "link", + "list": "lista", + "list operation": "liste os elementos", + "logic_group": "Lógica", + "loglevel": "Nível do log", + "media_group": "Mídia", + "members": "membros", + "memlimit": "Limite de RAM", + "message": "mensagem", + "messaging_group": "Mensagens", + "misc-data_group": "Dados variados", + "mixed": "misturado", + "mode": "modo", + "more": "mais", + "multi": "lista de valores", + "multimedia_group": "Multimídia", + "name": "nome", + "native": "nativo", + "network_group": "Rede", + "new certificate": "novo certificado", + "new group": "novo grupo", + "new script": "novo script", + "new user": "novo usuário", + "newObject": "Novo objeto", + "no-city": "sem cidade", + "node-red": "node-red", + "none": "nada", + "normal": "normal", + "not ack": "não conf.", + "not agree": "não concordo", + "npm error": "erro no NPM", + "number": "número", + "object": "objeto", + "object permissions": "permissões de objeto", + "of": "do", + "ok": "Ok", + "open web page": "Abrir a website do adaptador", + "os": "sistema operacional", + "other permissions": "outras permissões", + "parent name": "nome dos pais", + "password": "senha", + "permissionError": "Erro de permissão", + "place here": "coloque os arquivos aqui", + "planned": "planejado", + "platform": "plataforma", + "point": "ponto", + "popular": "popular", + "process": "processo", + "protocols_group": "Protocolos", + "raw": "Raw (apenas especialistas)", + "read": "ler", + "read operation": "ler", + "readme": "leia-me", + "reload": "recarregar", + "reload instance": "reiniciar a instância", + "rest": "além disso (apenas leitura)", + "restart": "reiniciar", + "restart script": "reiniciar o script", + "role": "função (tipo)", + "save": "gravar", + "schedule_group": "Programação de tempo", + "script_group": "Scripts e lógica", + "select member by double click": "selecione o membro clicando duas vezes", + "sendto operation": "Operação enviar para", + "service_group": "Manutenção", + "severity": "severidade", + "silly": "tudo", + "stable": "estável", + "state": "estado", + "state permissions": "permissões do estado", + "storage_group": "Armazenamento", + "string": "string", + "subscribe": "se inscrever", + "switch": "interruptor", + "terminal": "Terminal", + "third-party_group": "Outros", + "this adapter does not allow multiple instances": "Este adaptador não permite várias instâncias", + "title": "título", + "today": "hoje", + "true": "verdade", + "ts": "timestamp", + "type": "tipo", + "unit": "unidade", + "update": "atualizar", + "update adapter information": "atualizar informações do adaptador", + "update-part1": "Devido ao grande número de plataformas de hardware que o yunkong2 pode ser executado, você precisa atualizar o js-controller manualmente. Para fazer isso, execute os seguintes comandos no console do host:", + "updated": "Atualizada", + "updates": "atualizações", + "upload": "Upload", + "user permissions": "permissões de usuários", + "users": "usuários", + "users permissions": "permissões do usuário", + "utility_group": "Utilidade", + "val": "val", + "value": "valor", + "value.from": "Mudou de", + "value.lc": "Última mudança", + "value.q": "Código de qualidade", + "value.ts": "Timestamp", + "value.val": "valor", + "version": "versão", + "vis_group": "yunkong2.vis", + "visualisation_group": "Visualização", + "visualization-icons_group": "Ícones de visualização", + "visualization-widgets_group": "Widgets de visualização", + "visualization_group": "Visualização", + "warn": "aviso", + "weather_group": "Tempo", + "wetty": "Wetty", + "write": "escrever", + "write operation": "escrever", + "yesterday": "ontem" +} diff --git a/src/i18n/ru/translations.json b/src/i18n/ru/translations.json new file mode 100644 index 0000000..183e6b6 --- /dev/null +++ b/src/i18n/ru/translations.json @@ -0,0 +1,876 @@ +{ + " for %s": " для %s", + "%s added to %s": "%s добавлен в %s", + "%s object(s) processed": "Обработано объектов: %s", + "%s processes": "%s процессов", + "%s was imported": "%s был импортирован", + "(without prefix)": "(без префикса)", + "1 %d days ago": "%d день назад", + "2 %d days ago": "%d дня назад", + "5 %d days ago": "%d дней назад", + "A-Z": "А-Я", + "Access control": "Права доступа", + "Access control list": "Права доступа", + "Acknowledged": "Подтверждён", + "Activated. Click to stop.": "Активно. Нажать для остановки.", + "Active instances": "Драйверов активно", + "Active repository:": "Активный репозиторий:", + "Adapter configuration": "Настройки драйвера", + "Adapter settings for %s states": "Настройки адаптера для состояний %s", + "Adapters": "Драйвера", + "Adapters from this Group installed": "драйверов из этой группы установлено", + "Add": "Добавить", + "Add Objecttree from JSON File": "Добавить дерево объектов из JSON файла", + "Add certificate from file": "Добавить сертификат из файла", + "Add instance...": "Добавление экземпляра ...", + "Add member": "Добавить участника", + "Add new child object to selected parent": "Добавить объект к выделенному элементу", + "Add new field": "Добавить атрибут", + "Add new issue": "Сообщить об ошибке", + "Add new object: ": "Добавить новый объект: ", + "Add new object: %s": "Добавить новый объект: %s", + "Address:": "Адрес:", + "Admin is not enabled in cloud settings!": "Админ не включен в облачных настройках!", + "Administrator": "Администратор", + "Afghanistan": "Афганистан", + "Albania": "Албания", + "Algeria": "Алжир", + "All": "все", + "American Samoa": "Американское Самоа", + "Andorra": "Андорра", + "Angola": "Ангола", + "Anguilla": "Ангилья", + "Antarctica": "Антарктида", + "Antigua and Barbuda": "Антигуа и Барбуда", + "Apr": "апр", + "April": "апрель", + "Architecture": "Архитектура", + "Are you sure to delete %s?": "Удалить \"%s\"?", + "Are you sure to delete all children of %s?": "Вы действительно хотите удалить все нижележащие под \"%s\" объекты?", + "Are you sure to delete all children of %s?": "Вы действительно хотите удалить \"%s\" и все нижележащие объекты?", + "Are you sure to delete script %s?": "Вы действительно хотите удалить скрипт '%s'?", + "Are you sure you want to delete adapter %s?": "Вы действительно хотите удалить адаптер %s?", + "Are you sure you want to delete the instance %s?": "Вы действительно хотите удалить экземпляр %s ?", + "Are you sure?": "Вы уверены?", + "Are you sure? Changes are not saved.": "Изменения не сохранены. Всё равно закрыть?", + "Argentina": "Аргентина", + "Armenia": "Армения", + "Aruba": "Аруба", + "Aug": "авг", + "August": "август", + "Australia": "Австралия", + "Austria": "Австрия", + "Authentication was deactivated": "Аутентификация была отключена", + "Available": "Доступно", + "Available version:": "доступная версия:", + "Azerbaijan": "Азербайджан", + "Background": "Фон", + "Background color of the login screen": "Цвет фона экрана входа в систему", + "Background image": "Фоновое изображение", + "Bahamas": "Багамские о-ва", + "Bahrain": "Бахрейн", + "Bangladesh": "Бангладеш", + "Barbados": "Барбадос", + "Belarus": "Беларуссия", + "Belgium": "Бельгия", + "Belize": "Белиз", + "Benin": "Бенин", + "Bermuda": "Бермудские острова", + "Bhutan": "Бутан", + "Bolivia": "Боливия", + "Bosnia and Herzegovina": "Босния и Герцеговина", + "Botswana": "Ботсвана", + "Bouvet Island": "Остров Буве", + "Brazil": "Бразилия", + "British Indian Ocean Territory": "Британская территория Индийского океана", + "Brunei Darussalam": "Бруней-Даруссалам", + "Bulgaria": "Болгария", + "Burkina Faso": "Буркина-Фасо", + "Burundi": "Бурунди", + "CPUs": "CPUs", + "Calendar": "Календарь", + "Cambodia": "Камбоджа", + "Cameroon": "Камерун", + "Canada": "Канада", + "Cancel": "Отмена", + "Cannot create user: ": "Не могу создать пользователя: ", + "Cannot delete user: ": "Не могу удалить пользователя: ", + "Cannot disable admin!": "Нельзя деактивировать админа!", + "Cannot read file!": "Невозможно прочитать файл!", + "Cannot read version from NPM": "Невозможно было считать версию драйвера с NPM", + "Cannot set password: ": "Не могу изменить пароль: ", + "Cape Verde": "Кабо-Верде", + "Cayman Islands": "Каймановы острова", + "Central African Republic": "Центрально-Африканская Республика", + "Certificates": "Сертификаты", + "Chad": "Чад", + "Change": "Изменить", + "Changelog": "Журнал изменений", + "Channel": "Канал", + "Chart": "График", + "Chart for %s": "График для %s", + "Check all": "Выбрать все", + "Chile": "Чили", + "China": "Китай", + "Christmas Island": "Остров Рождества", + "City:": "Город:", + "Clear": "Сбросить", + "Clear list": "Сбросить", + "Clear log": "Очистить протокол на экране", + "Clear on disk permanent": "Удалить протокол на сервере", + "Click do activate events again, or just wait one minute": "Нажмите снова активировать события или просто подождите одну минуту", + "Click on icon": "Что бы открыть ссылку надо нажать на иконку", + "Close": "закрыть", + "Cocos Islands": "Кокосовые острова", + "Collapse all nodes": "Свернуть все узлы", + "Colombia": "Колумбия", + "Color": "Цвет", + "Comoros": "Коморские острова", + "Configuration not saved.": "Настройки не сохранены.", + "Congo": "Конго", + "Connected to %s: ": "Соединён с %s: ", + "Connected to host: ": "Общается с хостом: ", + "Cook Islands": "Острова Кука", + "Copy log": "Скопировать протокол", + "Copy to clipboard": "Скопировать в буфер обмена", + "Costa Rica": "Коста Рика", + "Country:": "Страна:", + "Create": "Создать", + "Create new category": "Создать новую под-категорию", + "Create new category, like %s": "Создайте новую под-категорию, например %s", + "Create new enum": "Создать новую категорию", + "Create new enum, like %s": "Создайте новую категорию, например %s", + "Create new group": "Создать новую группу", + "Create new user": "Создать нового пользователя", + "Created": "Выполнено", + "Croatia": "Хорватия", + "Cron expression": "Cron-Выражение", + "Cuba": "Куба", + "Currency:": "Знак валюты:", + "Custom": "Произвольный", + "Cyprus": "Кипр", + "Czech Republic": "Чехия", + "D$ecember": "декабрь", + "DD.MM.YY": "ДД.ММ.ГГ", + "DD.MM.YYYY": "ДД.ММ.ГГГГ", + "DD/MM/YYYY": "ДД/ММ/ГГГГ", + "Date From": "Дата от", + "Date To": "Дата до", + "Date format:": "Формат даты:", + "Deactivated. Click to start.": "Неактивно. Нажать для старта.", + "Debug outputs:": "Debug вывод", + "Dec": "дек", + "December": "December", + "Default ACL": "Default ACL", + "Default history instance:": "Инстанция истории по умолчанию:", + "Delete attribute": "Удалить атрибут", + "Delete category": "Удалить категорию", + "Delete enum": "Удалить категорию", + "Delete member": "Удалить участника", + "Delete object": "Удалить объект", + "Denmark": "Дания", + "Description": "Описание", + "Device": "Устройство", + "Device discovery": "Поиск устройств", + "Disable authentication": "Отключить аутентификацию", + "Disk free": "Свободно", + "Disk free:": "Свободно на диске:", + "Disk size": "Размер диска", + "Djibouti": "Джибути", + "Do you want to delete just one object or all children of %s too?": "Хотите удалить только один объект или все нижележащие от %s тоже?", + "Do you want to upgrade all adapters?": "Обновить все драйвера и контроллер?", + "Domains:": "Домены:", + "Dominica": "Доминика", + "Dominican Republic": "Доминиканская Республика", + "Done with error: %s": "Выполнено с ошибкой: %s", + "Download log": "Скачать журнал", + "Drop the files here": "Перетащить файлы сюда", + "Drop the icons here": "Перетащите иконку сюда", + "East Timor": "Восточный Тимор", + "Ecuador": "Эквадор", + "Edit": "Изменить", + "Edit category": "Изменить категорию", + "Edit enum": "Редактировать категорию", + "Edit in dialog": "Изменить в окне", + "Edit object": "Редактировать объект", + "Egypt": "Египет", + "El Salvador": "Сальвадор", + "Email for account:": "Email для аккаунта:", + "Enabled:": "Включено", + "Enums": "Категории", + "Equatorial Guinea": "Экваториальная Гвинея", + "Eritrea": "Эритрея", + "Error": "Ошибка", + "Estonia": "Эстония", + "Ethiopia": "Эфиопия", + "Event": "Тип", + "Events": "События", + "Everyone": "Все", + "Expand all nodes": "Развернуть все узлы", + "Failed to open JSON File": "Не удалось открыть файл в формате JSON", + "Falkland Islands (Malvinas)": "Фолклендские (Мальвинские) острова", + "Faroe Islands": "Фарерские острова", + "Feb": "фев", + "February": "февраль", + "Fiji": "Фиджи", + "File is too big!": "Слишком большой файл!", + "File rights": "Файл", + "Filter": "Искать", + "Filter:": "Искать", + "Filtered out": "Все отфильтровано", + "Find coordinates...": "Найти координаты...", + "Finland": "Финляндия", + "Float divider:": "Разделитель в числе с плавающей запятой:", + "France": "Франция", + "Free RAM:": "Свободно:", + "French Guiana": "Французская Гвиана", + "French Polynesia": "Французская Полинезия", + "French Southern Territories": "Южные Французские Территории", + "Fri": "пт", + "From": "От", + "From github": "С github", + "Function": "Функция", + "Gabon": "Габон", + "Gambia": "Гамбия", + "Generated ID:": "Сгенерированный ID:", + "Georgia": "Грузия", + "Germany": "Германия", + "Ghana": "Гана", + "Gibraltar": "Гибралтар", + "Go to Github...": "Перейти к Github...", + "Greece": "Греция", + "Greenland": "Гренландия", + "Grenada": "Гренада", + "Group": "Группа", + "Groups": "Группы", + "Guadeloupe": "Гваделупа", + "Guam": "Гуам", + "Guatemala": "Гватемала", + "Guernsey": "шерстяная фуфайка", + "Guinea": "Гвинея", + "Guinea-Bissau": "Гвинея-Бисау", + "Guyana": "Гайана", + "Haiti": "Гаити", + "Has no permission to %s %s %s": "Нет разрешения '%s' для %s %s", + "Heard and Mc Donald Islands": "Heard and Mc Donald Islands", + "Heartbeat: ": "Heartbeat: ", + "Honduras": "Гондурас", + "Hong Kong": "Гонконг", + "Host": "Сервер", + "Host %s is offline": "Хост %s недоступен", + "Host:": "Хост:", + "Hosts": "Сервера", + "Hungary": "Венгрия", + "ID": "ID", + "Iceland": "Исландия", + "Icon upload": "Загрузить картинку", + "Ignore warning": "Игнорировать предупреждение", + "In background": "В фоновом режиме", + "India": "Индия", + "Indonesia": "Индонезия", + "Info": "Инфо", + "Insert": "Вставить", + "Install": "Установить", + "Install adapter from URL": "Установить или обновить адаптер с URL-адреса", + "Install adapter from github": "Установить или обновить адаптер с github", + "Install from custom URL": "Установить из собственного источника", + "Install or update from URL...": "Установите или обновите URL-адрес ...", + "Installation counter": "Счетчик установок", + "Installations counter": "Счетчик установок", + "Installed": "Установлено", + "Installed from group": "Установлено из группы", + "Installed instances": "создано инстанций", + "Installed version": "установленная версия", + "Instances": "Настройки", + "Instructions": "Инструкции", + "Intro": "Страницы", + "Invalid version of %s": "Неподходящая версия %s", + "Invalid version of %s. Required %s": "Требуется другая версия драйвера %s. Требуется %s", + "Iran": "Иран", + "Iraq": "Ирак", + "Ireland": "Ирландия", + "Is yet in the list": "Уже в списке", + "Isle of Man": "Остров Мэн", + "Israel": "Израиль", + "Italy": "Италия", + "Ivory Coast": "Кот-д'Ивуар", + "Jamaica": "Ямайка", + "Jan": "янв", + "January": "январь", + "Japan": "Япония", + "Jersey": "Джерси", + "Jordan": "Иордания", + "Jul": "июл", + "July": "июль", + "Jun": "июн", + "June": "июнь", + "Kazakhstan": "Казахстан", + "Kenya": "Кения", + "Kiribati": "Кирибати", + "Known bugs for": "Известные ошибки для", + "Korea": "Корея", + "Kosovo": "Косово", + "Kuwait": "Кувейт", + "Kyrgyzstan": "Киргизия", + "Lao People's Democratic Republic": "Лаосская Народно-Демократическая Республика", + "Last changed": "Изменён", + "Last update": "Последнее обновление", + "Latitude:": "Широта:", + "Latvia": "Латвия", + "Lebanon": "Ливан", + "Lesotho": "Лесото", + "Let's Encrypt settings": "Настройки Let's Encrypt", + "Let's encrypt SSL": "Let's encrypt SSL", + "Liberia": "Либерия", + "Libyan Arab Jamahiriya": "Ливийская арабская джамахирия", + "License": "Лицензия", + "License terms": "Содержание лицензии", + "Liechtenstein": "Лихтенштейн", + "Listen on all IPs": "Слушать на всех адресах", + "Lithuania": "Литва", + "Loading...": "Загрузка...", + "Log": "Лог", + "Log file will be deleted. Are you sure?": "Файл протокола будет удален. Вы уверены?", + "Log size:": "Размер файла протокола:", + "Login timeout(sec):": "Логин таймаут(сек):", + "Logout": "Выйти", + "Longitude:": "Долгота:", + "Luxembourg": "Люксембург", + "MB": "Мб", + "Macau": "Макао", + "Macedonia": "Македония", + "Madagascar": "Мадагаскар", + "Mai": "май", + "Main": "Основные", + "Main settings": "Основные настройки", + "Malawi": "Малави", + "Malaysia": "Малайзия", + "Maldives": "Мальдивы", + "Mali": "Мали", + "Malta": "Мальта", + "Manually created": "Создан в админ", + "Mar": "мар", + "March": "март", + "Marshall Islands": "Маршалловы острова", + "Martinique": "Мартиника", + "Mauritania": "Мавритания", + "Mauritius": "Маврикий", + "Mayotte": "Майотта", + "Members": "Объекты", + "Message": "Сообщение", + "Message buffer overflow. Losing oldest": "Слишком много сообщений. Старые выбрасываются.", + "Mexico": "Мексика", + "Micronesia": "Микронезия", + "Model": "Модель", + "Moldova": "Молдова", + "Mon": "пн", + "Monaco": "Монако", + "Mongolia": "Монголия", + "Montenegro": "Черногория", + "Montserrat": "Монсеррат", + "Morocco": "Марокко", + "Mozambique": "Мозамбик", + "Myanmar": "Мьянма", + "NPM": "NPM", + "Name": "Имя", + "Name:": "Имя:", + "Namibia": "Намибия", + "Nauru": "Науру", + "Nepal": "Непал", + "Netherlands": "Нидерланды", + "Netherlands Antilles": "Нидерландские Антильские острова", + "New": "Добавить", + "New Caledonia": "Новая Каледония", + "New Zealand": "Новая Зеландия", + "New category": "Новая категория", + "New enum": "Новая категория", + "New group": "Новая группа", + "New object": "Новый объект", + "New objekt": "Новый объект", + "New user": "Новый пользователь", + "Nicaragua": "Никарагуа", + "Niger": "Нигер", + "Nigeria": "Нигерия", + "Niue": "Ниуэ", + "No data": "Нет данных", + "No states selected!": "Нечего настраивать!", + "No version of %s": "Нет информации о версии для %s", + "Node.js": "Node.js", + "Norfolk Island": "Остров Норфолк", + "Northern Mariana Islands": "Северные Марианские острова", + "Norway": "Норвегия", + "Not exists": "Не существует", + "Note:": "Замечание:", + "Nov": "ноя", + "November": "ноябрь", + "OS": "ОС", + "Object \"%s\" does not exists. Update the page.": "Объект \"%s\" не существует. Обновите страницу.", + "Object may not be deleted": "Объект не может быть удален", + "Object rights": "Для объекта", + "Objects": "Объекты", + "Oct": "окт", + "October": "октября", + "Ok": "Ok", + "Oman": "Oman", + "Only one": "Только один", + "Open original": "Открыть на новой вкладке", + "Owner": "Пользователь", + "Owner group": "Группа-Владелец", + "Owner user": "Пользователь-Владелец", + "Pakistan": "Пакистан", + "Palau": "Palau", + "Palestine": "Палестина", + "Panama": "Панама", + "Papua New Guinea": "Папуа - Новая Гвинея", + "Paraguay": "Парагвай", + "Parent": "Родитель", + "Parse error": "Ошибка анализа JSON", + "Password": "Пароль", + "Password and confirmation are not equal!": "Пароль и подтверждение пароля не совпадают!", + "Password cannot be empty!": "Пустой пароль!", + "Password repeat": "Повтор пароля", + "Path to storage:": "Путь к папке для сохранения:", + "Pause output": "Задержать вывод сообщений", + "Peru": "Peru", + "Philippines": "Филиппины", + "Pitcairn": "Питкэрн", + "Platform": "Платформа", + "Please confirm": "Пожалуйста подтвердите", + "Poland": "Польша", + "Popular": "часто используемые", + "Popular first": "По популярности", + "Port to check the domain:": "Порт для проверки доступности домена:", + "Portugal": "Португалия", + "Preserve ID": "ID не изменять", + "Preview": "Предосмотр", + "Processing...": "Обработка...", + "Puerto Rico": "Пуэрто-Рико", + "Qatar": "Катар", + "RAM": "RAM", + "RAM total usage:": "Всего RAM - Используется:", + "RAM usage": "Исп. RAM", + "Rebuild tree": "Восстановить дерево", + "Recently updated": "Недавно обновленные", + "Refresh log": "Обновить протокол", + "Removed": "Удалено", + "Removing of adapter...": "Удаление драйвера...", + "Removing of instance...": "Удаление экземпляра...", + "Rename": "Переименовать", + "Repositories": "Репозитории", + "Reunion": "Reunion", + "Rights": "Права доступа", + "Role": "Роль", + "Romania": "Румыния", + "Room": "Комната", + "Running: ": "Рабочая: ", + "Russian Federation": "Россия", + "Rwanda": "Rwanda", + "Saint Kitts and Nevis": "Сент-Китс и Невис", + "Saint Lucia": "Санкт-Люсия", + "Saint Vincent and the Grenadines": "Святой Винсент и Гренадины", + "Samoa": "Самоа", + "San Marino": "Сан-Марино", + "Sao Tome and Principe": "Сан-Томе и Принсипи", + "Sat": "сб", + "Saudi Arabia": "Саудовская Аравия", + "Save": "Сохранить", + "Save Objecttree as JSON File": "Сохранить объекты, как JSON файл", + "Save Objecttree is not possible": "Сохранить объекты НЕВОЗМОЖНО", + "Save configuration": "Сохранить конфигурацию", + "Script": "Скрипт", + "Scripts": "Скрипты", + "Select": "Выбрать", + "Select ID": "Выбрать ID", + "Select adapter:": "Выбрать драйвер", + "Select language": "Язык", + "Select options": "Выберите", + "Senegal": "Сенегал", + "Sent data:": "Отсылаемые данные:", + "Sep": "сен", + "September": "сентябрь", + "Serbia": "Сербия", + "Set": "Задать", + "Set CRON": "Применить", + "Set CRON schedule for restarts": "Установить расписание CRON для перезапуска", + "Settings": "Настройки", + "Settings for %s": "Настройки для %s", + "Seychelles": "Сейшельские острова", + "Show instances only for current host": "Показывать экземпляры только для выбранного сервера", + "Show values of instance": "Показать значения из", + "Show...": "Показать...", + "Sierra Leone": "Сьерра-Леоне", + "Singapore": "Сингапур", + "Size: %s, Free: %s": "Размер: %s, Доступно: %s", + "Slovakia": "Словакия", + "Slovenia": "Словения", + "Solomon Islands": "Соломоновы острова", + "Somalia": "Сомали", + "Some data are not stored. Discard?": "Некоторые данные не сохраняются. Откажитесь?", + "Sort alphabetically": "Сортировка в алфавитном порядке", + "South Africa": "Южная Африка", + "South Georgia South Sandwich Islands": "Южная Георгия Южные Сандвичевы острова", + "Spain": "Испания", + "Speed": "скорость", + "Sri Lanka": "Шри-Ланка", + "St. Helena": "Остров Св. Елены", + "St. Pierre and Miquelon": "Сен-Пьер и Микелон", + "Started...": "В процессе...", + "State": "Состояние", + "State type": "State type", + "States": "Состояния", + "States rights": "Состояние", + "Statistics": "Статистика", + "Statistics:": "Статистика:", + "Storage of %s": "Настройки для %s", + "Storage of %s states": "Настройки для %s состояний", + "Success!": "Успешно закончено!", + "Sudan": "Судан", + "Suggestion": "Совет", + "Sun": "вс", + "Suriname": "Суринам", + "Svalbard and Jan Mayen Islands": "Острова Свальбард и Ян-Майен", + "Swaziland": "Свазиленд", + "Sweden": "Швеция", + "Switzerland": "Швейцария", + "Syrian Arab Republic": "Сирийская Арабская Республика", + "System": "Система", + "System language:": "Системный язык:", + "System settings": "Системные настройки", + "System uptime": "System uptime", + "Table": "Таблица", + "Taiwan": "Тайвань", + "Tajikistan": "Таджикистан", + "Tanzania": "Танзания", + "Temperature units:": "Единицы измерения температуры:", + "Thailand": "Таиланд", + "This version of node.js \"%s\" on \"%s\" is deprecated. Please install node.js 6, 8 or newer": "Эта версия node.js \"%s\" на \"%s\" устарела. Установите node.js 6, 8, 10 или новее.", + "Thu": "чт", + "Time": "Время", + "Time From": "Время от", + "Time To": "Время до", + "Time stamp": "Время", + "Title": "Название", + "To": "до", + "Today": "сегодня", + "Toggle expert mode": "Показать системные объекты", + "Toggle states view": "Переключить вид состояний", + "Togo": "Того", + "Tokelau": "Токелау", + "Tonga": "Тонга", + "Too many events": "Слишком много событий", + "Total count in group": "Всего в группе", + "Trigger event": "Симулировать нажатие", + "Trinidad and Tobago": "Тринидад и Тобаго", + "Tue": "вт", + "Tunisia": "Тунис", + "Turkey": "Турция", + "Turkmenistan": "Туркменистан", + "Turks and Caicos Islands": "Острова Теркс и Кайкос", + "Tuvalu": "Тувалу", + "Type": "Тип", + "URL or file path:": "URL или путь к файлу", + "Uganda": "Уганда", + "Ukraine": "Украина", + "Uncheck All": "Убрать все", + "United Arab Emirates": "Объединенные Арабские Эмираты", + "United Kingdom": "Великобритания", + "United States": "Соединенные Штаты", + "United States minor outlying islands": "Небольшие морские острова Соединенных Штатов", + "Unknown file format!": "Неизвестный формат файла", + "Unsecure_Auth": "Пароль будет отправлен через незащищенное соединение. Для защиты ваших паролей активируйте безопасное соединение (HTTPS)!", + "Unsupported image format": "Неподдерживаемый формат изображения", + "Update": "Обновить", + "Update objects": "Обновить объекты", + "Update states": "Обновить состояния", + "Updated": "Обновлено", + "Upgrade all adapters": "Обновить все драйвера", + "Upload": "Загрузка файлов", + "Upload admin started": "Загрузка настроек началась", + "Upload started...": "Загрузка началась ...", + "Uptime": "Uptime", + "Uruguay": "Уругвай", + "Use Lets Encrypt certificates:": "Использовать сертификаты Let's Encrypt:", + "Use this instance for automatic update:": "Использовать эту инстанцию, для обновления сертификатов:", + "User": "Пользователь", + "User deleted": "Пользователь удален", + "User does not exist": "Пользователь не существует", + "User yet exists": "Имя занято", + "Users": "Пользователи", + "Uzbekistan": "Узбекистан", + "Value": "Значение", + "Values of %s": "Значения %s", + "Vanuatu": "Вануату", + "Vatican City State": "Государство Ватикан", + "Venezuela": "Венесуэла", + "Vietnam": "Вьетнам", + "Virgin Islands (British)": "Виргинские острова (англ.)", + "Virgin Islands (U.S.)": "Виргинские острова (США)", + "Wallis and Futuna Islands": "Острова Уоллис и Футуна", + "Warning!": "Предупреждение!", + "Wed": "ср", + "Western Sahara": "Западная Сахара", + "With": "с", + "Without": "без", + "Yemen": "Йемен", + "You are going to add new instance: ": "Вы добавите новый экземпляр:", + "You can check changelog here": "Список изменений можно посмотреть здесь", + "You can drag&drop the devices, channels and states to enums": "Вы можете перетаскивать устройства, каналы и состояния к категориям", + "You can drag&drop users to groups": "Вы можете перетаскивать пользователей в группы", + "You can't see events via cloud": "Вы не можете видеть события через облако", + "Your home": "Твой дом", + "Zaire": "Заир", + "Zambia": "Замбия", + "Zimbabwe": "Зимбабве", + "_All": "Все", + "_Toggle expert mode": "Режим эксперта", + "__different__": "разное", + "a-z": "а-я", + "ack": "Подтв.", + "actions": "действия", + "active": "активно", + "adapter with updates": "Драйвера с обновлениями", + "adapters count": "количество адаптеров", + "add": "Добавить", + "add children": "Добавить под-категории", + "add instance": "Установить драйвер", + "add repository": "Добавить репозиторий", + "agree": "Согласен(на)", + "alarm_group": "Сигнализация", + "alive": "Активен", + "all": "все", + "alpha": "alpha", + "array": "Массив", + "auto": "авто", + "available": "Доступен", + "beta": "beta", + "boolean": "Логическая переменная", + "bug": "Баг трекер", + "cancel": "отменить", + "cert_path_note": "вы можете использовать абсолютный путь к сертификату, например '/opt/certs/cert.pem', или просто перетащить файл сюда", + "certificate": "сертификат", + "change view mode": "сменить вид просмотра", + "channel": "Канал", + "clear": "Очистить", + "climate-control_group": "Климат-контроль", + "close on ready": "закрыть по окончании", + "collapse": "свернуть", + "collapse all": "Свернуть все группы", + "comma": "запятая", + "command execution": "Выполнение команды", + "common": "Общие параметры", + "common adapters_group": "Общие", + "common_color": "цвет", + "common_def": "значение по умолчанию", + "common_desc": "описание", + "common_icon": "значок", + "common_max": "максимальное значение", + "common_min": "минимальное значение", + "common_read": "читать разрешено", + "common_role": "роль", + "common_states": "предопределенные значения", + "common_type": "тип", + "common_unit": "единица измерения", + "common_write": "писать разрешено", + "communication_group": "Сетевые", + "config": "Настроить", + "config instance": "Настройка экземпляра", + "confirm password": "Подтверждение", + "connected": "Соединение", + "copy": "Копировать", + "copy note": "Нажмите Ctrl+A и Ctrl+C, что бы скопировать в буфер обмена и после этого нажмите мышкой в любом месте.", + "create operation": "Создавать", + "custom enum": "Своя группа", + "custom group": "Пользовательская группа", + "daemon": "Демон", + "date-and-time_group": "Дата и время", + "daysShortText": "д.", + "debug": "отладка", + "delete": "Удалить", + "delete adapter": "Удалить драйвер", + "delete group": "Удалить группу", + "delete instance": "Удалить экземпляр", + "delete operation": "Удалять", + "delete script": "Удалить скрипт", + "delete user": "Удалить пользователя", + "desc": "Описание", + "description": "Описание", + "device": "Устройство", + "diag-note": "Мы долго работали над этим проектом. Мы просим вас переслать нам статистику использования, как компенсацию.
Никакой приватной информации не отсылается в yunkong2.net. Каждый раз, когда список драйверов обновляется, будет отсылаться анонимная статистика.
Большое спасибо!", + "edit": "Редактировать", + "edit enum": "Изменить категорию", + "edit enums": "Изменить категории для", + "edit file": "Редактировать", + "edit group": "Редактировать группу", + "edit instance": "Редактировать экземпляр", + "edit script": "Редактировать скрипт", + "edit user": "Редактировать пользователя", + "edit value": "Изменить значение", + "enabled": "активно", + "energy_group": "Энергия", + "engine": "Движок", + "engine type": "Тип движка", + "error": "ошибки", + "events": "Сообщения", + "execute operation": "выполнить", + "expand": "раскрыть", + "expand all": "Развернуть все группы", + "extended": "расширенный", + "false": "нет", + "file permissions": "Разрешения для файлов", + "from": "От", + "garden_group": "Сад", + "general_group": "Основные приложения", + "geoposition_group": "Геопозиционирование", + "groups": "Группы", + "hardware_group": "Оборудование", + "history": "История", + "history data": "История данных", + "host": "Сервер", + "household_group": "Домашнее хозяйство", + "http operation": "http", + "id": "ID", + "info": "инфо", + "infrastructure_group": "Инфраструктура", + "install": "Установить", + "install specific version": "Установить определённую версию", + "installed": "Установлен", + "installed adapters": "Только драйвера, у которых есть инстация", + "instance": "Экземпляр", + "instance number": "Номер желаемого экземпляра", + "yunkong2 Enums": "yunkong2 категории", + "yunkong2 States": "yunkong2 состояния", + "yunkong2 adapter instances": "Экземпляр драйвера yunkong2", + "yunkong2 adapter scripts": "yunkong2 adapter скрипты", + "yunkong2 adapters": "yunkong2 драйвера", + "yunkong2 certificates": "yunkong2 сертификат", + "yunkong2 groups": "Группы yunkong2", + "yunkong2 hosts": "Сервера yunkong2", + "yunkong2 repositories": "Репозитории yunkong2", + "yunkong2 users": "Пользователи yunkong2", + "iot-system_group": "IoT системы", + "iot-systems_group": "Системы IoT", + "keywords": "keywords", + "lc": "Изменён", + "less": "Меньше", + "letsnecrypt_help": "Настройки для Let's Encrypt аккаунта. Это позволит получить бесплатные сертификаты для вашего домена. О Let's Encrypt можно почитать здесь.", + "letsnecrypt_help_domains": "Например: 'example.com, www.example.com'", + "letsnecrypt_help_email": "Почтовый адрес используется для создания аккаунта.", + "letsnecrypt_help_path": "Путь к папке, где будут хранится сертификаты. Относительно папки с настройками", + "license": "Лицензия", + "license agreement": "Лицензионное соглашение", + "license not agree": "Не согласен(на) с лицензией", + "license_checkbox": "Я соглашаюсь с анонимным сбором статистики.
(Можно деактивировать через настройки)", + "lighting_group": "Свет", + "link": "Ссылка", + "list": "Список", + "list operation": "Список", + "logic_group": "Логика", + "loglevel": "Уровень", + "media_group": "Медиа", + "members": "Части", + "memlimit": "Макс. RAM", + "message": "Сообщение", + "messaging_group": "Сообщения", + "misc-data_group": "Разное", + "mixed": "смешанный", + "mode": "Режим", + "more": "Больше", + "multi": "Перечисление", + "multimedia_group": "Мультимедиа", + "name": "Имя", + "native": "Уникальные параметры", + "network_group": "Сеть", + "new certificate": "новый сертификат", + "new group": "Новая группа", + "new script": "Новый скрипт", + "new user": "Новый пользователь", + "newObject": "Новый объект", + "no-city": "без города", + "node-red": "node-red", + "none": "ничего", + "normal": "обычный", + "not ack": "Не подтв.", + "not agree": "Не согласен(на)", + "npm error": "ошибка", + "number": "Число", + "object": "Объект", + "object permissions": "Разрешения для объектов", + "of": "из", + "ok": "Ok", + "open web page": "Открыть приложение", + "os": "Oперационная система", + "other permissions": "Разрешения", + "parent name": "Имя родителя", + "password": "Пароль", + "permissionError": "Ошибка доступа", + "place here": "разместите файлы здесь", + "planned": "в планах", + "platform": "Платформа", + "point": "точка", + "popular": "по популярности", + "process": "Процесс", + "protocols_group": "Протоколы", + "raw": "Raw (Эксперт)", + "read": "читать", + "read operation": "Читать", + "readme": "Описание драйвера", + "reload": "Перезагрузить", + "reload instance": "Перезагрузить экземпляр", + "rest": "Остальные параметры (только для чтения)", + "restart": "Перезапустить", + "restart script": "Перезапустить скрипт", + "role": "Функция", + "save": "Сохранить", + "schedule_group": "Расписание", + "script_group": "Скрипты и логика", + "select member by double click": "Выберите участника двойным щелчком", + "sendto operation": "Послать к", + "service_group": "Сервисные приложения", + "severity": "важность", + "silly": "всё", + "stable": "стабильно", + "state": "Состояние", + "state permissions": "Разрешения для состояний", + "storage_group": "Хранилище", + "string": "Строка", + "subscribe": "Подписка", + "switch": "Переключатель", + "terminal": "Терминал", + "third-party_group": "Другие", + "this adapter does not allow multiple instances": "этот адаптер не разрешает несколько экземпляров", + "title": "Название", + "today": "cегодня", + "true": "да", + "ts": "Время", + "type": "Тип", + "unit": "Единицы измерения", + "update": "Обновить", + "update adapter information": "Обновить информацию драйвера", + "update-part1": "Из-за очень разного оборудования на котором может бежать yunkong2 возможно только ручное обновлени. Для ручного обновления нужно открыть командную консоль и ввести следующее:", + "updated": "по времени", + "updates": "обновления", + "upload": "Обновить файлы в базе данных", + "user permissions": "права доступа", + "users": "Пользователи", + "users permissions": "Разрешения для групп", + "utility_group": "Утилита", + "val": "Значение", + "value": "значение", + "value.from": "Изменено из", + "value.lc": "Последние изменения", + "value.q": "Код качества", + "value.ts": "Время обновления", + "value.val": "значение", + "version": "Версия", + "vis_group": "yunkong2.vis", + "visualisation_group": "Визуализация", + "visualization-icons_group": "Иконки визуализации", + "visualization-widgets_group": "Визуальные виджеты", + "visualization_group": "Визуализация", + "warn": "предупреждения", + "weather_group": "Погода", + "wetty": "Wetty", + "write": "писать", + "write operation": "Писать", + "yesterday": "вчера" +} diff --git a/src/icon_licenses/license-670580-furniture.pdf b/src/icon_licenses/license-670580-furniture.pdf new file mode 100644 index 0000000..c0c5a95 Binary files /dev/null and b/src/icon_licenses/license-670580-furniture.pdf differ diff --git a/src/img/account_circle.png b/src/img/account_circle.png new file mode 100644 index 0000000..6684916 Binary files /dev/null and b/src/img/account_circle.png differ diff --git a/src/img/admin.png b/src/img/admin.png new file mode 100644 index 0000000..ecc452e Binary files /dev/null and b/src/img/admin.png differ diff --git a/src/img/arrow-down-10x10.png b/src/img/arrow-down-10x10.png new file mode 100644 index 0000000..ff0ae0e Binary files /dev/null and b/src/img/arrow-down-10x10.png differ diff --git a/src/img/background.png b/src/img/background.png new file mode 100644 index 0000000..c284081 Binary files /dev/null and b/src/img/background.png differ diff --git a/src/img/background_small.png b/src/img/background_small.png new file mode 100644 index 0000000..593d142 Binary files /dev/null and b/src/img/background_small.png differ diff --git a/src/img/common.png b/src/img/common.png new file mode 100644 index 0000000..817889b Binary files /dev/null and b/src/img/common.png differ diff --git a/src/img/communication.png b/src/img/communication.png new file mode 100644 index 0000000..3fbcc58 Binary files /dev/null and b/src/img/communication.png differ diff --git a/src/img/devices/light-bulb.svg b/src/img/devices/light-bulb.svg new file mode 100644 index 0000000..c7a50cd --- /dev/null +++ b/src/img/devices/light-bulb.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/img/github.svg b/src/img/github.svg new file mode 100644 index 0000000..b4c91f6 --- /dev/null +++ b/src/img/github.svg @@ -0,0 +1,28 @@ + + + + + + + diff --git a/src/img/group.png b/src/img/group.png new file mode 100644 index 0000000..840ba05 Binary files /dev/null and b/src/img/group.png differ diff --git a/src/img/hardware.png b/src/img/hardware.png new file mode 100644 index 0000000..f4fbb1b Binary files /dev/null and b/src/img/hardware.png differ diff --git a/src/img/info-big.png b/src/img/info-big.png new file mode 100644 index 0000000..8f21986 Binary files /dev/null and b/src/img/info-big.png differ diff --git a/src/img/info.png b/src/img/info.png new file mode 100644 index 0000000..5b86abb Binary files /dev/null and b/src/img/info.png differ diff --git a/src/img/le.png b/src/img/le.png new file mode 100644 index 0000000..1c917fe Binary files /dev/null and b/src/img/le.png differ diff --git a/src/img/logo.png b/src/img/logo.png new file mode 100644 index 0000000..37f1ee6 Binary files /dev/null and b/src/img/logo.png differ diff --git a/src/img/logout.png b/src/img/logout.png new file mode 100644 index 0000000..ec27719 Binary files /dev/null and b/src/img/logout.png differ diff --git a/src/img/media.png b/src/img/media.png new file mode 100644 index 0000000..8e3d043 Binary files /dev/null and b/src/img/media.png differ diff --git a/src/img/no-image.png b/src/img/no-image.png new file mode 100644 index 0000000..09f11fa Binary files /dev/null and b/src/img/no-image.png differ diff --git a/src/img/pin.png b/src/img/pin.png new file mode 100644 index 0000000..46301ce Binary files /dev/null and b/src/img/pin.png differ diff --git a/src/img/rooms/006-double-bed.svg b/src/img/rooms/006-double-bed.svg new file mode 100644 index 0000000..61170de --- /dev/null +++ b/src/img/rooms/006-double-bed.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/img/rooms/015-kitchen.svg b/src/img/rooms/015-kitchen.svg new file mode 100644 index 0000000..0f7e8b7 --- /dev/null +++ b/src/img/rooms/015-kitchen.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/img/rooms/016-armchair-1.svg b/src/img/rooms/016-armchair-1.svg new file mode 100644 index 0000000..60b4117 --- /dev/null +++ b/src/img/rooms/016-armchair-1.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/src/img/rooms/022-sofa-1.svg b/src/img/rooms/022-sofa-1.svg new file mode 100644 index 0000000..73c6809 --- /dev/null +++ b/src/img/rooms/022-sofa-1.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/img/rooms/025-desk.svg b/src/img/rooms/025-desk.svg new file mode 100644 index 0000000..12ce279 --- /dev/null +++ b/src/img/rooms/025-desk.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/img/rooms/026-crib.svg b/src/img/rooms/026-crib.svg new file mode 100644 index 0000000..7fe74e6 --- /dev/null +++ b/src/img/rooms/026-crib.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/src/img/rooms/garage.svg b/src/img/rooms/garage.svg new file mode 100644 index 0000000..be6adc8 --- /dev/null +++ b/src/img/rooms/garage.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/img/rooms/toilet.svg b/src/img/rooms/toilet.svg new file mode 100644 index 0000000..fcc279e --- /dev/null +++ b/src/img/rooms/toilet.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/src/img/schedule.png b/src/img/schedule.png new file mode 100644 index 0000000..4df2a37 Binary files /dev/null and b/src/img/schedule.png differ diff --git a/src/img/script.png b/src/img/script.png new file mode 100644 index 0000000..811e015 Binary files /dev/null and b/src/img/script.png differ diff --git a/src/img/service.png b/src/img/service.png new file mode 100644 index 0000000..421bab4 Binary files /dev/null and b/src/img/service.png differ diff --git a/src/img/storage.png b/src/img/storage.png new file mode 100644 index 0000000..c61a08b Binary files /dev/null and b/src/img/storage.png differ diff --git a/src/img/vis.png b/src/img/vis.png new file mode 100644 index 0000000..f265401 Binary files /dev/null and b/src/img/vis.png differ diff --git a/src/img/visualisation.png b/src/img/visualisation.png new file mode 100644 index 0000000..f904f94 Binary files /dev/null and b/src/img/visualisation.png differ diff --git a/src/img/weather.png b/src/img/weather.png new file mode 100644 index 0000000..b7a110f Binary files /dev/null and b/src/img/weather.png differ diff --git a/src/indexEnd.html b/src/indexEnd.html new file mode 100644 index 0000000..308b1d0 --- /dev/null +++ b/src/indexEnd.html @@ -0,0 +1,2 @@ + + diff --git a/src/indexStart.html b/src/indexStart.html new file mode 100644 index 0000000..e368fd9 --- /dev/null +++ b/src/indexStart.html @@ -0,0 +1,260 @@ + + + yunkong2.admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + +
+
+
+
+
+
+
+
+ +
+ + +
+
+ + + +
+
+ + + + +
+ + + + + + +
+ diff --git a/src/js/adapter-settings.js b/src/js/adapter-settings.js new file mode 100644 index 0000000..c825483 --- /dev/null +++ b/src/js/adapter-settings.js @@ -0,0 +1,2120 @@ +var path = location.pathname; +var parts = path.split('/'); +parts.splice(-3); + +if (location.pathname.match(/^\/admin\//)) { + parts = []; +} + +var systemConfig; +var socket = io.connect('/', {path: parts.join('/') + '/socket.io'}); +var instance = window.location.search.slice(1); +var common = null; // common information of adapter +var host = null; // host object on which the adapter runs +var changed = false; +var certs = []; +var adapter = ''; +var onChangeSupported = false; +var isMaterialize = false; +var ___onChange = null; + +function preInit () { + 'use strict'; + var tmp = window.location.pathname.split('/'); + adapter = tmp[tmp.length - 2]; + var id = 'system.adapter.' + adapter + '.' + instance; + + // Extend dictionary with standard words for adapter + if (typeof systemDictionary === 'undefined') systemDictionary = {}; + + systemDictionary.save = {"en": "Save", "fr": "Sauvegarder", "nl": "Opslaan", "es": "Salvar", "pt": "Salve", "it": "Salvare", "de": "Speichern", "pl": "Zapisać", "ru": "Сохранить"}; + systemDictionary.saveclose = {"en": "Save and close", "fr": "Sauver et fermer", "nl": "Opslaan en afsluiten","es": "Guardar y cerrar", "pt": "Salvar e fechar", "it": "Salva e chiudi", "de": "Speichern und schließen", "pl": "Zapisz i zamknij", "ru": "Сохранить и выйти"}; + systemDictionary.none = {"en": "none", "fr": "aucun", "nl": "geen", "es": "ninguna", "pt": "Nenhum", "it": "nessuna", "de": "keins", "pl": "Żaden", "ru": ""}; + systemDictionary.nonerooms = {"en": "", "fr": "", "nl": "", "es": "", "pt": "", "it": "", "de": "", "pl": "", "ru": ""}; + systemDictionary.nonefunctions = {"en": "", "fr": "", "nl": "", "es": "", "pt": "", "it": "", "de": "", "pl": "", "ru": ""}; + systemDictionary.all = {"en": "all", "fr": "tout", "nl": "alle", "es": "todas", "pt": "todos", "it": "tutti", "de": "alle", "pl": "wszystko", "ru": "все"}; + systemDictionary['Device list'] = {"en": "Device list", "fr": "Liste des périphériques", "nl": "Lijst met apparaten", "es": "Lista de dispositivos", "pt": "Lista de dispositivos", "it": "Elenco dispositivi", "de": "Geräteliste", "pl": "Lista urządzeń", "ru": "Список устройств"}; + systemDictionary['new device'] = {"en": "new device", "fr": "nouvel appareil", "nl": "nieuw apparaat", "es": "Nuevo dispositivo", "pt": "Novo dispositivo", "it": "nuovo dispositivo", "de": "Neues Gerät", "pl": "nowe urządzenie", "ru": "Новое устройство"}; + systemDictionary.edit = {"en": "edit", "fr": "modifier", "nl": "Bewerk", "es": "editar", "pt": "editar", "it": "modificare", "de": "Ändern", "pl": "edytować", "ru": "Изменить"}; + systemDictionary.delete = {"en": "delete", "fr": "effacer", "nl": "Delete", "es": "borrar", "pt": "excluir", "it": "Elimina", "de": "Löschen", "pl": "kasować", "ru": "Удалить"}; + systemDictionary.pair = {"en": "pair", "fr": "paire", "nl": "paar", "es": "par", "pt": "par", "it": "paio", "de": "Verbinden", "pl": "para", "ru": "Связать"}; + systemDictionary.unpair = {"en": "unpair", "fr": "unpair", "nl": "Unpair", "es": "desvincular", "pt": "unpair", "it": "Disaccoppia", "de": "Trennen", "pl": "unpair", "ru": "Разорвать связь"}; + systemDictionary.ok = {"en": "Ok", "fr": "D'accord", "nl": "OK", "es": "De acuerdo", "pt": "Está bem", "it": "Ok", "de": "Ok", "pl": "Ok", "ru": "Ok"}; + systemDictionary.cancel = {"en": "Cancel", "fr": "Annuler", "nl": "Annuleer", "es": "Cancelar", "pt": "Cancelar", "it": "Annulla", "de": "Abbrechen", "pl": "Anuluj", "ru": "Отмена"}; + systemDictionary.Message = {"en": "Message", "fr": "Message", "nl": "Bericht", "es": "Mensaje", "pt": "Mensagem", "it": "Messaggio", "de": "Mitteilung", "pl": "Wiadomość", "ru": "Сообщение"}; + systemDictionary.close = {"en": "Close", "fr": "Fermer", "nl": "Dichtbij", "es": "Cerca", "pt": "Fechar", "it": "Vicino", "de": "Schließen", "pl": "Blisko", "ru": "Закрыть"}; + systemDictionary.htooltip = {"en": "Click for help", "fr": "Cliquez pour obtenir de l'aide", "nl": "Klik voor hulp", "es": "Haz clic para obtener ayuda", "pt": "Clique para ajuda", "it": "Fai clic per chiedere aiuto", "de": "Anklicken", "pl": "Kliknij, aby uzyskać pomoc", "ru": "Перейти по ссылке"}; + systemDictionary.saveConfig = { + "en": "Save configuration to file", + "de": "Speichern Sie die Konfiguration in der Datei", + "ru": "Сохранить конфигурацию в файл", + "pt": "Salvar configuração no arquivo", + "nl": "Sla configuratie op naar bestand", + "fr": "Enregistrer la configuration dans un fichier", + "it": "Salva la configurazione nel file", + "es": "Guardar configuración en archivo", + "pl": "Zapisz konfigurację do pliku" + }; + systemDictionary.loadConfig = { + "en": "Load configuration from file", + "de": "Laden Sie die Konfiguration aus der Datei", + "ru": "Загрузить конфигурацию из файла", + "pt": "Carregar configuração do arquivo", + "nl": "Laad de configuratie uit het bestand", + "fr": "Charger la configuration à partir du fichier", + "it": "Carica la configurazione dal file", + "es": "Cargar configuración desde archivo", + "pl": "Załaduj konfigurację z pliku" + }; + systemDictionary.otherConfig = { + "en": "Configuration from other adapter %s", + "de": "Konfiguration von anderem Adapter %s", + "ru": "Конфигурация из другого адаптера %s", + "pt": "Configuração de outro adaptador %s", + "nl": "Configuratie vanaf andere adapter %s", + "fr": "Configuration à partir d'un autre adaptateur %s", + "it": "Configurazione da altro adattatore %s", + "es": "Configuración desde otro adaptador %s", + "pl": "Konfiguracja z innego adaptera %s" + }; + systemDictionary.invalidConfig = { + "en": "Invalid JSON file", + "de": "Ungültige JSON-Datei", + "ru": "Недопустимый файл JSON", + "pt": "Arquivo JSON inválido", + "nl": "Ongeldig JSON-bestand", + "fr": "Fichier JSON non valide", + "it": "File JSON non valido", + "es": "Archivo JSON no válido", + "pl": "Nieprawidłowy plik JSON" + }; + systemDictionary.configLoaded = { + "en": "Configuration was successfully loaded", + "de": "Die Konfiguration wurde erfolgreich geladen", + "ru": "Конфигурация успешно загружена", + "pt": "Configuração foi carregada com sucesso", + "nl": "Configuratie is succesvol geladen", + "fr": "La configuration a été chargée avec succès", + "it": "La configurazione è stata caricata correttamente", + "es": "La configuración se cargó correctamente", + "pl": "Konfiguracja została pomyślnie załadowana" + }; + systemDictionary.maxTableRaw = { + "en": "Maximum number of allowed raws", + "de": "Maximale Anzahl von erlaubten Tabellenzeilen", + "ru": "Достигнуто максимальное число строк", + "it": "Numero massimo di raw consentiti", + "fr": "Nombre maximum de raw autorisés", + "nl": "Maximumaantal toegestane raws", + "pt": "Número máximo de raias permitidas", + "es": "Número máximo de raws permitidos", + "pl": "Maksymalna liczba dozwolonych surowców" + }; + systemDictionary.maxTableRawInfo = {"en": "Warning", "de": "Warnung", "ru": "Внимание", "pt": "Atenção", "nl": "Waarschuwing", "fr": "Attention", "it": "avvertimento", "es": "Advertencia", "pl": "Ostrzeżenie"}; + systemDictionary["Main settings"] = { + "en": "Main settings", + "de": "Haupteinstellungen", + "ru": "Основные настройки", + "pt": "Configurações principais", + "nl": "Belangrijkste instellingen", + "fr": "Réglages principaux", + "it": "Impostazioni principali", + "es": "Ajustes principales", + "pl": "Ustawienia główne" + }; + + systemDictionary["Let's Encrypt SSL"] = { + "en": "Let's Encrypt Certificates", + "de": "Let's Encrypt Zertifikate", + "ru": "Let's Encrypt Сертификаты", + "pt": "Let's Encrypt Certificados", + "nl": "Let's Encrypt certificaten", + "fr": "Let's Encrypt Certificats", + "it": "Let's Encrypt certificati", + "es": "Let's Encrypt Certificados", + "pl": "Let's Encrypt certyfikaty" + }; + systemDictionary["Please activate secure communication"] = { + "en": "Please activate secure communication", + "de": "Bitte sichere Kommunikation aktivieren", + "ru": "Включите безопасную связь", + "pt": "Active a comunicação segura", + "nl": "Activeer alstublieft beveiligde communicatie", + "fr": "Veuillez activer la communication sécurisée", + "it": "Si prega di attivare la comunicazione sicura", + "es": "Por favor active la comunicación segura", + "pl": "Aktywuj bezpieczną komunikację" + }; + //socket.on('connection', function () { + loadSystemConfig(function () { + if (typeof translateAll === 'function') translateAll(); + loadSettings(prepareTooltips); + }); + //}); + var $body = $('body'); + $body.wrapInner('
'); + /*$body.prepend('
' + + ' ' + + ' ' + + ' ' + + '
'); + */ + $body.append( + '
'); + + var $navButtons = $('.dialog-config-buttons'); + $navButtons.find('.btn-save').on('click', function () { + if (typeof save === 'undefined') { + alert('Please implement save function in your admin/index.html'); + return; + } + save(function (obj, common, redirect) { + if (redirect && parent && parent.adapterRedirect) { + parent.adapterRedirect(redirect); + } + saveSettings(obj, common); + }); + }); + + $navButtons.find('.btn-save-close').on('click', function () { + if (typeof save === 'undefined') { + alert('Please implement save function in your admin/index.html'); + return; + } + save(function (obj, common, redirect) { + if (redirect && parent && parent.adapterRedirect) { + parent.adapterRedirect(redirect); + } + saveSettings(obj, common, function () { + // window.close(); + if (typeof parent !== 'undefined' && parent && parent.$iframeDialog && typeof parent.$iframeDialog.close === 'function') { + parent.$iframeDialog.close(); + } + }); + }); + }); + + $navButtons.find('.btn-cancel').on('click', function () { + if (typeof parent !== 'undefined' && parent && parent.$iframeDialog && typeof parent.$iframeDialog.close === 'function') { + parent.$iframeDialog.close(); + } + }); + + function saveSettings(native, common, callback) { + if (typeof common === 'function') { + callback = common; + common = null; + } + + socket.emit('getObject', id, function (err, oldObj) { + if (!oldObj) oldObj = {}; + + for (var a in native) { + if (native.hasOwnProperty(a)) { + oldObj.native[a] = native[a]; + } + } + + if (common) { + for (var b in common) { + if (common.hasOwnProperty(b)) { + oldObj.common[b] = common[b]; + } + } + } + + if (onChangeSupported) { + $navButtons.find('.btn-save').addClass('disabled'); + $navButtons.find('.btn-save-close').addClass('disabled'); + $navButtons.find('.btn-cancel').find('span').html(_('close')); + } + + socket.emit('setObject', id, oldObj, function (err) { + if (err) { + showMessage(err, _('Error'), 'alert'); + return; + } + changed = false; + if (callback) callback(); + }); + }); + } + + // Read language settings + function loadSystemConfig(callback) { + socket.emit('getObject', 'system.config', function (err, res) { + if (!err && res && res.common) { + systemLang = res.common.language || systemLang; + systemConfig = res; + } + socket.emit('getObject', 'system.certificates', function (err, res) { + if (!err && res) { + if (res.native && res.native.certificates) { + certs = []; + for (var c in res.native.certificates) { + if (!res.native.certificates.hasOwnProperty(c) || !res.native.certificates[c]) continue; + + // If it is filename, it could be everything + if (res.native.certificates[c].length < 700 && (res.native.certificates[c].indexOf('/') !== -1 || res.native.certificates[c].indexOf('\\') !== -1)) { + var __cert = { + name: c, + type: '' + }; + if (c.toLowerCase().indexOf('private') !== -1) { + __cert.type = 'private'; + } else if (res.native.certificates[c].toLowerCase().indexOf('private') !== -1) { + __cert.type = 'private'; + } else if (c.toLowerCase().indexOf('public') !== -1) { + __cert.type = 'public'; + } else if (res.native.certificates[c].toLowerCase().indexOf('public') !== -1) { + __cert.type = 'public'; + } + certs.push(__cert); + continue; + } + + var _cert = { + name: c, + type: (res.native.certificates[c].substring(0, '-----BEGIN RSA PRIVATE KEY'.length) === '-----BEGIN RSA PRIVATE KEY' || res.native.certificates[c].substring(0, '-----BEGIN PRIVATE KEY'.length) === '-----BEGIN PRIVATE KEY') ? 'private' : 'public' + }; + if (_cert.type === 'public') { + var m = res.native.certificates[c].split('-----END CERTIFICATE-----'); + var count = 0; + for (var _m = 0; _m < m.length; _m++) { + if (m[_m].replace(/\r\n|\r|\n/, '').trim()) count++; + } + if (count > 1) _cert.type = 'chained'; + } + + certs.push(_cert); + } + } + } + if (callback) callback(); + }); + }); + } + + // callback if something changed + function onChange(isChanged) { + onChangeSupported = true; + if (typeof isChanged === 'boolean' && isChanged === false) { + changed = false; + $navButtons.find('.btn-save').addClass('disabled'); + $navButtons.find('.btn-save-close').addClass('disabled'); + $navButtons.find('.btn-cancel').find('span').html(_('close')); + } else { + changed = true; + $navButtons.find('.btn-save').removeClass('disabled'); + $navButtons.find('.btn-save-close').removeClass('disabled'); + $navButtons.find('.btn-cancel').find('span').html(_('cancel')); + } + } + + function loadSettings(callback) { + socket.emit('getObject', id, function (err, res) { + if (!err && res && res.native) { + $('.adapter-instance').html(adapter + '.' + instance); + $('.adapter-config').html('system.adapter.' + adapter + '.' + instance); + common = res.common; + if (res.common && res.common.name) $('.adapter-name').html(res.common.name); + if (typeof load === 'undefined') { + alert('Please implement save function in your admin/index.html'); + } else { + load(res.native, onChange); + // init selects + if (isMaterialize) { + $('select').select(); + M.updateTextFields(); + + // workaround for materialize checkbox problem + $('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + } + } + if (typeof callback === 'function') { + callback(); + } + } else { + if (typeof callback === 'function') { + callback(); + } + alert('error loading settings for ' + id + '\n\n' + err); + } + }); + } + ___onChange = onChange; +} + +$(document).ready(function () { + 'use strict'; + + if (window.location.pathname.indexOf('/index_m.html') === -1) { + // load materialize + var cssLink = document.createElement('link'); + cssLink.href = '../../lib/css/materialize.css'; + cssLink.type = 'text/css'; + cssLink.rel = 'stylesheet'; + cssLink.media = 'screen,print'; + document.getElementsByTagName('head')[0].appendChild(cssLink); + + // load materialize.js + var jsLink = document.createElement('script'); + jsLink.onload = preInit; + jsLink.src = '../../lib/js/materialize.js'; + document.head.appendChild(jsLink); + } else { + isMaterialize = true; + preInit(); + } +}); + +function handleFileSelect(evt) { + var f = evt.target.files[0]; + if (f) { + var r = new FileReader(); + r.onload = function(e) { + var contents = e.target.result; + try { + var json = JSON.parse(contents); + if (json.native && json.common) { + if (json.common.name !== common.name) { + showError(_('otherConfig', json.common.name)); + } else { + load(json.native, ___onChange); + // init selects + if (isMaterialize) { + $('select').select(); + M.updateTextFields(); + + // workaround for materialize checkbox problem + $('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + } + ___onChange(); + showToast(null, _('configLoaded')); + } + } else { + showError(_('invalidConfig')); + } + } catch (e) { + showError(e.toString()); + } + }; + r.readAsText(f); + } else { + alert('Failed to open JSON File'); + } +} + +function generateFile(filename, obj) { + var el = document.createElement('a'); + el.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(obj, null, 2))); + el.setAttribute('download', filename); + + el.style.display = 'none'; + document.body.appendChild(el); + + el.click(); + + document.body.removeChild(el); +} + +function prepareTooltips() { + if (isMaterialize) { + // init tabs + $('.tabs').mtabs(); + + var buttonsText = ''; + if (common && common.readme) { + buttonsText += ' ' + + ' live_help' + + ' '; + } + + buttonsText += ' ' + + ' file_upload' + + ' '; + + buttonsText += ' ' + + ' file_download' + + ' '; + + + if (buttonsText) { + // add help link after logo + var $logo = $('.logo').first().parent(); + if ($logo.length) { + $('').insertAfter($logo); + } + $('.adapter-config-load').click(function () { + var input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.setAttribute('id', 'files'); + input.setAttribute('opacity', 0); + input.addEventListener('change', function (e) { + handleFileSelect(e, function () {}); + }, false); + (input.click)(); + }); + $('.adapter-config-save').click(function () { + save(function (native, cmn) { + var result = { + _id: 'system.adapter.' + common.name + '.' + instance, + common: JSON.parse(JSON.stringify(common)), + native: native + }; + // remove unimportant information + if (result.common.news) { + delete result.common.news; + } + if (result.common.titleLang) { + delete result.common.titleLang; + } + if (result.common.desc) { + delete result.common.desc; + } + if (cmn) { + for (var b in cmn) { + if (cmn.hasOwnProperty(b)) { + result.common[b] = cmn[b]; + } + } + } + + //window.open('data:application/yunkong2; content-disposition=attachment; filename=' + result._id + '.json,' + JSON.stringify(result, null, 2)); + generateFile(result._id + '.json', result); + }); + }) + } + + $('.value').each(function () { + var $this = $(this); + + // replace all labels after checkboxes to span (bug in materialize) + if ($this.attr('type') === 'checkbox') { + $this.addClass('filled-in'); + + var $label = $this.next(); + if ($label.prop('tagName') === 'LABEL') { + $label.replaceWith('' + $label.html() +''); + $label = $this.next(); + } + + $label.off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + } + + var id = $this.data('id'); + var tooltip = ''; + if (systemDictionary['tooltip_' + id]) { + tooltip = systemDictionary['tooltip_' + id][systemLang] || systemDictionary['tooltip_' + id].en; + } + + var link = $this.data('link'); + if (link && common) { + if (link === true) { + if (common.readme) { + link = common.readme + '#' + id; + } else { + link = 'https://github.com/yunkong2/yunkong2.' + common.name + '#' + id; + } + } + if (!link.match('^https?:\/\/')) { + if (common.readme) { + link = common.readme + '#' + link; + } else { + link = 'https://github.com/yunkong2/yunkong2.' + common.name + '#' + link; + } + } + } + + if (link) { + $('live_help').insertBefore($this); + } else if (tooltip) { + $('help_outline').insertBefore($this); + } + }); + } else { + $('.admin-icon').each(function () { + var id = $(this).data('id'); + if (!id) { + var $prev = $(this).prev(); + var $input = $prev.find('input'); + if (!$input.length) $input = $prev.find('select'); + if (!$input.length) $input = $prev.find('textarea'); + + if (!$input.length) { + $prev = $prev.parent(); + $input = $prev.find('input'); + if (!$input.length) $input = $prev.find('select'); + if (!$input.length) $input = $prev.find('textarea'); + } + if ($input.length) id = $input.attr('id'); + } + + if (!id) return; + + var tooltip = ''; + if (systemDictionary['tooltip_' + id]) { + tooltip = systemDictionary['tooltip_' + id][systemLang] || systemDictionary['tooltip_' + id].en; + } + + var icon = ''; + var link = $(this).data('link'); + if (link && common) { + if (link === true) { + if (common.readme) { + link = common.readme + '#' + id; + } else { + link = 'https://github.com/yunkong2/yunkong2.' + common.name + '#' + id; + } + } + if (!link.match('^https?:\/\/')) { + if (common.readme) { + link = common.readme + '#' + link; + } else { + link = 'https://github.com/yunkong2/yunkong2.' + common.name + '#' + link; + } + } + icon += ''; + } else if (tooltip) { + icon += ''; + } + + if (icon) { + $(this).html(icon); + } + }); + $('.admin-text').each(function () { + var id = $(this).data('id'); + if (!id) { + var $prev = $(this).prev(); + var $input = $prev.find('input'); + if (!$input.length) $input = $prev.find('select'); + if (!$input.length) $input = $prev.find('textarea'); + if (!$input.length) { + $prev = $prev.parent(); + $input = $prev.find('input'); + if (!$input.length) $input = $prev.find('select'); + if (!$input.length) $input = $prev.find('textarea'); + } + if ($input.length) id = $input.attr('id'); + } + + if (!id) return; + + // check if translation for this exist + if (systemDictionary['info_' + id]) { + $(this).html('' + (systemDictionary['info_' + id][systemLang] || systemDictionary['info_' + id].en) + ''); + } + }); + } +} + +function showMessageJQ(message, title, icon, width) { + var $dialogMessage = $('#dialog-message-settings'); + if (!$dialogMessage.length) { + $('body').append(''); + $dialogMessage = $('#dialog-message-settings'); + $dialogMessage.dialog({ + autoOpen: false, + modal: true, + buttons: [ + { + text: _('Ok'), + click: function () { + $(this).dialog('close'); + } + } + ] + }); + } + $dialogMessage.dialog('option', 'width', width + 500); + + if (typeof _ !== 'undefined') { + $dialogMessage.dialog('option', 'title', title || _('Message')); + } else { + $dialogMessage.dialog('option', 'title', title || 'Message'); + } + $('#dialog-message-text-settings').html(message); + if (icon) { + $('#dialog-message-icon-settings') + .show() + .attr('class', '') + .addClass('ui-icon ui-icon-' + icon); + } else { + $('#dialog-message-icon-settings').hide(); + } + $dialogMessage.dialog('open'); +} + +function showMessage(message, title, icon) { + if (!isMaterialize) { + return showMessageJQ(message, title, icon); + } + var $dialogMessage; + // noinspection JSJQueryEfficiency + $dialogMessage = $('#dialog-message'); + if (!$dialogMessage.length) { + $('body').append( + '
'); + $dialogMessage = $('#dialog-message'); + } + if (icon) { + $dialogMessage.find('.dialog-icon') + .show() + .html(icon); + } else { + $dialogMessage.find('.dialog-icon').hide(); + } + if (title) { + $dialogMessage.find('.dialog-title').html(title).show(); + } else { + $dialogMessage.find('.dialog-title').hide(); + } + $dialogMessage.find('.dialog-text').html(message); + $dialogMessage.modal().modal('open'); +} + +function confirmMessageJQ(message, title, icon, buttons, callback) { + var $dialogConfirm = $('#dialog-confirm-settings'); + if (!$dialogConfirm.length) { + $('body').append(''); + $dialogConfirm = $('#dialog-confirm-settings'); + $dialogConfirm.dialog({ + autoOpen: false, + modal: true + }); + } + if (typeof buttons === 'function') { + callback = buttons; + $dialogConfirm.dialog('option', 'buttons', [ + { + text: _('Ok'), + click: function () { + var cb = $(this).data('callback'); + $(this).data('callback', null); + $(this).dialog('close'); + if (cb) cb(true); + } + }, + { + text: _('Cancel'), + click: function () { + var cb = $(this).data('callback'); + $(this).data('callback', null); + $(this).dialog('close'); + if (cb) cb(false); + } + } + + ]); + } else if (typeof buttons === 'object') { + for (var b = 0; b < buttons.length; b++) { + buttons[b] = { + text: buttons[b], + id: 'dialog-confirm-button-' + b, + click: function (e) { + var id = parseInt(e.currentTarget.id.substring('dialog-confirm-button-'.length), 10); + var cb = $(this).data('callback'); + $(this).data('callback', null); + $(this).dialog('close'); + if (cb) cb(id); + } + } + } + $dialogConfirm.dialog('option', 'buttons', buttons); + } + + $dialogConfirm.dialog('option', 'title', title || _('Message')); + $('#dialog-confirm-text-settings').html(message); + if (icon) { + $('#dialog-confirm-icon-settings') + .show() + .attr('class', '') + .addClass('ui-icon ui-icon-' + icon); + } else { + $('#dialog-confirm-icon-settings').hide(); + } + $dialogConfirm.data('callback', callback); + $dialogConfirm.dialog('open'); +} + +function confirmMessage(message, title, icon, buttons, callback) { + if (!isMaterialize) { + return confirmMessageJQ(message, title, icon, buttons, callback); + } + + var $dialogConfirm; + // noinspection JSJQueryEfficiency + $dialogConfirm = $('#dialog-confirm'); + if (!$dialogConfirm.length) { + $('body').append( + '
' + ); + $dialogConfirm = $('#dialog-confirm'); + } + if (typeof buttons === 'function') { + callback = buttons; + $dialogConfirm.find('.modal-footer').html( + '' + _('Ok') + '' + + '' + _('Cancel') + ''); + $dialogConfirm.find('.modal-footer .modal-action').on('click', function () { + var cb = $dialogConfirm.data('callback'); + cb && cb($(this).data('result')); + }); + } else if (typeof buttons === 'object') { + var tButtons = ''; + for (var b = buttons.length - 1; b >= 0; b--) { + tButtons += '' + buttons[b] + ''; + } + $dialogConfirm.find('.modal-footer').html(tButtons); + $dialogConfirm.find('.modal-footer .modal-action').on('click', function () { + var cb = $dialogConfirm.data('callback'); + cb && cb($(this).data('id')); + }); + } + + $dialogConfirm.find('.dialog-title').text(title || _('Please confirm')); + if (icon) { + $dialogConfirm.find('.dialog-icon') + .show() + .html(icon); + } else { + $dialogConfirm.find('.dialog-icon').hide(); + } + if (title) { + $dialogConfirm.find('.dialog-title').html(title).show(); + } else { + $dialogConfirm.find('.dialog-title').hide(); + } + $dialogConfirm.find('.dialog-text').html(message); + $dialogConfirm.data('callback', callback); + $dialogConfirm.modal({ + dismissible: false + }).modal('open'); +} + +function showError(error) { + showMessage(_(error), _('Error'), 'error_outline'); +} + +function showToast(parent, message, icon, duration, isError, classes) { + if (typeof parent === 'string') { + classes = isError; + isError = duration; + icon = message; + message = parent; + parent = null; + } + if (parent && parent instanceof jQuery) { + parent = parent[0]; + } + classes = classes || []; + + if (typeof classes === 'string') { + classes = [classes]; + } + isError && classes.push('dropZone-error'); + + M.toast({ + parentSelector: parent || $('body')[0], + html: message + (icon ? '' + icon + '' : ''), + displayLength: duration || 3000, + classes: classes + }); +} + +function getObject(id, callback) { + socket.emit('getObject', id, function (err, res) { + if (!err && res) { + if (callback) callback(err, res); + } else { + if (callback) callback(null); + } + }); +} + +function getState(id, callback) { + socket.emit('getState', id, function (err, res) { + if (!err && res) { + if (callback) callback(err, res); + } else { + if (callback) callback(null); + } + }); +} + +function getEnums(_enum, callback) { + socket.emit('getObjectView', 'system', 'enum', {startkey: 'enum.' + _enum, endkey: 'enum.' + _enum + '.\u9999'}, function (err, res) { + if (!err && res) { + var _res = {}; + for (var i = 0; i < res.rows.length; i++) { + if (res.rows[i].id === 'enum.' + _enum) continue; + _res[res.rows[i].id] = res.rows[i].value; + } + if (callback) callback(null, _res); + } else { + if (callback) callback(err, []); + } + }); +} + +function getGroups(callback) { + socket.emit('getObjectView', 'system', 'group', {startkey: 'system.group.', endkey: 'system.group.\u9999'}, function (err, res) { + if (!err && res) { + var _res = {}; + for (var i = 0; i < res.rows.length; i++) { + _res[res.rows[i].id] = res.rows[i].value; + } + if (callback) callback(null, _res); + } else { + if (callback) callback(err, []); + } + }); +} + +function getUsers(callback) { + socket.emit('getObjectView', 'system', 'user', {startkey: 'system.user.', endkey: 'system.user.\u9999'}, function (err, res) { + if (!err && res) { + var _res = {}; + for (var i = 0; i < res.rows.length; i++) { + _res[res.rows[i].id] = res.rows[i].value; + } + if (callback) callback(null, _res); + } else { + if (callback) callback(err, []); + } + }); +} + +function fillUsers(elemId, current, callback) { + getUsers(function (err, users) { + // Answer is like + // { + // "admin": { <<=== This is a common.name! + // "type": "user", + // "common": { + // "name": "admin", + // "password": "aaa", + // "dontDelete": true, + // "enabled": true, + // "icon": "data:image/png;base64,xxx", + // "color": "#ca0808", + // "desc": "" + // }, + // "_id": "system.user.admin" + // }, + // ... + // } + + var text = ''; + // Warning u is name of user and not ID. + var len = 'system.user.'.length; + for (var u in users) { + if (users.hasOwnProperty(u)) { + var id = users[u]._id.substring(len); + text += '\n'; + } + } + $(elemId).html(text); + if (isMaterialize) { + $(elemId).select(); + } + }); +} + +function getIPs(host, callback) { + if (typeof host === 'function') { + callback = host; + host = null; + } + + socket.emit('getHostByIp', host || common.host, function (ip, _host) { + if (_host) { + host = _host; + var IPs4 = [{name: '[IPv4] 0.0.0.0 - ' + _('Listen on all IPs'), address: '0.0.0.0', family: 'ipv4'}]; + var IPs6 = [{name: '[IPv6] ::', address: '::', family: 'ipv6'}]; + if (host.native.hardware && host.native.hardware.networkInterfaces) { + for (var eth in host.native.hardware.networkInterfaces) { + if (!host.native.hardware.networkInterfaces.hasOwnProperty(eth)) continue; + for (var num = 0; num < host.native.hardware.networkInterfaces[eth].length; num++) { + if (host.native.hardware.networkInterfaces[eth][num].family !== 'IPv6') { + IPs4.push({name: '[' + host.native.hardware.networkInterfaces[eth][num].family + '] ' + host.native.hardware.networkInterfaces[eth][num].address + ' - ' + eth, address: host.native.hardware.networkInterfaces[eth][num].address, family: 'ipv4'}); + } else { + IPs6.push({name: '[' + host.native.hardware.networkInterfaces[eth][num].family + '] ' + host.native.hardware.networkInterfaces[eth][num].address + ' - ' + eth, address: host.native.hardware.networkInterfaces[eth][num].address, family: 'ipv6'}); + } + } + } + } + for (var i = 0; i < IPs6.length; i++) { + IPs4.push(IPs6[i]); + } + callback(IPs4); + } + }); +} + +function fillSelectIPs(id, actualAddr, noIPv4, noIPv6, callback) { + getIPs(function (ips) { + var str = ''; + for (var i = 0; i < ips.length; i++) { + if (noIPv4 && ips[i].family === 'ipv4') continue; + if (noIPv6 && ips[i].family === 'ipv6') continue; + str += ''; + } + + $(id).html(str); + if (isMaterialize) { + $(id).select(); + } + if (typeof callback === 'function') { + callback(); + } + }); +} + +function sendTo(_adapter_instance, command, message, callback) { + socket.emit('sendTo', (_adapter_instance || adapter + '.' + instance), command, message, callback); +} + +function sendToHost(host, command, message, callback) { + socket.emit('sendToHost', host || common.host, command, message, callback); +} + +function getInterfaces(onlyNames, callback) { + if (typeof onlyNames === 'function') { + callback = onlyNames; + onlyNames = false; + } + + socket.emit('sendToHost', common.host, 'getInterfaces', null, function (result) { + if (result && result.result) { + if (onlyNames) { + callback(null, Object.keys(result.result)); + } else { + callback(null, result); + } + } else { + callback((result && result.error) || 'cannot read'); + } + }); +} + +// fills select with names of the certificates and preselect it +function fillSelectCertificates(id, type, actualValued) { + var str = ''; + for (var i = 0; i < certs.length; i++) { + if (certs[i].type && certs[i].type !== type) continue; + str += ''; + } + + $(id).html(str); + if (isMaterialize) { + $(id).select(); + } + +} + +function getAdapterInstances(_adapter, callback) { + if (typeof _adapter === 'function') { + callback = _adapter; + _adapter = null; + } + + socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.' + (_adapter || adapter), endkey: 'system.adapter.' + (_adapter || adapter) + '.\u9999'}, function (err, doc) { + if (err) { + if (callback) callback ([]); + } else { + if (doc.rows.length === 0) { + if (callback) callback ([]); + } else { + var res = []; + for (var i = 0; i < doc.rows.length; i++) { + res.push(doc.rows[i].value); + } + if (callback) callback(res); + } + } + + }); +} + +function getExtendableInstances(_adapter, callback) { + if (typeof _adapter === 'function') { + callback = _adapter; + _adapter = null; + } + + socket.emit('getObjectView', 'system', 'instance', null, function (err, doc) { + if (err) { + if (callback) callback ([]); + } else { + if (doc.rows.length === 0) { + if (callback) callback ([]); + } else { + var res = []; + for (var i = 0; i < doc.rows.length; i++) { + if (doc.rows[i].value.common.webExtendable) { + res.push(doc.rows[i].value); + } + } + if (callback) callback (res); + } + } + }); +} + +function getIsAdapterAlive(_adapter, callback) { + if (typeof _adapter === 'function') { + callback = _adapter; + _adapter = null; + } + getState('system.adapter.' + (_adapter || adapter) + '.' + instance + '.alive', function (err, obj) { + if (!obj || !obj.val) { + callback(false); + } else { + callback(true); + } + }); +} + +// Adds one entry to the created with editTable table +// tabId - the id of the table used by editTable +// value - is one object to add in form {ip: '3.3.3.3', room: 'enum.room.bla3', desc: 'Bla3'} +// $grid - [optional] - object returned by editTable to speed up the addition +// _isInitial - [optional] - if it is initial fill of the table. To not trigger the onChange +function addToTable(tabId, value, $grid, _isInitial) { + $grid = $grid || $('#' + tabId); + var obj = {_id: $grid[0]._maxIdx++}; + var cols = $grid[0]._cols; + + for (var i = 0; i < cols.length; i++) { + if (cols[i] === 'room') { + obj[cols[i]] = ($grid[0]._rooms[value[cols[i]]]) ? $grid[0]._rooms[value[cols[i]]].common.name : value[cols[i]]; + } else { + obj[cols[i]] = value[cols[i]]; + } + } + obj._commands = + '' + + '' + + '' + + ''; + + $grid.jqGrid('addRowData', tabId + '_' + obj._id, obj); + + _editInitButtons($grid, tabId, obj._id); + + if (!_isInitial && $grid[0]._onChange) $grid[0]._onChange('add', value); +} + +function _editInitButtons($grid, tabId, objId) { + var search = objId ? '[data-' + tabId + '-id="' + objId + '"]' : ''; + + $('.' + tabId + '-edit-submit' + search).off('click').button({ + icons: {primary: 'ui-icon-pencil'}, + text: false + }).on('click', function () { + var id = $(this).attr('data-' + tabId + '-id'); + + $('.' + tabId + '-edit-submit').hide(); + $('.' + tabId + '-delete-submit').hide(); + $('.' + tabId + '-ok-submit[data-' + tabId + '-id="' + id + '"]').show(); + $('.' + tabId + '-cancel-submit[data-' + tabId + '-id="' + id + '"]').show(); + + $grid.jqGrid('editRow', tabId + '_' + id, {url: 'clientArray'}); + if ($grid[0]._edited.indexOf(id) === -1) { + $grid[0]._edited.push(id); + } + changed = true; + var $navButtons = $('.dialog-config-buttons'); + $navButtons.find('.btn-save').removeClass('disabled'); + $navButtons.find('.btn-save-close').removeClass('disabled'); + if (onChangeSupported) { + $navButtons.find('.btn-cancel').find('span').html(_('cancel')); + } + }).css({'height': '18px', width: '22px'}); + + $('.' + tabId + '-delete-submit' + search).off('click').button({ + icons: {primary: 'ui-icon-trash'}, + text: false + }).on('click', function () { + var id = $(this).attr('data-' + tabId + '-id'); + $grid.jqGrid('delRowData', tabId + '_' + id); + + changed = true; + var $navButtons = $('.dialog-config-buttons'); + $navButtons.find('.btn-save').removeClass('disabled'); + $navButtons.find('.btn-save-close').removeClass('disabled'); + if (onChangeSupported) { + $navButtons.find('.btn-cancel').find('span').html(_('cancel')); + } + + var pos = $grid[0]._edited.indexOf(id); + if (pos !== -1) { + $grid[0]._edited.splice(pos, 1); + } + if ($grid[0]._onChange) $grid[0]._onChange('del', id); + }).css({'height': '18px', width: '22px'}); + + $('.' + tabId + '-ok-submit' + search).off('click').button({ + icons: {primary: 'ui-icon-check'}, + text: false + }).on('click', function () { + var id = $(this).attr('data-' + tabId + '-id'); + + $('.' + tabId + '-edit-submit').show(); + $('.' + tabId + '-delete-submit').show(); + $('.' + tabId + '-ok-submit').hide(); + $('.' + tabId + '-cancel-submit').hide(); + + $grid.jqGrid('saveRow', tabId + '_' + id, {url: 'clientArray'}); + + changed = true; + var $navButtons = $('.dialog-config-buttons'); + $navButtons.find('.btn-save').removeClass('disabled'); + $navButtons.find('.btn-save-close').removeClass('disabled'); + if (onChangeSupported) { + $navButtons.find('.btn-cancel').find('span').html(_('cancel')); + } + + var pos = $grid[0]._edited.indexOf(id); + if (pos !== -1) { + $grid[0]._edited.splice(pos, 1); + } + if ($grid[0]._onChange) $grid[0]._onChange('changed', $grid.jqGrid('getRowData', tabId + '_' + id)); + }).css({'height': '18px', width: '22px'}); + + $('.' + tabId + '-cancel-submit' + search).off('click').button({ + icons: {primary: 'ui-icon-close'}, + text: false + }).on('click', function () { + var id = $(this).attr('data-' + tabId + '-id'); + + $('.' + tabId + '-edit-submit').show(); + $('.' + tabId + '-delete-submit').show(); + $('.' + tabId + '-ok-submit').hide(); + $('.' + tabId + '-cancel-submit').hide(); + + $grid.jqGrid('restoreRow', tabId + '_' + id, false); + var pos = $grid[0]._edited.indexOf(id); + if (pos !== -1) { + $grid[0]._edited.splice(pos, 1); + } + }).css({'height': '18px', width: '22px'}); +} + +function _editTable(tabId, cols, values, rooms, top, onChange) { + var title = 'Device list'; + if (typeof tabId === 'object') { + cols = tabId.cols; + values = tabId.values; + rooms = tabId.rooms; + top = tabId.top; + onChange = tabId.onChange; + if (tabId.title) title = tabId.title; + tabId = tabId.tabId; + } + + initGridLanguage(systemLang); + var colNames = []; + var colModel = []; + var $grid = $('#' + tabId); + var room; + + colNames.push('id'); + colModel.push({ + name: '_id', + index: '_id', + hidden: true + }); + for (var i = 0; i < cols.length; i++) { + var width = null; + var checkbox = null; + + if (typeof cols[i] === 'object') { + width = cols[i].width; + if (cols[i].checkbox) checkbox = true; + cols[i] = cols[i].name; + } + colNames.push(_(cols[i])); + var _obj = { + name: cols[i], + index: cols[i], +// width: 160, + editable: true + }; + if (width) _obj.width = width; + if (checkbox) { + _obj.edittype = 'checkbox'; + _obj.editoptions = {value: 'true:false'}; + } + + if (cols[i] === 'room') { + var list = {'': _('none')}; + for (room in rooms) { + list[room] = _(translateName(rooms[room].common.name)); + } + _obj.stype = 'select'; + _obj.edittype = 'select'; + _obj.editoptions = {value: list}; + _obj.searchoptions = { + sopt: ['eq'], + value: ':' + _('all') + }; + for (room in rooms) { + _obj.searchoptions.value += ';' + _(translateName(rooms[room].common.name)) + ':' + _(translateName(rooms[room].common.name)); + } + } + colModel.push(_obj); + } + colNames.push(''); + colModel.push({name: '_commands', index: '_commands', width: 50, editable: false, align: 'center', search: false}); + + $grid[0]._cols = cols; + $grid[0]._rooms = rooms; + $grid[0]._maxIdx = 0; + $grid[0]._top = top; + $grid[0]._edited = []; + $grid[0]._onChange = onChange; + + $grid.jqGrid({ + datatype: 'local', + colNames: colNames, + colModel: colModel, + width: 800, + height: 330, + pager: $('#pager-' + tabId), + rowNum: 1000, + rowList: [1000], + ondblClickRow: function (rowid) { + var id = rowid.substring((tabId + '_').length); + $('.' + tabId + '-edit-submit').hide(); + $('.' + tabId + '-delete-submit').hide(); + $('.' + tabId + '-ok-submit[data-' + tabId + '-id="' + id + '"]').show(); + $('.' + tabId + '-cancel-submit[data-' + tabId + '-id="' + id + '"]').show(); + $grid.jqGrid('editRow', rowid, {url: 'clientArray'}); + if ($grid[0]._edited.indexOf(id) === -1) $grid[0]._edited.push(id); + + changed = true; + var $navButtons = $('.dialog-config-buttons'); + $navButtons.find('.btn-save').removeClass('disabled'); + $navButtons.find('.btn-save-close').removeClass('disabled'); + if (onChangeSupported) { + $navButtons.find('.btn-cancel').find('span').html(_('cancel')); + } + }, + sortname: 'id', + sortorder: 'desc', + viewrecords: false, + pgbuttons: false, + pginput: false, + pgtext: false, + caption: _(title), + ignoreCase: true, + loadComplete: function () { + _editInitButtons($grid, tabId); + }, + onSortCol: function () { + changed = true; + var $navButtons = $('.dialog-config-buttons'); + $navButtons.find('.btn-save').removeClass('disabled'); + $navButtons.find('.btn-save-close').removeClass('disabled'); + if (onChangeSupported) { + $navButtons.find('.btn-cancel').find('span').html(_('cancel')); + } + } + }).jqGrid('filterToolbar', { + defaultSearch: 'cn', + autosearch: true, + searchOnEnter: false, + enableClear: false, + afterSearch: function () { + _editInitButtons($grid, tabId); + } + }); + + $('#pager-' + tabId + '_center').hide(); + + if ($('#pager-' + tabId).length) { + $grid.navGrid('#pager-' + tabId, { + search: false, + edit: false, + add: false, + del: false, + refresh: false + }).jqGrid('navButtonAdd', '#pager-' + tabId, { + caption: '', + buttonicon: 'ui-icon-plus', + onClickButton: function () { + // Find new unique name + var found; + var newText = _('New'); + var ids = $grid.jqGrid('getDataIDs'); + var idx = 1; + var obj; + do { + found = true; + for (var _id = 0; _id < ids.length; _id++) { + obj = $grid.jqGrid('getRowData', ids[_id]); + if (obj && obj[$grid[0]._cols[0]] === newText + idx) { + idx++; + found = false; + break; + } + } + } while (!found); + + obj = {}; + for (var t = 0; t < $grid[0]._cols.length; t++) { + obj[$grid[0]._cols[t]] = ''; + } + obj[$grid[0]._cols[0]] = newText + idx; + + changed = true; + var $navButtons = $('.dialog-config-buttons'); + $navButtons.find('.btn-save').removeClass('disabled'); + $navButtons.find('.btn-save-close').removeClass('disabled'); + if (onChangeSupported) { + $navButtons.find('.btn-cancel').find('span').html(_('cancel')); + } + + addToTable(tabId, obj, $grid); + }, + position: 'first', + id: 'add-cert', + title: _('new device'), + cursor: 'pointer' + }); + } + + if (values) { + for (var u = 0; u < values.length; u++) { + addToTable(tabId, values[u], $grid, true); + } + } + $(window).on('resize', function () { + $grid.setGridHeight($(this).height() - top).setGridWidth($(this).width() - 10); + }); + $(window).trigger('resize'); + + // hide scrollbar + $('.ui-jqgrid-bdiv').css({'overflow-x': 'hidden'}); + + return $grid; +} + +// converts "enum.room.Sleeping_room" to "Sleeping room" +// As input gets the list from getEnum +function enumName2Id(enums, name) { + name = name.toLowerCase(); + for (var enumId in enums) { + if (!enums.hasOwnProperty(enumId)) continue; + if (enums[enumId] && enums[enumId].common && enums[enumId].common.name) { + if (typeof enums[enumId].common.name === 'object') { + for (var lang in enums[enumId].common.name) { + if (enums[enumId].common.name.hasOwnProperty(lang) && enums[enumId].common.name[lang].toLowerCase() === name) { + return enumId; + } + } + } else { + if (enums[enumId].common.name && enums[enumId].common.name.toLowerCase() === name) return enumId; + } + } + if (enums[enumId] && enums[enumId].name) { + if (typeof enums[enumId].name === 'object') { + for (var lang in enums[enumId].name) { + if (enums[enumId].name.hasOwnProperty(lang) && enums[enumId].name[lang].toLowerCase() === name) { + return enumId; + } + } + } else { + if (enums[enumId].name.toLowerCase() === name) return enumId; + } + } + } + return ''; +} + +// Creates edit table for any configuration array +// tabId - is id of table where the jqGrid must be created. E.g:
+// cols - array with names of the properties of entry. E.g: ['ip', 'room', 'desc'] +// if column has name room, for that will be automatically the room enums loaded and shown +// values - array with values in form [{ip: '1.1.1.1', room: 'enum.room.bla1', desc: 'Bla1'}, {ip: '2.2.2.2', room: 'enum.room.bla2', desc: 'Bla2'} +// top - top position of the table to set the height of the table automatically. Table must be always as last on the page. +// onChange - callback called if something is changed in the table +// +// returns the jquery object of $('#tabId') +// To extract data from table +function editTable(tabId, cols, values, top, onChange) { + if (typeof tabId === 'object') { + cols = tabId.cols; + } + + if (cols.indexOf('room') !== -1) { + getEnums('rooms', function (err, list) { + return _editTable(tabId, cols, values, list, top, onChange); + }); + } else { + return _editTable(tabId, cols, values, null, top, onChange); + } +} + +// Extract edited array from table +// tabId - is id of table where the jqGrid must be created. E.g:
+// cols - array with names of the properties of entry. E.g: ['ip', 'room', 'desc'] +// +// Returns array with values +function getTableResult(tabId, cols) { + var $grid = $('#' + tabId); + for (var j = 0; j < $grid[0]._edited.length; j++) { + $grid.jqGrid('saveRow', tabId + '_' + $grid[0]._edited[j], {url: 'clientArray'}); + } + + $('.' + tabId + '-edit-submit').show(); + $('.' + tabId + '-delete-submit').show(); + $('.' + tabId + '-ok-submit').hide(); + $('.' + tabId + '-cancel-submit').hide(); + + var data = $grid.jqGrid('getRowData'); + var res = []; + for (var i = 0; i < data.length; i++) { + var obj = {}; + for (var z = 0; z < cols.length; z++) { + if (cols[z] === 'room') { + obj[cols[z]] = enumName2Id($grid[0]._rooms, data[i][cols[z]]); + } else { + obj[cols[z]] = data[i][cols[z]]; + } + } + res.push(obj); + } + return res; +} + +/* +
+ + + + + + + + + + + +
ContextRoomRoomEnabled
+ + */ + +/** + * Create edit table from javascript array. + * + * This function creates a html edit table. + * + *

+ *   
+ * + *
+ * + * + * + * + * + * + * + * + * + * + * + *
ContextContextRoomRoomEnabled
+ *
+ *
+ *

+ *
+ * @param {string} divId name of the html element (or empty).
+ * @param {string} values data array
+ * @param {function} onChange this function will be called if something changed
+ * @param {function} onReady called, when the table is ready (may be to modify some elements of it)
+ * @param {number} maxRaw maximal number of rows
+ * @return {object} array with values
+ */
+function values2table(divId, values, onChange, onReady, maxRaw) {
+    if (typeof values === 'function') {
+		typeof onChange === 'number' ? maxRaw = onChange : maxRaw = null;
+        onChange = values;
+        values   = divId;
+        divId    = '';
+    }
+	
+	if (typeof onReady === 'number') {
+        maxRaw = onReady;
+        onReady = null;
+    } else if (typeof maxRaw === 'undefined') {
+        maxRaw = null;
+    }
+
+    values = values || [];
+    var names = [];
+    var $div;
+    if (!divId) {
+        $div = $('body');
+    } else {
+        $div = $('#' + divId);
+    }
+    var $add = $div.find('.table-button-add');
+	$add.data('raw', values.length);
+	
+	if (maxRaw) {
+	    $add.data('maxraw', maxRaw);
+    }
+
+    if (!$add.data('inited')) {
+        $add.data('inited', true);
+
+        var addText = $add.text();
+
+        if (!isMaterialize) {
+            $add.button({
+                icons: {primary: 'ui-icon-plus'},
+                text: !!addText,
+                label: addText ? _(addText) : undefined
+            });
+        }
+
+        $add.on('click', function () {
+            if (!$add.data('maxraw') || ($add.data('raw') < $add.data('maxraw'))) {
+                var $table = $div.find('.table-values');
+                var values = $table.data('values');
+                var names  = $table.data('names');
+                var obj = {};
+                for (var i = 0; i < names.length; i++) {
+                    if (!names[i]) continue;
+                    obj[names[i].name] = names[i].def;
+                }
+                values.push(obj);
+                onChange && onChange();
+                setTimeout(function () {
+                    values2table(divId, values, onChange, onReady);
+                }, 100);
+                $add.data('raw', $add.data('raw') + 1);
+            } else {
+                confirmMessage(_('maxTableRaw') + ': ' + $add.data('maxraw'), _('maxTableRawInfo'), 'alert', ['Ok']);
+            }
+        });
+    }
+
+    if (values) {
+        var buttons = [];
+        var $table = $div.find('.table-values');
+        $table.data('values', values);
+
+        // load rooms
+        if (!$table.data('rooms') && $table.find('th[data-name="room"]').length) {
+            getEnums('rooms', function (err, list) {
+                var result = {};
+                var trRooms = _('nonerooms');
+				if (trRooms !== 'nonerooms') {
+                    result[_('none')] = trRooms;
+                } else {
+                    result[_('none')] = '';
+                }
+                var nnames = [];
+                for (var n in list) {
+                    if (list.hasOwnProperty(n)) {
+                        nnames.push(n);
+                    }
+                }
+                nnames.sort(function (a, b) {
+                    a = a.toLowerCase();
+                    b = b.toLowerCase();
+                    if (a > b) return 1;
+                    if (a < b) return -1;
+                    return 0;
+                });
+
+                for (var l = 0; l < nnames.length; l++) {
+                    result[nnames[l]] = list[nnames[l]].common.name || l;
+                    if (typeof result[nnames[l]] === 'object') {
+                        result[nnames[l]] = result[nnames[l]][systemLang] || result[nnames[l]].en;
+                    }
+                }
+                $table.data('rooms', result);
+                values2table(divId, values, onChange, onReady);
+            });
+            return;
+        }
+        // load functions
+        if (!$table.data('functions') && $table.find('th[data-name="func"]').length) {
+            getEnums('functions', function (err, list) {
+                var result = {};
+                var trFuncs = _('nonefunctions');
+				if (trFuncs !== 'nonefunctions') {
+                    result[_('none')] = trFuncs;
+                } else {
+                    result[_('none')] = '';
+                }
+
+                var nnames = [];
+                for (var n in list) {
+                    if (list.hasOwnProperty(n)) {
+                        nnames.push(n);
+                    }
+                }
+                nnames.sort(function (a, b) {
+                    a = a.toLowerCase();
+                    b = b.toLowerCase();
+                    if (a > b) return 1;
+                    if (a < b) return -1;
+                    return 0;
+                });
+
+                for (var l = 0; l < nnames.length; l++) {
+                    result[nnames[l]] = list[nnames[l]].common.name || l;
+                    if (typeof result[nnames[l]] === 'object') {
+                        result[nnames[l]] = result[nnames[l]][systemLang] || result[nnames[l]].en;
+                    }
+                }
+                $table.data('functions', result);
+                values2table(divId, values, onChange, onReady);
+            });
+            return;
+        }
+        $table.find('th').each(function () {
+            var name = $(this).data('name');
+            if (name) {
+                var obj = {
+                    name:    name,
+                    type:    $(this).data('type') || 'text',
+                    def:     $(this).data('default'),
+                    style:   $(this).data('style'),
+					tdstyle: $(this).data('tdstyle')					 
+                };
+                if (obj.type === 'checkbox') {
+                    if (obj.def === 'false') obj.def = false;
+                    if (obj.def === 'true')  obj.def = true;
+                    obj.def = !!obj.def;
+                } else if (obj.type === 'select' || obj.type === 'select multiple') {
+                    var vals = ($(this).data('options') || '').split(';');
+                    obj.options = {};
+                    for (var v = 0; v < vals.length; v++) {
+                        var parts = vals[v].split('/');
+                        obj.options[parts[0]] = _(parts[1] || parts[0]);
+                        if (v === 0) obj.def = (obj.def === undefined) ? parts[0] : obj.def;
+                    }
+                } else {
+                    obj.def = obj.def || '';
+                }
+                names.push(obj);
+            } else {
+                names.push(null);
+            }
+
+            name = $(this).data('buttons');
+
+            if (name) {
+                var bs = name.split(' ');
+                buttons.push(bs);
+            } else {
+                buttons.push(null);
+            }
+        });
+
+        $table.data('names', names);
+
+        var text = '';
+        for (var v = 0; v < values.length; v++) {
+            var idName = values[v] && values[v].id;
+            if (!idName && values[v]) {
+                if (names[0] === '_index') {
+                    idName = values[v][names[1]];
+                } else {
+                    idName = values[v][names[0]];
+                }
+            }
+            text += '';
+
+            for (var i = 0; i < names.length; i++) {
+                text += '';
+                        if (isMaterialize) {
+                            line += '';
+                        }
+                    } else if (names[i].type.substring(0, 6) === 'select') {                        
+                        line += (names[i].type.substring(7, 16) === 'multiple' ? '';
+                    } else {
+                        line += '';
+                    }
+                }
+
+                if (buttons[i]) {
+                    style = 'text-align: center; white-space: nowrap;';
+                    for (var b = 0; b < buttons[i].length; b++) {
+                        if ((!v && buttons[i][b] === 'up') || (v === values.length - 1 && buttons[i][b] === 'down')) {
+                            if (isMaterialize) {
+                                line += 'add';
+                            } else {
+                                line += '';
+                            }
+                        } else {
+                            if (isMaterialize) {
+                                line += 'add';
+                            } else {
+                                line += '';
+                            }
+                        }
+                    }
+                }
+                if (style.length || tdstyle.length) {
+                    text += ' style="' + style + tdstyle + '">' + line + '';
+                } else {
+                    text += '>' + line + '';
+                }						
+            }
+
+            text += '';
+        }
+        var $lines = $div.find('.table-lines');
+        if (!$lines.length) {
+            $table.append('');
+            $lines = $div.find('.table-lines');
+        }
+
+        $lines.html(text);
+
+        $lines.find('.values-input').each(function () {
+            var $this = $(this);
+            var type = $this.attr('type');
+            var name = $this.data('name');
+            var id = $this.data('index');
+            $this.data('old-value', values[id][name]);
+            if (type === 'checkbox') {
+                $this.prop('checked', values[id][name]);
+            } else {
+                $this.val(values[id][name]);
+            }
+        });
+        $lines.find('.values-buttons').each(function () {
+            var command = $(this).data('command');
+            if (command === 'copy') {
+                if (!isMaterialize) {
+                    $(this).button({
+                        icons: {primary: 'ui-icon-copy'},
+                        text: false
+                    })
+                        .css({width: '1em', height: '1em'});
+                } else {
+                    $(this).find('i').html('content_copy');
+                }
+
+                $(this).on('click', function () {
+                    if (!$add.data('maxraw') || ($add.data('raw') < $add.data('maxraw'))) {
+                        var id = $(this).data('index');
+                        var elem = JSON.parse(JSON.stringify(values[id]));
+                        values.push(elem);
+                        onChange && onChange();
+
+                        setTimeout(function () {
+                            if (typeof tableEvents === 'function') {
+                                tableEvents(values.length - 1, elem, 'add');
+                            }
+
+                            values2table(divId, values, onChange, onReady);
+                        }, 100);
+
+                        if ($add.data('maxraw')) {
+                            $add.data('raw', $add.data('raw') + 1);
+                        }
+                    }
+                });
+            } else
+            if (command === 'delete') {
+                if (!isMaterialize) {
+                    $(this).button({
+                        icons: {primary: 'ui-icon-trash'},
+                        text: false
+                    })
+                        .css({width: '1em', height: '1em'});
+                } else {
+                    $(this).addClass('red').find('i').html('delete');
+                }
+
+                $(this).on('click', function () {
+                    var id = $(this).data('index');
+                    var elem = values[id];
+                    values.splice(id, 1);
+                    onChange && onChange();
+
+                    setTimeout(function () {
+                        if (typeof tableEvents === 'function') {
+                            tableEvents(id, elem, 'delete');
+                        }
+
+                        values2table(divId, values, onChange, onReady);
+                    }, 100);
+
+                    if ($add.data('maxraw')) {
+                        $add.data('raw', $add.data('raw') - 1);
+                    }
+                });
+            } else if (command === 'up') {
+                if (!isMaterialize) {
+                    $(this).button({
+                        icons: {primary: 'ui-icon-triangle-1-n'},
+                        text: false
+                    })
+                        .css({width: '1em', height: '1em'})
+                } else {
+                    $(this).find('i').html('arrow_upward');
+                }
+                $(this).on('click', function () {
+                    var id = $(this).data('index');
+                    var elem = values[id];
+                    values.splice(id, 1);
+                    values.splice(id - 1, 0, elem);
+                    onChange && onChange();
+                    setTimeout(function () {
+                        values2table(divId, values, onChange, onReady);
+                    }, 100);
+                });
+            } else if (command === 'down') {
+                if (!isMaterialize) {
+                    $(this).button({
+                        icons: {primary: 'ui-icon-triangle-1-s'},
+                        text: false
+                    })
+                        .css({width: '1em', height: '1em'});
+                } else {
+                    $(this).find('i').html('arrow_downward');
+                }
+                $(this).on('click', function () {
+                    var id = $(this).data('index');
+                    var elem = values[id];
+                    values.splice(id, 1);
+                    values.splice(id + 1, 0, elem);
+                    onChange && onChange();
+                    setTimeout(function () {
+                        values2table(divId, values, onChange, onReady);
+                    }, 100);
+                });
+            } else if (command === 'pair') {
+                if (!isMaterialize) {
+                    $(this).button({
+                        icons: {primary: 'ui-icon-transferthick-e-w'},
+                        text: false
+                    })
+                        .css({width: '1em', height: '1em'});
+                } else {
+                    $(this).find('i').html('leak_add');
+                }
+                $(this).on('click', function () {
+                    if (typeof tableEvents === 'function') {
+                        var id = $(this).data('index');
+                        var elem = values[id];
+                        tableEvents(id, elem, 'pair');
+                    }
+                }).attr('title', _('pair'));
+            } else if (command === 'unpair') {
+                if (!isMaterialize) {
+                    $(this).button({
+                        icons: {primary: 'ui-icon-scissors'},
+                        text: false
+                    })
+                        .css({width: '1em', height: '1em'});
+                } else {
+                    $(this).find('i').html('leak_remove');
+                }
+                $(this).on('click', function () {
+                    if (typeof tableEvents === 'function') {
+                        var id = $(this).data('index');
+                        var elem = values[id];
+                        tableEvents(id, elem, 'unpair');
+                    }
+                }).attr('title', _('unpair'));
+            } else if (command === 'edit') {
+                if (!isMaterialize) {
+                    $(this).button({
+                        icons: {primary: 'ui-icon-pencil'},
+                        text: false
+                    })
+                        .css({width: '1em', height: '1em'});
+                } else {
+                    $(this).find('i').html('edit');
+                }
+                $(this).on('click', function () {
+                    var id = $(this).data('index');
+                    if (typeof editLine === 'function') {
+                        setTimeout(function () {
+                            editLine(id, JSON.parse(JSON.stringify(values[id])), function (err, id, newValues) {
+                                if (!err) {
+                                    if (JSON.stringify(values[id]) !== JSON.stringify(newValues)) {
+                                        onChange && onChange();
+                                        values[id] = newValues;
+                                        values2table(divId, values, onChange, onReady);
+                                    }
+                                }
+                            });
+                        }, 100);
+                    }
+                });
+            }
+        });
+
+        $lines.find('.values-input').on('change.adaptersettings', function () {
+            if ($(this).attr('type') === 'checkbox') {
+                if ($(this).prop('checked').toString() !== $(this).data('old-value')) onChange();
+                values[$(this).data('index')][$(this).data('name')] = $(this).prop('checked');
+            } else {
+                if ($(this).val() !== $(this).data('old-value')) onChange();
+                values[$(this).data('index')][$(this).data('name')] = $(this).val();
+            }
+        }).on('keyup', function () {
+            $(this).trigger('change.adaptersettings');
+        });
+    }
+    if (isMaterialize) {
+        if (!divId) {
+            M.updateTextFields();
+            $('select').select();
+        } else {
+            M.updateTextFields('#' + divId);
+            $('#' + divId).find('select').select();
+        }
+
+        // workaround for materialize checkbox problem
+        $div.find('input[type="checkbox"]+span').off('click').on('click', function () {
+            var $input = $(this).prev();
+            if (!$input.prop('disabled')) {
+                $input.prop('checked', !$input.prop('checked')).trigger('change');
+            }
+        });
+    }
+    if (typeof onReady === 'function') onReady();
+}
+
+/**
+ * Extract the values from table.
+ *
+ * This function extracts the values from edit table, that was generated with values2table function.
+ *
+ * @param {string} divId name of the html element (or nothing).
+ * @return {object} array with values
+ */
+function table2values(divId) {
+    var $div;
+    if (!divId) {
+        $div = $('body');
+    } else {
+        $div = $('#' + divId);
+    }
+    var names = [];
+    $div.find('.table-values th').each(function () {
+        var name = $(this).data('name');
+        if (name) {
+            names.push(name);
+        } else {
+            names.push('___ignore___');
+        }
+    });
+
+    var values = [];
+    var j = 0;
+    $div.find('.table-lines tr').each(function () {
+        values[j] = {};
+
+        $(this).find('td').each(function () {
+            var $input = $(this).find('input');
+            if ($input.length) {
+                var name = $input.data('name');
+                if (name) {
+                    if ($input.attr('type') === 'checkbox') {
+                        values[j][name] = $input.prop('checked');
+                    } else {
+                        values[j][name] = $input.val();
+                    }
+                }
+            }
+            var $select = $(this).find('select');
+            if ($select.length) {
+                var name = $select.data('name');
+                values[j][name] = $select.val() || '';
+            }
+        });
+        j++;
+    });
+
+    return values;
+}
diff --git a/src/js/admin.js b/src/js/admin.js
new file mode 100644
index 0000000..ba8282b
--- /dev/null
+++ b/src/js/admin.js
@@ -0,0 +1,2198 @@
+/* jshint -W097 */
+/* jshint strict:true */
+/* jslint vars: true */
+/* global io:false */
+/* global jQuery:false */
+/* jslint browser:true */
+/* jshint browser:true */
+/* global _ */
+/* global ace */
+/* global console */
+/* global alert */
+/* global confirm */
+/* global systemLang: true */
+/* global license */
+/* global translateAll */
+/* global initGridLanguage */
+'use strict';
+
+//if (typeof Worker === 'undefined') alert('your browser does not support WebWorkers :-(');
+
+Array.prototype.remove = function () {
+    var what;
+    var a = arguments;
+    var L = a.length;
+    var ax;
+    while (L && this.length) {
+        what = a[--L];
+        while ((ax = this.indexOf(what)) !== -1) {
+            this.splice(ax, 1);
+        }
+    }
+    return this;
+};
+// for IE
+if (!console.debug) {
+    console.debug = console.log;
+}
+if (typeof Number === 'undefined') {
+    console.log('define Number');
+    Number = function (obj) {
+        return parseFloat(obj);
+    }
+}
+if (!Object.assign) {
+    Object.assign = $.extend;
+}
+
+
+var $iframeDialog = null; // used in adapter settings window
+var configNotSaved = null; // used in adapter settings window
+var showConfig = null; // used in adapter settings window
+var defaults = {};
+var customPostInits = {};
+var FORBIDDEN_CHARS = /[\]\[*,;'"`<>\\\s?]/g;
+
+// used in adapter settings window
+var adapterRedirect = function (redirect, timeout) {
+    if (redirect) {
+        setTimeout(function () {
+            redirect += document.location.pathname;
+            redirect += document.location.hash;
+            document.location.href = redirect;
+        }, timeout || 5000);
+    }
+};
+var gMain = null; // for google maps
+
+function detectIE() {
+    var ua = window.navigator.userAgent;
+
+    var msie = ua.indexOf('MSIE ');
+    if (msie > 0) {
+        // IE 10 or older => return version number
+        return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
+    }
+
+    var trident = ua.indexOf('Trident/');
+    if (trident > 0) {
+        // IE 11 => return version number
+        var rv = ua.indexOf('rv:');
+        return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
+    }
+
+    var edge = ua.indexOf('Edge/');
+    if (edge > 0) {
+        // Edge (IE 12+) => return version number
+        return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
+    }
+
+    // other browser
+    return false;
+}
+
+(function ($) {
+$(document).ready(function () {
+    var path = location.pathname + 'socket.io';
+    if (location.pathname.match(/^\/admin\//)) {
+        path = '/socket.io';
+    }
+
+    var allTabs = {};
+
+    var main = {
+        objects:        {},
+        states:         {},
+        currentHost:    '',
+        currentTab:     null,
+        currentDialog:  null,
+        currentUser:    '',
+        subscribesStates: {},
+        subscribesObjects: {},
+        subscribesLogs: 0,
+        socket:         io.connect('/', {path: path}),
+        systemConfig:   null,
+        instances:      null,
+        objectsLoaded:  false,
+        waitForRestart: false,
+        tabs:           null,
+        dialogs:        {},
+        selectId:       null,
+        config:         {},
+        ignoreJSupdate: false, // set to true after some global script updated and till system.adapter.javascript.x updated
+        addEventMessage: function (id, stateOrObj, isMessage, isState) {
+            // cannot directly use tabs.events.add, because to init time not available.
+            tabs.events.add(id, stateOrObj, isMessage, isState);
+        },
+        saveConfig:     function (attr, value) {
+            if (attr) main.config[attr] = value;
+
+            if (typeof storage !== 'undefined') {
+                storage.set('adminConfig', JSON.stringify(main.config));
+            }
+        },
+        saveTabs:       function () {
+            this.socket.emit ('setObject', 'system.config', this.systemConfig, function (err) {
+                if (err) {
+                    this.showError (err);
+                }
+            })
+        },
+
+        // Helper methods
+        upToDate:       function (_new, old) {
+            _new = _new.split('.');
+            old = old.split('.');
+            _new[0] = parseInt(_new[0], 10);
+            old[0] = parseInt(old[0], 10);
+            if (_new[0] > old[0]) {
+                return false;
+            } else if (_new[0] === old[0]) {
+                _new[1] = parseInt(_new[1], 10);
+                old[1] = parseInt(old[1], 10);
+                if (_new[1] > old[1]) {
+                    return false;
+                } else if (_new[1] === old[1]) {
+                    _new[2] = parseInt(_new[2], 10);
+                    old[2] = parseInt(old[2], 10);
+                    return (_new[2] <= old[2]);
+                } else {
+                    return true;
+                }
+            } else {
+                return true;
+            }
+        },
+
+        // Methods
+        cmdExec:        function (host, cmd, callback) {
+            host = host || main.currentHost;
+            $stdout.val('');
+
+            $dialogCommand.modal('open');
+
+            stdout = '$ ./yunkong2 ' + cmd;
+            $dialogCommand.data('finished', false).find('.btn').html(_('In background'));
+            $dialogCommand.find('.command').html(stdout);
+            $dialogCommand.find('.progress-dont-close').removeClass('disabled');
+            $adminSideMain.find('.button-command').removeClass('error').addClass('in-progress');
+            $dialogCommand.data('max', null);
+            $dialogCommand.data('error', '');
+            $dialogCommandProgress.addClass('indeterminate').removeClass('determinate');
+
+            if (cmd.match(/^upload /)) {
+                $dialogCommand.find('.progress-text').html(_('Upload started...')).removeClass('error');
+            } else if (cmd.match(/^del [-_\w\d]+\.[\d]+$/)) {
+                $dialogCommand.find('.progress-text').html(_('Removing of instance...')).removeClass('error');
+            } else if (cmd.match(/^del /)) {
+                $dialogCommand.find('.progress-text').html(_('Removing of adapter...')).removeClass('error');
+            } else if (cmd.match(/^url /)) {
+                $dialogCommand.find('.progress-text').html(_('Install or update from URL...')).removeClass('error');
+            }  else if (cmd.match(/^add /)) {
+                $dialogCommand.find('.progress-text').html(_('Add instance...')).removeClass('error');
+            } else{
+                $dialogCommand.find('.progress-text').html(_('Started...')).removeClass('error');
+            }
+
+            $stdout.val(stdout);
+            // generate the unique id to coordinate the outputs
+            activeCmdId = Math.floor(Math.random() * 0xFFFFFFE) + 1;
+            cmdCallback = callback;
+            main.socket.emit('cmdExec', host, activeCmdId, cmd, function (err) {
+                if (err) {
+                    stdout += '\n' + _(err);
+                    $stdout.val(stdout);
+                    cmdCallback = null;
+                    callback(err);
+                } else {
+                    if (callback) callback();
+                }
+            });
+        },
+        confirmMessage: function (message, title, icon, buttons, callback) {
+            // if standard buttons
+            if (typeof buttons === 'function') {
+                callback = buttons;
+                $dialogConfirm.find('.modal-footer').html(
+                    '' + _('Ok') + '' +
+                    '' + _('Cancel') + '');
+                $dialogConfirm.find('.modal-footer .modal-action').on('click', function () {
+                    var cb = $dialogConfirm.data('callback');
+                    cb && cb($(this).data('result'));
+                });
+            } else if (typeof buttons === 'object') {
+                var tButtons = '';
+                for (var b = buttons.length - 1; b >= 0; b--) {
+                    tButtons += '' + buttons[b] + '';
+                }
+                $dialogConfirm.find('.modal-footer').html(tButtons);
+                $dialogConfirm.find('.modal-footer .modal-action').on('click', function () {
+                    var cb = $dialogConfirm.data('callback');
+                    cb && cb($(this).data('id'));
+                });
+            }
+
+            $dialogConfirm.find('.dialog-title').text(title || _('Please confirm'));
+            if (icon) {
+                $dialogConfirm.find('.dialog-icon')
+                    .show()
+                    .html(icon);
+            } else {
+                $dialogConfirm.find('.dialog-icon').hide();
+            }
+            $dialogConfirm.find('.dialog-text').html(message);
+            $dialogConfirm.data('callback', callback);
+            $dialogConfirm.modal('open');
+        },
+        showMessage:    function (message, title, icon) {
+            $dialogMessage.find('.dialog-title').text(title || _('Message'));
+            if (icon) {
+                $dialogMessage.find('.dialog-icon')
+                    .show()
+                    .html(icon);
+            } else {
+                $dialogMessage.find('.dialog-icon').hide();
+            }
+            $dialogMessage.find('.dialog-text').html(message);
+            $dialogMessage.modal('open');
+        },
+        showError:      function (error) {
+            main.showMessage(_(error),  _('Error'), 'error_outline');
+        },
+        showToast:      function (parent, message, icon, duration, isError, classes) {
+            if (parent && parent instanceof jQuery) {
+                parent = parent[0];
+            }
+            classes = classes || [];
+
+            if (typeof classes === 'string') {
+                classes = [classes];
+            }
+            isError && classes.push('dropZone-error');
+
+            M.toast({
+                parentSelector: parent || $('body')[0],
+                html:           message + (icon ? '' + icon + '' : ''),
+                displayLength:  duration || 3000,
+                classes:        classes
+            });
+        },
+        formatDate:     function (dateObj, justTime) {
+            //return dateObj.getFullYear() + '-' +
+            //    ("0" + (dateObj.getMonth() + 1).toString(10)).slice(-2) + '-' +
+            //    ("0" + (dateObj.getDate()).toString(10)).slice(-2) + ' ' +
+            //    ("0" + (dateObj.getHours()).toString(10)).slice(-2) + ':' +
+            //    ("0" + (dateObj.getMinutes()).toString(10)).slice(-2) + ':' +
+            //    ("0" + (dateObj.getSeconds()).toString(10)).slice(-2);
+            // Following implementation is 5 times faster
+            if (!dateObj) return '';
+            var text = typeof dateObj;
+            if (text === 'string') {
+                if (justTime) {
+                    return dateObj.substring(8);
+                } else {
+                    return dateObj;
+                }
+            }
+            // if less 2000.01.01 00:00:00
+            if (text !== 'object') dateObj = dateObj < 946681200000 ? new Date(dateObj * 1000) : new Date(dateObj);
+
+            var v;
+            if (!justTime) {
+                text = dateObj.getFullYear();
+                v = dateObj.getMonth() + 1;
+                if (v < 10) {
+                    text += '-0' + v;
+                } else {
+                    text += '-' + v;
+                }
+
+                v = dateObj.getDate();
+                if (v < 10) {
+                    text += '-0' + v;
+                } else {
+                    text += '-' + v;
+                }
+            } else {
+                v = dateObj.getDate();
+                if (v < 10) {
+                    text = '0' + v;
+                } else {
+                    text = v;
+                }
+            }
+
+            v = dateObj.getHours();
+            if (v < 10) {
+                text += ' 0' + v;
+            } else {
+                text += ' ' + v;
+            }
+            v = dateObj.getMinutes();
+            if (v < 10) {
+                text += ':0' + v;
+            } else {
+                text += ':' + v;
+            }
+
+            v = dateObj.getSeconds();
+            if (v < 10) {
+                text += ':0' + v;
+            } else {
+                text += ':' + v;
+            }
+
+            v = dateObj.getMilliseconds();
+            if (v < 10) {
+                text += '.00' + v;
+            } else if (v < 100) {
+                text += '.0' + v;
+            } else {
+                text += '.' + v;
+            }
+
+            return text;
+        },
+        /*initSelectId:   function () {
+            if (main.selectId) return main.selectId;
+            main.selectId = $('#dialog-select-member').selectId('init',  {
+                objects: main.objects,
+                states:  main.states,
+                filter: {type: 'state'},
+                name:   'admin-select-member',
+                texts: {
+                    select:   _('Select'),
+                    cancel:   _('Cancel'),
+                    all:      _('All'),
+                    id:       _('ID'),
+                    name:     _('Name'),
+                    role:     _('Role'),
+                    room:     _('Room'),
+                    value:    _('Value'),
+                    selectid: _('Select ID'),
+                    from:     _('From'),
+                    lc:       _('Last changed'),
+                    ts:       _('Time stamp'),
+                    wait:     _('Processing...'),
+                    ack:      _('Acknowledged')
+                },
+                columns: ['image', 'name', 'role', 'room', 'value']
+            });
+            return main.selectId;
+        },*/
+        updateWizard:   function () {
+            var $wizard = $('#button-wizard');
+            if (main.objects['system.adapter.discovery.0']) {
+                if (!$wizard.data('inited')) {
+                    $wizard.data('inited', true);
+                    $wizard/*.button({
+                        icons: {primary: ' ui-icon-search'},
+                        text: false
+                    })*/.on('click', function () {
+                        // open configuration dialog
+                        main.navigate({
+                            tab: 'instances',
+                            dialog: 'config',
+                            params: 'system.adapter.discovery.0'
+                        });
+                    }).attr('title', _('Device discovery'));
+                }
+                $wizard.show();
+
+                // Show wizard dialog
+                if (!main.systemConfig.common.wizard && main.systemConfig.common.licenseConfirmed) {
+                    $wizard.trigger('click');
+                }
+            } else {
+                $wizard.hide();
+            }
+        },
+        getUser:        function () {
+            if (!main.currentUser) {
+                main.socket.emit('authEnabled', function (auth, user) {
+                    main.currentUser = 'system.user.' + user;
+                    if (!auth) {
+                        $('#button-logout').remove();
+                    } else {
+                        main._lastTimer = (new Date()).getTime();
+                        monitor();
+                    }
+                });
+            } else if (main.objects[main.currentUser]) {
+                var obj = main.objects[main.currentUser];
+                var name = '';
+                if (!obj || !obj.common || !obj.common.name) {
+                    name = main.currentUser.replace(/^system\.user\./);
+                    name = name[0].toUpperCase() + name.substring(1).toLowerCase();
+                } else {
+                    name = translateName(obj.common.name);
+                }
+                if (obj && obj.common && obj.common.icon) {
+                    var objs = {};
+                    objs[main.currentUser] = obj;
+                    $('#current-user-icon').html(main.getIcon(main.currentUser, null, objs));
+                } else {
+                    $('#current-user-icon').html('account_circle');
+                }
+                $('#current-user').html(name);
+                var groups = [];
+                for (var i = 0; i < tabs.users.groups.length; i++) {
+                    var group = main.objects[tabs.users.groups[i]];
+                    if (group && group.common && group.common.members && group.common.members.indexOf(main.currentUser) !== -1) {
+                        groups.push(_(translateName(group.common.name)));
+                    }
+                }
+                $('#current-group').html(groups.join(', '));
+            }
+        },
+
+        // Delete objects
+        _delObject:     function (idOrList, callback) {
+            var id;
+            if (!Array.isArray(idOrList)) {
+                if (typeof idOrList !== 'string') return callback && callback('invalid idOrList parameter');
+                idOrList = [idOrList];
+            }
+
+            function doIt() {
+                if (idOrList.length === 0) {
+                    return callback && setTimeout(callback, 0, null, id);
+                }
+                id = idOrList.pop();
+                if (main.objects[id] && main.objects[id].common && (main.objects[id].common['object-non-deletable'] || main.objects[id].common.dontDelete)) {
+                    main.showMessage (_ ('Cannot delete "%s" because not allowed', id), '', 'notifications');
+                    setTimeout(doIt, 0);
+                } else {
+                    var obj = main.objects[id];
+                    main.socket.emit('delObject', id, function (err) {
+                        if (err && err !== 'Not exists') {
+                            main.showError (err);
+                            return callback(err);
+                        }
+                        if (obj && obj.type === 'state') {
+                            main.socket.emit ('delState', id, function (err) {
+                                if (err && err !== 'Not exists') {
+                                    main.showError (err);
+                                    return callback(err);
+                                }
+                                setTimeout(doIt, 0);
+                            });
+                        } else {
+                            setTimeout(doIt, 0);
+                        }
+                    });
+                }
+            }
+            doIt();
+        },
+        /*_delObject_old: function (idOrList, callback) {*
+            var id;
+            if (typeof idOrList === 'object') {
+                if (!idOrList || !idOrList.length) {
+                    if (callback) callback(null);
+                    return;
+                }
+                id = idOrList.pop();
+            } else {
+                id = idOrList;
+            }
+
+            if (main.objects[id] && main.objects[id].common && (main.objects[id].common['object-non-deletable'] || main.objects[id].common.dontDelete)) {
+                main.showMessage(_('Cannot delete "%s" because not allowed', id), '', 'notice');
+                if (typeof idOrList === 'object') {
+                    setTimeout(function () {
+                        this._delObject(idOrList, callback);
+                    }.bind(this), 0);
+                } else {
+                    if (callback) {
+                        setTimeout(function () {
+                            callback(null, idOrList);
+                        }, 0);
+                    }
+                }
+            } else {
+                var obj = main.objects[id];
+                main.socket.emit('delObject', id, function (err) {
+                    if (err && err !=='Not exists') {
+                        main.showError(err);
+                        return;
+                    }
+                    if (obj && obj.type === 'state') {
+                        main.socket.emit('delState', id, function (err) {
+                            if (err && err !=='Not exists') {
+                                main.showError(err);
+                                return;
+                            }
+                            if (typeof idOrList === 'object') {
+                                setTimeout(function () {
+                                    this._delObject(idOrList, callback);
+                                }.bind(this), 0);
+                            } else {
+                                if (callback) {
+                                    setTimeout(function () {
+                                        callback(null, idOrList);
+                                    }, 0);
+                                }
+                            }
+                        }.bind(this));
+                    } else {
+                        if (typeof idOrList === 'object') {
+                            setTimeout(function () {
+                                this._delObject(idOrList, callback);
+                            }.bind(this), 0);
+                        } else {
+                            if (callback) {
+                                setTimeout(function () {
+                                    callback(null, idOrList);
+                                }, 0);
+                            }
+                        }
+                    }
+                }.bind(this));
+            }
+        },*/
+        _delObjects:    function (rootId, isAll, callback) {
+            if (!isAll) {
+                this._delObject(rootId, callback);
+            } else {
+                var list = [];
+                for (var id in main.objects) {
+                    if (main.objects.hasOwnProperty(id) && id.substring(0, rootId.length + 1) === rootId + '.') {
+                        list.push(id);
+                    }
+                }
+                list.push(rootId);
+                list.sort();
+
+                this._delObject(list, function () {
+                    if (callback) callback();
+                });
+            }
+        },
+        delObject:      function ($tree, id, callback) {
+            var leaf = $tree ? $tree.selectId('getTreeInfo', id) : null;
+            if (main.objects[id]) {
+                if (leaf && leaf.children) {
+                    // ask if only object must be deleted or just this one
+                    main.confirmMessage(_('Do you want to delete just one object or all children of %s too?', id), null, 'help_outline', [_('_All'), _('Only one'), _('Cancel')], function (result) {
+                        // If all
+                        if (result === 0) {
+                            main._delObjects(id, true, callback);
+                        } else
+                        // if only one object
+                        if (result === 1) {
+                            main._delObjects(id, false, callback);
+                        } // else do nothing
+                    });
+                } else {
+                    main.confirmMessage(_('Are you sure to delete %s?', id), null, 'help_outline', function (result) {
+                        // If all
+                        if (result) main._delObjects(id, true, callback);
+                    });
+                }
+            } else if (leaf && leaf.children) {
+                main.confirmMessage(_('Are you sure to delete all children of %s?', id), null, 'help_outline', function (result) {
+                    // If all
+                    if (result) main._delObjects(id, true, callback);
+                });
+            } else {
+                main.showMessage(_('Object "%s" does not exists. Update the page.', id), _('Error'), 'help_outline', function (result) {
+                    // If all
+                    if (result) main._delObjects(id, true, callback);
+                });
+            }
+        }
+    };
+
+    gMain = main; // for google maps
+
+    var tabs = {
+        hosts:      new Hosts(main), // must be first to read the list of hosts
+        objects:    new Objects(main),
+        adapters:   new Adapters(main),
+        instances:  new Instances(main),
+        users:      new Users(main),
+        //groups:     new Groups(main),
+        enums:      new Enums(main),
+        events:     new Events(main),
+        logs:       new Logs(main),
+        states:     null,
+        intro:      new Intro(main)
+    };
+
+    if (typeof States !== 'undefined') {
+        tabs.states = new States(main);
+    }
+
+    main.instances     = tabs.instances.list;
+    main.tabs          = tabs;
+    main.dialogs       = {
+        system:     new System(main),
+        customs:    new Customs(main),
+        config:     new Config(main),
+        editobject: new EditObject(main),
+        issue:      new Issue(main),
+        readme:     new Readme(main)
+    };
+
+    var stdout;
+    var cmdCallback    = null;
+    var activeCmdId    = null;
+    var $stdout        = $('#stdout');
+
+    var $dialogCommand = $('#dialog-command');
+    var $dialogLicense = $('#dialog-license-main');
+    var $dialogMessage = $('#dialog-message');
+    var $dialogConfirm = $('#dialog-confirm');
+    var $dialogCommandProgress = $dialogCommand.find('.progress div');
+
+    var $adminSideMenu = $('#admin_sidemenu_menu');
+    var $adminSideMain = $('#admin_sidemenu_main');
+
+    var firstConnect   = true;
+
+    // detect touch devices
+    if (!('ontouchstart' in window || navigator.maxTouchPoints)) {
+        $('body').addClass('desktop-screen');
+    }
+    if (navigator.userAgent.indexOf('Safari') !== -1 &&
+        navigator.userAgent.indexOf('Chrome') === -1 &&
+        navigator.userAgent.indexOf('Android') === -1) {
+        $('body').addClass('safari');
+        main.browser = 'safari';
+        main.noSelect = true;
+    } else if (detectIE()) {
+        $('body').addClass('ie');
+        // workaround
+        main.browser = 'ie';
+        main.browserVersion = detectIE();
+        main.noSelect = true;
+        $('#host-adapters-btn').css('margin-top', '10px');
+    }
+
+    // Read all positions, selected widgets for every view,
+    // Selected view, selected menu page,
+    // Selected widget or view page
+    // Selected filter
+    if (typeof storage !== 'undefined') {
+        try {
+            main.config = storage.get('adminConfig');
+            if (main.config) {
+                main.config = JSON.parse(main.config);
+            } else {
+                main.config = {};
+            }
+        } catch (e) {
+            console.log('Cannot load edit config');
+            main.config = {};
+        }
+    }
+
+    function globalClickHandler(event){
+        $('#admin_sidemenu_dialog').html('');
+        $('html').off('click', globalClickHandler);
+    }
+
+    function initHtmlButtons() {
+        main.socket.emit('getVersion', function (err, version) {
+			var $versionBtn = $('.button-version');
+	        if (!$versionBtn.hasClass('vendor')) {
+	            $versionBtn.text('yunkong2.admin ' + version);
+	        }
+        });
+
+        $('.choose-tabs-config-button').off('click').on('click', function(event) {
+            var $dialog = $('#admin_sidemenu_dialog');
+            var html = $dialog.html();
+            if (html) {
+                $dialog.html('');
+                // disable global handler
+                $('html').off('click', globalClickHandler);
+                return;
+            }
+            setTimeout(function () {
+                // enable global handler
+                $('html').on('click', globalClickHandler);
+            }, 100);
+            var $e = $(event.target);
+            var offs = $e.offset();
+            offs.top += $e.height() - 2;
+
+            var text =
+                '' + // style="overflow: visible; z-index: 999; ">'
+                '
' + + '
    '; + + var $lis = $adminSideMenu; + for (var tid in allTabs) { + var name = allTabs[tid]; + var found = $adminSideMenu.find('.admin-sidemenu-items[data-tab="' + tid + '"]').length; + // TABS + /*$adminSideMenu.each(function (i, e) { + if (tid === $(e).attr('aria-controls')) { + found = $(e); + return false; + } + });*/ + var id = 'chk-' + tid; + text += + '
  • ' + + '' + _(name) + ''; + } + text += '' + + '
' + + '
' + + '
'; + $dialog.append(text); + + $dialog.find('.chk-tab').off('change').on('change', function (event) { + var id = $(this).attr('id').substr(4); + if ($(this).prop('checked')) { + main.systemConfig.common.tabs.push(id); + } else { + var pos = main.systemConfig.common.tabs.indexOf(id); + if (id !== -1) { + main.systemConfig.common.tabs.splice(pos, 1); + } + } + main.saveTabs(); + initTabs(); + }); + // workaround for materialize checkbox problem + $dialog.find('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + }); + + main.updateWizard(); + + $('#button-logout').on('click', function () { + window.location.href = '/logout/'; + }); + + window.onhashchange = function () { + main.navigateDo(); + }; + main.navigateDo(); + } + + function initHtmlTabs() { + // jQuery UI initializations + initSideNav(); + + if (!main.tabsInited) { + main.tabsInited = true; + + initHtmlButtons(); + + $('#events_threshold').on('click', function () { + main.socket.emit('eventsThreshold', false); + }); + } else { + var $menu = $adminSideMenu; + var panelSelector = $menu.data('problem-link'); + if (panelSelector) { + var $panel = $(panelSelector); + // Init source for iframe + if ($panel.length) { + var link = $panel.data('src'); + if (link && link.indexOf('%') === -1) { + var $iframe = $panel.find('iframe'); + if ($iframe.length && !$iframe.attr('src')) { + $iframe.attr('src', link); + $menu.data('problem-link', null); + } + } + } + } + // show current tab + main.currentHash = null; + main.navigateDo(); + } + } + + function initTabs() { + // extract all additional instances + var text = ''; + var list = []; + var addTabs = []; + + allTabs = {}; + for (var i = 0; i < main.instances.length; i++) { + var instance = main.instances[i]; + var instanceObj = main.objects[instance]; + if (!instanceObj.common || !instanceObj.common.adminTab) continue; + if (instanceObj.common.adminTab.singleton) { + var isFound = false; + var inst1 = instance.replace(/\.(\d+)$/, '.'); + for (var j = 0; j < addTabs.length; j++) { + var inst2 = addTabs[j].replace(/\.(\d+)$/, '.'); + if (inst1 === inst2) { + isFound = true; + break; + } + } + if (!isFound) addTabs.push(instance); + } else { + addTabs.push(instance); + } + } + + // Build the standard tabs together + $('.admin-tab').each(function () { + var $this = $(this); + var id = $this.attr('id'); + list.push(id); + allTabs[id] = $this.data('name'); + }); + + // Look for adapter tabs + for (var a = 0; a < addTabs.length; a++) { + var tab = main.objects[addTabs[a]]; + var name = 'tab-' + tab.common.name; + + var link = tab.common.adminTab.link || '/adapter/' + tab.common.name + '/tab.html'; + if (tab.common.materializeTab) { + link = tab.common.adminTab.link || '/adapter/' + tab.common.name + '/tab_m.html'; + } + + var parts = addTabs[a].split('.'); + var buttonName; + + if (tab.common.adminTab.name) { + if (typeof tab.common.adminTab.name === 'object') { + if (tab.common.adminTab.name[systemLang]) { + buttonName = tab.common.adminTab.name[systemLang]; + } else if (tab.common.adminTab.name.en) { + buttonName = _(tab.common.adminTab.name.en); + } else { + buttonName = _(tab.common.name); + } + } else { + buttonName = _(tab.common.adminTab.name); + } + } else { + buttonName = _(tab.common.name); + } + + // if (main.objects[addTabs[a]].common.adminTab.name) { + // if (typeof main.objects[addTabs[a]].common.adminTab.name === 'object') { + // if (main.objects[addTabs[a]].common.adminTab.name[systemLang]) { + // buttonName = main.objects[addTabs[a]].common.adminTab.name[systemLang]; + // } else if (main.objects[addTabs[a]].common.adminTab.name.en) { + // buttonName = _(main.objects[addTabs[a]].common.adminTab.name.en); + // } else { + // buttonName = _(main.objects[addTabs[a]].common.name); + // } + // } else { + // buttonName = _(main.objects[addTabs[a]].common.adminTab.name); + // } + // } else { + // buttonName = _(main.objects[addTabs[a]].common.name); + // } + + if (!tab.common.adminTab.singleton) { + if (link.indexOf('?') !== -1) { + link += '&instance=' + parts[3]; + } else { + link += '?instance=' + parts[3]; + } + buttonName += '.' + parts[3]; + name += '-' + parts[3]; + } else { + parts[3] = 0; + } + + list.push(name); + allTabs[name] = buttonName; + + if (!main.systemConfig.common.tabs || main.systemConfig.common.tabs.indexOf(name) !==-1) { + var isReplace = false; + if (!link) { + link = '/adapter/' + parts[2] + '/tab.html'; + if (tab.common.materilizeTab) { + link = '/adapter/' + parts[2] + '/tab_m.html'; + } + } else { + // convert "http://%ip%:%port%" to "http://localhost:1880" + /*main.tabs.instances._replaceLinks(link, parts[2], parts[3], name, function (link, adapter, instance, arg) { + $('#' + arg).data('src', link); + });*/ + isReplace = link.indexOf('%') !== -1; + } + + text += '
  • ' + buttonName + '
  • \n'; + + // noinspection JSJQueryEfficiency + if (!$('#' + name).length) { + var div = ''; + $(div).hide().appendTo($('body')); + + // TODO: temporary, until other tab will be adapted + $('#' + name).find ('.iframe-in-tab').on('load', function () { + var elem = $ (this).contents ().find('body>header'); + if (!elem || !elem.length) elem = $(this).contents ().find('head'); + if (elem && elem.length) elem.append(''); + }); + } else { + $('#' + name).hide().appendTo($('body')); + } + } else { + $('#' + name).hide().appendTo($('body')); + } + } + $('.tab-custom').each(function () { + if (list.indexOf($(this).attr('id')) === -1) { + $('#' + $(this).attr('id')).remove(); + } + }); + + if (!main.systemConfig.common.tabs) main.systemConfig.common.tabs = list; + + if ($('.link-replace').length) { + var countLink = 0; + + // If some objects cannot be read => go by timeout + var loadTimeout = setTimeout(function() { + loadTimeout = null; + initHtmlTabs(/*showTabs*/); + }, 100); + + $('.link-replace').each(function () { + // convert "http://%ip%:%port%" to "http://localhost:1880" + countLink++; + main.tabs.instances._replaceLinks($(this).data('src'), $(this).data('adapter'), $(this).data('instance'), $(this).attr('id'), function (link, adapter, instance, arg) { + $('#' + arg).data('src', link).removeClass('link-replace'); + if (!--countLink) { + if (loadTimeout) { + clearTimeout(loadTimeout); + loadTimeout = null; + initHtmlTabs(/*showTabs*/); + } + } + }); + }); + } else { + initHtmlTabs(); + } + } + + main.initHostsList = function (isFirstInit) { + // fill the host list (select) on adapter tab + var $selHosts = $('#host-adapters'); + if (isFirstInit && $selHosts.data('inited')) { + return + } + + $selHosts.data('inited', true); + + main.currentHost = main.currentHost || main.config.currentHost || ''; + + var lines = []; + var color; + var curId; + for (var i = 0; i < main.tabs.hosts.list.length; i++) { + lines.push('
  • ' + main.getHostIcon(main.objects[main.tabs.hosts.list[i].id], 'imgHost left') + main.tabs.hosts.list[i].name + '
  • '); + if (!main.currentHost) { + main.currentHost = main.tabs.hosts.list[i].name; + } + if (main.currentHost === main.tabs.hosts.list[i].name) { + curId = main.tabs.hosts.list[i].id; + } + } + $selHosts.html(lines); + + var $selBtn = $('#host-adapters-btn').show(); + $selBtn + .text(_('Host:') + ' ' + main.currentHost) + .dropdown(); + + if (main.objects[curId] && main.objects[curId].common) { + color = main.objects[curId].common.color; + } + + $selBtn.append($(main.getHostIcon(main.objects[curId], 'imgHost left'))); + if (color) { + // set color of button + } + + if (main.tabs.hosts.list.length < 2) { + $selBtn.addClass('disabled'); + } else { + $selBtn.removeClass('disabled'); + } + + // host selector + $selHosts.find('a').on('click', function () { + var val = $(this).data('value'); + var id = 'system.host.' + val + '.alive'; + if (!main.states[id] || !main.states[id].val || main.states[id].val === 'null') { + main.showMessage(_('Host %s is offline', $(this).val())); + return; + } + + main.currentHost = val; + + $('#host-adapters-btn') + .text(_('Host:') + ' ' + main.currentHost) + .append($(this).find('.imgHost').clone()); + // destroy current view and load anew + console.log(main.currentTab); + if (tabsInfo['tab-' + main.currentTab] && tabsInfo['tab-' + main.currentTab].host) { + // destroy actual tab + if (main.tabs[main.currentTab] && typeof main.tabs[main.currentTab].destroy === 'function') { + main.tabs[main.currentTab].destroy(); + } + + // init new tab + if (main.tabs[main.currentTab] && typeof main.tabs[main.currentTab].init === 'function') { + main.tabs[main.currentTab].init(); + } + } + + main.saveConfig('currentHost', main.currentHost); + }); + }; + + // Use the function for this because it must be done after the language was read + function initAllDialogs() { + // todo delete it because jqgrid does not used any more + if (typeof initGridLanguage === 'function') { + initGridLanguage(main.systemConfig.common.language); + } + + $dialogCommand.modal({ + dismissible: false + }); + $dialogMessage.modal(); + $dialogConfirm.modal({ + dismissible: false + }); + + $dialogCommand.find('.progress-show-more').off('change').on('change', function () { + var val = $(this).prop('checked'); + main.saveConfig('progressMore', val); + if (val) { + $dialogCommand.find('.textarea').show(); + } else { + $dialogCommand.find('.textarea').hide(); + } + }); + if (main.config.progressClose === undefined) { + main.config.progressClose = true; + } + $dialogCommand.find('.progress-dont-close input').on('change', function () { + main.saveConfig('progressClose', $(this).prop('checked')); + }); + // workaround for materialize checkbox problem + $dialogCommand.find('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + // ignore switch + if ($input.parent().parent().hasClass('switch')) return; + + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + $dialogCommand.find('.progress-dont-close input').prop('checked', main.config.progressClose); + $dialogCommand.find('.progress-show-more').prop('checked', !!main.config.progressMore).trigger('change'); + $dialogCommand.find('.btn').on('click', function () { + if ($dialogCommand.data('finished')) { + $adminSideMain.find('.button-command').hide(); + } else { + $adminSideMain.find('.button-command').show(); + } + }); + + $adminSideMain.find('.button-command').on('click', function () { + $dialogCommand.modal('open'); + }); + } + + function checkNodeJsVersions(hosts, index) { + index = index || 0; + if (hosts && index < hosts.length) { + main.socket.emit('sendToHost', hosts[index].name, 'getHostInfo', null, function (result) { + if (result && result['Node.js']) { + var major = parseInt(result['Node.js'].split('.').shift().replace('v', ''), 10); + if (major < 6 || major === 7 || major === 9 ) { // we allow 6, 8 and 10+ + main.showMessage(_('This version of node.js "%s" on "%s" is deprecated. Please install node.js 6, 8 or newer', result['Node.js'], hosts[index].name), _('Suggestion'), 'error_outline'); + } + } + setTimeout(function () { + checkNodeJsVersions(hosts, index + 1); + }, 100); + }); + } + } + + // ----------------------------- Objects show and Edit ------------------------------------------------ + function getObjects(callback) { + main.socket.emit('getAllObjects', function (err, res) { + if (err) { + // following errors are possible + // permissionError + // Admin is not enabled in cloud settings! + window.alert(_(err)); + return; + } + + setTimeout(function () { + var obj; + main.objects = res; + for (var id in main.objects) { + if (!main.objects.hasOwnProperty(id) || id.slice(0, 7) === '_design') continue; + + obj = main.objects[id]; + + if (obj.type === 'instance') main.instances.push(id); + if (obj.type === 'enum') tabs.enums.list.push(id); + if (obj.type === 'user') tabs.users.list.push(id); + if (obj.type === 'group') tabs.users.groups.push(id); + if (obj.type === 'adapter') tabs.adapters.list.push(id); + if (obj.type === 'host') tabs.hosts.addHost(obj); + + // convert obj.history into obj.custom + if (obj.common && obj.common.history) { + obj.common.custom = JSON.parse(JSON.stringify(obj.common.history)); + delete obj.common.history; + } + } + main.objectsLoaded = true; + main.initHostsList(true); + + initTabs(); + // init dialogs + for (var dialog in main.dialogs) { + if (main.dialogs.hasOwnProperty(dialog) && typeof main.dialogs[dialog].prepare === 'function') { + main.dialogs[dialog].prepare(); + } + } + + // Detect node.js version + checkNodeJsVersions(tabs.hosts.list); + + main.getUser(); + + if (typeof callback === 'function') callback(); + }, 0); + }); + } + // ----------------------------- States show and Edit ------------------------------------------------ + + function getStates(callback) { + if (tabs.states) tabs.states.clear(); + main.socket.emit('getStates', function (err, res) { + main.states = res; + if (typeof callback === 'function') { + setTimeout(function () { + callback(); + }, 0); + } + }); + } + + function stateChange(id, state) { + id = id ? id.replace(/\s/g, '_') : ''; + + if (!id || !id.match(/\.messagebox$/)) { + if (tabs.states) { + tabs.states.stateChange(id, state); + } + tabs.objects.stateChange(id, state); + tabs.hosts.stateChange(id, state); + + // Update alive and connected of main.instances + tabs.instances.stateChange(id, state); + tabs.adapters.stateChange(id, state); + main.dialogs.customs.stateChange(id, state); + + if (main.selectId) { + main.selectId.selectId('state', id, state); + } + main.addEventMessage(id, state, false, true); + } else { + main.addEventMessage(id, state, true, true); + } + } + + function objectChange(id, obj) { + //var changed = false; + //var oldObj = null; + var action = 'update'; + + // update main.objects cache + if (obj) { + if (obj._rev && main.objects[id]) main.objects[id]._rev = obj._rev; + if (!main.objects[id]) { + action = 'add'; + } + if (action === 'add' || JSON.stringify(main.objects[id]) !== JSON.stringify(obj)) { + main.objects[id] = obj; + } + } else if (main.objects[id]) { + action = 'delete'; + delete main.objects[id]; + } + + // update to event table + main.addEventMessage(id, obj, false, false); + + tabs.objects.objectChange(id, obj, action); + + main.selectId && main.selectId.selectId('object', id, obj, action); + + tabs.enums.objectChange(id, obj, action); + tabs.intro.objectChange(id, obj, action); + + // If system config updated + if (id === 'system.config') { + // Check language + if (main.systemConfig.common.language !== obj.common.language) { + window.location.reload(); + } + + main.systemConfig = obj; + initTabs(); + } + + if (id === 'system.adapter.discovery.0') { + main.updateWizard(); + } + + if (id.match(/^system\.host\.[-\w]+$/)) { + main.initHostsList(); + } + + tabs.instances.objectChange(id, obj, action); + + if (id.match(/^script\.js\.global\..*/)) { + main.ignoreJSupdate = true; + } + + if (obj && id.match(/^system\.adapter\.[\w-]+\.[0-9]+$/)) { + if (obj.common && + obj.common.adminTab && + !obj.common.adminTab.ignoreConfigUpdate + ) { + // one exception for javascript. To able work with global scripts normally + if (!id.match(/^system\.adapter\.javascript\.[0-9]+$/) || !main.ignoreJSupdate) { + initTabs(); + } else { + main.ignoreJSupdate = false; + } + } + + if (obj && obj.type === 'instance' && obj.common.supportCustoms) { + // Update all states if customs enabled or disabled + tabs.objects.reinit(); + } + } + + tabs.hosts.objectChange(id, obj, action); + + // Update users + tabs.users.objectChange(id, obj, action); + + // update user in side menu + if (id === main.currentUser) { + main.getUser(); + } + } + + function monitor() { + if (main._timer) return; + var ts = (new Date()).getTime(); + if (ts - main._lastTimer > 30000) { + // It seems, that PC was in a sleep => Reload page to request authentication anew + location.reload(); + } else { + main._lastTimer = ts; + } + main._timer = setTimeout(function () { + main._timer = null; + monitor(); + }, 10000); + } + + // ---------------------------- Subscribes --------------------------------------------- + main.resubscribeStates = function () { + for (var pattern in main.subscribesStates) { + if (main.subscribesStates.hasOwnProperty(pattern) && main.subscribesStates[pattern]) { + console.debug('Re-Subscribe: ' + pattern); + main.socket.emit('subscribe', pattern); + } + } + }; + + main.resubscribeObjects = function () { + for (var pattern in main.subscribesObjects) { + if (main.subscribesObjects.hasOwnProperty(pattern) && main.subscribesObjects[pattern]) { + main.socket.emit('subscribeObjects', pattern); + } + } + }; + + main.resubscribeLogs = function () { + if (main.subscribesLogs) { + console.debug('Subscribe LOG'); + main.socket.emit('requireLog', true); + } + }; + + main.subscribeStates = function (patterns) { + if (!patterns) return; + if (typeof patterns === 'object') { + for (var s = 0; s < patterns.length; s++) { + main.subscribesStates[patterns[s]] = main.subscribesStates[patterns[s]] || 0; + main.subscribesStates[patterns[s]]++; + if (main.subscribesStates[patterns[s]] === 1) { + console.debug('Subscribe: ' + patterns[s]); + main.socket.emit('subscribe', patterns[s]); + } + } + } else { + main.subscribesStates[patterns] = main.subscribesStates[patterns] || 0; + main.subscribesStates[patterns]++; + if (main.subscribesStates[patterns] === 1) { + console.debug('Subscribe: ' + patterns); + main.socket.emit('subscribe', patterns); + } + } + }; + + main.unsubscribeStates = function (patterns) { + if (!patterns) return; + if (typeof patterns === 'object') { + for (var s = 0; s < patterns.length; s++) { + if (main.subscribesStates[patterns[s]]) { + main.subscribesStates[patterns[s]]--; + } + if (main.subscribesStates[patterns[s]] === 0) { + console.debug('Unsibscribe: ' + patterns[s]); + main.socket.emit('unsubscribe', patterns[s]); + delete main.subscribesStates[patterns[s]]; + } + } + } else { + if (main.subscribesStates[patterns]) { + main.subscribesStates[patterns]--; + } + if (main.subscribesStates[patterns] === 0) { + console.debug('Unsibscribe: ' + patterns); + main.socket.emit('unsubscribe', patterns); + delete main.subscribesStates[patterns]; + } + } + }; + + main.subscribeObjects = function (patterns) { + if (!patterns) return; + if (typeof patterns === 'object') { + for (var s = 0; s < patterns.length; s++) { + main.subscribesObjects[patterns[s]] = main.subscribesObjects[patterns[s]] || 0; + main.subscribesObjects[patterns[s]]++; + if (main.subscribesObjects[patterns[s]] === 1) { + main.socket.emit('subscribeObjects', patterns[s]); + } + } + } else { + main.subscribesObjects[patterns] = main.subscribesObjects[patterns] || 0; + main.subscribesObjects[patterns]++; + if (main.subscribesObjects[patterns] === 1) { + main.socket.emit('subscribeObjects', patterns); + } + } + }; + + main.unsubscribeObjects = function (patterns) { + if (!patterns) return; + if (typeof patterns === 'object') { + for (var s = 0; s < patterns.length; s++) { + if (main.subscribesObjects[patterns[s]]) { + main.subscribesObjects[patterns[s]]--; + } + if (main.subscribesObjects[patterns[s]] === 0) { + main.socket.emit('unsubscribeObjects', patterns[s]); + delete main.subscribesObjects[patterns[s]]; + } + } + } else { + if (main.subscribesObjects[patterns]) { + main.subscribesObjects[patterns]--; + } + if (main.subscribesObjects[patterns] === 0) { + main.socket.emit('unsubscribeObjects', patterns); + delete main.subscribesObjects[patterns]; + } + } + }; + + main.subscribeLogs = function (isSubscribe) { + if (isSubscribe) { + main.subscribesLogs++; + if (main.subscribesLogs === 1) { + console.debug('Subscribe Logs'); + main.socket.emit('requireLog', true); + } + } else { + main.subscribesLogs--; + if (main.subscribesLogs <= 0) { + main.subscribesLogs = 0; + console.debug('Unsubscribe Logs'); + main.socket.emit('requireLog', false); + } + } + }; + + // ---------------------------- Navigation --------------------------------------------- + main.navigateCheckDialog = function (callback) { + if (main.currentDialog && main.dialogs[main.currentDialog] && typeof main.dialogs[main.currentDialog].allStored === 'function') { + if (main.dialogs[main.currentDialog].allStored() === false) { + return main.confirmMessage(_('Some data are not stored. Discard?'), _('Please confirm'), null, function (result) { + callback(!result); + }); + } + } else { + if (configNotSaved) { + return main.confirmMessage(_('Some data are not stored. Discard?'), _('Please confirm'), null, function (result) { + callback(!result); + }); + } + } + callback(false); + }; + + main.navigateGetParams = function () { + var parts = decodeURI(window.location.hash).split('/'); + return parts[2] ? decodeURIComponent(parts[2]) : null; + }; + + main.navigate = function (options) { + if (!options) { + options = {}; + } + if (typeof options === 'string') { + options = { + tab: options, + dialog: '', + params: '' + }; + } + + // get actual tab + if (!options.tab) { + var parts = decodeURI(window.location.hash).split('/'); + options.tab = parts[0].replace(/^#/, '').replace(/^tab-/, ''); + } + + window.location.hash = '#tab-' + encodeURIComponent(options.tab) + (options.dialog ? '/' + options.dialog + (options.params ? '/' + encodeURIComponent(options.params) : '') : ''); + }; + + // Router + main.navigateDo = function () { + // ignore if hash not changed + if (window.location.hash === main.currentHash) { + return; + } + // if config dialog opened and has some unsaved data + main.navigateCheckDialog(function (err) { + if (!err) { + configNotSaved = null; + main.currentHash = window.location.hash; + // hash has following structure => #tabName/dialogName/ids + var parts = main.currentHash.split('/'); + var tab = parts[0].replace(/^#/, '').replace(/^tab-/, ''); + var dialog = parts[1]; + var params = decodeURIComponent(parts[2]); + + // set default page + if (!tab || tab === '!') { + if (!main.systemConfig.common.tabs || main.systemConfig.common.tabs.indexOf('tab-intro') !== -1) { + tab = 'intro'; + } else if (main.systemConfig.common.tabs.indexOf('tab-adapters') !== -1) { + tab = 'adapters'; + } else { + tab = main.systemConfig.common.tabs[0].replace(/^#/, '').replace(/^tab-/, ''); + } + } + // do tab is not found + + var $adminBody = $('.admin-sidemenu-body'); + var $actualTab = $adminBody.find('.admin-sidemenu-body-content'); + var $panel = $('#tab-' + tab); + + $adminBody.find('.admin-preloader').remove(); + + if (!$panel.length) { + tab = 'intro'; + } + + // if tab was changed + if (main.currentTab !== tab || !$actualTab.length) { + var link; + // destroy actual tab + if (main.currentTab && tabs[main.currentTab] && typeof tabs[main.currentTab].destroy === 'function') { + tabs[main.currentTab].destroy(); + } else if (main.currentTab) { + var $oldPanel = $('#tab-' + main.currentTab); + // destroy current iframe + if ($oldPanel.length && (link = $oldPanel.data('src'))) { + var $iframe_ = $oldPanel.find('>iframe'); + if ($iframe_.attr('src')) { + console.log('clear'); + $iframe_.attr('src', ''); + } + } + } + main.currentTab = tab; + + $actualTab.hide().appendTo('body'); + if (!dialog) { + $panel.addClass('admin-sidemenu-body-content').show().appendTo($adminBody); + $actualTab = $panel; + } + + // init new tab + if (tabs[tab] && typeof tabs[tab].init === 'function') { + tabs[tab].init(); + } + + // if iframe like node-red + if ($panel.length && (link = $panel.data('src'))) { + if (link.indexOf('%') === -1) { + var $iframe = $panel.find('>iframe'); + if ($iframe.length && !$iframe.attr('src')) { + $iframe.attr('src', link); + } + } else { + $adminSideMenu.data('problem-link', 'tab-' + tab); + } + } + } + + // select menu element + var $tab = $adminSideMenu.find('.admin-sidemenu-items[data-tab="tab-' + tab + '"]'); + $adminSideMenu.find('.admin-sidemenu-items').not($tab).removeClass('admin-sidemenu-active'); + $tab.addClass('admin-sidemenu-active'); + + if (tabsInfo['tab-' + tab] && tabsInfo['tab-' + tab].host) { + $('#host-adapters-btn').css('opacity', 1); + } else { + $('#host-adapters-btn').css('opacity', 0.3); + } + document.title = tab + ' - yunkong2'; + // if some dialog opened or must be shown + if (main.currentDialog !== dialog) { + // destroy it + if (main.dialogs[main.currentDialog] && typeof main.dialogs[main.currentDialog].destroy === 'function') { + main.dialogs[main.currentDialog].destroy(); + } + main.currentDialog = dialog; + if (dialog && main.dialogs[dialog]) { + if (typeof main.dialogs[dialog].init === 'function') { + main.dialogs[dialog].init(params ? params.split(',') : undefined); + } + tabs[main.currentTab] && tabs[main.currentTab].saveScroll && tabs[main.currentTab].saveScroll(); + $actualTab.hide().appendTo('body'); + $('#dialog-' + dialog).addClass('admin-sidemenu-body-content').show().appendTo($adminBody); + } else if ($actualTab.attr('id') !== $panel.attr('id')) { + $actualTab.hide().appendTo('body'); + $panel.addClass('admin-sidemenu-body-content').show().appendTo($adminBody); + tabs[main.currentTab] && tabs[main.currentTab].restoreScroll && tabs[main.currentTab].restoreScroll(); + } + } + } else { + // restore hash link + window.location.hash = main.currentHash || ''; + } + }); + }; + + function getIconHtml(obj, classes) { + var icon; + var alt; + var isCommon = obj && obj.common; + + if (isCommon.icon) { + if (!isCommon.icon.match(/^data:image\//)) { + if (isCommon.icon.indexOf('.') !== -1) { + var instance; + if (obj.type === 'instance') { + icon = '/adapter/' + obj.common.name + '/' + obj.common.icon; + } else if (obj._id.match(/^system\.adapter\./)) { + instance = obj._id.split('.', 3); + if (isCommon.icon[0] === '/') { + instance[2] += isCommon.icon; + } else { + instance[2] += '/' + isCommon.icon; + } + icon = '/adapter/' + instance[2]; + } else { + instance = obj._id.split('.', 2); + if (isCommon.icon[0] === '/') { + instance[0] += isCommon.icon; + } else { + instance[0] += '/' + isCommon.icon; + } + icon = '/adapter/' + instance[0]; + } + } else { + return '' + isCommon.icon + ''; + } + + } else { + icon = isCommon.icon; + } + alt = obj.type; + } + return {icon: icon, alt: alt} + } + + main.getIconFromObj = function (obj, imgPath, classes) { + var icon = ''; + var alt = ''; + if (obj && obj.common) { + if (obj.common.icon) { + var result = getIconHtml(obj); + icon = result.icon; + alt = result.alt; + } else { + imgPath = imgPath || 'lib/css/fancytree/'; + if (obj.type === 'device') { + icon = imgPath + 'device.png'; + alt = 'device'; + } else if (obj.type === 'channel') { + icon = imgPath + 'channel.png'; + alt = 'channel'; + } else if (obj.type === 'state') { + icon = imgPath + 'state.png'; + alt = 'state'; + } + } + } + + if (icon) return '' + (alt || '') + ''; + return ''; + }; + + // static, just used from many places + main.getIcon = function(id, imgPath, objects, classes) { + return main.getIconFromObj((objects || main.objects)[id], imgPath, classes); + }; + + main.getHostIcon = function (obj, classes) { + var icon = ''; + var alt = ''; + + if (obj && obj.common && obj.common.icon) { + var result = getIconHtml(obj); + icon = result.icon; + alt = result.alt; + } + icon = icon || 'img/no-image.png'; + alt = alt || ''; + + return '' + alt + ''; + }; + + main.formatBytes = function (bytes) { + if (Math.abs(bytes) < 1024) { + return bytes + ' B'; + } + var units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; + var u = -1; + do { + bytes /= 1024; + ++u; + } while (Math.abs(bytes) >= 1024 && u < units.length - 1); + return bytes.toFixed(1) + ' ' + units[u]; + }; + + // https://stackoverflow.com/questions/35969656/how-can-i-generate-the-opposite-color-according-to-current-color + main.invertColor = function (hex) { + if (hex.indexOf('#') === 0) { + hex = hex.slice(1); + } + // convert 3-digit hex to 6-digits. + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + if (hex.length !== 6) { + return false; + } + var r = parseInt(hex.slice(0, 2), 16), + g = parseInt(hex.slice(2, 4), 16), + b = parseInt(hex.slice(4, 6), 16); + // http://stackoverflow.com/a/3943023/112731 + return (r * 0.299 + g * 0.587 + b * 0.114) <= 186; + }; + + var tabsInfo = { + 'tab-intro': {order: 1, icon: 'apps'}, + 'tab-adapters': {order: 2, icon: 'store', host: true}, + 'tab-instances': {order: 3, icon: 'subtitles', host: true}, + 'tab-objects': {order: 4, icon: 'view_list'}, + 'tab-enums': {order: 5, icon: 'art_track'}, + 'tab-logs': {order: 6, icon: 'view_headline', host: true}, + 'tab-scenes': {order: 7, icon: 'subscriptions'}, + 'tab-events': {order: 8, icon: 'flash_on'}, + 'tab-users': {order: 10, icon: 'person_outline'}, + 'tab-javascript': {order: 11, icon: 'code'}, + 'tab-text2command-0': {order: 12, icon: 'ac_unit'}, + 'tab-text2command-1': {order: 12, icon: 'ac_unit'}, + 'tab-text2command-2': {order: 12, icon: 'ac_unit'}, + 'tab-node-red-0': {order: 20, icon: 'device_hub'}, + 'tab-node-red-1': {order: 21, icon: 'device_hub'}, + 'tab-node-red-2': {order: 22, icon: 'device_hub'}, + 'tab-hosts': {order: 100, icon: 'storage'}, + 'tab-fullcalendar-0': {order: 30, icon: 'perm_contact_calendar'}, + 'tab-fullcalendar-1': {order: 31, icon: 'perm_contact_calendar'}, + 'tab-fullcalendar-2': {order: 32, icon: 'perm_contact_calendar'} + }; + + function initSideNav() { + var lines = ''; + + var elements = []; + $('.admin-tab').each(function () { + var id = $(this).attr('id'); + if (!main.systemConfig.common.tabs || main.systemConfig.common.tabs.indexOf(id) !== -1) { + elements.push({ + line: '
  • ' + + (tabsInfo[id] && tabsInfo[id].icon ? '' + tabsInfo[id].icon + '' : 'live_help') + + _($(this).data('name')) + '
  • ', + id: id + }); + } + }); + $('.tab-custom').each(function () { + var id = $(this).attr('id'); + if (!main.systemConfig.common.tabs || main.systemConfig.common.tabs.indexOf(id) !== -1) { + var icon; + if (tabsInfo[id] && tabsInfo[id].icon) { + icon = tabsInfo[id].icon; + } else { + var _id = 'system.adapter.' + id.substring(4); + if (main.objects[_id] && main.objects[_id].adminTab && main.objects[_id]['fa-icon']) { + icon = main.objects[_id]['fa-icon']; + } + } + + elements.push({ + line: '
  • ' + + (icon ? '' + icon + '' : 'live_help') + + $(this).data('name') + '
  • ', + id: id + }); + } + }); + + elements.sort(function (a, b) { + if (!tabsInfo[a.id] && !tabsInfo[b.id]) return 0; + if (!tabsInfo[a.id]) return 1; + if (!tabsInfo[b.id]) return -1; + if (tabsInfo[a.id].order < tabsInfo[b.id].order) return -1; + if (tabsInfo[a.id].order > tabsInfo[b.id].order) return 1; + return 0; + }); + + for (var e = 0; e < elements.length; e++) { + lines += elements[e].line; + } + $adminSideMenu.find('.admin-sidemenu-menu').html(lines); + + $('.admin-sidemenu-close').off('click').on('click', function () { + $adminSideMain.toggleClass('admin-sidemenu-closed'); + $adminSideMenu.toggleClass('admin-sidemenu-closed'); + $('.admin-sidemenu-close i').toggleClass('hide'); + + setTimeout(function () { + //resizeGrids(); + $(window).trigger('resize'); + }, 400); + }); + + $('.admin-sidemenu-items').off('click').on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + window.location.hash = '#' + $(this).data('tab'); + }); + $('.admin-sidemenu-items a').off('click').on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + window.location.hash = '#' + $(this).parent().data('tab'); + }); + + // Show if update available + tabs.hosts.updateCounter(); + tabs.adapters.updateCounter(); + } + + // ---------------------------- Socket.io methods --------------------------------------------- + main.socket.on('log', function (message) { + tabs.logs.add(message); + }); + main.socket.on('error', function (error) { + console.log(error); + }); + main.socket.on('permissionError', function (err) { + main.showMessage(_('Has no permission to %s %s %s', err.operation, err.type, (err.id || ''))); + }); + main.socket.on('stateChange', function (id, obj) { + setTimeout(stateChange, 0, id, obj); + }); + main.socket.on('objectChange', function (id, obj) { + setTimeout(objectChange, 0, id, obj); + }); + main.socket.on('cmdStdout', function (_id, text) { + if (activeCmdId === _id) { + var m = text.match(/^upload \[(\d+)]/); + if (m) { + if ($dialogCommand.data('max') === null) { + $dialogCommand.data('max', parseInt(m[1], 10)); + $dialogCommandProgress.removeClass('indeterminate').addClass('determinate'); + } + var max = $dialogCommand.data('max'); + var value = parseInt(m[1], 10); + $dialogCommandProgress.css('width', (100 - Math.round((value / max) * 100)) + '%'); + } else { + m = text.match(/^got [-_:\/\\.\w\d]+\/admin$/); + if (m) { + // upload of admin + $dialogCommand.find('.progress-text').html(_('Upload admin started')); + $dialogCommand.data('max', null); + } else { + // got ..../www + m = text.match(/^got [-_:\/\\.\w\d]+\/www$/); + if (m) { + // upload of www + $dialogCommand.find('.progress-text').html(_('Upload www started')); + $dialogCommand.data('max', null); + } else { + + } + } + } + + stdout += '\n' + text; + $stdout.val(stdout); + $stdout.scrollTop($stdout[0].scrollHeight - $stdout.height()); + } + }); + main.socket.on('cmdStderr', function (_id, text) { + if (activeCmdId === _id) { + if (!$dialogCommand.data('error')) { + $dialogCommand.data('error', text); + } + stdout += '\nERROR: ' + text; + $stdout.val(stdout); + $stdout.scrollTop($stdout[0].scrollHeight - $stdout.height()); + } + }); + main.socket.on('cmdExit', function (_id, exitCode) { + if (activeCmdId === _id) { + + exitCode = parseInt(exitCode, 10); + stdout += '\n' + (exitCode !== 0 ? 'ERROR: ' : '') + 'process exited with code ' + exitCode; + $stdout.val(stdout); + $stdout.scrollTop($stdout[0].scrollHeight - $stdout.height()); + + $dialogCommand.find('.progress-dont-close').addClass('disabled'); + $dialogCommandProgress.removeClass('indeterminate').css({'width': '100%'}); + $dialogCommand.find('.btn').html(_('Close')); + $dialogCommand.data('finished', true); + $dialogCommand.data('max', true); + var $backButton = $adminSideMain.find('.button-command'); + $backButton.removeClass('in-progress'); + + if (!exitCode) { + $dialogCommand.find('.progress-text').html(_('Success!')); + $backButton.hide(); + if ($dialogCommand.find('.progress-dont-close input').prop('checked')) { + setTimeout(function () { + $dialogCommand.modal('close'); + }, 1500); + } + } else { + var error = $dialogCommand.data('error'); + if (error) { + var m = error.match(/error: (.*)$/); + if (m) { + error = m[1]; + } + + $dialogCommand.find('.progress-text').html(_('Done with error: %s', _(error))).addClass('error'); + } else { + $dialogCommand.find('.progress-text').html(_('Done with error')).addClass('error'); + } + $backButton.addClass('error'); + $backButton.show(); + } + if (cmdCallback) { + cmdCallback(exitCode); + cmdCallback = null; + } + } + }); + main.socket.on('eventsThreshold', function (isActive) { + if (isActive) { + $('#events_threshold').show(); + } else { + $('#events_threshold').hide(); + } + }); + main.socket.on('connect', function () { + $('#connecting').hide(); + if (firstConnect) { + firstConnect = false; + + main.getUser(); + + main.socket.emit('getUserPermissions', function (err, acl) { + main.acl = acl; + // Read system configuration + main.socket.emit('getObject', 'system.config', function (errConfig, data) { + main.systemConfig = data; + + // set logo and set branding + if (data && data.native && data.native.vendor) { + var vendor = data.native.vendor; + if (vendor.icon) { + $('.admin-sidemenu-header .button-icon img').attr('src', data.native.vendor.icon); + } + if (vendor.name) { + $('.admin-sidemenu-header .button-version').html(data.native.vendor.name).addClass('vendor'); + } + if (vendor.admin && vendor.admin.noCustomInstall) { + $('#btn_filter_custom_url').hide(); + } + if (vendor.admin && vendor.admin.css) { + if (vendor.admin.css.sideNavUser) { + $('.side-nav .user-view').css(vendor.admin.css.sideNavUser); + } + if (vendor.admin.css.sideNavMenu) { + $('.side-nav').css(vendor.admin.css.sideNavMenu); + } + if (vendor.admin.css.header) { + $adminSideMain.find('.admin-sidemenu-header nav').css(vendor.admin.css.header); + } + // apply rules + if (vendor.admin.css.rules) { + for (var r = 0; r < vendor.admin.css.rules.length; r++) { + $(vendor.admin.css.rules[r].selector).css(vendor.admin.css.rules[r].css); + } + } + if (vendor.admin.styles) { + $('head').append(''); + } + } + } + + // rename log => logs (back compatibility) + if (main.systemConfig && main.systemConfig.common && main.systemConfig.common.tabs) { + var pos = main.systemConfig.common.tabs.indexOf('tab-log'); + if (pos !== -1) { + main.systemConfig.common.tabs[pos] = 'tab-logs'; + } + } + + main.socket.emit('getObject', 'system.repositories', function (errRepo, repo) { + main.dialogs.system.systemRepos = repo; + main.socket.emit('getObject', 'system.certificates', function (errCerts, certs) { + setTimeout(function () { + main.dialogs.system.systemCerts = certs; + if (errConfig === 'permissionError') { + main.systemConfig = {common: {language: systemLang}, error: 'permissionError'}; + } else { + if (!errConfig && main.systemConfig && main.systemConfig.common) { + systemLang = main.systemConfig.common.language || systemLang; + main.systemConfig.common.city = main.systemConfig.common.city || ''; + main.systemConfig.common.country = main.systemConfig.common.country || ''; + main.systemConfig.common.longitude = main.systemConfig.common.longitude || ''; + main.systemConfig.common.latitude = main.systemConfig.common.latitude || ''; + + if (!main.systemConfig.common.licenseConfirmed) { + // Show license agreement + var language = (main.systemConfig.common.language || window.navigator.userLanguage || window.navigator.language || '').substring(0, 2); + if (language !== 'en' && language !== 'de' && language !== 'ru') language = 'en'; + + systemLang = language; + + $dialogLicense.find('.license_text').html(license[language] || license.en); + + $dialogLicense.find('.license_checkbox').prop('checked', false); + + // on language change + $dialogLicense.find('.license_language') + .data('licenseConfirmed', false) + .val(language) + .on('change', function () { + language = $(this).val(); + $dialogLicense.find('.license_language_label').html(translateWord('Select language', language)); + $dialogLicense.find('.license_text').html(license[language] || license.en); + $dialogLicense.find('.license_checkbox').html(translateWord('license_checkbox', language)); + $dialogLicense.find('.license_agree .translate').html(translateWord('agree', language)); + $dialogLicense.find('.license_non_agree .translate').html(translateWord('not agree', language)); + $dialogLicense.find('.license_terms').html(translateWord('License terms', language)); + $dialogLicense.find('.license_agreement_label').html(translateWord('license agreement', language)); + }).select(); + + $dialogLicense.find('.license_diag').on('change', function () { + if ($(this).prop('checked')) { + $dialogLicense.find('.license_agree').removeClass('disabled'); + } else { + $dialogLicense.find('.license_agree').addClass('disabled'); + } + }); + + // workaround for materialize checkbox problem + $dialogLicense.find('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + + $dialogLicense.modal({ + dismissible: false, + complete: function () { + $dialogLicense.find('.license_text').html(''); + location.reload(); + } + }).modal('open'); + + $dialogLicense.find('.license_agree').addClass('disabled').off('click').on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + + main.socket.emit('getObject', 'system.config', function (err, obj) { + if (err || !obj) { + main.showError(_('Cannot confirm: ' + err)); + return; + } + obj.common = obj.common || {}; + obj.common.licenseConfirmed = true; + obj.common.language = language; + main.socket.emit('setObject', 'system.config', obj, function (err) { + if (err) { + main.showError(err); + } + $dialogLicense.modal('close'); + $dialogLicense.find('.license_agree').off('click'); + $dialogLicense.find('.license_non_agree').off('click'); + }); + }); + }); + $dialogLicense.find('.license_non_agree').off('click').on('click', function (e) { + location.reload(); + }); + } + } else { + main.systemConfig = { + type: 'config', + common: { + name: 'system.config', + city: '', // City for weather + country: '', // Country for weather + longitude: '', // longitude for javascript + latitude: '', // longitude for javascript + language: '', // Default language for adapters. Adapters can use different values. + tempUnit: '°C', // Default temperature units. + currency: '', // Default currency sign. + dateFormat: 'DD.MM.YYYY', // Default date format. + isFloatComma: true, // Default float divider ('.' - false, ',' - true) + licenseConfirmed: false, // If license agreement confirmed, + defaultHistory: '', // Default history instance + tabs: [ // Show by default only these tabs + 'tab-intro', + 'tab-adapters', + 'tab-instances', + 'tab-objects', + 'tab-logs', + 'tab-scenes', + 'tab-javascript', + 'tab-text2command-0' + ] + } + }; + main.systemConfig.common.language = window.navigator.userLanguage || window.navigator.language; + + if (main.systemConfig.common.language !== 'en' && main.systemConfig.common.language !== 'de' && main.systemConfig.common.language !== 'ru') { + main.systemConfig.common.language = 'en'; + } + } + } + + translateCron(); + translateAll(); + + // Here we go! + initAllDialogs(); + // call prepare + for (var t in tabs) { + if (tabs.hasOwnProperty(t) && tabs[t] && typeof tabs[t].prepare === 'function') { + tabs[t].prepare(); + } + } + // TABS + // resizeGrids(); + + getStates(getObjects); + }, 0); + }); + }); + }); + }); + } else { + main.resubscribeStates(); + main.resubscribeObjects(); + main.resubscribeLogs(); + } + if (main.waitForRestart) { + location.reload(); + } + }); + main.socket.on('disconnect', function () { + $('#connecting').show(); + }); + main.socket.on('reconnect', function () { + $('#connecting').hide(); + if (main.waitForRestart) { + location.reload(); + } + }); + main.socket.on('repoUpdated', function () { + setTimeout(function () { + tabs.adapters.init(true); + }, 0); + }); + main.socket.on('reauthenticate', function () { + location.reload(); + }); + + /*function resizeGrids() { + var x = $(window).width(); + var y = $(window).height(); + if (x < 720) { + x = 720; + } + if (y < 480) { + y = 480; + } + for (var tab in tabs.events) { + if (tabs.events.hasOwnProperty(tab) && tabs[tab] && tabs[tab].resize) { + tabs[tab].resize(x, y); + } + } + } + + $(window).resize(resizeGrids); + */ +}); +})(jQuery); diff --git a/src/js/adminAdapters.js b/src/js/adminAdapters.js new file mode 100644 index 0000000..5562e37 --- /dev/null +++ b/src/js/adminAdapters.js @@ -0,0 +1,1735 @@ +function Adapters(main) { + 'use strict'; + + var that = this; + + this.curRepository = null; + this.curRepoLastUpdate = null; + this.curInstalled = null; + this.curRepoLastHost = null; + + this.list = []; + this.$tab = $('#tab-adapters'); + this.$grid = this.$tab.find('#grid-adapters'); + this.$tiles = this.$tab.find('#grid-adapters-tiles'); + this.$installDialog = $('#dialog-install-url'); + this.main = main; + this.tree = []; + this.data = {}; + this.urls = {}; + this.groupImages = { + 'common adapters_group': 'img/common.png', + 'general_group': 'img/common.png', + 'hardware_group': 'img/hardware.png', + 'lighting_group': 'img/hardware.png', + 'energy_group': 'img/hardware.png', + 'household_group': 'img/hardware.png', + 'iot-systems_group': 'img/hardware.png', + 'climate-control_group': 'img/hardware.png', + 'infrastructure_group': 'img/hardware.png', + 'garden_group': 'img/hardware.png', + 'alarm_group': 'img/hardware.png', + 'script_group': 'img/script.png', + 'logic_group': 'img/script.png', + 'media_group': 'img/media.png', + 'multimedia_group': 'img/media.png', + 'communication_group': 'img/communication.png', + 'protocols_group': 'img/communication.png', + 'network_group': 'img/communication.png', + 'messaging_group': 'img/communication.png', + 'visualisation_group': 'img/visualisation.png', + 'visualization_group': 'img/visualisation.png', + 'visualization-icons_group': 'img/visualisation.png', + 'visualization-widgets_group': 'img/visualisation.png', + 'storage_group': 'img/storage.png', + 'weather_group': 'img/weather.png', + 'schedule_group': 'img/schedule.png', + 'vis_group': 'img/vis.png', + 'date-and-time_group': 'img/service.png', + 'geoposition_group': 'img/service.png', + 'utility_group': 'img/service.png', + 'misc-data_group': 'img/service.png', + 'service_group': 'img/service.png', + 'third-party_group': 'img/service.png' + }; + this.inited = false; + + this.isList = false; + this.filterVals = {length: 0}; + this.onlyInstalled = false; + this.onlyUpdatable = false; + this.currentFilter = ''; + this.currentType = ''; + this.isCollapsed = {}; + this.isTiles = true; + + this.types = { + occ: 'schedule' + }; + + function getVersionClass(version) { + if (version) { + var tmp = version.split ('.'); + if (tmp[0] === '0' && tmp[1] === '0' && tmp[2] === '0') { + version = 'planned'; + } else if (tmp[0] === '0' && tmp[1] === '0') { + version = 'alpha'; + } else if (tmp[0] === '0') { + version = 'beta' + } else if (version === 'npm error') { + version = 'error'; + } else { + version = 'stable'; + } + } + return version; + } + + function prepareTable() { + that.$grid.show(); + that.$tiles.html('').hide(); + that.$tab.find('#main-toolbar-table-types-btn').hide(); + + if (!that.$grid.data('inited')) { + that.$grid.data('inited', true); + that.$grid.fancytree({ + extensions: ['table', 'gridnav', 'filter', 'themeroller'], + checkbox: false, + strings: { + noData: _('No data') + }, + table: { + indentation: 5 // indent 20px per node level + }, + show: function (currentId, filter, onSuccess) { + that.sortTree(); + }, + source: that.tree, + renderColumns: function(event, data) { + var node = data.node; + var $tdList = $(node.tr).find('>td'); + var obj = that.data[node.key]; + + function ellipsis(txt) { + return '
    ' + txt + '
    '; + } + + if (!obj) { + $tdList.eq(0).css({'font-weight': 'bold'}); + $tdList.eq(0).find('img').remove(); + $tdList.eq(0).find('span.fancytree-title').attr('style', 'padding-left: 0px !important'); + + // Calculate total count of adapter and count of installed adapter + for (var c = 0; c < that.tree.length; c++) { + if (that.tree[c].key === node.key) { + $tdList.eq(1).html(that.tree[c].desc || '').css({'overflow': 'hidden', 'white-space': 'nowrap', position: 'relative'}); + var installed = 0; + for (var k = 0; k < that.tree[c].children.length; k++) { + if (that.data[that.tree[c].children[k].key].installed) installed++; + } + that.tree[c].installed = installed; + node.data.installed = installed; + var title; + //if (!that.onlyInstalled && !that.onlyUpdatable) { + title = '[' + installed + ' / ' + that.tree[c].children.length + ']'; + $tdList.eq(1).html(ellipsis('' + installed + ' ' + _('of') + ' ' + that.tree[c].children.length + ' ' + _('Adapters from this Group installed'))); + break; + } + } + return; + } + + $tdList.eq(0).css({'overflow': 'hidden', 'white-space': 'nowrap'}); + + function setHtml(no, html) { + return $tdList.eq(no).html(ellipsis(html)); + } + + var idx = obj.desc.indexOf('= 0 ? obj.desc.substr(0, idx) : obj.desc; + $tdList.eq(1).html(ellipsis(obj.desc)) + .attr('title', desc) + .css({'white-space': 'nowrap', position: 'relative', 'font-weight': obj.bold ? 'bold' : null}).find('>div>div') + .css('height: 22px !important') + ; + + setHtml(2, obj.keywords).attr('title', obj.keywords); + + $tdList.eq(3).html(obj.installed); + $tdList.eq(4).html(obj.version); //.css({ position: 'relative'}); + + // setHtml(5, obj.platform);// actually there is only one platform + setHtml(5, obj.license); + setHtml(6, obj.install); + + that.initButtons(node.key); + // If we render this element, that means it is expanded + if (that.isCollapsed[obj.group]) { + that.isCollapsed[obj.group] = false; + that.main.saveConfig('adaptersIsCollapsed', JSON.stringify(that.isCollapsed)); + } + }, + gridnav: { + autofocusInput: false, + handleCursorKeys: true + }, + filter: { + mode: 'hide', + autoApply: true + }, + collapse: function(event, data) { + if (that.isCollapsed[data.node.key]) return; + that.isCollapsed[data.node.key] = true; + that.main.saveConfig('adaptersIsCollapsed', JSON.stringify(that.isCollapsed)); + } + }); + + that.$tab.find('#btn_collapse_adapters').show().off('click').on('click', function () { + that.$tab.find('.process-adapters').show(); + setTimeout(function () { + that.$grid.fancytree('getRootNode').visit(function (node) { + if (!that.filterVals.length || node.match || node.subMatch) node.setExpanded(false); + }); + that.$tab.find('.process-adapters').hide(); + }, 100); + }); + + that.$tab.find('#btn_expand_adapters').show().off('click').on('click', function () { + that.$tab.find('.process-adapters').show(); + setTimeout(function () { + that.$grid.fancytree('getRootNode').visit(function (node) { + if (!that.filterVals.length || node.match || node.subMatch) + node.setExpanded(true); + }); + that.$tab.find('.process-adapters').hide(); + }, 100); + }); + + that.$tab.find('#btn_list_adapters').show().off('click').on('click', function () { + var $processAdapters = that.$tab.find('.process-adapters'); + $processAdapters.show(); + that.isList = !that.isList; + if (that.isList) { + that.$tab.find('#btn_list_adapters').addClass('red lighten-3'); + that.$tab.find('#btn_expand_adapters').hide(); + that.$tab.find('#btn_collapse_adapters').hide(); + $(this).attr('title', _('list')); + } else { + that.$tab.find('#btn_list_adapters').removeClass('red lighten-3'); + that.$tab.find('#btn_expand_adapters').show(); + that.$tab.find('#btn_collapse_adapters').show(); + $(this).attr('title', _('tree')); + } + that.main.saveConfig('adaptersIsList', that.isList); + $processAdapters.show(); + + setTimeout(function () { + that._postInit(true); + $processAdapters.hide(); + }, 200); + }); + } else { + that.$tab.find('#btn_collapse_adapters').show(); + that.$tab.find('#btn_expand_adapters').show(); + that.$tab.find('#btn_list_adapters').show(); + } + + if (that.isList) { + that.$tab.find('#btn_list_adapters').addClass('red lighten-3').attr('title', _('tree')); + that.$tab.find('#btn_expand_adapters').hide(); + that.$tab.find('#btn_collapse_adapters').hide(); + } else { + that.$tab.find('#btn_list_adapters').removeClass('red lighten-3').attr('title', _('list')); + that.$tab.find('#btn_expand_adapters').show(); + that.$tab.find('#btn_collapse_adapters').show(); + } + + that.$tab.find('.filter-input').trigger('change'); + } + + function prepareTiles() { + that.$grid.hide(); + that.$tiles.show(); + that.$tab.find('#main-toolbar-table-types-btn').show(); + that.$tab.find('#btn_list_adapters').hide(); + that.$tab.find('#btn_collapse_adapters').hide(); + that.$tab.find('#btn_expand_adapters').hide(); + that.$tab.find('.filter-input').trigger('change'); + } + + function onOnlyUpdatableChanged() { + if (that.onlyUpdatable) { + that.$tab.find('#btn_filter_updates').addClass('red lighten-3'); + that.$tab.find('#btn_upgrade_all').show(); + } else { + that.$tab.find('#btn_upgrade_all').hide(); + that.$tab.find('#btn_filter_updates').removeClass('red lighten-3'); + } + } + + function onExpertmodeChanged() { + if (that.main.config.expertMode) { + that.$tab.find('#btn_adapters_expert_mode').addClass('red lighten-3'); + that.$tab.find('#btn_upgrade_all').show(); + } else { + that.$tab.find('#btn_adapters_expert_mode').removeClass('red lighten-3'); + onOnlyUpdatableChanged(); + } + } + + function filterTiles() { + var anyVisible = false; + // filter + if (that.currentFilter) { + that.$tiles.find('.tile').each(function () { + var $this = $(this); + if (that.currentType && !$this.hasClass('class-' + that.currentType)) { + $this.hide(); + return; + } + + if (customFilter({key: $this.data('id')})) { + anyVisible = true; + $this.show(); + } else { + $this.hide(); + } + }); + } else { + if (!that.currentType) { + that.$tiles.find('.tile') + .show() + .each(function () { + if ($(this).is(':visible')) { + anyVisible = true; + return false; + } + }); + } else { + that.$tiles.find('.tile').hide(); + that.$tiles.find('.class-' + that.currentType).show(); + that.$tiles.find('.tile').each(function () { + if ($(this).is(':visible')) { + anyVisible = true; + return false; + } + }); + } + } + + if (anyVisible) { + that.$tiles.find('.filtered-out').hide(); + } else { + that.$tiles.find('.filtered-out').show(); + } + } + + this.prepare = function () { + this.$tab.find('#btn_switch_adapters').off('click').on('click', function () { + that.$tab.find('.process-adapters').show(); + that.isTiles = !that.isTiles; + + if (that.isTiles) { + that.$tab.removeClass('view-table').addClass('view-tiles'); + $(this).find('i').text('view_list'); + } else { + $(this).find('i').text('view_module'); + that.$tab.removeClass('view-tiles').addClass('view-table'); + } + + that.main.saveConfig('adaptersIsTiles', that.isTiles); + + setTimeout(function () { + if (that.isTiles) { + prepareTiles(); + } else { + prepareTable(); + } + that._postInit(true); + that.$tab.find('.process-adapters').hide(); + }, 50); + }); + + this.$tab.find('#btn_filter_adapters').off('click').on('click', function () { + that.$tab.find('.process-adapters').show(); + that.onlyInstalled = !that.onlyInstalled; + if (that.onlyInstalled) { + that.$tab.find('#btn_filter_adapters').addClass('red lighten-3'); + } else { + that.$tab.find('#btn_filter_adapters').removeClass('red lighten-3'); + } + that.main.saveConfig('adaptersOnlyInstalled', that.onlyInstalled); + + setTimeout(function () { + that._postInit(true); + that.$tab.find('.process-adapters').hide(); + }, 50); + }); + + this.$tab.find('#btn_filter_updates').off('click').on('click', function () { + that.$tab.find('.process-adapters').show(); + that.onlyUpdatable = !that.onlyUpdatable; + onOnlyUpdatableChanged(); + + that.main.saveConfig('adaptersOnlyUpdatable', that.onlyUpdatable); + + setTimeout(function () { + that._postInit(true); + that.$tab.find('.process-adapters').hide(); + }, 200); + }); + + this.$tab.find('#btn_filter_custom_url') + .off('click') + .on('click', function () { + // prepare adapters + var text = ''; + var order = []; + var url; + for (url in that.urls) { + if (that.urls.hasOwnProperty(url)) { + order.push(url); + } + } + order.sort(); + + for (var o = 0; o < order.length; o++) { + var user = that.urls[order[o]].match(/\.com\/([-_$§A-Za-z0-9]+)\/([-._$§A-Za-z0-9]+)\//); + if (user && user.length >= 2 && (that.main.config.expertMode || order[o].indexOf('js-controller') === -1)) { + text += ''; + } + } + that.$installDialog.find('#install-github-link').html(text).val(that.main.config.adaptersGithub || ''); + + that.$installDialog.modal(); + + that.$installDialog.find('.btn-install').off('click').on('click', function () { + var isCustom = !that.$installDialog.find('a[href="#tabs-install-github"]').hasClass('active');//!!that.$installDialog.find('#tabs-install').tabs('option', 'active'); + var url; + var debug; + var adapter; + if (isCustom) { + url = that.$installDialog.find('#install-url-link').val(); + debug = that.$installDialog.find('#install-url-debug').prop('checked') ? ' --debug' : ''; + adapter = ''; + } else { + var parts = that.$installDialog.find('#install-github-link').val().split(' '); + url = parts[0]; + debug = that.$installDialog.find('#install-github-debug').prop('checked') ? ' --debug' : ''; + adapter = ' ' + parts[1]; + } + + if (!url) { + that.main.showError(_('Invalid link')); + return; + } + + that.main.cmdExec(null, 'url "' + url + '"' + adapter + debug, function (exitCode) { + if (!exitCode) { + that.init(true, true); + } + }); + }); + that.$installDialog.find('#install-github-link').select(); + // workaround for materialize checkbox problem + that.$installDialog.find('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + that.$installDialog.modal('open'); + that.$installDialog.find('.tabs').mtabs({ + nShow: function (tab) { + if (!tab) return; + that.main.saveConfig('adaptersInstallTab', $(tab).attr('id')); + } + }); + + if (that.main.config.adaptersInstallTab && !that.main.noSelect) { + that.$installDialog.find('.tabs').mtabs('select', that.main.config.adaptersInstallTab); + } + }); + + this.$tab.find('#btn_upgrade_all').off('click').on('click', function () { + that.main.confirmMessage(_('Do you want to upgrade all adapters?'), _('Please confirm'), 'help', function (result) { + if (result) { + that.main.cmdExec(null, 'upgrade', function (exitCode) { + if (!exitCode) that._postInit(true); + }); + } + }); + }); + + this.$tab.find('#btn_adapters_expert_mode').on('click', function () { + that.main.config.expertMode = !that.main.config.expertMode; + that.main.saveConfig('expertMode', that.main.config.expertMode); + that.updateExpertMode(); + that.main.tabs.instances.updateExpertMode(); + }); + + if (that.main.config.expertMode) { + that.$tab.find('#btn_adapters_expert_mode').addClass('red lighten-3'); + } + + // save last selected adapter + this.$installDialog.find('#install-github-link').on('change', function () { + that.main.saveConfig('adaptersGithub', $(this).val()); + }); + this.$installDialog.find('#install-url-link').on('keyup', function (event) { + if (event.which === 13) { + that.$installDialog.find('#dialog-install-url-button').trigger('click'); + } + }); + + // Load settings + this.isTiles = (this.main.config.adaptersIsTiles !== undefined && this.main.config.adaptersIsTiles !== null) ? this.main.config.adaptersIsTiles : true; + this.isList = this.main.config.adaptersIsList || false; + this.onlyInstalled = this.main.config.adaptersOnlyInstalled || false; + this.onlyUpdatable = this.main.config.adaptersOnlyUpdatable || false; + this.currentFilter = this.main.config.adaptersCurrentFilter || ''; + this.currentType = this.main.config.adaptersCurrentType || ''; + this.currentOrder = this.main.config.adaptersCurrentOrder || 'a-z'; + this.isCollapsed = this.main.config.adaptersIsCollapsed ? JSON.parse(this.main.config.adaptersIsCollapsed) : {}; + if (this.currentFilter) { + this.$tab.find('.filter-input').addClass('input-not-empty').val(that.currentFilter); + this.$tab.find('.filter-clear').show(); + } else { + this.$tab.find('.filter-clear').hide(); + } + + if (this.onlyInstalled) { + this.$tab.find('#btn_filter_adapters').addClass('red lighten-3'); + } else { + this.$tab.find('#btn_filter_adapters').removeClass('red lighten-3'); + } + + if (this.onlyUpdatable) { + this.$tab.find('#btn_filter_updates').addClass('red lighten-3'); + } else { + this.$tab.find('#btn_filter_updates').removeClass('red lighten-3'); + } + + // fix for IE + if (this.main.browser === 'ie' && this.main.browserVersion <= 10) { + this.isTiles = false; + this.$tab.find('#btn_switch_adapters').hide(); + } + + onExpertmodeChanged(); + + this.$tab.find('#btn_refresh_adapters').on('click', function () { + that.init(true, true); + }); + + // add filter processing + this.$tab.find('.filter-input').on('keyup', function () { + $(this).trigger('change'); + }).on('change', function (event) { + if (that.filterTimer) { + clearTimeout(that.filterTimer); + } + that.filterTimer = setTimeout(function () { + that.filterTimer = null; + that.currentFilter = that.$tab.find('.filter-input').val().toLowerCase(); + event && event.target && $(event.target)[that.currentFilter ? 'addClass' : 'removeClass']('input-not-empty'); + if (that.currentFilter) { + that.$tab.find('.filter-clear').show(); + } else { + that.$tab.find('.filter-clear').hide(); + } + + that.main.saveConfig('adaptersCurrentFilter', that.currentFilter); + if (that.isTiles) { + filterTiles(); + } else { + that.$grid.fancytree('getTree').filterNodes(customFilter, false); + } + }, 400); + }); + + this.$tab.find('.filter-clear').on('click', function () { + that.$tab.find('.filter-input').val('').trigger('change'); + }); + + if (this.isTiles) { + this.$tab.find('#btn_switch_adapters').find('i').text('view_list'); + that.$tab.removeClass('view-table').addClass('view-tiles'); + prepareTiles(); + } else { + that.$tab.removeClass('view-tiles').addClass('view-table'); + prepareTable(); + } + }; + + this.updateExpertMode = function () { + this.init(true); + onExpertmodeChanged(); + }; + + function customFilter(node) { + //if (node.parent && node.parent.match) return true; + + if (that.currentFilter) { + if (!that.data[node.key]) return false; + + var title = that.data[node.key].title; + if (title && typeof title === 'object') { + title = title[systemLang] || title.en; + } + var desc = that.data[node.key].desc; + if (desc && typeof desc === 'object') { + desc = desc[systemLang] || desc.en; + } + + if ((that.data[node.key].name && that.data[node.key].name.toLowerCase().indexOf(that.currentFilter) !== -1) || + (title && title.toLowerCase().indexOf(that.currentFilter) !== -1) || + (that.data[node.key].keywords && that.data[node.key].keywords.toLowerCase().indexOf(that.currentFilter) !== -1) || + (desc && desc.toLowerCase().indexOf(that.currentFilter) !== -1)){ + return true; + } else { + return false; + } + } else { + return true; + } + } + + this.getAdaptersInfo = function (host, update, updateRepo, callback) { + if (!host) return; + + if (!callback) throw 'Callback cannot be null or undefined'; + if (update) { + // Do not update too often + if (!this.curRepoLastUpdate || ((new Date()).getTime() - this.curRepoLastUpdate > 1000)) { + this.curRepository = null; + this.curInstalled = null; + } + } + + if (this.curRunning) { + this.curRunning.push(callback); + return; + } + + if (!this.curRepository || this.curRepoLastHost !== host) { + this.curRepository = null; + this.main.socket.emit('sendToHost', host, 'getRepository', {repo: this.main.systemConfig.common.activeRepo, update: updateRepo}, function (_repository) { + if (_repository === 'permissionError') { + console.error('May not read "getRepository"'); + _repository = {}; + } + + that.curRepository = _repository || {}; + if (that.curRepository && that.curInstalled && that.curRunning) { + that.curRepoLastUpdate = (new Date()).getTime(); + setTimeout(function () { + for (var c = 0; c < that.curRunning.length; c++) { + that.curRunning[c](that.curRepository, that.curInstalled); + } + that.curRunning = null; + }, 0); + } + }); + } + if (!this.curInstalled || this.curRepoLastHost !== host) { + this.curInstalled = null; + this.main.socket.emit('sendToHost', host, 'getInstalled', null, function (_installed) { + if (_installed === 'permissionError') { + console.error('May not read "getInstalled"'); + _installed = {}; + } + + that.curInstalled = _installed || {}; + if (that.curRepository && that.curInstalled) { + that.curRepoLastUpdate = (new Date()).getTime(); + setTimeout(function () { + for (var c = 0; c < that.curRunning.length; c++) { + that.curRunning[c](that.curRepository, that.curInstalled); + } + that.curRunning = null; + }, 0); + } + }); + } + + this.curRepoLastHost = host; + + if (this.curInstalled && this.curRepository) { + setTimeout(function () { + if (that.curRunning) { + for (var c = 0; c < that.curRunning.length; c++) { + that.curRunning[c](that.curRepository, that.curInstalled); + } + that.curRunning = null; + } + if (callback) callback(that.curRepository, that.curInstalled); + }, 0); + } else { + this.curRunning = [callback]; + } + }; + + this.enableColResize = function () { + if (!$.fn.colResizable) return; + if (this.$grid.is(':visible')) { + this.$grid.colResizable({liveDrag: true}); + } + }; + + function getNews(actualVersion, adapter) { + var text = ''; + if (adapter.news) { + for (var v in adapter.news) { + if (adapter.news.hasOwnProperty(v)) { + if (systemLang === v) text += (text ? '\n' : '') + adapter.news[v]; + if (v === 'en' || v === 'ru' || v === 'de') continue; + if (v === actualVersion) break; + text += (text ? '\n' : '') + (adapter.news[v][systemLang] || adapter.news[v].en); + } + } + } + return text; + } + + function checkDependencies(dependencies) { + if (!dependencies) return ''; + // like [{"js-controller": ">=0.10.1"}] + var adapters; + if (dependencies instanceof Array) { + adapters = {}; + for (var a = 0; a < dependencies.length; a++) { + if (typeof dependencies[a] === 'string') continue; + for (var b in dependencies[a]) { + if (dependencies[a].hasOwnProperty(b)) { + adapters[b] = dependencies[a][b]; + } + } + } + } else { + adapters = dependencies; + } + + for (var adapter in adapters) { + if (adapters.hasOwnProperty(adapter)) { + if (adapter === 'js-controller') { + if (!semver.satisfies(that.main.objects['system.host.' + that.main.currentHost].common.installedVersion, adapters[adapter])) return _('Invalid version of %s. Required %s', adapter, adapters[adapter]); + } else { + if (!that.main.objects['system.adapter.' + adapter] || !that.main.objects['system.adapter.' + adapter].common || !that.main.objects['system.adapter.' + adapter].common.installedVersion) return _('No version of %s', adapter); + if (!semver.satisfies(that.main.objects['system.adapter.' + adapter].common.installedVersion, adapters[adapter])) return _('Invalid version of %s', adapter); + } + } + } + return ''; + } + + this.sortTree = function() { + function sort(c1, c2) { + //var d1 = that.data[c1.key], d2 = that.data[c1.key]; + var inst1 = c1.data.installed || 0, inst2 = c2.data.installed || 0; + var ret = inst2 - inst1; + if (ret) return ret; + var t1 = c1.titleLang || c1.title || ''; + if (typeof t1 === 'object') { + t1 = t1[systemLang] || t1.en; + } + var t2 = c2.titleLang || c2.title || ''; + if (typeof t2 === 'object') { + t2 = t2[systemLang] || t2.en; + } + + t1 = t1.toLowerCase(); + t2 = t2.toLowerCase(); + if (t1 > t2) return 1; + if (t1 < t2) return -1; + return 0; + } + that.$grid.fancytree('getRootNode').sortChildren(sort, true); + }; + + function getInterval(time, todayText, yesterdayText, x1DayAgoText, x2DaysAgoText, x5DaysAgoText, now) { + now = now || Date.now(); + if (!time) return ''; + if (typeof time === 'string' || typeof time === 'number') { + time = new Date(time); + } + var interval = now.getTime() - time.getTime(); + var days = Math.floor(interval / (24 * 3600000)); + if (days === 0) { + if (now.getDate() === time.getDate()) { + return todayText; + } else { + return yesterdayText; + } + } else if (days === 1) { + if (now.getDate() - time.getDate() === 1) { + return yesterdayText; + } else { + return x2DaysAgoText.replace('%d', days + 1); + } + } else { + var t = days % 10; + var tt = days % 100; + // 2, 3, 4, 22, 23, 24, 32, 33, 34, 111, ...x2, x3, h4 + if ((tt < 10 || tt > 20) && t >= 2 && t <= 4) { + return x2DaysAgoText.replace('%d', days); + } else + // 1, 21, 31, 41, 121.... + if ((tt < 10 || tt > 20) && t === 1) { + return x1DayAgoText.replace('%d', days); + } else { + return x5DaysAgoText.replace('%d', days); + } + } + } + + this._postInit = function (update, updateRepo) { + if (typeof this.$grid !== 'undefined') { + + that.$tab.find('.process-adapters').show(); + + this.$grid.find('tbody').html(''); + + this.getAdaptersInfo(this.main.currentHost, update, updateRepo, function (repository, installedList) { + var obj; + var version; + var rawVersion; + var adapter; + var adaptersToUpdate = 0; + + var listInstalled = []; + var listNonInstalled = []; + var nowObj = new Date(); + var localTexts = { + 'add instance': _('add instance'), + 'update': _('update'), + 'upload': _('upload'), + 'Available version:': _('Available version:'), + 'Active instances': _('Active instances'), + 'Installed version': _('Installed version'), + 'readme': _('readme'), + 'delete adapter': _('delete adapter'), + 'install specific version': _('install specific version'), + 'all': _('all'), + 'Last update': _('Last update'), + 'Installations counter': _('Installation counter'), + 'today': _('today'), + 'yesterday': _('yesterday'), + '1 %d days ago': _('1 %d days ago'), + '2 %d days ago': _('2 %d days ago'), + '5 %d days ago': _('5 %d days ago') + }; + + if (installedList) { + for (adapter in installedList) { + if (!installedList.hasOwnProperty(adapter)) continue; + obj = installedList[adapter]; + if (!obj || obj.controller || adapter === 'hosts') continue; + listInstalled.push(adapter); + } + listInstalled.sort(); + } + + that.urls = {}; + // List of adapters for repository + for (adapter in repository) { + if (!repository.hasOwnProperty(adapter)) continue; + if (installedList && installedList[adapter] && !installedList[adapter].versionDate) { + installedList[adapter].versionDate = repository[adapter].versionDate; + } + + // it is not possible to install this adapter from git + if (!repository[adapter].nogit) { + that.urls[adapter] = repository[adapter].meta; + } + obj = repository[adapter]; + if (!obj || obj.controller) continue; + version = ''; + if (installedList && installedList[adapter]) continue; + listNonInstalled.push(adapter); + } + listNonInstalled.sort(); + + function getVersionString(version, updatable, news, updatableError) { + //var span = getVersionSpan(version); + var color = getVersionClass(version); + var title = color + '\n\r' + (news || ''); + //version = '' + + version = //'
    ' + + '
    ' + + '' + + '' + + '
    ' + version + ''; + if (updatable) { //xxx + version += ''; + } + version += '
    '; + return version; + } + + that.tree = []; + that.data = {}; + + // list of the installed adapters + for (var i = 0; i < listInstalled.length; i++) { + adapter = listInstalled[i]; + + obj = installedList ? installedList[adapter] : null; + + if (!obj || obj.controller || adapter === 'hosts') continue; + var installed = ''; + var rawInstalled = ''; + var icon = obj.icon; + version = ''; + + if (repository[adapter] && repository[adapter].version) version = repository[adapter].version; + + if (repository[adapter] && repository[adapter].extIcon) icon = repository[adapter].extIcon; + + var _instances = 0; + var _enabled = 0; + if (obj.version) { + var news = ''; + var updatable = false; + var updatableError = ''; + if (!that.main.upToDate(version, obj.version)) { + news = getNews(obj.version, repository[adapter]); + // check if version is compatible with current adapters and js-controller + updatable = true; + updatableError = checkDependencies(repository[adapter].dependencies); + adaptersToUpdate++; + } + // TODO: move style to class + installed = '' + + ''; + + // Show information about installed and enabled instances + for (var z = 0; z < that.main.instances.length; z++) { + if (that.main.objects[that.main.instances[z]] && + that.main.objects[that.main.instances[z]].common.name === adapter) { + _instances++; + if (that.main.objects[that.main.instances[z]].common.enabled) _enabled++; + } + } + + + if (_instances) { + // TODO: move style to class + installed += ''; + } else { + // TODO: move style to class + installed += ''; + } + // TODO: move style to class + installed += ''; + rawInstalled = '' + obj.version + ''; + + //tmp = installed.split('.'); + // if (updatable) { //xxx + // //TODO + // // installed += ''; + // // version = version.replace('class="', 'class="updateReady '); + // // $('a[href="#tab-adapters"]').addClass('updateReady'); + // } else if (that.onlyUpdatable) { + // continue; + // } + + installed += '
    '; + if (_enabled !== _instances) { + installed += '' + _instances + ''; + if (_enabled) installed += ' ~ '; + } + if (_enabled) installed += '' + _enabled + ''; + installed += '' + obj.version + '
    '; + if (!updatable && that.onlyUpdatable) continue; + } + rawVersion = version; + version = getVersionString(version, updatable, news, updatableError); + + var group = (obj.type || that.types[adapter] || 'common adapters') + '_group'; + var desc = (typeof obj.desc === 'object') ? (obj.desc[systemLang] || obj.desc.en) : obj.desc; + desc = desc || ''; + desc += showUploadProgress(group, adapter, that.main.states['system.adapter.' + adapter + '.upload'] ? that.main.states['system.adapter.' + adapter + '.upload'].val : 0); + var title = obj.titleLang || obj.title; + title = (typeof title === 'object') ? (title[systemLang] || title.en) : title; + + that.data[adapter] = { + image: icon ? '' : '', + icon: icon || '', + stat: repository[adapter] ? repository[adapter].stat : 0, + name: adapter, + title: (title || '').replace('yunkong2 Visualisation - ', ''), + desc: desc, + news: news, + updatableError: updatableError, + keywords: obj.keywords ? obj.keywords.join(' ') : '', + version: version, + installed: installed, + rawVersion: rawVersion, + instances: _instances, + rawInstalled: rawInstalled, + versionDate: obj.versionDate, + updatable: updatable, + bold: obj.highlight || false, + install: '' + + '' + + ((that.main.config.expertMode) ? '' : '') + + '' + + ((that.main.config.expertMode) ? '' : ''), + // platform: obj.platform, actually there is only one platform + group: group, + license: obj.license || '', + licenseUrl: obj.licenseUrl || '' + }; + + if (!obj.type) console.log('"' + adapter + '": "common adapters",'); + if (obj.type && that.types[adapter]) console.log('Adapter "' + adapter + '" has own type. Remove from admin.'); + + if (!that.isList) { + var iGroup = -1; + for (var jj = 0; jj < that.tree.length; jj++) { + if (that.tree[jj].key === that.data[adapter].group) { + iGroup = jj; + break; + } + } + if (iGroup < 0) { + if (!localTexts[that.data[adapter].group]) localTexts[that.data[adapter].group] = _(that.data[adapter].group); + that.tree.push({ + title: localTexts[that.data[adapter].group], + desc: showUploadProgress(group), + key: that.data[adapter].group, + folder: true, + expanded: !that.isCollapsed[that.data[adapter].group], + children: [], + icon: that.groupImages[that.data[adapter].group] + }); + iGroup = that.tree.length - 1; + } + that.tree[iGroup].children.push({ + icon: icon, + title: that.data[adapter].title || adapter, + key: adapter + }); + } else { + that.tree.push({ + icon: icon, + title: that.data[adapter].title || adapter, + key: adapter + }); + } + } + //that.sortTree(); + + if (!that.onlyInstalled && !that.onlyUpdatable) { + for (i = 0; i < listNonInstalled.length; i++) { + adapter = listNonInstalled[i]; + + obj = repository[adapter]; + if (!obj || obj.controller) continue; + version = ''; + if (installedList && installedList[adapter]) continue; + + if (obj && obj.version) { + version = obj.version; + rawVersion = version; + version = getVersionString(version); + } + + var group = (obj.type || that.types[adapter] || 'common adapters') + '_group'; + var desc = (typeof obj.desc === 'object') ? (obj.desc[systemLang] || obj.desc.en) : obj.desc; + desc = desc || ''; + desc += showUploadProgress(group, adapter, that.main.states['system.adapter.' + adapter + '.upload'] ? that.main.states['system.adapter.' + adapter + '.upload'].val : 0); + + title = obj.titleLang || obj.title; + title = (typeof title === 'object') ? (title[systemLang] || title.en) : title; + + that.data[adapter] = { + image: obj.extIcon ? '' : '', + icon: obj.extIcon, + stat: obj.stat, + name: adapter, + title: (title || '').replace('yunkong2 Visualisation - ', ''), + desc: desc, + keywords: obj.keywords ? obj.keywords.join(' ') : '', + rawVersion: rawVersion, + version: version, + bold: obj.highlight, + installed: '', + versionDate: obj.versionDate, + install: '' + + '' + + '' + + ((that.main.config.expertMode) ? '' : ''), + // TODO do not show adapters not for this platform + // platform: obj.platform, // actually there is only one platform + license: obj.license || '', + licenseUrl: obj.licenseUrl || '', + group: group + }; + + if (!obj.type) console.log('"' + adapter + '": "common adapters",'); + if (obj.type && that.types[adapter]) console.log('Adapter "' + adapter + '" has own type. Remove from admin.'); + + if (!that.isList) { + var igroup = -1; + for (var j = 0; j < that.tree.length; j++){ + if (that.tree[j].key === that.data[adapter].group) { + igroup = j; + break; + } + } + if (igroup < 0) { + if (!localTexts[that.data[adapter].group]) localTexts[that.data[adapter].group] = _(that.data[adapter].group); + that.tree.push({ + title: localTexts[that.data[adapter].group], + key: that.data[adapter].group, + folder: true, + expanded: !that.isCollapsed[that.data[adapter].group], + children: [], + icon: that.groupImages[that.data[adapter].group] + }); + igroup = that.tree.length - 1; + } + that.tree[igroup].children.push({ + title: that.data[adapter].title || adapter, + icon: obj.extIcon, + desc: showUploadProgress(group), + key: adapter + }); + } else { + that.tree.push({ + icon: obj.extIcon, + title: that.data[adapter].title || adapter, + key: adapter + }); + } + } + } + + if (that.currentOrder === 'popular' || that.currentOrder === 'updated') { + var akeys = Object.keys(that.data); + + if (that.currentOrder === 'popular') { + akeys.sort(function (a, b) { + if (that.data[a].stat > that.data[b].stat) return -1; + if (that.data[a].stat < that.data[b].stat) return 1; + return 0; + }); + } else if (that.currentOrder === 'updated') { + akeys.sort(function (a, b) { + if (that.data[a].versionDate && !that.data[b].versionDate) return -1; + if (!that.data[a].versionDate && that.data[b].versionDate) return 1; + if (that.data[a].versionDate > that.data[b].versionDate) return -1; + if (that.data[a].versionDate < that.data[b].versionDate) return 1; + if (a > b) return -1; + if (a < b) return 1; + return 0; + }); + } + var newData = {}; + for (var u = 0; u < akeys.length; u++) { + newData[akeys[u]] = that.data[akeys[u]]; + } + that.data = newData; + } + + // build tiles + if (that.isTiles && (that.main.browser !== 'ie' || that.main.browserVersion > 10)) { + var text = ''; + var types = []; + for (var a in that.data) { + if (!that.data.hasOwnProperty(a)) continue; + var ad = that.data[a]; + if (types.indexOf(ad.group) === -1) { + types.push(ad.group); + } +// text += '
    '; +// text += '
    '; +// text += '
    ' + ad.title + '
    '; +// if (that.currentOrder === 'popular' && ad.stat) { +// text += '
    ' + ad.stat + '
    '; +// } else if (that.currentOrder === 'updated' && ad.versionDate) { +// text += '
    ' + getInterval(ad.versionDate, localTexts['today'], localTexts['yesterday'], localTexts['1 %d days ago'], localTexts['2 %d days ago'], localTexts['5 %d days ago'], nowObj) + '
    '; +// } +// text += '
    '; +// text += '
    '; +// text += ' '; +// text += '
    ' + ad.desc + '
    '; +// text += '
    '; +// text += '
    '; +// text += '
    ' + ad.version + (ad.installed ? '' + ad.rawInstalled : '') + '
    '; +// text += '
    ' + ad.install + '
    '; +// text += '
    '; +// text += '
    '; + + text += '
    '; + text += '
    '; + text += '
    '; + text += '
    '; + text += ' '; + text += ' ' + ad.title + ''; + text += ' more_vert'; + text += '
      '; + text += '
    • ' + localTexts['Available version:'] + ' ' + ad.rawVersion + '' + + (ad.updatable ? '' : '') + + '
    • '; + if (ad.installed) { + text += '
    • ' + localTexts['Installed version'] + ': '+ ad.rawInstalled + '
    • '; + } + if (ad.instances) { + text += '
    • ' + _('Installed instances') + ': ' + ad.instances + '
    • '; + } + text += '
    '; + text += '
    '; + text += ' '; + text += '
    '; + text += ' close'; + text += '

    ' + ad.desc + '

    '; + text += '
    '; + text += ad.install; + text += '
    '; + text += '
    '; + + if (that.currentOrder === 'popular' && ad.stat) { + text += '
    ' + ad.stat + '
    '; + } else if (that.currentOrder === 'updated' && ad.versionDate) { + text += '
    ' + getInterval(ad.versionDate, localTexts['today'], localTexts['yesterday'], localTexts['1 %d days ago'], localTexts['2 %d days ago'], localTexts['5 %d days ago'], nowObj) + '
    '; + } + + + text += '
    '; + text += '
    '; + } + + + // Add filtered out tile + text += '
    '; + text += '
    '; + text += '
    '; + text += '
    '; + //text += ' '; + text += ' ' + _('Filtered out') + ''; + text += '
    '; + text += ' '; + text += '
    '; + text += '
    '; + + that.$tiles.html(text); + // init buttons + for (var b in that.data) { + if (that.data.hasOwnProperty(b)) { + that.initButtons(b); + } + } + + var tTypes = '
  • ' + localTexts['all'] + '
  • \n'; + for (var g = 0; g < types.length; g++) { + tTypes += '
  • ' + _(types[g]) + '
  • \n'; + } + var $types = that.$tab.find('#main-toolbar-table-types'); + $types.html(tTypes); + $types.find('.main-toolbar-table-types-item').show().off('click').on('click', function () { + that.currentType = $(this).data('type') || ''; + filterTiles(); + that.$tab.find('#main-toolbar-table-types-btn').html(_(that.currentType || 'all')); + that.main.saveConfig('adaptersCurrentType', that.currentType); + }); + if (that.currentType && !localTexts[that.currentType]) localTexts[that.currentType] = _(that.currentType); + that.$tab.find('#main-toolbar-table-types-btn').html(localTexts[that.currentType || 'all']).dropdown({ + constrainWidth: false, // Does not change width of dropdown to that of the activator + // hover: true, // Activate on hover + gutter: 0 + }); + + $types = that.$tab.find('#main-toolbar-table-order'); + $types.find('.main-toolbar-table-order-item').off('click').on('click', function () { + that.currentOrder = $(this).data('type') || ''; + //filterTiles(); + that.$tab.find('#main-toolbar-table-order-btn').html(_(that.currentOrder || 'a-z')); + that.main.saveConfig('adaptersCurrentOrder', that.currentOrder); + that._postInit(); + }); + if (that.currentOrder && !localTexts[that.currentOrder]) localTexts[that.currentOrder] = _(that.currentOrder); + that.$tab.find('#main-toolbar-table-order-btn').show().html(localTexts[that.currentOrder || 'a-z']).dropdown({ + constrainWidth: false, // Does not change width of dropdown to that of the activator + // hover: true, // Activate on hover + gutter: 0 + }); + + filterTiles(); + } else { + that.$tab.find('#main-toolbar-table-types-btn').hide(); + that.$tab.find('#main-toolbar-table-order-btn').hide(); + // build tree + that.$grid.fancytree('getTree').reload(that.tree); + that.$grid.find('.fancytree-icon').each(function () { + if ($(this).attr('src')) { + $(this).css({width: 18, height: 18}); + } + + $(this).on('hover', function () { + var text = '
    '; + var $big = $(text); + $big.insertAfter($(this)); + $(this).data('big', $big[0]); + var h = parseFloat($big.height()); + var top = Math.round($(this).position().top - ((h - parseFloat($(this).height())) / 2)); + if (h + top > (window.innerHeight || document.documentElement.clientHeight)) { + top = (window.innerHeight || document.documentElement.clientHeight) - h; + } + $big.css({top: top}); + + }, function () { + var big = $(this).data('big'); + $(big).remove(); + $(this).data('big', undefined); + }); + }); + + if (that.currentFilter) { + that.$grid.fancytree('getTree').filterNodes(customFilter, false); + } + + that.sortTree(); + that.enableColResize(); + var classes = [ + 'tab-adapters-table-name', + 'tab-adapters-table-description', + 'tab-adapters-table-keywords', + 'tab-adapters-table-installed', + 'tab-adapters-table-available', + 'tab-adapters-table-license', + 'tab-adapters-table-install' + ]; + that.$grid.find('tbody tr').each(function () { + var i = 0; + $(this).find('td').each(function () { + $(this).addClass(classes[i]); + i++; + }); + }) + } + that.$tab.find('.grid-main-div').removeClass('order-a-z order-popular order-updated').addClass(that.currentOrder ? 'order-' + that.currentOrder : ''); + that.$tab.find('.process-adapters').hide(); + that.updateCounter(adaptersToUpdate); + }); + } else { + this.enableColResize(); + } + this.restoreScroll(); + }; + this.saveScroll = function () { + this.scrollTop = this.$tab.find('.grid-main-div').scrollTop(); + }; + this.restoreScroll = function () { + if (this.scrollTop) { + this.$tab.find('.grid-main-div').scrollTop(this.scrollTop); + } + }; + + this.updateCounter = function (counter) { + if (counter === undefined) { + this.getAdaptersInfo(this.main.currentHost, false, false, function (repository, installedList) { + var adaptersToUpdate = 0; + + for (var adapter in installedList) { + if (!installedList.hasOwnProperty(adapter)) continue; + var obj = installedList ? installedList[adapter] : null; + if (!obj || obj.controller || adapter === 'hosts') continue; + + var version = ''; + if (repository[adapter] && repository[adapter].version) version = repository[adapter].version; + + if (obj.version && !that.main.upToDate(version, obj.version)) { + adaptersToUpdate++; + } + } + that.updateCounter(adaptersToUpdate); + }); + } else if (counter) { + var $updates = $('#updates-for-adapters'); + if ($updates.length) { + $updates.text(counter); + } else { + $('' + counter + '').appendTo('.admin-sidemenu-items[data-tab="tab-adapters"] a'); + } + } else { + $('#updates-for-adapters').remove(); + } + }; + + // ----------------------------- Adapters show and Edit ------------------------------------------------ + this.init = function (update, updateRepo) { + if (this.inited && !update) { + return; + } + + if (!this.main.objectsLoaded) { + setTimeout(function () { + that.init(update, updateRepo); + }, 250); + return; + } + + // update info + // Required is list of hosts and repository (done in getAdaptersInfo) + if (!this.inited) { + this.inited = true; + this.main.subscribeObjects('system.host.*'); + this.main.subscribeStates('system.host.*'); + } + this.main.tabs.hosts.getHosts(function () { + that._postInit(update, updateRepo); + }); + }; + + this.destroy = function () { + if (this.inited) { + this.saveScroll(); + this.inited = false; + this.main.unsubscribeObjects('system.host.*'); + this.main.unsubscribeStates('system.host.*'); + } + }; + + function showAddInstanceDialog(adapter, desc, callback) { + if (that.main.tabs.hosts.list.length <= 1 && !that.main.config.expertMode) { + return callback(true, that.main.currentHost, ''); + } + + var $dialogAddInstance = $('#dialog-add-instance'); + $dialogAddInstance.find('.dialog-add-instance-name').html(adapter); + $dialogAddInstance.find('.dialog-add-description').html(desc); + + // fill the hosts + var text = ''; + for (var h = 0; h < that.main.tabs.hosts.list.length; h++) { + var host = that.main.tabs.hosts.list[h]; + text += ''; + } + + if (that.main.tabs.hosts.list.length <= 1) { + $dialogAddInstance.find('.dialog-add-instance-host').addClass('disabled').prop('disabled', true); + } else { + $dialogAddInstance.find('.dialog-add-instance-host').removeClass('disabled').prop('disabled', false); + } + $dialogAddInstance.find('.dialog-add-instance-host').html(text).select(); + + // find free instance numbers + var min = -1; + var used = []; + for (var i = 0; i < that.main.tabs.instances.list.length; i++) { + var parts = that.main.tabs.instances.list[i].split('.'); + if (parts[parts.length - 2] === adapter) { + var index = parseInt(parts[parts.length - 1], 10); + used.push(index); + if (index > min) { + min = index; + } + } + } + min += 10; + text = ''; + for (var m = 0; m < min; m++) { + if (used.indexOf(m) !== -1) continue; + text += ''; + } + $dialogAddInstance.find('.dialog-add-instance-number').html(text).select(); + $dialogAddInstance.find('.dialog-add-install-btn').off('click').on('click', function (e) { + if (callback) { + callback(true, $dialogAddInstance.find('.dialog-add-instance-host').val(), $dialogAddInstance.find('.dialog-add-instance-number').val()); + callback = null; + } + $dialogAddInstance.find('.dialog-add-cancel-btn').off('click'); + $dialogAddInstance.find('.dialog-add-instance-number').off('click'); + }); + + $dialogAddInstance.find('.dialog-add-cancel-btn').off('click').on('click', function (e) { + if (callback) { + callback(false); + callback = null; + } + $dialogAddInstance.find('.dialog-add-cancel-btn').off('click'); + $dialogAddInstance.find('.dialog-add-instance-number').off('click'); + }); + $dialogAddInstance.modal({ + dismissible: false, + complete: function () { + $dialogAddInstance.find('.dialog-add-instance-name').html(''); + } + }).modal('open'); + } + + function showLicenseDialog(adapter, callback) { + var $dialogLicense = $('#dialog-license'); + // Is adapter installed + if (that.data[adapter].installed || !that.data[adapter].licenseUrl) { + callback(true); + return; + } + + var timeout = setTimeout(function () { + timeout = null; + callback(true); + }, 10000); + + if (!that.data[adapter].licenseUrl) { + that.data[adapter].licenseUrl = 'https://raw.githubusercontent.com/yunkong2/yunkong2.' + (that.data[adapter].name || adapter) + '/master/LICENSE'; + } + if (typeof that.data[adapter].licenseUrl === 'object') { + that.data[adapter].licenseUrl = that.data[adapter].licenseUrl[systemLang] || that.data[adapter].licenseUrl.en; + } + // Workaround + // https://github.com/yunkong2/yunkong2.vis/blob/master/LICENSE => + // https://raw.githubusercontent.com/yunkong2/yunkong2.vis/master/LICENSE + if (that.data[adapter].licenseUrl.indexOf('github.com') !== -1) { + that.data[adapter].licenseUrl = that.data[adapter].licenseUrl.replace('github.com', 'raw.githubusercontent.com').replace('/blob/', '/'); + } + + that.main.socket.emit('httpGet', that.data[adapter].licenseUrl, function (error, response, body) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + + if (!error && body) { + $dialogLicense.css({'z-index': 200}); + body = body.toString().replace(/\r\n/g, '
    '); + body = body.replace(/\n/g, '
    '); + $dialogLicense.find('.license_text').html(body); + $dialogLicense.find('.license_agreement_name').text(_(' for %s', adapter)); + + $dialogLicense.modal({ + dismissible: false, + complete: function () { + $dialogLicense.find('.license_text').html(''); + } + }).modal('open'); + + $dialogLicense.find('.license_agree').off('click').on('click', function (e) { + if (callback) { + callback(true); + callback = null; + } + $dialogLicense.find('.license_agree').off('click'); + $dialogLicense.find('.license_non_agree').off('click'); + }); + + $dialogLicense.find('.license_non_agree').off('click').on('click', function (e) { + if (callback) { + callback(false); + callback = null; + } + $dialogLicense.find('.license_agree').off('click'); + $dialogLicense.find('.license_non_agree').off('click'); + }); + } else { + callback && callback(true); + callback = null; + } + } + }); + } + + this.initButtons = function (adapter) { + this.$tab.find('.adapter-install-submit[data-adapter-name="' + adapter + '"]').off('click').on('click', function () { + var adapter = $(this).attr('data-adapter-name'); + var desc = $(this).attr('data-adapter-desc'); + + // show config dialog + showAddInstanceDialog(adapter, desc, function (result, host, index) { + if (!result) return; + + that.getAdaptersInfo(host, false, false, function (repo, installed) { + var obj = repo[adapter]; + + if (!obj) obj = installed[adapter]; + + if (!obj) return; + + if (obj.license && obj.license !== 'MIT') { + // Show license dialog! + showLicenseDialog(adapter, function (isAgree) { + if (isAgree) { + that.main.cmdExec(null, 'add ' + adapter + ' ' + index + ' --host ' + host, function (exitCode) { + if (!exitCode) that._postInit(true); + }); + } + }); + } else { + that.main.cmdExec(null, 'add ' + adapter + ' ' + index + ' --host ' + host, function (exitCode) { + if (!exitCode) that._postInit(true); + }); + } + }); + }); + }); + + this.$tab.find('.adapter-delete-submit[data-adapter-name="' + adapter + '"]').off('click').on('click', function () { + var name = $(this).attr('data-adapter-name'); + that.main.confirmMessage(_('Are you sure you want to delete adapter %s?', name), _('Please confirm'), 'help', function (result) { + if (result) { + that.main.cmdExec(null, 'del ' + name, function (exitCode) { + if (!exitCode) that._postInit(true); + }); + } + }); + }); + + this.$tab.find('.adapter-readme-submit[data-adapter-name="' + adapter + '"]').off('click').on('click', function () { + that.main.navigate({ + tab: 'adapters', + dialog: 'readme', + params: $(this).data('adapter-name') + }); + }); + + this.$tab.find('.adapter-update-submit[data-adapter-name="' + adapter + '"]').off('click').on('click', function () { + var aName = $(this).attr('data-adapter-name'); + if (aName === 'admin') that.main.waitForRestart = true; + + that.main.cmdExec(null, 'upgrade ' + aName, function (exitCode) { + if (!exitCode) that._postInit(true); + }); + }); + + this.$tab.find('.adapter-upload-submit[data-adapter-name="' + adapter + '"]').off('click').on('click', function () { + var aName = $(this).attr('data-adapter-name'); + + that.main.cmdExec(null, 'upload ' + aName, function (exitCode) { + if (!exitCode) that._postInit(true); + }); + }); + + var $button = this.$tab.find('.adapter-update-custom-submit[data-adapter-name="' + adapter + '"]'); + $button.off('click').on('click', function () { + var versions = []; + if (that.main.objects['system.adapter.' + adapter].common.news) { + var news = that.main.objects['system.adapter.' + adapter].common.news; + for (var id in news) { + if (news.hasOwnProperty(id)) { + versions.push(id); + } + } + } else { + versions.push(that.main.objects['system.adapter.' + adapter].common.version); + } + var menu = '
    '; + for (var v = 0; v < versions.length; v++) { + var nnews = (news[versions[v]] ? news[versions[v]][systemLang] || news[versions[v]].en : ''); + menu += '' + versions[v] + ' - '; + } + menu += '
    '; + + var $adaptersMenu = $('#adapters-menu'); + if (!$adaptersMenu.length) { + //$adaptersMenu = $(''); + $adaptersMenu = $(''); + $adaptersMenu.appendTo($('.materialize-dialogs').first()); + $adaptersMenu.modal(); + } + $adaptersMenu.data('trigger', this); + + $adaptersMenu.find('p').html(menu); + $adaptersMenu.find('h4').html(_('Versions of %s', adapter)); + + $adaptersMenu.find('.adapters-versions-link').off('click').on('click', function () { + //if ($(this).data('link')) window.open($(this).data('link'), $(this).data('instance-id')); + $adaptersMenu.modal('close'); + var adapter = $(this).data('adapter-name'); + var version = $(this).data('version'); + if (version && adapter) { + that.main.cmdExec(null, 'upgrade ' + adapter + '@' + version, function (exitCode) { + if (!exitCode) that._postInit(true); + }); + } + }); + + /*$(this).dropdown({ + onCloseEnd: function () { + var $adaptersMenu = $('#adapters-menu'); + var trigger = $adaptersMenu.data('trigger'); + $(trigger).dropdown('close').dropdown('destroy'); + $adaptersMenu.data('trigger', null).hide(); + $adaptersMenu.remove(); + } + }).dropdown('open');*/ + $adaptersMenu.modal('open'); + + + // does not work... must be fixed. + //$adaptersMenu.find('.tooltipped').tooltip(); + }); + + if (!that.main.objects['system.adapter.' + adapter]) { + $button.hide();//addClass('disabled'); + } + }; + + this.objectChange = function (id, obj) { + // Update Adapter Table + if (id.match(/^system\.adapter\.[a-zA-Z0-9-_]+$/)) { + if (obj) { + if (this.list.indexOf(id) === -1) this.list.push(id); + } else { + var j = this.list.indexOf(id); + if (j !== -1) { + this.list.splice(j, 1); + } + } + + if (typeof this.$grid !== 'undefined' && this.$grid[0]._isInited) { + this.init(true); + } + } + }; + + function showUploadProgress(group, adapter, percent) { + var text = ''; + var opened; + if (adapter || typeof group === 'string') { + if (adapter) { + // text += '
    '; + opened = true; + } else { + percent = group; + group = null; + } + //percent = 80; + if (percent) { + text += + '' + + '' + + '' + + '' + + '' + + '
    ' + ; + } + //text += percent ? '
    ' : ''; + + if (opened) { + //text += '
    '; + } + return text; + } + + this.stateChange = function (id, state) { + if (id && state) { + var adapter = id.match(/^system\.adapter\.([\w\d-]+)\.upload$/); + if (adapter) { + var $adapter = this.$tab.find('.adapter-upload-progress[data-adapter-name="' + adapter[1] + '"]'); + var text = showUploadProgress(state.val); + $adapter.html(text).css({opacity: state.val ? 0.7 : 0}); + this.$tab.find('.group-upload-progress[data-adapter-group="' + $adapter.data('adapter-group') + '"]').html(text).css({opacity: state.val ? 0.7 : 0}); + } + } + }; +} diff --git a/src/js/adminConfig.js b/src/js/adminConfig.js new file mode 100644 index 0000000..a7e8f0b --- /dev/null +++ b/src/js/adminConfig.js @@ -0,0 +1,60 @@ +function Config(main) { + 'use strict'; + var that = this; + this.$dialog = $('#dialog-config'); + this.$configFrame = this.$dialog.find('#config-iframe'); + this.main = main; + + this.prepare = function () { + // id = 'system.adapter.NAME.X' + $iframeDialog = this; + }; + + this.init = function () { + if (this.inited) return; + + this.inited = true; + + + var id = this.main.navigateGetParams(); + + var parts = id.split('.'); + if (this.main.objects[id] && this.main.objects[id].common && this.main.objects[id].common.materialize) { + this.$configFrame.attr('src', 'adapter/' + parts[2] + '/index_m.html?' + parts[3]); + } else { + this.$configFrame.attr('src', 'adapter/' + parts[2] + '/?' + parts[3]); + } + + var name = id.replace(/^system\.adapter\./, ''); + this.$dialog.data('name', name); + this.$dialog.find('.title').html(_('Adapter configuration') + ': ' + name); + }; + + this.allStored = function () { + return !window.frames['config-iframe'].changed; + }; + + // this function is called by the configuration code in iFrame + this.close = function () { + that.main.navigate(); + }; + + this.destroy = function () { + if (this.inited) { + this.inited = false; + this.$configFrame.attr('src', ''); + + // If after wizard some configurations must be shown + if (typeof showConfig !== 'undefined' && showConfig && showConfig.length) { + var configId = showConfig.shift(); + setTimeout(function () { + that.main.navigate({ + tab: 'instances', + dialog: 'config', + params: configId + }); + }, 1000); + } + } + } +} \ No newline at end of file diff --git a/src/js/adminCustoms.js b/src/js/adminCustoms.js new file mode 100644 index 0000000..8e707cf --- /dev/null +++ b/src/js/adminCustoms.js @@ -0,0 +1,1055 @@ +function Customs(main) { + 'use strict'; + + var STR_DIFFERENT = '__different__'; + var that = this; + this.main = main; + this.$dialog = $('#dialog-customs'); + this.customEnabled = null; + this.currentCustoms = null; // Id of the currently shown customs dialog + + var $table; + var $outer; + var hdr; + var lastHistoryTimeStamp; + + var $tableDateFrom; + var $tableDateTo; + var $tableTimeFrom; + var $tableTimeTo; + + var $chartDateFrom; + var $chartDateTo; + var $chartTimeFrom; + var $chartTimeTo; + + var $historyTableInstance; + var $historyChartInstance; + + // ----------------------------- CUSTOMS ------------------------------------------------ + this.check = function () { + var found = false; + for (var u = 0; u < this.main.instances.length; u++) { + if (this.main.objects[this.main.instances[u]].common && + (this.main.objects[this.main.instances[u]].common.type === 'storage' || this.main.objects[this.main.instances[u]].common.supportCustoms) && + this.main.objects[this.main.instances[u]].common.enabled) { + if (this.customEnabled !== null && this.customEnabled !== true) { + this.customEnabled = true; + // update customs buttons + if (this.inited) { + this.init(null, true); + } + } else { + this.customEnabled = true; + } + found = true; + return; + } + } + if (this.customEnabled !== null && this.customEnabled !== false) { + this.customEnabled = false; + // update custom button + if (this.inited) { + this.init(null, true); + } + } else { + this.customEnabled = false; + } + }; + + this.stateChange = function (id /*, state */) { + if (this.currentCustoms === id) { + updateTable(); + } + }; + + this.initCustomsTabs = function (ids, instances) { + var $customTabs = this.$dialog.find('#customs-tabs'); + ids = ids || []; + $customTabs.html(''); + var wordDifferent = _(STR_DIFFERENT); + this.defaults = {}; + var collapsed = this.main.config['object-customs-collapsed']; + collapsed = collapsed ? collapsed.split(',') : []; + + var commons = {}; + var type = null; + var role = null; + // calculate common settings + for (var i = 0; i < instances.length; i++) { + var inst = instances[i].replace(/^system\.adapter\./, ''); + commons[inst] = {}; + for (var id = 0; id < ids.length; id++) { + var custom = main.objects[ids[id]].common.custom; + var sett = custom ? custom[inst] : null; + + if (main.objects[ids[id]].common) { + if (type === null) { + type = main.objects[ids[id]].common.type; + } else if (type !== '' && type !== main.objects[ids[id]].common.type) { + type = ''; + } + if (role === null) { + role = main.objects[ids[id]].common.role; + } else if (role !== '' && role !== main.objects[ids[id]].common.role) { + role = ''; + } + } + + if (sett) { + for (var _attr in sett) { + if (!sett.hasOwnProperty(_attr)) continue; + if (commons[inst][_attr] === undefined) { + commons[inst][_attr] = sett[_attr]; + } else if (commons[inst][_attr] !== sett[_attr]) { + commons[inst][_attr] = STR_DIFFERENT; + } + } + } else { + var a = inst.split('.')[0]; + var _default = null; + // Try to get default values + if (defaults[a]) { + if (typeof defaults[a] === 'function') { + _default = defaults[a](that.main.objects[ids[id]], that.main.objects['system.adapter.' + inst]); + } else { + _default = defaults[a]; + } + } else { + _default = this.defaults[a]; + } + + for (var attr in _default) { + if (!_default.hasOwnProperty(attr)) continue; + if (commons[inst][attr] === undefined) { + commons[inst][attr] = _default[attr]; + } else if (commons[inst][attr] !== _default[attr]) { + commons[inst][attr] = STR_DIFFERENT; + } + } + } + } + } + + // add all tabs to div + for (var j = 0; j < instances.length; j++) { + // try to find settings + var parts = instances[j].split('.'); + var adapter = parts[2]; + var instance = parts[3]; + var data = adapter + '.' + instance; + var img = this.main.objects['system.adapter.' + adapter].common.icon; + img = '/adapter/' + adapter + '/' + img; + var tab = + '
  • ' + + '
    ' + + ' ' + _('Settings for %s', data) + + ' ' + _('active') + '' + + '
    ' + + '
    ' + + $('script[data-template-name="' + adapter + '"]').html() + + '
    ' + + '
  • '; + + var $tab = $(tab); + this.defaults[adapter] = {}; + // set values + $tab.find('input, select').each(function() { + var $this = $(this); + $this.attr('data-instance', adapter + '.' + instance); + var field = $this.attr('data-field'); + var def = $this.attr('data-default'); + if (def === 'true') def = true; + if (def === 'false') def = false; + if (def !== undefined && def.toString().replace(/\+/, '') === parseFloat(def).toString()) { + def = parseFloat(def); + } + + that.defaults[adapter][field] = def; + if (field === 'enabled') { + $this.on('click', function (event) { + event.stopPropagation(); + if ($(this).prop('checked')) { + + } else { + + } + }); + } + }); + + $customTabs.append($tab); + // post init => add custom logic + if (customPostInits.hasOwnProperty(adapter) && typeof customPostInits[adapter] === 'function') { + customPostInits[adapter]($tab, commons[adapter + '.' + instance], that.main.objects['system.adapter.' + adapter + '.' + instance], type, role); + } + } + + // set values + $customTabs.find('input, select').each(function() { + var $this = $(this); + var instance = $this.data('instance'); + var adapter = instance.split('.')[0]; + var attr = $this.data('field'); + + if (commons[instance][attr] !== undefined) { + if ($this.attr('type') === 'checkbox') { + if (commons[instance][attr] === STR_DIFFERENT) { + /*$('').insertBefore($this); + $this.hide().attr('data-field', '').data('field', '');*/ + $this[0].indeterminate = true; + } else { + $this.prop('checked', commons[instance][attr]); + } + } else { + if (commons[instance][attr] === STR_DIFFERENT) { + if ($this.attr('type') === 'number') { + $this.attr('type', 'text'); + } + if ($this.prop('tagName').toUpperCase() === 'SELECT'){ + $this.prepend(''); + $this.val(wordDifferent); + } else { + $this.val('').attr('placeholder', wordDifferent); + } + } else { + $this.val(commons[instance][attr]); + } + } + } else { + var def; + if (that.defaults[adapter] && that.defaults[adapter][attr] !== undefined) { + def = that.defaults[adapter][attr]; + } + if (def !== undefined) { + if ($this.attr('type') === 'checkbox') { + $this.prop('checked', def); + } else { + $this.val(def); + } + } + } + + if ($this.attr('type') === 'checkbox') { + $this.on('change', function () { + that.$dialog.find('.dialog-system-buttons .btn-save').removeClass('disabled'); + if ($(this).data('field') === 'enabled') { + var instance = $this.data('instance'); + var $headerActive = $customTabs.find('.activated[data-adapter="' + instance + '"]'); + if ($(this).prop('checked')) { + $headerActive.css('opacity', 1); + } else { + $headerActive.css('opacity', 0); + } + } + }); + } else { + $this.on('change', function () { + that.$dialog.find('.dialog-system-buttons .btn-save').removeClass('disabled'); + }).on('keyup', function () { + $(this).trigger('change'); + }); + } + }); + + this.showCustomsData(ids.length > 1 ? null : ids[0]); + this.$dialog.find('.dialog-system-buttons .btn-save').addClass('disabled'); + translateAll('#dialog-customs'); + var $collapsible = that.$dialog.find('.collapsible'); + $collapsible.collapsible({ + onOpenEnd: function (el) { + // store settings + var _collapsed = that.main.config['object-customs-collapsed']; + _collapsed = _collapsed ? _collapsed.split(',') : []; + var id = $(el).data('adapter'); + var pos = _collapsed.indexOf(id); + if (pos !== -1) _collapsed.splice(pos, 1); + that.main.saveConfig('object-customs-collapsed', _collapsed.join(',')); + }, + onCloseEnd: function (el) { + // store settings + var _collapsed = that.main.config['object-customs-collapsed']; + _collapsed = _collapsed ? _collapsed.split(',') : []; + var id = $(el).data('adapter'); + var pos = _collapsed.indexOf(id); + if (pos === -1) _collapsed.push(id); + that.main.saveConfig('object-customs-collapsed', _collapsed.join(',')); + } + }); + + that.$dialog.find('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev();//.addClass('filled-in'); + if (!$input.prop('disabled')) { + if ($input[0].indeterminate) { + $input[0].indeterminate = false; + $input.prop('checked', true).trigger('change'); + } else { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + } + }); + $customTabs.find('select').select(); + M.updateTextFields('#dialog-customs'); + + this.resizeHistory(); + }; + + function installColResize() { + if (!$.fn.colResizable) return; + if ($outer.is(':visible')) { + if (!$outer.data('inited')) { + hdr = new IobListHeader('grid-history-header', {list: $outer, colWidthOffset: 1, prefix: 'log-filter'}); + + // todo define somehow the width of every column + hdr.add('text', 'val'); + hdr.add('text', 'ack'); + hdr.add('text', 'from'); + hdr.add('text', 'ts'); + hdr.add('text', 'lc'); + } + + // Fix somehow, that columns have different widths + $outer.colResizable({ + liveDrag: true, + + partialRefresh: true, + marginLeft: 5, + postbackSafe:true, + + onResize: function (event) { + return hdr.syncHeader(); + } + }); + + hdr.syncHeader(); + } else { + setTimeout(function () { + installColResize(); + }, 200) + } + } + + function updateTable(delay) { + // Load data again from adapter + if (delay) { + if (that.historyTimeout) { + clearTimeout(that.historyTimeout) + } + } else if (that.historyTimeout) { + return; + } + + that.historyTimeout = setTimeout(function () { + that.historyTimeout = null; + if ($historyTableInstance) { + that.loadHistoryTable($historyTableInstance.data('id'), true); + } + }, delay || 5000); + } + + function download(filename, text) { + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + } + + this.loadHistoryTable = function (id, isSilent, isDownload) { + $outer = $outer || that.$dialog.find('#grid-history'); + $table = $table || that.$dialog.find('#grid-history-body'); + + if (!isSilent) { + $table.html('' + _('Loading...') + ''); + } + + var request = { + aggregate: 'none', + instance: $historyTableInstance.val(), + from: true, + ack: true, + q: true + }; + + if (!$tableDateFrom) { + $tableDateFrom = this.$dialog.find('#tab-customs-table .datepicker.date-from'); + $tableDateTo = this.$dialog.find('#tab-customs-table .datepicker.date-to'); + $tableTimeFrom = this.$dialog.find('#tab-customs-table .timepicker.time-from'); + $tableTimeTo = this.$dialog.find('#tab-customs-table .timepicker.time-to'); + } + + var dateFrom = $tableDateFrom.val() ? M.Datepicker.getInstance($tableDateFrom).toString('yyyy.mm.dd') : ''; + var timeFrom = $tableTimeFrom.val(); + var dateTo = $tableDateTo.val() ? M.Datepicker.getInstance($tableDateTo).toString('yyyy.mm.dd') : ''; + var timeTo = $tableTimeTo.val(); + var empty = true; + if (dateTo) { + dateTo = new Date(dateTo); + empty = false; + dateTo.setHours(23); + dateTo.setMinutes(59); + dateTo.setSeconds(59); + dateTo.setMilliseconds(999); + } else { + dateTo = new Date(); + } + if (timeTo) { + var parts = timeTo.split(':'); + dateTo.setHours(parts[0]); + dateTo.setMinutes(parts[1]); + dateTo.setSeconds(59); + dateTo.setMilliseconds(999); + empty = false; + } + dateTo = dateTo.getTime(); + if (empty) dateTo += 10000; + request.end = dateTo; + + if (dateFrom || timeFrom) { + dateFrom = new Date(dateFrom || dateTo); + if (timeFrom) { + var part__ = timeFrom.split(':'); + dateFrom.setHours(part__[0]); + dateFrom.setMinutes(part__[1]); + } else { + dateFrom.setHours(0); + dateFrom.setMinutes(0); + } + dateFrom.setSeconds(0); + dateFrom.setMilliseconds(0); + request.start = dateFrom.getTime(); + } else { + request.count = 50; + } + var fileName; + if (isDownload) { + fileName = new Date(dateTo).toISOString() + '_' + (request.start ? new Date(request.start) : request.count + 'points') + '_' + id + '__' + request.instance + '.csv'; + } + + + main.socket.emit('getHistory', id, request, function (err, res) { + setTimeout(function () { + var csv = 'value;acknowledged;from;timestamp;lastchanged;\n'; + if (!err) { + var text = ''; + if (res && res.length) { + for (var i = res.length - 1; i >= 0; i--) { + var from = (res[i].from || '').replace('system.adapter.', '').replace('system.', ''); + text += '' + + ' ' + res[i].val + '' + + ' ' + res[i].ack + '' + + ' ' + from + '' + + ' ' + main.formatDate(res[i].ts) + '' + + ' ' + main.formatDate(res[i].lc) + '' + + '\n'; + + if (isDownload) { + csv += res[i].val + ';' + res[i].ack + ';' + (from || '') + ';' + (res[i].ts ? new Date(res[i].ts).toISOString() : '') + ';' + (res[i].lc ? new Date(res[i].lc).toISOString() : '') + ';\n'; + } + } + lastHistoryTimeStamp = res[res.length - 1].ts; + } else { + text = '' + _('No data') + '' + } + $table.html(text); + } else { + console.error(err); + $table.html('' + err + ''); + } + installColResize(); + if (isDownload) { + download(fileName, csv); + } + }, 0); + }); + }; + + this.loadHistoryChart = function (id) { + if (!$chartDateFrom) { + $chartDateFrom = this.$dialog.find('#tab-customs-chart .datepicker.date-from'); + $chartDateTo = this.$dialog.find('#tab-customs-chart .datepicker.date-to'); + } + + if (id) { + var port = 0; + var chart = false; + var isSecure = false; + for (var i = 0; i < this.main.instances.length; i++) { + if (this.main.objects[main.instances[i]].common.name === 'flot' && this.main.objects[this.main.instances[i]].common.enabled) { + chart = 'flot'; + } else + if (!chart && this.main.objects[main.instances[i]].common.name === 'rickshaw' && this.main.objects[this.main.instances[i]].common.enabled) { + chart = 'rickshaw'; + } else + if (this.main.objects[this.main.instances[i]].common.name === 'web' && this.main.objects[this.main.instances[i]].common.enabled) { + port = this.main.objects[this.main.instances[i]].native.port; + isSecure = this.main.objects[this.main.instances[i]].native.secure; + } + if (chart === 'flot' && port) break; + } + var $chart = this.$dialog.find('#iframe-history-chart'); + + var linkTemplate = 'http{isSecure}://{hostname}:{port}/{chart}/index.html?range=1440&zoom=true&axeX=lines&axeY=inside&hoverDetail=true&aggregate=onchange&chartType=step&live=30&instance={instance}&l%5B0%5D%5Bid%5D={id}&l%5B0%5D%5Boffset%5D=0&l%5B0%5D%5Baggregate%5D=minmax&l%5B0%5D%5Bcolor%5D=%231868a8&l%5B0%5D%5Bthickness%5D=1&l%5B0%5D%5Bshadowsize%5D=1&l%5B0%5D%5Bsmoothing%5D=0&l%5B0%5D%5BafterComma%5D=0&l%5B0%5D%5BignoreNull%5D=false&aggregateType=step&aggregateSpan=300&relativeEnd=now&timeType=relative&noBorder=noborder&bg=rgba(0%2C0%2C0%2C0)&timeFormat=%25H%3A%25M&useComma={comma}&noedit=false&animation=0'; + linkTemplate = linkTemplate.replace('{isSecure}', (isSecure ? 's' : '')); + linkTemplate = linkTemplate.replace('{hostname}', location.hostname); + linkTemplate = linkTemplate.replace('{port}', port); + linkTemplate = linkTemplate.replace('{chart}', chart); + linkTemplate = linkTemplate.replace('{instance}', that.$dialog.find('#tab-customs-chart .select-instance').val()); + linkTemplate = linkTemplate.replace('{id}', encodeURI(id)); + linkTemplate = linkTemplate.replace('{comma}', that.main.systemConfig && that.main.systemConfig.common && that.main.systemConfig.isFloatComma); + + // find out + $chart.attr('src', linkTemplate);//'http' + (isSecure ? 's' : '') + '://' + location.hostname + ':' + port + '/' + chart + '/index.html?range=1440&zoom=true&axeX=lines&axeY=inside&_ids=' + encodeURI(id) + '&width=' + ($chart.width() - 50) + '&hoverDetail=true&height=' + ($chart.height() - 50) + '&aggregate=onchange&chartType=step&live=30&instance=' + that.$dialog.find('#tab-customs-chart .select-instance').val()); + } else { + this.$dialog.find('#iframe-history-chart').attr('src', ''); + } + }; + + this.showCustomsData = function (id) { + var $tabs = this.$dialog.find('#tabs-customs'); + + var port = 0; + var chart = false; + + initTab('tab-customs-settings'); + + if (id) { + $tabs.data('id', id); + + // Check if chart enabled and set + for (var i = 0; i < main.instances.length; i++) { + if (main.objects[main.instances[i]].common.name === 'flot' && main.objects[main.instances[i]].common.enabled) { + chart = 'flot'; + } else + if (!chart && main.objects[main.instances[i]].common.name === 'rickshaw' && main.objects[main.instances[i]].common.enabled) { + chart = 'rickshaw'; + } else + if (main.objects[main.instances[i]].common.name === 'web' && main.objects[main.instances[i]].common.enabled) { + port = main.objects[main.instances[i]].native.port; + } + if (chart === 'flot' && port) break; + } + that.loadHistoryTable(id); + + $tabs.find('.tabs .tab-table').removeClass('disabled'); + + if (port && chart && that.currentCustoms) { + $tabs.find('.tabs .tab-chart').removeClass('disabled'); + } else { + $tabs.find('.tabs .tab-chart').addClass('disabled'); + } + } else { + $tabs.find('.tabs .tab-table').addClass('disabled'); + $tabs.find('.tabs .tab-chart').addClass('disabled'); + } + }; + + function getCustomTemplate(adapter, callback) { + $.ajax({ + headers: { + Accept: 'text/html' + }, + cache: true, + url: '/adapter/' + adapter + '/custom_m.html', + success: function (_data) { + callback(null, _data); + }, + error: function (jqXHR) { + // todo: remove some days 2017.12.19 (for admin2) + $.ajax({ + headers: { + Accept: 'text/html' + }, + cache: true, + url: '/adapter/' + adapter + '/custom.html', + success: function (_data) { + callback(null, _data); + }, + error: function (jqXHR) { + callback(jqXHR.responseText); + } + }); + } + }); + } + + // Set modified custom states + this.setCustoms = function (ids, callback) { + var id = ids.pop(); + if (id) { + this.$dialog.find('#tab-customs-settings .title').html(_('Adapter settings for %s states', ids.length)); + + that.main.socket.emit('setObject', id, this.main.objects[id], function (err) { + if (err) { + that.main.showMessage(_(err), _('Error'), 'error_outline'); + } else { + setTimeout(function () { + that.setCustoms(ids, callback); + }, 50); + } + }); + } else { + if (callback) callback(); + } + }; + + this.resizeHistory = function () { + // resize only if chart is visible + var $iFrame = this.$dialog.find('#iframe-history-chart'); + if ($iFrame.attr('src')) { + var timeout = $iFrame.data('timeout'); + if (timeout) clearTimeout(timeout); + + $iFrame.data('timeout', setTimeout(function () { + that.$dialog.find('#iframe-history-chart').data('timeout', null); + that.loadHistoryChart(that.$dialog.find('#tabs-customs').data('id')); // reinit iframe + }, 1000)); + } + }; + + function onButtonSave(e) { + e.stopPropagation(); + e.preventDefault(); + + var $tabs = that.$dialog.find('#customs-tabs'); + var ids = $tabs.data('ids'); + + // do not update charts + that.currentCustoms = null; + var wordDifferent = _(STR_DIFFERENT); + + // collect default values + var $inputs = $tabs.find('input, select'); + + //that.historyIds = ids; + $inputs.each(function () { + var instance = $(this).data('instance'); + var field = $(this).data('field'); + if (!field) return; + + var val; + if ($(this).attr('type') === 'checkbox') { + if (this.indeterminate) return; + val = $(this).prop('checked'); + } else { + val = $(this).val(); + } + // if not changed + if (val === wordDifferent) return; + + if (val === null) val = ''; + if (val === undefined) val = ''; + if (val === 'false') val = false; + if (val === 'true') val = true; + var f = parseFloat(val); + // replace trailing 0 and prefix + + if (val.toString().replace(/^\+/, '').replace(/([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/,'$1') === f.toString()) { + val = f; + } + + for (var i = 0; i < ids.length; i++) { + var custom = that.main.objects[ids[i]].common.custom; + custom = that.main.objects[ids[i]].common.custom = custom || {}; + + if (custom[instance] === undefined) { + var adapter = instance.split('.')[0]; + var _default; + // Try to get default values + if (defaults[adapter]) { + if (typeof defaults[adapter] === 'function') { + _default = defaults[adapter](that.main.objects[ids[i]], that.main.objects['system.adapter.' + instance]); + } else { + _default = defaults[adapter]; + } + } else { + _default = that.defaults[adapter]; + } + custom[instance] = _default || {}; + } + custom[instance][field] = val; + } + }); + + + if (ids) { + that.$dialog.find('.dialog-system-buttons .btn-save').addClass('disabled'); + + for (var i = 0; i < ids.length; i++) { + var found = false; + var custom_ = that.main.objects[ids[i]].common.custom; + for (var inst in custom_) { + if (!custom_.hasOwnProperty(inst)) continue; + if (!custom_[inst].enabled) { + delete custom_[inst]; + } else { + found = true; + } + } + if (!found) { + that.main.objects[ids[i]].common.custom = null; + } + } + that.setCustoms(ids, function () { + // disable iframe + that.loadHistoryChart(); // disable iframe + that.main.navigate(); + }); + } + + } + + // return true if all data are stored + this.allStored = function () { + return that.$dialog.find('.dialog-system-buttons .btn-save').hasClass('disabled'); + }; + + function initTab(id) { + switch (id) { + case 'tab-customs-settings': + that.loadHistoryChart(); // disable iframe + break; + + case 'tab-customs-table': + $historyTableInstance.select(); + that.loadHistoryChart(); // disable iframe + break; + + case 'tab-customs-chart': + that.$dialog.find('#tab-customs-chart .select-instance').select(); + var $tabs = that.$dialog.find('#tabs-customs'); + that.loadHistoryChart($tabs.data('id')); // init iframe + break; + } + } + + this.init = function (_ids, isUpdate) { + if (this.inited && !isUpdate) { + return; + } + + var ids = this.main.navigateGetParams(); + + if (ids) { + ids = ids.split(','); + } + // if the list of IDs is too long, it was saved into this.ids + if (!ids || !ids.length) { + ids = this.ids; + this.ids = undefined; + } + var instances = []; + + // clear global defaults object + this.defaults = {}; + + // collect all custom instances + var count = 0; + var data = ''; + var urls = []; + for (var u = 0; u < this.main.instances.length; u++) { + var inst = this.main.objects[this.main.instances[u]]; + if (inst && inst.common && (inst.common.type === 'storage' || inst.common.supportCustoms)) { + instances.push(this.main.instances[u]); + var url = this.main.instances[u].split('.'); + if (urls.indexOf(url[2]) === -1) { + urls.push(url[2]); + count++; + getCustomTemplate(url[2], function (err, result) { + if (err) console.error(err); + if (result) data += result; + if (!--count) { + that.$dialog.find('#customs-templates').html(data); + that.initCustomsTabs(ids, instances); + } + }); + } + } + } + var _instances = []; + if (ids) { + for (var i = ids.length - 1; i >= 0; i--) { + if (!this.main.objects[ids[i]]) { + console.warn('Null object: ' + ids[i]); + ids.splice(i, 1); + } else { + var custom = this.main.objects[ids[i]].common.custom; + if (custom) { + var found = false; + // delete disabled entries + for (var h in custom) { + if (!custom.hasOwnProperty(h)) continue; + if (custom[h].enabled === false) { + delete custom[h]; + } else { + if (ids.length === 1) _instances.push(h); + found = true; + } + } + if (!found) { + delete this.main.objects[ids[i]].common.custom; + } + } + } + } + } + + var title; + $historyTableInstance = this.$dialog.find('#tab-customs-table .select-instance'); + $historyChartInstance = this.$dialog.find('#tab-customs-chart .select-instance'); + var $historyTableInstanceBtn = this.$dialog.find('#tab-customs-table .refresh'); + var $historyTableDownloadBtn = this.$dialog.find('#tab-customs-table .download'); + var $historyChartInstanceBtn = this.$dialog.find('#tab-customs-chart .refresh'); + + if (ids && ids.length === 1) { + title = _('Storage of %s', ids[0]); + this.currentCustoms = _instances.length ? ids[0] : null; + var text = ''; + for (var k = 0; k < _instances.length; k++) { + var insta = this.main.objects['system.adapter.' + _instances[k]]; + if (insta && insta.common && (insta.common.enabled || + (this.main.states['system.adapter.' + _instances[k] + '.alive'] && this.main.states['system.adapter.' + _instances[k] + '.alive'].val))) { + text += '\n'; + } + } + if (text) { + $historyTableInstance + .data('id', ids[0]) + .html(text) + .show() + .off('change') + .on('change', function () { + that.main.saveConfig('object-history-table', $historyTableInstance.val()); + that.loadHistoryTable($(this).data('id')); + }).select(); + + $historyChartInstance + .data('id', ids[0]) + .html(text) + .show() + .off('change') + .on('change', function () { + that.main.saveConfig('object-history-chart', $historyChartInstance.val()); + that.loadHistoryChart($(this).data('id')); // reinit iframe + }).select(); + + if (this.main.config['object-history-table'] !== undefined) { + $historyTableInstance.val(this.main.config['object-history-table']) + } + if (this.main.config['object-history-chart'] !== undefined) { + $historyChartInstance.val(this.main.config['object-history-chart']) + } + $historyTableInstanceBtn + .data('id', ids[0]) + .show() + .off('click') + .on('click', function () { + that.$dialog.find('#grid-history-body').html(''); + that.loadHistoryTable($(this).data('id')); + }); + $historyChartInstanceBtn + .data('id', ids[0]) + .show() + .off('click').on('click', function () { + that.loadHistoryChart($(this).data('id')); // reinit iframe + }); + + $historyTableDownloadBtn + .data('id', ids[0]) + .show() + .off('click') + .on('click', function () { + that.loadHistoryTable($(this).data('id'), false, true); + }); + + var yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + var i18n = { + today: _('Today'), + clear: _('Clear'), + done: _('Ok'), + months : [_('January'),_('February'),_('March'),_('April'),_('May'),_('June'),_('July'),_('August'),_('September'),_('October'),_('November'),_('December')], + monthsShort : [_('Jan'),_('Feb'),_('Mar'),_('Apr'),_('May'),_('Jun'),_('Jul'),_('Aug'),_('Sep'),_('Oct'),_('Nov'),_('Dec')], + weekdaysShort : [_('Sun'),_('Mon'),_('Tue'),_('Wed'),_('Thu'),_('Fri'),_('Sat')], + weekdays : [_('Sunday'),_('Monday'),_('Tuesday'),_('Wednesday'),_('Thursday'),_('Friday'),_('Saturday')], + weekdaysAbbrev : ['S','M','T','W','T','F','S'] + }; + for (var n = 0; n < i18n.weekdaysAbbrev.length; n++) { + i18n.weekdaysAbbrev[n] = i18n.weekdaysShort[n][0]; + } + if (!$tableDateFrom) { + $tableDateFrom = this.$dialog.find('#tab-customs-table .datepicker.date-from'); + $tableDateTo = this.$dialog.find('#tab-customs-table .datepicker.date-to'); + $tableTimeFrom = this.$dialog.find('#tab-customs-table .timepicker.time-from'); + $tableTimeTo = this.$dialog.find('#tab-customs-table .timepicker.time-to'); + + $chartDateFrom = this.$dialog.find('#tab-customs-chart .datepicker.date-from'); + $chartDateTo = this.$dialog.find('#tab-customs-chart .datepicker.date-to'); + } + + $tableDateFrom.datepicker({ + defaultDate: yesterday, + showDaysInNextAndPreviousMonths: true, + minYear: 2014, + maxYear: 2032, + i18n: i18n, + setDefaultDate: true, + firstDay: 1, + onSelect: function (date) { + $tableDateFrom.datepicker('setInputValue'); + $tableDateFrom.datepicker('close'); + } + }); + $tableDateFrom.on('change', function () { + updateTable(1000); + }); + + $tableTimeFrom.timepicker({ + defaultTime: '00:00', + twelveHour: false, // TODO + doneText: _('Ok'), + clearText: _('Clear'), + cancelText: _('Cancel'), + autoClose: true + }); + $tableTimeFrom.on('change', function () { + updateTable(1000); + }); + + $tableTimeTo.timepicker({ + defaultTime: 'now', + twelveHour: false, // TODO + doneText: _('Ok'), + clearText: _('Clear'), + cancelText: _('Cancel'), + autoClose: true + }); + $tableTimeTo.on('change', function () { + updateTable(1000); + }); + + $tableDateTo.datepicker({ + defaultDate: new Date(), + showDaysInNextAndPreviousMonths: true, + minYear: 2014, + maxYear: 2032, + i18n: i18n, + setDefaultDate: true, + firstDay: 1, + onSelect: function (date) { + $tableDateTo.datepicker('setInputValue'); + $tableDateTo.datepicker('close'); + } + }); + $tableDateTo.on('change', function () { + updateTable(1000); + }); + + + $chartDateFrom.datepicker({ + defaultDate: yesterday, + showDaysInNextAndPreviousMonths: true, + minYear: 2014, + maxYear: 2032, + i18n: i18n, + setDefaultDate: true, + firstDay: 1, + onSelect: function (date) { + $chartDateFrom.datepicker('setInputValue'); + $chartDateFrom.datepicker('close'); + } + }); + $chartDateFrom.on('change', function () { + that.loadHistoryChart($historyChartInstance.data('id')); + }); + $chartDateTo.datepicker({ + defaultDate: new Date(), + showDaysInNextAndPreviousMonths: true, + minYear: 2014, + maxYear: 2032, + i18n: i18n, + setDefaultDate: true, + firstDay: 1, + onSelect: function (date) { + $chartDateTo.datepicker('setInputValue'); + $chartDateTo.datepicker('close'); + } + }); + $chartDateTo.on('change', function () { + that.loadHistoryChart($historyChartInstance.data('id')); + }); + } else { + $historyTableInstance.hide(); + $historyChartInstance.hide(); + $historyTableInstanceBtn.hide(); + $historyChartInstanceBtn.hide(); + $historyTableDownloadBtn.hide(); + } + if (this.currentCustoms) { + that.main.subscribeStates(this.currentCustoms); + } + this.$dialog.find('#tab-customs-table .title').html(_('Values of %s', ids[0])); + this.$dialog.find('#tab-customs-chart .title').html(_('Chart for %s', ids[0])); + } else if (ids) { + $historyTableInstance.hide(); + $historyChartInstance.hide(); + $historyTableInstanceBtn.hide(); + $historyChartInstanceBtn.hide(); + $historyTableDownloadBtn.hide(); + title = _('Storage of %s states', ids.length); + this.currentCustoms = null; + } + + this.$dialog.find('#tab-customs-settings .title').html(title); + + var $tabs = this.$dialog.find('#tabs-customs'); + $tabs.find('.tabs').mtabs({ + onShow: function (tab) { + if (!tab) return; + initTab($(tab).attr('id')); + } + }); + this.$dialog.find('#customs-tabs').data('ids', ids); + that.$dialog.find('.dialog-system-buttons .btn-save').off('click').on('click', onButtonSave); + that.$dialog.find('.dialog-system-buttons .btn-cancel').off('click').on('click', function (e) { + e.stopPropagation(); + e.preventDefault(); + if (!that.$dialog.find('.dialog-system-buttons .btn-save').hasClass('disabled')) { + that.main.confirmMessage(_('Are you sure? Changes are not saved.'), _('Please confirm'), 'error_outline', function (result) { + if (result) { + that.$dialog.find('.dialog-system-buttons .btn-save').addClass('disabled'); + // disable iframe + that.loadHistoryChart(); + that.main.navigate(); + } + }); + } else { + // disable iframe + that.loadHistoryChart(); + that.main.navigate(); + } + }); + }; + + this.destroy = function () { + if (this.inited) { + this.$dialog.find('.collapsible').collapsible('destroy'); + this.inited = false; + // disable iframe + this.loadHistoryChart(); + if (this.currentCustoms) { + that.main.unsubscribeStates(this.currentCustoms); + } + } + }; + + return this; +} diff --git a/src/js/adminEditObject.js b/src/js/adminEditObject.js new file mode 100644 index 0000000..af03b1a --- /dev/null +++ b/src/js/adminEditObject.js @@ -0,0 +1,627 @@ +function EditObject(main) { + 'use strict'; + + var that = this; + this.$dialog = $('#dialog-editobject'); + this.$dialogNewField = $('#dialog-new-field'); + this.main = main; + this.prepared = false; + this.inited = false; + this.$dialogSave = this.$dialog.find('.dialog-editobject-buttons .btn-save'); + this.iconVal = null; + + function loadObjectFields(selector, object, part, objectType) { + var text = ''; + for (var attr in object) { + if (!object.hasOwnProperty(attr) || (part === 'common' && (attr === 'name' || attr === 'icon'))) continue; + + if (false && objectType === 'state' && part === 'common' && attr === 'role') { // autocomplete is temporally disabled because buggy + text += '
    ' + + 'textsms' + + ''; + } else { + text += '
    \n
    \n'; + if (objectType === 'state' && part === 'common' && attr === 'type') { + text += ''; + } else if (typeof object[attr] === 'string') { + text += '\n'; + } else if (typeof object[attr] === 'number') { + text += '\n'; + } else if (typeof object[attr] === 'boolean') { + text += '\n'; + } else { + text += '\n'; + } + } + + var title = attr; + // translations + if (part === 'common' && systemDictionary['common_' + attr] && systemDictionary['common_' + attr][systemLang]) { + title = _('common_' + attr); + } + + // workaround for materialize + if (typeof object[attr] === 'boolean') { + text += '' + title + '\n'; + } else { + text += '\n'; + } + + text += '
    \n\n'; + text += '
    \n'; + } + + that.$dialog.find(selector).html(text); + /*that.$dialog.find(selector).find('.autocomplete').each(function () { + $(this).mautocomplete({ + data: { + 'state': null, + 'switch': null, + 'button': null, + 'value': null, + 'level': null, + 'indicator': null, + 'value.temperature': null, + 'value.humidity': null, + 'level.temperature': null, + 'level.dimmer': null + }, + minLength: 0 // The minimum length of the input for the autocomplete to start. Default: 1. + }); + });*/ + } + + function saveObjectFields(selector, object) { + var $htmlId = that.$dialog.find(selector); + $htmlId.find('.object-tab-edit-string').each(function () { + object[$(this).data('attr')] = $(this).val(); + }); + $htmlId.find('.object-tab-edit-number').each(function () { + object[$(this).data('attr')] = parseFloat($(this).val()); + }); + $htmlId.find('.object-tab-edit-boolean').each(function () { + object[$(this).data('attr')] = $(this).prop('checked'); + }); + var err = null; + $htmlId.find('.object-tab-edit-object').each(function () { + try { + object[$(this).data('attr')] = JSON.parse($(this).val()); + } catch (e) { + err = $(this).data('attr'); + return false; + } + }); + + if (object.write !== undefined) { + if (object.write === 'false' || object.write === '0' || object.write === 0) object.write = false; + if (object.write === 'true' || object.write === '1' || object.write === 1) object.write = true; + } + + if (object.read !== undefined) { + if (object.read === 'false' || object.read === '0' || object.read === 0) object.read = false; + if (object.read === 'true' || object.read === '1' || object.read === 1) object.read = true; + } + + if (object.min === null) { + delete object.min; + } + if (object.min !== undefined) { + var f = parseFloat(object.min); + if (f.toString() === object.min.toString()) object.min = f; + + if (object.min === 'false') object.min = false; + if (object.min === 'true') object.min = true; + } + if (object.max === null) { + delete object.max; + } + if (object.max !== undefined) { + var m = parseFloat(object.max); + if (m.toString() === object.max.toString()) object.max = m; + + if (object.max === 'false') object.max = false; + if (object.max === 'true') object.max = true; + } + if (object.def === null) { + delete object.def; + } + + if (object.def !== undefined) { + var d = parseFloat(object.def); + if (d.toString() === object.def.toString()) object.def = d; + + if (object.def === 'false') object.def = false; + if (object.def === 'true') object.def = true; + } + + // common part cannot have "true" or "false". Only true and false. + if (selector.indexOf('common') !== -1) { + for (var attr in object) { + if (object.hasOwnProperty(attr)) { + if (object[attr] === 'true') { + object[attr] = true; + } + if (object[attr] === 'false') { + object[attr] = false; + } + if (parseFloat(object[attr]).toString() === object[attr]) { + object[attr] = parseFloat(object[attr]); + } + } + } + } + + return err; + } + + function showMessage(text, duration, isError) { + if (typeof duration === 'boolean') { + isError = duration; + duration = 3000; + } + that.main.showToast(that.$dialog, text, null, duration, isError); + } + + // only init if required + this._prepare = function () { + if (this.prepared) { + return; + } + this.prepared = true; + this.$dialogSave.on('click', function () { + that.save(); + }); + this.$dialog.find('.dialog-editobject-buttons .btn-cancel').on('click', function () { + that.editor.setValue(''); + that.$dialogSave.addClass('disabled'); + that.main.navigate(); + }); + + this.$dialog.find('.btn-add-common').on('click', function () { + that.$dialogNewField.find('.object-tab-new-icon').show(); + that.$dialogNewField.modal('open'); + var $name = that.$dialogNewField.find('.object-tab-new-name'); + $name.data('type', 'common').focus(); + if (!$name.hasClass('autocomplete')) { + $name.addClass('autocomplete'); + $name.mautocomplete({ + data: { + type: null, + desc: null, + min: null, + max: null, + def: null, + role: null, + unit: null, + read: null, + write: null, + states: null + }, + minLength: 0 // The minimum length of the input for the autocomplete to start. Default: 1. + }); + } + $name.focus() + }); + + this.$dialog.find('.btn-add-native').on('click', function () { + that.$dialogNewField.find('.object-tab-new-icon').hide(); + that.$dialogNewField.modal('open'); + var $name = that.$dialogNewField.find('.object-tab-new-name'); + if ($name.hasClass('autocomplete')) { + $name.mautocomplete('destroy'); + $name.removeClass('autocomplete'); + } + M.updateTextFields('#dialog-new-field'); + $name.data('type', 'native').focus(); + }); + this.$dialogNewField.find('.object-tab-new-name').keypress(function (e) { + if (e.which === 13) { + that.$dialogNewField.find('.btn-add').trigger('click'); + } + }); + + if (!this.editor) { + this.editor = ace.edit('view-object-raw'); + this.editor.getSession().setMode('ace/mode/json'); + this.editor.$blockScrolling = true; + this.editor.getSession().on('change', function() { + that.$dialogSave.removeClass('disabled'); + }); + } + this.$dialogNewField.modal(); + + this.$dialog.find('.tabs').mtabs({ + onShow: function (tab) { + if (!tab) return; + var id = $(tab).attr('id'); + if (id === 'object-tab-common') { + showMessage(_('Drop the icons here')); + } else + if (id === 'object-tab-raw') { + var obj = that.saveFromTabs(); + + if (!obj) return false; + + that.editor.setValue(JSON.stringify(obj, null, 2)); + } else if (id === 'object-tab-raw') { + var _obj; + try { + _obj = JSON.parse(that.editor.getValue()); + } catch (e) { + that.main.showMessage(e, _('Parse error'), 'error_outline'); + if (!that.main.noSelect) { + that.$dialog.find('.tabs').mtabs('select', 'object-tab-raw'); + } + return false; + } + that.load(_obj); + } + that.main.saveConfig('object-edit-active', id); + return true; + } + }); + + this.$dialogNewField.find('.btn-add').on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + + var $tab = that.$dialogNewField.find('.object-tab-new-name'); + var type = $tab.data('type') || 'common'; + var field = $tab.val().trim(); + var obj = that.saveFromTabs(); + + if (!field || field.indexOf(' ') !== -1) { + that.main.showError(_('Invalid field name: %s', field)); + return; + } + if (obj[type][field] !== undefined) { + that.main.showError(_('Field %s yet exists!', field)); + return; + } + + obj[type][field] = ''; + + that.load(obj); + that.$dialogNewField.find('.object-tab-new-name').val(''); + that.$dialogNewField.modal('close'); + that.$dialogSave.removeClass('disabled'); + }); + this.$dialogNewField.find('.btn-cancel').on('click', function (e) { + that.$dialogNewField.find('.object-tab-new-name').val(''); + }); + this.$dialog.find('.edit-object-name').on('change', function () { + that.$dialogSave.removeClass('disabled'); + }).on('keyup', function () { + $(this).trigger('change'); + }); + this.$dialog.find('.edit-object-type').on('change', function () { + that.$dialogSave.removeClass('disabled'); + }); + this.$dialog.find('.object-tab-rights input').on('change', function () { + that.$dialogSave.removeClass('disabled'); + }); + + installFileUpload(this.$dialog.find('#object-tab-common'), 50000, function (err, text) { + if (err) { + showMessage(err, true); + } else { + if (!text.match(/^data:image\//)) { + showMessage(_('Unsupported image format'), true); + return; + } + + that.$dialogSave.removeClass('disabled'); + that.iconVal = text; + + var $tab = that.$dialog.find('.icon-editor'); + $tab.find('.icon').show().html(''); + $tab.find('.icon .treetable-icon').attr('src', text); + } + }); + this.$dialog.find('.icon-editor .icon-upload').off('click').on('click', function () { + that.$dialog.find('.drop-file').trigger('click'); + }); + this.$dialog.find('.icon-editor .icon-clear').off('click').on('click', function () { + if (that.iconVal) { + that.iconVal = null; + that.$dialog.find('.icon-editor').hide().appendTo(that.$dialog); + that.$dialogSave.removeClass('disabled'); + } + }); + }; + + this.init = function () { + this._prepare(); + if (this.inited) { + return; + } + this.inited = true; + var id = that.main.navigateGetParams(); + var isSetDefaultState = !!(id || '').match(/,def$/); + id = id.replace(/,def$/, ''); + + var obj = this.main.objects[id]; + if (!obj) return; + + if (this.main.config['object-edit-active'] !== undefined && !that.main.noSelect) { + this.$dialog.find('.tabs').mtabs('select', this.main.config['object-edit-active']); + } + + // fill users + var text = ''; + var name; + for (var u = 0; u < this.main.tabs.users.list.length; u++) { + name = translateName(this.main.objects[this.main.tabs.users.list[u]].common.name); + text += ''; + } + this.$dialog.find('.object-tab-acl-owner').html(text); + + // fill groups + text = ''; + for (u = 0; u < this.main.tabs.users.groups.length; u++) { + name = translateName(this.main.objects[this.main.tabs.users.groups[u]].common.name); + text += ''; + } + this.$dialog.find('.object-tab-acl-group').html(text); + this.load(obj); + + if (isSetDefaultState) { + this.$dialog.data('cb', function (_obj) { + if (_obj.type === 'state') { + // create state + that.main.socket.emit('getState', _obj._id, function (err, state) { + if (!state || state.val === null || state.val === undefined) { + that.main.socket.emit('setState', _obj._id, _obj.common.def === undefined ? null : _obj.common.def, true); + } + }); + } + }); + } else { + this.$dialog.data('cb', null); + } + that.$dialogSave.addClass('disabled'); + }; + + this.destroy = function () { + if (this.inited) { + this.inited = false; + } + }; + + this.load = function (obj) { + if (!obj) return; + obj.common = obj.common || {}; + obj.native = obj.native || {}; + obj.acl = obj.acl || {}; + this.$dialog.find('.title-id').text(obj._id); + this.$dialog.find('.edit-object-name').val(obj.common ? translateName(obj.common.name) : obj._id); + this.$dialog.find('.edit-object-type').val(obj.type); + this.$dialog.find('.object-tab-acl-owner').val(obj.acl.owner || 'system.user.admin'); + this.$dialog.find('.object-tab-acl-group').val(obj.acl.ownerGroup || 'system.group.administrator'); + + this.$dialog.find('.icon-editor').hide().appendTo(this.$dialog); + + loadObjectFields('.object-tab-common-table', obj.common || {}, 'common', obj.type); + loadObjectFields('.object-tab-native-table', obj.native || {}, 'native', obj.type); + + if (obj.common.icon !== undefined) { + this.iconVal = obj.common.icon; + this.$dialog.find('.object-tab-common-table').prepend(this.$dialog.find('.icon-editor').show()); + this.$dialog.find('.icon-editor .icon').html(that.main.getIconFromObj(obj)); + } else { + this.iconVal = null; + } + + this.$dialog.find('.object-tab-field-delete').on('click', function () { + var part = $(this).data('part'); + var field = $(this).data('attr'); + that.main.confirmMessage(_('Delete attribute'), _('Please confirm'), 'error_outline', function (result) { + if (result) { + var _obj = that.saveFromTabs(); + delete _obj[part][field]; + that.load(_obj); + } + }); + }); + + obj.acl = obj.acl || {}; + if (obj.acl.object === undefined) obj.acl.object = 0x666; + + this.$dialog.find('#object-tab-acl-obj-owner-read') .prop('checked', obj.acl.object & 0x400); + this.$dialog.find('#object-tab-acl-obj-owner-write').prop('checked', obj.acl.object & 0x200); + this.$dialog.find('#object-tab-acl-obj-group-read'). prop('checked', obj.acl.object & 0x40); + this.$dialog.find('#object-tab-acl-obj-group-write').prop('checked', obj.acl.object & 0x20); + this.$dialog.find('#object-tab-acl-obj-every-read'). prop('checked', obj.acl.object & 0x4); + this.$dialog.find('#object-tab-acl-obj-every-write').prop('checked', obj.acl.object & 0x2); + + if (obj.type !== 'state') { + this.$dialog.find('.object-tab-acl-state').hide(); + } else { + this.$dialog.find('.object-tab-acl-state').show(); + if (obj.acl.state === undefined) obj.acl.state = 0x666; + + this.$dialog.find('#object-tab-acl-state-owner-read') .prop('checked', obj.acl.state & 0x400); + this.$dialog.find('#object-tab-acl-state-owner-write').prop('checked', obj.acl.state & 0x200); + this.$dialog.find('#object-tab-acl-state-group-read'). prop('checked', obj.acl.state & 0x40); + this.$dialog.find('#object-tab-acl-state-group-write').prop('checked', obj.acl.state & 0x20); + this.$dialog.find('#object-tab-acl-state-every-read'). prop('checked', obj.acl.state & 0x4); + this.$dialog.find('#object-tab-acl-state-every-write').prop('checked', obj.acl.state & 0x2); + } + + var _obj = JSON.parse(JSON.stringify(obj)); + this.editor.setValue(JSON.stringify(_obj, null, 2)); + if (_obj._id) delete _obj._id; + if (_obj.common) delete _obj.common; + if (_obj.type) delete _obj.type; + if (_obj.native) delete _obj.native; + if (_obj.acl) delete _obj.acl; + this.$dialog.find('#view-object-rest').val(JSON.stringify(_obj, null, ' ')); + this.$dialog.find('select').select(); + + // workaround for materialize checkbox problem + this.$dialog.find('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + // enable save + this.$dialog.find('input').on('change', function () { + that.$dialogSave.removeClass('disabled'); + }).on('keyup', function () { + $(this).trigger('change'); + }); + + this.$dialog.find('select').on('change', function () { + that.$dialogSave.removeClass('disabled'); + }); + + this.$dialog.find('textarea').on('change', function () { + that.$dialogSave.removeClass('disabled'); + }).on('keyup', function () { + $(this).trigger('change'); + }); + + if (obj.common.color !== undefined) { + var time = Date.now(); + + var $color = this.$dialog.find('.object-tab-edit-string[data-attr="color"]').parent(); + $color.prepend('color_lens' + _('Color') + ''); + $color.colorpicker({ + component: '.btn', + color: $color, + container: true + }).colorpicker('setValue', obj.common.color || '#fff').on('showPicker.colorpicker', function (/* event */) { + }).on('changeColor.colorpicker', function (event){ + if (Date.now() - time > 100) { + $color.find('input').val(event.color.toHex()).trigger('change'); + } + }); + } + + }; + + this.saveFromTabs = function () { + var obj; + try { + obj = this.$dialog.find('#view-object-rest').val(); + if (!obj) { + obj = {}; + } else { + obj = JSON.parse(obj); + } + } catch (err) { + this.main.showMessage(_('Cannot parse.'), _('Error in %s', err), 'error_outline'); + return false; + } + + obj.common = {}; + obj.native = {}; + obj.acl = {}; + obj._id = this.$dialog.find('.title-id').text(); + obj.common.name = this.$dialog.find('.edit-object-name').val(); // no support of multilanguage if edited + obj.type = this.$dialog.find('.edit-object-type').val(); + var err = saveObjectFields('.object-tab-common-table', obj.common); + if (err) { + this.main.showMessage(_('Cannot parse.'), _('Error in %s', err), 'error_outline'); + return false; + } + err = saveObjectFields('.object-tab-native-table', obj.native); + if (err) { + this.main.showMessage(_('Cannot parse.'), _('Error in %s', err), 'error_outline'); + return false; + } + obj.acl.object = 0; + obj.acl.object |= this.$dialog.find('#object-tab-acl-obj-owner-read').prop('checked') ? 0x400 : 0; + obj.acl.object |= this.$dialog.find('#object-tab-acl-obj-owner-write').prop('checked') ? 0x200 : 0; + obj.acl.object |= this.$dialog.find('#object-tab-acl-obj-group-read').prop('checked') ? 0x40 : 0; + obj.acl.object |= this.$dialog.find('#object-tab-acl-obj-group-write').prop('checked') ? 0x20 : 0; + obj.acl.object |= this.$dialog.find('#object-tab-acl-obj-every-read').prop('checked') ? 0x4 : 0; + obj.acl.object |= this.$dialog.find('#object-tab-acl-obj-every-write').prop('checked') ? 0x2 : 0; + + obj.acl.owner = this.$dialog.find('.object-tab-acl-owner').val(); + obj.acl.ownerGroup = this.$dialog.find('.object-tab-acl-group').val(); + + if (obj.type === 'state') { + obj.acl.state = 0; + obj.acl.state |= this.$dialog.find('#object-tab-acl-state-owner-read').prop('checked') ? 0x400 : 0; + obj.acl.state |= this.$dialog.find('#object-tab-acl-state-owner-write').prop('checked') ? 0x200 : 0; + obj.acl.state |= this.$dialog.find('#object-tab-acl-state-group-read').prop('checked') ? 0x40 : 0; + obj.acl.state |= this.$dialog.find('#object-tab-acl-state-group-write').prop('checked') ? 0x20 : 0; + obj.acl.state |= this.$dialog.find('#object-tab-acl-state-every-read').prop('checked') ? 0x4 : 0; + obj.acl.state |= this.$dialog.find('#object-tab-acl-state-every-write').prop('checked') ? 0x2 : 0; + } + + if (this.iconVal !== null && this.iconVal !== undefined) { + obj.common.icon = this.iconVal; + } + + return obj; + }; + + this.saveFromRaw = function () { + var obj; + try { + obj = JSON.parse(this.editor.getValue()); + } catch (e) { + this.main.showMessage(e, _('Parse error'), 'error_outline'); + if (!that.main.noSelect) { + this.$dialog.find('.tabs').mtabs('select', 'object-tab-raw'); + } + return false; + } + return obj; + }; + + this.save = function () { + if (this.main.config['object-edit-active'] === 'object-tab-raw') { + var _obj = this.saveFromRaw(); + if (!_obj) return; + + this.main.socket.emit('setObject', _obj._id, _obj, function (err) { + if (err) { + that.main.showError(err); + } else { + var cb = that.$dialog.data('cb'); + if (cb) cb(_obj); + that.$dialogSave.addClass('disabled'); + that.main.navigate(); + } + }); + } else { + var obj = that.saveFromTabs(); + if (!obj) return; + this.main.socket.emit('getObject', obj._id, function (err, _obj) { + if (err) { + return that.main.showError(err); + } + + _obj.common = obj.common; + _obj.native = obj.native; + _obj.acl = obj.acl; + that.main.socket.emit('setObject', obj._id, _obj, function (err) { + if (err) { + that.main.showError(err); + } else { + var cb = that.$dialog.data('cb'); + if (cb) cb(obj); + that.$dialogSave.addClass('disabled'); + that.main.navigate(); + } + }); + }); + } + }; + + this.allStored = function () { + return that.$dialogSave.hasClass('disabled'); + }; + +} \ No newline at end of file diff --git a/src/js/adminEnums.js b/src/js/adminEnums.js new file mode 100644 index 0000000..b68b76e --- /dev/null +++ b/src/js/adminEnums.js @@ -0,0 +1,1694 @@ +function Enums(main) { + 'use strict'; + + // enum is first level like enum.function or enum.rooms + // category is second level like enum.function.light or enum.room.living_room + + var that = this; + + this.main = main; + this.list = []; + this.$gridEnum = $('#tab-enums'); + this.$gridList = this.$gridEnum.find('.tab-enums-list'); + this.$grid = this.$gridEnum.find('.tab-enums-objects'); + this.updateTimers = null; + this.editMode = false; + this.isTiles = false; + + var tasks = []; + var standardEnums = { + 'enum.rooms': { + "_id": "enum.rooms", + "common": { + "icon": "home", + "name": { + "en": "Rooms", + "de": "Räume", + "ru": "Комнаты", + "pt": "Quartos", + "nl": "Kamers", + "fr": "Pièces", + "it": "Camere", + "es": "Habitaciones" + }, + "desc": { + "en": "List of the rooms", + "de": "Liste der Räumen", + "ru": "Список комнат", + "pt": "Lista dos quartos", + "nl": "Lijst met kamers", + "fr": "Liste des chambres", + "it": "Elenco delle stanze", + "es": "Lista de las habitaciones" + }, + "members": [], + "dontDelete": true + }, + "type": "enum" + }, + 'enum.functions': { + "_id": "enum.functions", + "common": { + "icon": "lightbulb_outline", + "name": { + "en": "Functions", + "de": "Funktionen", + "ru": "функции", + "pt": "Funções", + "nl": "functies", + "fr": "Les fonctions", + "it": "funzioni", + "es": "Funciones" + }, + "desc": { + "en": "List of the functions", + "de": "Liste der Funktionen", + "ru": "Список функций", + "pt": "Lista das funções", + "nl": "Lijst met functies", + "fr": "Liste des fonctions", + "it": "Elenco delle funzioni", + "es": "Lista de las funciones" + }, + "members": [], + "dontDelete": true + }, + "type": "enum" + }, + 'enum.favorites': { + "_id": "enum.favorites", + "common": { + "icon": "favorite_border", + "name": { + "en": "Favorites", + "de": "Favoriten", + "ru": "Избранные", + "pt": "Favoritos", + "nl": "favorieten", + "fr": "Favoris", + "it": "Preferiti", + "es": "Favoritos" + }, + "desc": { + "en": "List of favorites objects", + "de": "Liste der Favoritenobjekte", + "ru": "Список избранных объектов", + "pt": "Lista de objetos favoritos", + "nl": "Lijst met favorietenobjecten", + "fr": "Liste des objets favoris", + "it": "Elenco di oggetti preferiti", + "es": "Lista de objetos favoritos" + }, + "members": [] + }, + "type": "enum" + } + }; + + var standardGroups = { + 'enum.rooms': { + "enum.rooms.living_room": { + "_id": "enum.rooms.living_room", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/PjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDQ4MC4wNDYgNDgwLjA0NiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDgwLjA0NiA0ODAuMDQ2OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PGc+PHBhdGggZD0iTTMyOC4wMzQsMzIwLjA0NmgtMjR2LTg4YzAtNC40MTgtMy41ODItOC04LThoLTI1NmMtNC40MTgsMC04LDMuNTgyLTgsOHY4OGgtMjRjLTQuNDE4LDAtOCwzLjU4Mi04LDh2MTI4YzAsNC40MTgsMy41ODIsOCw4LDhoMjR2MTZoMTZ2LTE2aDI0MHYxNmgxNnYtMTZoMjRjNC40MTgsMCw4LTMuNTgyLDgtOHYtMTI4QzMzNi4wMzQsMzIzLjYyOCwzMzIuNDUyLDMyMC4wNDYsMzI4LjAzNCwzMjAuMDQ2eiBNODAuMDM0LDQ0OC4wNDZoLTY0di0xMTJoNjRWNDQ4LjA0NnogTTI0MC4wMzQsNDQ4LjA0NmgtMTQ0di02NGgxNDRWNDQ4LjA0NnogTTI0MC4wMzQsMzI4LjA0NnY0MGgtMTQ0di00MGMwLTQuNDE4LTMuNTgyLTgtOC04aC00MHYtODBoMjQwdjgwaC00MEMyNDMuNjE1LDMyMC4wNDYsMjQwLjAzNCwzMjMuNjI4LDI0MC4wMzQsMzI4LjA0NnogTTMyMC4wMzQsNDQ4LjA0NmgtNjR2LTExMmg2NFY0NDguMDQ2eiIvPjwvZz48L2c+PGc+PGc+PHBhdGggZD0iTTQ3OS45NTQsMTUxLjE2NmwtMTYtMTQ0Yy0wLjQ0Ny00LjA0MS0zLjg1NC03LjEwNC03LjkyLTcuMTJoLTExMmMtNC4wOTYtMC4wMjUtNy41NDksMy4wNDktOCw3LjEybC0xNiwxNDRjLTAuMjc2LDIuMjU4LDAuNDIyLDQuNTI4LDEuOTIsNi4yNGMxLjU1LDEuNzE4LDMuNzY3LDIuNjgsNi4wOCwyLjY0aDY0djI3MmgtMzJjLTQuNDE4LDAtOCwzLjU4Mi04LDh2MzJjMCw0LjQxOCwzLjU4Miw4LDgsOGg4MGM0LjQxOCwwLDgtMy41ODIsOC04di0zMmMwLTQuNDE4LTMuNTgyLTgtOC04aC0zMnYtMjcyaDY0YzIuMjg1LDAuMDE3LDQuNDY5LTAuOTQzLDYtMi42NEM0NzkuNTMyLDE1NS42OTQsNDgwLjIzLDE1My40MjUsNDc5Ljk1NCwxNTEuMTY2eiBNNDMyLjAzNCw0NDguMDQ2djE2aC02NHYtMTZINDMyLjAzNHogTTMzNi45OTQsMTQ0LjA0NmwxNC4yNC0xMjhoOTcuNmwxNC4yNCwxMjhIMzM2Ljk5NHoiLz48L2c+PC9nPjxnPjxnPjxwYXRoIGQ9Ik0yNzIuMDM0LDQ4LjA0NmgtNTIuNzJsLTQ1LjYtNDUuNjhjLTMuMTExLTMuMTM3LTguMTc3LTMuMTU4LTExLjMxNC0wLjA0NmMtMC4wMTYsMC4wMTUtMC4wMzEsMC4wMzEtMC4wNDYsMC4wNDZsLTQ1LjYsNDUuNjhoLTUyLjcyYy00LjQxOCwwLTgsMy41ODItOCw4djEyOGMwLDQuNDE4LDMuNTgyLDgsOCw4aDIwOGM0LjQxOCwwLDgtMy41ODIsOC04di0xMjhDMjgwLjAzNCw1MS42MjgsMjc2LjQ1Miw0OC4wNDYsMjcyLjAzNCw0OC4wNDZ6IE0xNjguMDM0LDE5LjMyNmwyOC43MiwyOC43MmgtNTcuNDRMMTY4LjAzNCwxOS4zMjZ6IE0yNjQuMDM0LDE3Ni4wNDZoLTE5MnYtMTEyaDE5MlYxNzYuMDQ2eiIvPjwvZz48L2c+PGc+PGc+PHBhdGggZD0iTTg4LjAzNCw4MC4wNDZ2ODBoMTYwdi04MEg4OC4wMzR6IE0yMzIuMDM0LDE0NC4wNDZoLTEyOHYtNDhoMTI4VjE0NC4wNDZ6Ii8+PC9nPjwvZz48L3N2Zz4=", + "name": { + "en": "Living room", + "de": "Wohnzimmer", + "ru": "Гостиная", + "pt": "Sala de estar", + "nl": "Woonkamer", + "fr": "Salon", + "it": "Soggiorno", + "es": "Sala" + }, + "members": [] + }, + "type": "enum" + }, + "enum.rooms.sleeping_room": { + "_id": "enum.rooms.sleeping_room", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNDgwIDQ4MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDgwIDQ4MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZD0iTTQ2NCwyNjRWODhIMTZ2MTc2SDB2ODBoNDh2NDhoNjR2LTQ4aDI1NnY0OGg2NHYtNDhoNDh2LTgwSDQ2NHogTTMyLDEwNGg0MTZ2MTYwaC0xNnYtMjRjMC0yMi4wOTEtMTcuOTA5LTQwLTQwLTQwDQoJCWgtMTIuNDhjMi45MDgtNC44MzIsNC40NTYtMTAuMzYsNC40OC0xNmMwLTE3LjY3My0xNC4zMjctMzItMzItMzJoLTY0Yy0xNy42NzMsMC0zMiwxNC4zMjctMzIsMzINCgkJYzAuMDI0LDUuNjQsMS41NzIsMTEuMTY4LDQuNDgsMTZoLTQwLjk2YzIuOTA4LTQuODMyLDQuNDU2LTEwLjM2LDQuNDgtMTZjMC0xNy42NzMtMTQuMzI3LTMyLTMyLTMyaC02NA0KCQljLTE3LjY3MywwLTMyLDE0LjMyNy0zMiwzMmMwLjAyNCw1LjY0LDEuNTcyLDExLjE2OCw0LjQ4LDE2SDg4Yy0yMi4wOTEsMC00MCwxNy45MDktNDAsNDB2MjRIMzJWMTA0eiBNMzkyLDIxNg0KCQljMTMuMjU1LDAsMjQsMTAuNzQ1LDI0LDI0djI0SDY0di0yNGMwLTEzLjI1NSwxMC43NDUtMjQsMjQtMjRIMzkyeiBNMTEyLDE4NGMwLTguODM3LDcuMTYzLTE2LDE2LTE2aDY0YzguODM3LDAsMTYsNy4xNjMsMTYsMTYNCgkJcy03LjE2MywxNi0xNiwxNmgtNjRDMTE5LjE2MywyMDAsMTEyLDE5Mi44MzcsMTEyLDE4NHogTTI3MiwxODRjMC04LjgzNyw3LjE2My0xNiwxNi0xNmg2NGM4LjgzNywwLDE2LDcuMTYzLDE2LDE2DQoJCXMtNy4xNjMsMTYtMTYsMTZoLTY0QzI3OS4xNjMsMjAwLDI3MiwxOTIuODM3LDI3MiwxODR6IE05NiwzNzZINjR2LTMyaDMyVjM3NnogTTQxNiwzNzZoLTMydi0zMmgzMlYzNzZ6IE00NjQsMzI4SDE2di00OGg0NDhWMzI4DQoJCXoiLz4NCjwvZz4NCjxnPg0KCTxyZWN0IHg9IjQ4IiB5PSIxMjAiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIvPg0KPC9nPg0KPGc+DQoJPHJlY3QgeD0iNDE2IiB5PSIxMjAiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIvPg0KPC9nPg0KPGc+DQoJPHJlY3QgeD0iODAiIHk9IjEyMCIgd2lkdGg9IjMyMCIgaGVpZ2h0PSIxNiIvPg0KPC9nPg0KPGc+DQoJPHJlY3QgeD0iNDgiIHk9IjE1MiIgd2lkdGg9IjE2IiBoZWlnaHQ9IjQwIi8+DQo8L2c+DQo8Zz4NCgk8cmVjdCB4PSI0MTYiIHk9IjE1MiIgd2lkdGg9IjE2IiBoZWlnaHQ9IjQwIi8+DQo8L2c+DQoNCjwvc3ZnPg0K", + "name": { + "en": "Sleeping room", + "de": "Schlafzimmer", + "ru": "Спальня", + "pt": "Quarto de dormir", + "nl": "Slaapkamer", + "fr": "Chambre à coucher", + "it": "Camera da letto", + "es": "Dormitorio", + "pl": "Sypialnia" + }, + "members": [] + }, + "type": "enum" + }, + "enum.rooms.kitchen": { + "_id": "enum.rooms.kitchen", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNDgwIDQ4MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDgwIDQ4MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZD0iTTAsMjA4djQ4aDE2djIyNGg0NDhWMjU2aDE2di00OEgweiBNMjA4LDQ2NEgzMlYyNTZoMTc2VjQ2NHogTTI4OCw0NjRoLTY0VjMyMGg2NFY0NjR6IE0zNjgsNDY0aC02NFYzMjBoNjRWNDY0eg0KCQkgTTQ0OCw0NjRoLTY0VjMyMGg2NFY0NjR6IE00NDgsMzA0SDIyNHYtNDhoMjI0VjMwNHogTTQ2NCwyNDBIMTZ2LTE2aDQ0OFYyNDB6Ii8+DQo8L2c+DQo8Zz4NCgk8cGF0aCBkPSJNNDgsMzM2djExMmgxNDRWMzM2SDQ4eiBNMTc2LDQzMkg2NHYtODBoMTEyVjQzMnoiLz4NCjwvZz4NCjxnPg0KCTxwYXRoIGQ9Ik03MiwyNzJjLTEzLjI1NSwwLTI0LDEwLjc0NS0yNCwyNHMxMC43NDUsMjQsMjQsMjRzMjQtMTAuNzQ1LDI0LTI0Uzg1LjI1NSwyNzIsNzIsMjcyeiBNNzIsMzA0Yy00LjQxOCwwLTgtMy41ODItOC04DQoJCQlzMy41ODItOCw4LThzOCwzLjU4Miw4LDhTNzYuNDE4LDMwNCw3MiwzMDR6Ii8+DQo8L2c+DQo8Zz4NCgk8cGF0aCBkPSJNMTY4LDI3MmMtMTMuMjU1LDAtMjQsMTAuNzQ1LTI0LDI0czEwLjc0NSwyNCwyNCwyNHMyNC0xMC43NDUsMjQtMjRTMTgxLjI1NSwyNzIsMTY4LDI3MnogTTE2OCwzMDRjLTQuNDE4LDAtOC0zLjU4Mi04LTgNCgkJczMuNTgyLTgsOC04czgsMy41ODIsOCw4UzE3Mi40MTgsMzA0LDE2OCwzMDR6Ii8+DQo8L2c+DQo8Zz4NCgk8cmVjdCB4PSIzMDQiIHk9IjI3MiIgd2lkdGg9IjY0IiBoZWlnaHQ9IjE2Ii8+DQo8L2c+DQo8Zz4NCgk8cmVjdCB4PSIyNTYiIHk9IjMzNiIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+DQo8L2c+DQo8Zz4NCgk8cmVjdCB4PSIzMzYiIHk9IjM2OCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjMyIi8+DQo8L2c+DQo8Zz4NCgk8cmVjdCB4PSI0MDAiIHk9IjM2OCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjMyIi8+DQo8L2c+DQo8Zz4NCgk8cGF0aCBkPSJNMjA4LDB2ODYuMDhsLTI0LTQ4VjBINDB2MzguMDhMMC44OCwxMTYuNGMtMS45ODgsMy45NDYtMC40MDEsOC43NTYsMy41NDQsMTAuNzQ0QzUuNTM0LDEyNy43MDMsNi43NTgsMTI3Ljk5Niw4LDEyOGg0NzINCgkJVjBIMjA4eiBNNTYsMTZoMTEydjE2SDU2VjE2eiBNMjAuOTYsMTEybDMyLTY0aDExOC4wOGwzMiw2NEgyMC45NnogTTMzNiwxMTJIMjI0VjE2aDExMlYxMTJ6IE00NjQsMTEySDM1MlYxNmgxMTJWMTEyeiIvPg0KPC9nPg0KPGc+DQoJPHJlY3QgeD0iMzA0IiB5PSI4MCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+DQo8L2c+DQo8Zz4NCgk8cmVjdCB4PSIzNjgiIHk9IjgwIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiLz4NCjwvZz4NCjxnPg0KCTxyZWN0IHg9Ijk2IiB5PSIzNjgiIHdpZHRoPSI0OCIgaGVpZ2h0PSIxNiIvPg0KPC9nPg0KPC9zdmc+DQo=", + "name": { + "en": "Kitchen", + "de": "Küche", + "ru": "Кухня", + "pt": "Cozinha", + "nl": "Keuken", + "fr": "Cuisine", + "it": "Cucina", + "es": "Cocina", + "pl": "Kuchnia" + }, + "members": [] + }, + "type": "enum" + }, + "enum.rooms.office": { + "_id": "enum.rooms.office", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNDgwIDQ4MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDgwIDQ4MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJCTxwYXRoIGQ9Ik0yMjQsMTIwdjExMmgyMjRWMTIwSDIyNHogTTQzMiwyMTZIMjQwdi04MGgxOTJWMjE2eiIvPg0KPC9nPg0KPGc+DQoJCTxwYXRoIGQ9Ik0zMzYsMTUyYy0xMy4yNTUsMC0yNCwxMC43NDUtMjQsMjRzMTAuNzQ1LDI0LDI0LDI0czI0LTEwLjc0NSwyNC0yNFMzNDkuMjU1LDE1MiwzMzYsMTUyeiBNMzM2LDE4NGMtNC40MTgsMC04LTMuNTgyLTgtOA0KCQkJczMuNTgyLTgsOC04czgsMy41ODIsOCw4UzM0MC40MTgsMTg0LDMzNiwxODR6Ii8+DQo8L2c+DQo8Zz4NCgkJPHBhdGggZD0iTTE3Niw4OFY0OGMwLTQuNDE4LTMuNTgyLTgtOC04aC04VjE2YzAtNC40MTgtMy41ODItOC04LThINDBjLTQuNDE4LDAtOCwzLjU4Mi04LDh2MjRoLThjLTQuNDE4LDAtOCwzLjU4Mi04LDh2NDBIMHYzODQNCgkJCWgyMDhWMjY0aDIwOHYyMDhoNjRWODhIMTc2eiBNNDgsMjRoOTZ2MTZINDhWMjR6IE0zMiw1NmgxMjh2MzJIMzJWNTZ6IE0xOTIsNDU2SDE2VjIwMGgxNzZWNDU2eiBNMTkyLDE4NEgxNnYtODBoMTc2VjE4NHoNCgkJCSBNNDY0LDQ1NmgtMzJWMjY0aDMyVjQ1NnogTTQ2NCwyNDhIMjA4VjEwNGgyNTZWMjQ4eiIvPg0KPC9nPg0KPGc+DQoJCTxyZWN0IHg9IjgwIiB5PSIxMzYiIHdpZHRoPSI0OCIgaGVpZ2h0PSIxNiIvPg0KPC9nPg0KPGc+DQoJCTxwYXRoIGQ9Ik0xNTIsMjE2Yy0xMy4yNTUsMC0yNCwxMC43NDUtMjQsMjRzMTAuNzQ1LDI0LDI0LDI0czI0LTEwLjc0NSwyNC0yNFMxNjUuMjU1LDIxNiwxNTIsMjE2eiBNMTUyLDI0OGMtNC40MTgsMC04LTMuNTgyLTgtOA0KCQkJczMuNTgyLTgsOC04czgsMy41ODIsOCw4UzE1Ni40MTgsMjQ4LDE1MiwyNDh6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==", + "name": { + "en": "Office", + "de": "Büro", + "ru": "офис", + "pt": "Escritório", + "nl": "Kantoor", + "fr": "Bureau", + "it": "Ufficio", + "es": "Oficina", + "pl": "Gabinet" + }, + "members": [] + }, + "type": "enum" + }, + "enum.rooms.nursery": { + "_id": "enum.rooms.nursery", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNDgwIDQ4MCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDgwIDQ4MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZD0iTTQwLDQzMmMtMTMuMjU1LDAtMjQsMTAuNzQ1LTI0LDI0czEwLjc0NSwyNCwyNCwyNHMyNC0xMC43NDUsMjQtMjRTNTMuMjU1LDQzMiw0MCw0MzJ6IE00MCw0NjRjLTQuNDE4LDAtOC0zLjU4Mi04LTgNCgkJczMuNTgyLTgsOC04czgsMy41ODIsOCw4UzQ0LjQxOCw0NjQsNDAsNDY0eiIvPg0KPC9nPg0KPGc+DQoJPHBhdGggZD0iTTQ0MCw0MzJjLTEzLjI1NSwwLTI0LDEwLjc0NS0yNCwyNHMxMC43NDUsMjQsMjQsMjRzMjQtMTAuNzQ1LDI0LTI0UzQ1My4yNTUsNDMyLDQ0MCw0MzJ6IE00NDAsNDY0Yy00LjQxOCwwLTgtMy41ODItOC04DQoJCXMzLjU4Mi04LDgtOHM4LDMuNTgyLDgsOFM0NDQuNDE4LDQ2NCw0NDAsNDY0eiIvPg0KPC9nPg0KPGc+DQoJPHBhdGggZD0iTTQxNiwxMTJ2NDhINjR2LTQ4SDE2djMyMGg0OHYtNDhoMzUydjQ4aDQ4VjExMkg0MTZ6IE00OCw0MTZIMzJWMTI4aDE2VjQxNnogTTY0LDIwOGgyNHY4MEg2NFYyMDh6IE02NCwzMDRoMjR2MzJINjRWMzA0DQoJCXogTTQxNiwzNjhINjR2LTE2aDM1MlYzNjh6IE0xMjAsMzA0djMyaC0xNnYtMzJIMTIweiBNMTA0LDI4OHYtODBoMTZ2ODBIMTA0eiBNMTUyLDMwNHYzMmgtMTZ2LTMySDE1MnogTTEzNiwyODh2LTgwaDE2djgwSDEzNnoNCgkJIE0xODQsMzA0djMyaC0xNnYtMzJIMTg0eiBNMTY4LDI4OHYtODBoMTZ2ODBIMTY4eiBNMjE2LDMwNHYzMmgtMTZ2LTMySDIxNnogTTIwMCwyODh2LTgwaDE2djgwSDIwMHogTTI0OCwzMDR2MzJoLTE2di0zMkgyNDh6DQoJCSBNMjMyLDI4OHYtODBoMTZ2ODBIMjMyeiBNMjgwLDMwNHYzMmgtMTZ2LTMySDI4MHogTTI2NCwyODh2LTgwaDE2djgwSDI2NHogTTMxMiwzMDR2MzJoLTE2di0zMkgzMTJ6IE0yOTYsMjg4di04MGgxNnY4MEgyOTZ6DQoJCSBNMzQ0LDMwNHYzMmgtMTZ2LTMySDM0NHogTTMyOCwyODh2LTgwaDE2djgwSDMyOHogTTM3NiwzMDR2MzJoLTE2di0zMkgzNzZ6IE0zNjAsMjg4di04MGgxNnY4MEgzNjB6IE00MTYsMzM2aC0yNHYtMzJoMjRWMzM2eg0KCQkgTTQxNiwyODhoLTI0di04MGgyNFYyODh6IE00MTYsMTkySDY0di0xNmgzNTJWMTkyeiBNNDQ4LDQxNmgtMTZWMTI4aDE2VjQxNnoiLz4NCjwvZz4NCjxnPg0KCTxwYXRoIGQ9Ik0yOTYsNTAuNzJjLTQuNTA5LDAuMDM0LTguOTE3LDEuMzM3LTEyLjcyLDMuNzZjLTEwLjMxMy03LjY4MS0yMi40OTUtMTIuNDYtMzUuMjgtMTMuODRWMGgtMTZ2NDAuNjQNCgkJYy0xMi43ODUsMS4zOC0yNC45NjcsNi4xNTktMzUuMjgsMTMuODRjLTMuODAzLTIuNDIzLTguMjExLTMuNzI2LTEyLjcyLTMuNzZjLTEzLjI1NSwwLTI0LDEwLjc0NS0yNCwyNGMwLDEzLjI1NSwxMC43NDUsMjQsMjQsMjQNCgkJczI0LTEwLjc0NSwyNC0yNGMtMC4wMTUtMi43MjMtMC40NzUtNS40MjUtMS4zNi04YzE5Ljg1NS0xNC41NjUsNDYuODY1LTE0LjU2NSw2Ni43MiwwYy0wLjg4NSwyLjU3NS0xLjM0NSw1LjI3Ny0xLjM2LDgNCgkJYzAsMTMuMjU1LDEwLjc0NSwyNCwyNCwyNHMyNC0xMC43NDUsMjQtMjRDMzIwLDYxLjQ2NSwzMDkuMjU1LDUwLjcyLDI5Niw1MC43MnogTTE4NCw4Mi43MmMtNC40MTgsMC04LTMuNTgyLTgtOA0KCQljMC00LjQxOCwzLjU4Mi04LDgtOHM4LDMuNTgyLDgsOEMxOTIsNzkuMTM4LDE4OC40MTgsODIuNzIsMTg0LDgyLjcyeiBNMjk2LDgyLjcyYy00LjQxOCwwLTgtMy41ODItOC04YzAtNC40MTgsMy41ODItOCw4LTgNCgkJczgsMy41ODIsOCw4QzMwNCw3OS4xMzgsMzAwLjQxOCw4Mi43MiwyOTYsODIuNzJ6Ii8+DQo8L2c+DQoNCjwvc3ZnPg0K", + "name": { + "en": "Nursery", + "de": "Kinderzimmer", + "ru": "Детская", + "pt": "Berçário", + "nl": "Kwekerij", + "fr": "Garderie", + "it": "Asilo nido", + "es": "Guardería", + "pl": "Żłobek" + }, + "members": [] + }, + "type": "enum" + }, + "enum.rooms.wc": { + "_id": "enum.rooms.wc", + "common": { + "icon": "data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDUxMiA1MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIiB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMjU2cHgiPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik00NTEsMzE3di0zMGMwLTI0LjgxMy0yMC4xODctNDUtNDUtNDVIMjI2Yy01LjI1OSwwLTEwLjMwNSwwLjkxNS0xNSwyLjU4Vjg3LjQyYzE3LjQ1OS02LjE5MiwzMC0yMi44NjUsMzAtNDIuNDIgICAgYzAtMjQuODEzLTIwLjE4Ny00NS00NS00NUg3NmMtOC4yODQsMC0xNSw2LjcxNi0xNSwxNWMwLDI3LjcxLDAsMjQ2LjM4NSwwLDI3MmMwLDE5LjU1NSwxMi41NDEsMzYuMjI4LDMwLDQyLjQyVjM3NyAgICBjMCwyNy41MTksMTAuODU1LDUzLjkyNywzMCw3My40ODJWNDk3YzAsOC4yODQsNi43MTYsMTUsMTUsMTVjMTcuMzMsMCwyNTQuODQ2LDAsMjcwLDBjOC4yODQsMCwxNS02LjcxNiwxNS0xNXMtNi43MTYtMTUtMTUtMTUgICAgaC00NXYtMTcuOTkxQzQxNS45ODgsNDM1Ljk0Myw0NTEsMzc5LjI4Myw0NTEsMzE3eiBNMjI2LDI3MmgxODBjOC4yNzEsMCwxNSw2LjcyOSwxNSwxNXYxNUgyMTF2LTE1ICAgIEMyMTEsMjc4LjcyOSwyMTcuNzI5LDI3MiwyMjYsMjcyeiBNOTEsMzBoMTA1YzguMjcxLDAsMTUsNi43MjksMTUsMTVzLTYuNzI5LDE1LTE1LDE1SDkxVjMweiBNMTA2LDMwMmMtOC4yNzEsMC0xNS02LjcyOS0xNS0xNSAgICBWOTBoOTBjMCwxNy4zOSwwLDIwMC4yNzEsMCwyMTJIMTA2eiBNMzM5Ljk5NCw0NDAuNzczYy01LjQ2MywyLjM4Ny04Ljk5NCw3Ljc4My04Ljk5NCwxMy43NDVWNDgySDE1MXYtMzcuOTE3ICAgIGMwLTQuMjY3LTEuODE3LTguMzMyLTQuOTk2LTExLjE3N0MxMzAuMTEzLDQxOC42ODQsMTIxLDM5OC4zMDcsMTIxLDM3N3YtNDUuMWMzNC41MzUsMCwyOTYuNTQ1LDAsMjk5LjE2OCwwICAgIEM0MTQuODY0LDM3OS40OTMsMzg0LjU3NSw0MjEuMjk1LDMzOS45OTQsNDQwLjc3M3oiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K", + "name": { + "en": "WC", + "de": "Toilette", + "ru": "Туалет", + "pt": "Banheiro", + "nl": "WC", + "fr": "Toilettes", + "it": "Bagno", + "es": "Baño", + "pl": "Toaleta" + }, + "members": [] + }, + "type": "enum" + }, + "enum.rooms.garage": { + "_id": "enum.rooms.kitchen", + "common": { + "icon": "data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDQ4MC4wMTMgNDgwLjAxMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDgwLjAxMyA0ODAuMDEzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjI1NnB4IiBoZWlnaHQ9IjI1NnB4Ij4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMzkwLjk5OSwzMTYuOTUybC0zMC4yOTYtMjQuMjMyYy0xLjExMy0wLjg4Ny0xLjk2OS0yLjA1NS0yLjQ4LTMuMzg0bC0xMS42OC01NC40ICAgIGMtMi40MzItMTEuMDItMTIuMTc5LTE4Ljg4My0yMy40NjQtMTguOTI4SDE1Ni44MDdjLTExLjI2OC0wLjA1Mi0yMS4wMzYsNy43ODgtMjMuNDI0LDE4LjhsLTEyLDUzLjk1MiAgICBjLTAuNDAzLDEuNzgzLTEuMzk5LDMuMzc2LTIuODI0LDQuNTJsLTI5LjYsMjMuNjhjLTUuNjg3LDQuNTUyLTguOTg0LDExLjQ1Mi04Ljk1MiwxOC43MzZ2ODAuMzEyICAgIGMwLjAyMSw2Ljc4OCwyLjkzLDEzLjI0Niw4LDE3Ljc2djMwLjI0YzAsOC44MzcsNy4xNjMsMTYsMTYsMTZoMzJjOC44MzcsMCwxNi03LjE2MywxNi0xNnYtMjRoMTc2djI0YzAsOC44MzcsNy4xNjMsMTYsMTYsMTZoMzIgICAgYzguODM3LDAsMTYtNy4xNjMsMTYtMTZ2LTMwLjI0YzUuMDctNC41MTQsNy45NzktMTAuOTcyLDgtMTcuNzZ2LTgwLjMxMkM0MDAuMDI0LDMyOC40LDM5Ni43MDcsMzIxLjQ5NiwzOTAuOTk5LDMxNi45NTJ6ICAgICBNMjE4LjE0MywyMzguMzI4bDAtMC4wMDhsNC4yMDgtNi4zMmgxMDAuNzI4YzMuNzcyLTAuMDAxLDcuMDMyLDIuNjMyLDcuODI0LDYuMzJsMTAuNzA0LDQ5LjY4OEgyMjQuNzAzICAgIGM1Ljg4OC0xMS4wOTgsMy44NzEtMjQuNzM2LTQuOTc2LTMzLjY1NkMyMTUuNDYxLDI1MC4wNjQsMjE0Ljc5OSwyNDMuMzY4LDIxOC4xNDMsMjM4LjMyOHogTTE4NC4wNzksMjM4LjMyOHYtMC4wMDhsNC4yMDgtNi4zMiAgICBoMTUuMTQ0Yy01Ljg4LDExLjEwMS0zLjg2MSwyNC43MzYsNC45ODQsMzMuNjU2YzQuMjU4LDQuMjgsNC45MjYsMTAuOTYyLDEuNiwxNmwtNC4yMDgsNi4zMmgtMTUuMTYgICAgYzUuODgtMTEuMTAxLDMuODYxLTI0LjczNi00Ljk4NC0zMy42NTZDMTgxLjQxNCwyNTAuMDM3LDE4MC43NTMsMjQzLjM2MSwxODQuMDc5LDIzOC4zMjh6IE0xNDkuMDM5LDIzOC4yNjQgICAgYzAuNzkyLTMuNjU1LDQuMDI4LTYuMjYyLDcuNzY4LTYuMjU2aDEyLjU2Yy01Ljg4LDExLjEwMS0zLjg2MSwyNC43MzYsNC45ODQsMzMuNjU2YzQuMjU4LDQuMjgsNC45MjYsMTAuOTYyLDEuNiwxNmwtNC4yMDgsNi4zMiAgICBoLTMzLjc2TDE0OS4wMzksMjM4LjI2NHogTTEzNi4wMDcsNDY0LjAwOGgtMzJ2LTI0aDMyVjQ2NC4wMDh6IE0zNzYuMDA3LDQ2NC4wMDhoLTMydi0yNGgzMlY0NjQuMDA4eiBNMzg0LjAwNyw0MTYuMDA4ICAgIGMwLDQuNDE4LTMuNTgyLDgtOCw4aC0yNzJjLTQuNDE4LDAtOC0zLjU4Mi04LTh2LThoMjg4VjQxNi4wMDh6IE0zODQuMDA3LDM5Mi4wMDhoLTI4OHYtNTYuMzEyICAgIGMtMC4wMDYtMi40MzEsMS4xMDMtNC43MywzLjAwOC02LjI0bDI5LjYtMjMuNjhjMC42NDktMC41NTYsMS4yNjktMS4xNDYsMS44NTYtMS43NjhoMjE4LjkyOGMwLjQzMiwwLjQsMC44LDAuOCwxLjI5NiwxLjIgICAgbDMwLjMwNCwyNC4yNGMxLjksMS41MTcsMy4wMDcsMy44MTYsMy4wMDgsNi4yNDhWMzkyLjAwOHoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik0xMzIuMDA3LDMyOC4wMDhjLTE1LjQ2NCwwLTI4LDEyLjUzNi0yOCwyOGMwLDE1LjQ2NCwxMi41MzYsMjgsMjgsMjhzMjgtMTIuNTM2LDI4LTI4ICAgIEMxNjAuMDA3LDM0MC41NDQsMTQ3LjQ3MSwzMjguMDA4LDEzMi4wMDcsMzI4LjAwOHogTTEzMi4wMDcsMzY4LjAwOGMtNi42MjcsMC0xMi01LjM3My0xMi0xMmMwLTYuNjI3LDUuMzczLTEyLDEyLTEyICAgIGM2LjYyNywwLDEyLDUuMzczLDEyLDEyQzE0NC4wMDcsMzYyLjYzNSwxMzguNjM0LDM2OC4wMDgsMTMyLjAwNywzNjguMDA4eiIgZmlsbD0iIzAwMDAwMCIvPgoJPC9nPgo8L2c+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTM0OC4wMDcsMzI4LjAwOGMtMTUuNDY0LDAtMjgsMTIuNTM2LTI4LDI4YzAsMTUuNDY0LDEyLjUzNiwyOCwyOCwyOGMxNS40NjQsMCwyOC0xMi41MzYsMjgtMjggICAgQzM3Ni4wMDcsMzQwLjU0NCwzNjMuNDcxLDMyOC4wMDgsMzQ4LjAwNywzMjguMDA4eiBNMzQ4LjAwNywzNjguMDA4Yy02LjYyNywwLTEyLTUuMzczLTEyLTEyYzAtNi42MjcsNS4zNzMtMTIsMTItMTIgICAgYzYuNjI3LDAsMTIsNS4zNzMsMTIsMTJDMzYwLjAwNywzNjIuNjM1LDM1NC42MzQsMzY4LjAwOCwzNDguMDA3LDM2OC4wMDh6IiBmaWxsPSIjMDAwMDAwIi8+Cgk8L2c+CjwvZz4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMjg4LjAwNywzMjguMDA4aC05NmMtOC44MzcsMC0xNiw3LjE2My0xNiwxNnYyNGMwLDguODM3LDcuMTYzLDE2LDE2LDE2aDk2YzguODM3LDAsMTYtNy4xNjMsMTYtMTZ2LTI0ICAgIEMzMDQuMDA3LDMzNS4xNzEsMjk2Ljg0NCwzMjguMDA4LDI4OC4wMDcsMzI4LjAwOHogTTI4OC4wMDcsMzY4LjAwOGgtOTZ2LTI0aDk2VjM2OC4wMDh6IiBmaWxsPSIjMDAwMDAwIi8+Cgk8L2c+CjwvZz4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNNDc1Ljg3MSwxMjlsLTIzMi0xMjhjLTIuNDA1LTEuMzI3LTUuMzIzLTEuMzI3LTcuNzI4LDBsLTIzMiwxMjhjLTMuODcsMi4xMzEtNS4yODEsNi45OTYtMy4xNSwxMC44NjYgICAgYzEuNDA3LDIuNTU2LDQuMDk1LDQuMTQ0LDcuMDEzLDQuMTQyaDh2MzM2aDQ4di0yOTZoMzUydjI5Nmg0OHYtMzM2aDhjNC40MTgsMC4wMDMsOC4wMDMtMy41NzYsOC4wMDYtNy45OTUgICAgQzQ4MC4wMTUsMTMzLjA5NSw0NzguNDI3LDEzMC40MDcsNDc1Ljg3MSwxMjl6IE0yNDAuMDA3LDE3LjE1MmwyMDAuOTM2LDExMC44NTZIMzkuMDcxTDI0MC4wMDcsMTcuMTUyeiBNNDQ4LjAwNyw0NjQuMDA4aC0xNiAgICB2LTI5NmgtMzg0djI5NmgtMTZ2LTMyMGg0MTZWNDY0LjAwOHoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik0xNjguMDA3LDc1LjA2NHY0NC45NDRoNjRWNDMuMDY0TDE2OC4wMDcsNzUuMDY0eiBNMjE2LjAwNywxMDQuMDA4aC0zMlY4NC45NTJsMzItMTZWMTA0LjAwOHoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik0yNDguMDA3LDQzLjA2NHY3Ni45NDRoNjRWNzUuMDY0TDI0OC4wMDcsNDMuMDY0eiBNMjk2LjAwNywxMDQuMDA4aC0zMlY2OC45NTJsMzIsMTZWMTA0LjAwOHoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K", + "name": { + "en": "Garage", + "de": "Garage", + "ru": "Гараж", + "pt": "Garagem", + "nl": "Garage", + "fr": "Garage", + "it": "Box auto", + "es": "Garaje", + "pl": "Garaż" + }, + "members": [] + }, + "type": "enum" + } + + + // todo + // + }, + 'enum.functions': { + "enum.functions.light": { + "_id": "enum.functions.light", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgJy0vL1czQy8vRFREIFNWRyAxLjEvL0VOJyAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIj4KICA8Zz4KICAgIDxnPgogICAgICA8cGF0aCBkPSJtMjU2LDkyLjNjLTc0LjIsMC0xMjcuOCw1NS4zLTEzNi4zLDExNC43LTUuMywzOS42IDcuNSw3OC4yIDM0LjEsMTA3LjQgMjMuNCwyNSAzNi4yLDU4LjQgMzYuMiw5Mi44bC0uMSw1NC4yYzAsMjEuOSAxOC4xLDM5LjYgNDAuNSwzOS42aDUyLjJjMjIuNCwwIDQwLjUtMTcuNyA0MC41LTM5LjZsLjEtNTQuMmMwLTM1LjQgMTEuNy02Ny44IDM0LjEtOTAuNyAyNC41LTI1IDM3LjMtNTcuMyAzNy4zLTkwLjctMC4xLTc0LjEtNjMtMTMzLjUtMTM4LjYtMTMzLjV6bTQ2LjgsMzY5LjFjMCwxMC40LTguNSwxOC44LTE5LjIsMTguOGgtNTIuMmMtMTAuNywwLTE5LjItOC4zLTE5LjItMTguOHYtMjRoOTAuNXYyNHptMzkuNi0xNTkuNWMtMjYuNiwyNy4xLTQwLjUsNjQuNi00MC41LDEwNS4zdjkuNGgtOTAuNXYtOS40YzAtMzguNi0xNi03Ny4xLTQyLjYtMTA2LjMtMjMuNC0yNS0zMy01Ny4zLTI4LjgtOTAuNyA3LjUtNTAgNTQtOTcgMTE2LjEtOTcgNjUsMCAxMTcuMiw1MS4xIDExNy4yLDExMi42IDAsMjguMS0xMC43LDU1LjItMzAuOSw3Ni4xeiIvPgogICAgICA8cmVjdCB3aWR0aD0iMjEuMyIgeD0iMjQ1LjMiIHk9IjExIiBoZWlnaHQ9IjUwIi8+CiAgICAgIDxwb2x5Z29uIHBvaW50cz0iMzg1LjEsMTA3LjQgNDAwLDEyMi4zIDQzNi41LDg3LjIgNDIxLjUsNzIuMyAgICIvPgogICAgICA8cmVjdCB3aWR0aD0iNTIuMiIgeD0iNDQ4LjgiIHk9IjIzNi4yIiBoZWlnaHQ9IjIwLjkiLz4KICAgICAgPHJlY3Qgd2lkdGg9IjUyLjIiIHg9IjExIiB5PSIyMzYuMiIgaGVpZ2h0PSIyMC45Ii8+CiAgICAgIDxwb2x5Z29uIHBvaW50cz0iOTAuMSw3Mi4yIDc1LjEsODcuMSAxMTEuNiwxMjIuMiAxMjYuNSwxMDcuMyAgICIvPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg==", + "name": { + "en": "Light", + "de": "Licht", + "ru": "Свет", + "pt": "Luz", + "nl": "Licht", + "fr": "Lumière", + "it": "Soggiorno", + "es": "Luz" + }, + "members": [] + }, + "type": "enum" + }, + "enum.functions.blinds": { + "_id": "enum.functions.blinds", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTEyIDUxMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJCTxwYXRoIGQ9Ik0wLDB2NTEyaDUxMlYwSDB6IE00ODIsNDgySDMwVjMwaDQ1MlY0ODJ6Ii8+DQo8L2c+DQo8Zz4NCgkJPHBhdGggZD0iTTYwLDYwdjM5MmgzOTJWNjBINjB6IE0yNDEsNDIySDkwVjI3MWgxNTFWNDIyeiBNMjQxLDI0MUg5MFY5MGgxNTFWMjQxeiBNNDIyLDQyMkgyNzFWMjcxaDE1MVY0MjJ6IE00MjIsMjQxSDI3MVY5MGgxNTENCgkJCVYyNDF6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==", + "name": { + "en": "Blinds", + "de": "Rollladen", + "ru": "Жалюзи", + "pt": "Cortinas", + "nl": "blinds", + "fr": "Stores", + "it": "blinds", + "es": "Persianas", + "pl": "Żaluzje" + }, + "members": [] + }, + "type": "enum" + }, + "enum.functions.weather": { + "_id": "enum.functions.weather", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNjAgNjAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDYwIDYwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8cGF0aCBkPSJNNTQuOTQ5LDI4LjI1MWMwLjAxOC0wLjA2OSwwLjAzOS0wLjEzNSwwLjA0Mi0wLjIwN0M1NC45OTIsMjguMDI5LDU1LDI4LjAxNiw1NSwyOEM1NSwxNC41NSw0NC4zMjMsMy41NTMsMzEsMy4wMjVWMQ0KCWMwLTAuNTUzLTAuNDQ4LTEtMS0xcy0xLDAuNDQ3LTEsMXYyLjAyNUMxNS42NzcsMy41NTMsNSwxNC41NSw1LDI4YzAsMC4wMTYsMC4wMDgsMC4wMjksMC4wMDksMC4wNDUNCgljMC4wMDMsMC4wNzEsMC4wMjMsMC4xMzcsMC4wNDIsMC4yMDdjMC4wMTYsMC4wNTksMC4wMjUsMC4xMTgsMC4wNSwwLjE3MmMwLjAyNSwwLjA1MywwLjA2NCwwLjA5NywwLjA5OCwwLjE0NQ0KCWMwLjA0MiwwLjA1OSwwLjA4MSwwLjExOCwwLjEzNSwwLjE2N2MwLjAxMSwwLjAxLDAuMDE2LDAuMDI1LDAuMDI4LDAuMDM1YzAuMDM2LDAuMDMsMC4wOCwwLjA0LDAuMTE4LDAuMDY0DQoJYzAuMDU5LDAuMDM3LDAuMTE1LDAuMDc0LDAuMTgyLDAuMDk4YzAuMDcsMC4wMjYsMC4xNDEsMC4wMzQsMC4yMTMsMC4wNDNDNS45MTgsMjguOTgsNS45NTYsMjksNiwyOQ0KCWMwLjAxNSwwLDAuMDI3LTAuMDA4LDAuMDQyLTAuMDA4YzAuMDc2LTAuMDAzLDAuMTQ3LTAuMDI0LDAuMjItMC4wNDRjMC4wNTQtMC4wMTUsMC4xMDktMC4wMjMsMC4xNTgtMC4wNDcNCgljMC4wNTctMC4wMjYsMC4xMDQtMC4wNjgsMC4xNTYtMC4xMDVjMC4wNTUtMC4wNCwwLjExMS0wLjA3NywwLjE1Ny0wLjEyN2MwLjAxMS0wLjAxMiwwLjAyNi0wLjAxNywwLjAzNy0wLjAzDQoJQzguMTgsMjYuOTM3LDEwLjAzOCwyNiwxMiwyNnMzLjgyLDAuOTM3LDUuMjMsMi42MzljMC4wMTksMC4wMjMsMC4wNDgsMC4wMzIsMC4wNjksMC4wNTNjMC4wMjQsMC4wMjQsMC4wMzYsMC4wNTYsMC4wNjMsMC4wNzgNCgljMC4wNCwwLjAzMywwLjA4OCwwLjA0NSwwLjEzLDAuMDcxYzAuMDUxLDAuMDMxLDAuMDk4LDAuMDYyLDAuMTUyLDAuMDgyYzAuMDgyLDAuMDMyLDAuMTY0LDAuMDQ1LDAuMjQ5LDAuMDU0DQoJQzE3LjkzLDI4Ljk4MSwxNy45NjQsMjksMTgsMjljMC4wMTYsMCwwLjAzMS0wLjAwNywwLjA0Ni0wLjAwOGMwLjA1MS0wLjAwMiwwLjA5OC0wLjAxNSwwLjE0OC0wLjAyNQ0KCWMwLjA2OS0wLjAxNCwwLjEzNS0wLjAzLDAuMi0wLjA1OWMwLjA1LTAuMDIyLDAuMDk1LTAuMDUsMC4xNDEtMC4wODFjMC4wMzMtMC4wMjEsMC4wNy0wLjAzMiwwLjEwMS0wLjA1OA0KCWMwLjAyNC0wLjAyLDAuMDM1LTAuMDQ4LDAuMDU3LTAuMDdjMC4wMjMtMC4wMjMsMC4wNTQtMC4wMzUsMC4wNzUtMC4wNjFDMjAuMTgsMjYuOTM3LDIyLjAzOCwyNiwyNCwyNmMxLjg2LDAsMy42MiwwLjg1LDUsMi4zODZWNTUNCgljMCwxLjY1NC0xLjM0NiwzLTMsM3MtMy0xLjM0Ni0zLTNjMC0wLjU1My0wLjQ0OC0xLTEtMXMtMSwwLjQ0Ny0xLDFjMCwyLjc1NywyLjI0Myw1LDUsNXM1LTIuMjQzLDUtNVYyOC4zODYNCglDMzIuMzgsMjYuODUsMzQuMTQsMjYsMzYsMjZjMS45NjIsMCwzLjgyLDAuOTM3LDUuMjMsMi42MzljMC4wMTksMC4wMjMsMC4wNDgsMC4wMzIsMC4wNjksMC4wNTMNCgljMC4wMjQsMC4wMjQsMC4wMzYsMC4wNTYsMC4wNjMsMC4wNzhjMC4wNCwwLjAzMywwLjA4OCwwLjA0NCwwLjEzLDAuMDdjMC4wNTEsMC4wMzEsMC4wOTgsMC4wNjIsMC4xNTMsMC4wODMNCgljMC4wODEsMC4wMzEsMC4xNjMsMC4wNDUsMC4yNDksMC4wNTRDNDEuOTMsMjguOTgxLDQxLjk2NCwyOSw0MiwyOWMwLjAxNiwwLDAuMDMxLTAuMDA3LDAuMDQ2LTAuMDA4DQoJYzAuMDUxLTAuMDAyLDAuMDk5LTAuMDE1LDAuMTQ5LTAuMDI2YzAuMDY4LTAuMDE0LDAuMTM0LTAuMDMsMC4xOTktMC4wNThjMC4wNTEtMC4wMjIsMC4wOTYtMC4wNTEsMC4xNDItMC4wODENCgljMC4wMzMtMC4wMjEsMC4wNy0wLjAzMiwwLjEwMS0wLjA1N2MwLjAyNC0wLjAyLDAuMDM1LTAuMDQ4LDAuMDU2LTAuMDdjMC4wMjMtMC4wMjMsMC4wNTQtMC4wMzUsMC4wNzYtMC4wNjENCglDNDQuMTgsMjYuOTM3LDQ2LjAzOCwyNiw0OCwyNnMzLjgyLDAuOTM3LDUuMjMsMi42MzljMC4wMSwwLjAxMiwwLjAyNCwwLjAxNSwwLjAzNSwwLjAyN2MwLjA3LDAuMDc3LDAuMTU0LDAuMTM1LDAuMjQ1LDAuMTg4DQoJYzAuMDMxLDAuMDE4LDAuMDU1LDAuMDQ1LDAuMDg4LDAuMDU5QzUzLjcyMSwyOC45NjcsNTMuODU2LDI5LDU0LDI5aDBoMGMwLjEyMywwLDAuMjQ1LTAuMDI3LDAuMzYxLTAuMDczDQoJYzAuMDQ1LTAuMDE4LDAuMDgyLTAuMDQ3LDAuMTI0LTAuMDcxYzAuMDUxLTAuMDI5LDAuMTA2LTAuMDQ4LDAuMTUyLTAuMDg3YzAuMDEyLTAuMDEsMC4wMTctMC4wMjQsMC4wMjgtMC4wMzUNCgljMC4wNTQtMC4wNDksMC4wOTItMC4xMDcsMC4xMzUtMC4xNjdjMC4wMzQtMC4wNDgsMC4wNzMtMC4wOTMsMC4wOTgtMC4xNDVDNTQuOTI0LDI4LjM2OSw1NC45MzQsMjguMzEsNTQuOTQ5LDI4LjI1MXogTTQ4LDI0DQoJYy0yLjIxOCwwLTQuMzEyLDAuODk1LTYsMi41MzRDNDAuMzEyLDI0Ljg5NSwzOC4yMTgsMjQsMzYsMjRzLTQuMzEyLDAuODk1LTYsMi41MzRDMjguMzEyLDI0Ljg5NSwyNi4yMTgsMjQsMjQsMjQNCglzLTQuMzEyLDAuODk1LTYsMi41MzRDMTYuMzEyLDI0Ljg5NSwxNC4yMTgsMjQsMTIsMjRjLTEuNzUsMC0zLjQxOCwwLjU2Ni00Ljg3NSwxLjYwNUM4LjMyNywxNC4wNDQsMTguMTI3LDUsMzAsNQ0KCXMyMS42NzMsOS4wNDQsMjIuODc1LDIwLjYwNUM1MS40MTgsMjQuNTY2LDQ5Ljc1LDI0LDQ4LDI0eiIvPg0KPC9zdmc+DQo=", + "name": { + "en": "Weather", + "de": "Wetter", + "ru": "Погода", + "pt": "Clima", + "nl": "Weer", + "fr": "Météo", + "it": "Tempo metereologico", + "es": "Clima", + "pl": "Pogoda" + }, + "members": [] + }, + "type": "enum" + }, + "enum.functions.heating": { + "_id": "enum.functions.heating", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTEyIDUxMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZD0iTTQ0MC4zNzEsMTU5LjI5MWM4LjA1MSwwLDE1LjQwMy0zLjAzNSwyMC45ODMtOC4wMTZINTA0LjVjNC4xNDIsMCw3LjUtMy4zNTgsNy41LTcuNWMwLTQuMTQyLTMuMzU4LTcuNS03LjUtNy41aC0zMy43NjMNCgkJYzAuNzY0LTIuNzE2LDEuMTgyLTUuNTc2LDEuMTgyLTguNTMzYzAtMi45NTctMC40MTgtNS44MTYtMS4xODItOC41MzJINTA0LjVjNC4xNDIsMCw3LjUtMy4zNTgsNy41LTcuNQ0KCQljMC00LjE0Mi0zLjM1OC03LjUtNy41LTcuNWgtNDMuMTQ2Yy01LjU4LTQuOTgxLTEyLjkzMi04LjAxNi0yMC45ODMtOC4wMTZjLTYuMTMzLDAtMTEuODU5LDEuNzY1LTE2LjcwNyw0LjgwNQ0KCQljLTEuMzQyLTExLjcxMS0xMS4zMDktMjAuODM3LTIzLjM3My0yMC44MzdoLTI0LjA0OWMtMTAuMzU0LDAtMTkuMTYsNi43MjQtMjIuMywxNi4wMzJoLTE5LjUzDQoJCWMtMy4xMzktOS4zMDgtMTEuOTQ2LTE2LjAzMi0yMi4zLTE2LjAzMmgtMjQuMDQ4Yy0xMC4zNTQsMC0xOS4xNiw2LjcyNC0yMi4zLDE2LjAzMmgtMTkuNTMNCgkJYy0zLjEzOS05LjMwOC0xMS45NDYtMTYuMDMyLTIyLjMtMTYuMDMyaC0yNC4wNDhjLTEwLjM1NCwwLTE5LjE2LDYuNzI0LTIyLjMsMTYuMDMyaC0xOS41Mw0KCQljLTMuMTM5LTkuMzA4LTExLjk0Ni0xNi4wMzItMjIuMy0xNi4wMzJoLTI0LjA0OWMtMTAuMzU0LDAtMTkuMTYsNi43MjQtMjIuMywxNi4wMzJINjkuODgNCgkJYy0zLjEzOS05LjMwOC0xMS45NDYtMTYuMDMyLTIyLjMtMTYuMDMySDIzLjUzMkMxMC41NTcsODAuMTYxLDAsOTAuNzE4LDAsMTAzLjY5M3YzMDQuNjEzYzAsMTIuOTc2LDEwLjU1NywyMy41MzIsMjMuNTMyLDIzLjUzMg0KCQlINDcuNThjMTAuMzU0LDAsMTkuMTYtNi43MjQsMjIuMy0xNi4wMzJoMTkuNTNjMy4xMzksOS4zMDgsMTEuOTQ2LDE2LjAzMiwyMi4zLDE2LjAzMmgyNC4wNDljMTAuMzU0LDAsMTkuMTYtNi43MjQsMjIuMy0xNi4wMzINCgkJaDE5LjUzYzMuMTM5LDkuMzA4LDExLjk0NiwxNi4wMzIsMjIuMywxNi4wMzJoMjQuMDQ4YzEwLjM1NCwwLDE5LjE2LTYuNzI0LDIyLjMtMTYuMDMyaDE5LjUzDQoJCWMzLjEzOSw5LjMwOCwxMS45NDYsMTYuMDMyLDIyLjMsMTYuMDMyaDI0LjA0OGMxMC4zNTQsMCwxOS4xNi02LjcyNCwyMi4zLTE2LjAzMmgxOS41M2MzLjEzOSw5LjMwOCwxMS45NDYsMTYuMDMyLDIyLjMsMTYuMDMyDQoJCWgyNC4wNDljMTIuOTc2LDAsMjMuNTMyLTEwLjU1NywyMy41MzItMjMuNTMydi0wLjUxNkg1MDQuNWM0LjE0MiwwLDcuNS0zLjM1OCw3LjUtNy41YzAtNC4xNDItMy4zNTgtNy41LTcuNS03LjVoLTgwLjY3N3YtMTcuMDY1DQoJCUg1MDQuNWM0LjE0MiwwLDcuNS0zLjM1OCw3LjUtNy41YzAtNC4xNDItMy4zNTgtNy41LTcuNS03LjVoLTgwLjY3N1YxNTQuNTg3QzQyOC42MzcsMTU3LjU2Niw0MzQuMzA2LDE1OS4yOTEsNDQwLjM3MSwxNTkuMjkxeg0KCQkgTTQ0MC4zNzEsMTExLjE5M2M5LjEyNSwwLDE2LjU0OCw3LjQyMywxNi41NDgsMTYuNTQ4cy03LjQyMywxNi41NDktMTYuNTQ4LDE2LjU0OXMtMTYuNTQ4LTcuNDI0LTE2LjU0OC0xNi41NDkNCgkJUzQzMS4yNDYsMTExLjE5Myw0NDAuMzcxLDExMS4xOTN6IE02My42MTMsMzM2LjY3NmMtNC4xNDIsMC03LjUsMy4zNTgtNy41LDcuNXY2NC4xM2MwLDQuNzA1LTMuODI4LDguNTMzLTguNTMyLDguNTMzSDIzLjUzMg0KCQljLTQuNzA1LDAtOC41MzItMy44MjgtOC41MzItOC41MzJWMTAzLjY5M2MwLTQuNzA1LDMuODI4LTguNTMyLDguNTMyLTguNTMySDQ3LjU4YzQuNzA1LDAsOC41MzIsMy44MjgsOC41MzIsOC41MzJWMzEyLjExDQoJCWMwLDQuMTQyLDMuMzU4LDcuNSw3LjUsNy41YzQuMTQyLDAsNy41LTMuMzU4LDcuNS03LjVWMTU5LjI5aDE3LjA2NHYxOTMuNDE5SDcxLjExM3YtOC41MzMNCgkJQzcxLjExMywzNDAuMDM0LDY3Ljc1NSwzMzYuNjc2LDYzLjYxMywzMzYuNjc2eiBNODguMTc3LDQwMC44MDZINzEuMTEzdi0zMy4wOTdoMTcuMDY0VjQwMC44MDZ6IE04OC4xNzcsMTQ0LjI5SDcxLjExM3YtMzMuMDk3DQoJCWgxNy4wNjRWMTQ0LjI5eiBNMTQ0LjI5LDQwOC4zMDZjMC4wMDEsNC43MDUtMy44MjcsOC41MzMtOC41MzIsOC41MzNoLTI0LjA0OWMtNC43MDUsMC04LjUzMi0zLjgyOC04LjUzMi04LjUzMlYxMDMuNjkzDQoJCWMwLTQuNzA1LDMuODI4LTguNTMyLDguNTMyLTguNTMyaDI0LjA0OWM0LjcwNSwwLDguNTMyLDMuODI4LDguNTMyLDguNTMyVjQwOC4zMDZ6IE0xNzYuMzU1LDQwMC44MDZoLTE3LjA2NHYtMzMuMDk3aDE3LjA2NA0KCQlWNDAwLjgwNnogTTE3Ni4zNTUsMzUyLjcxaC0xNy4wNjRWMTU5LjI5MWgxNy4wNjRWMzUyLjcxeiBNMTc2LjM1NSwxNDQuMjkxaC0xNy4wNjR2LTMzLjA5N2gxNy4wNjRWMTQ0LjI5MXogTTIzMi40NjgsNDA4LjMwNg0KCQljMCw0LjcwNS0zLjgyOCw4LjUzMy04LjUzMiw4LjUzM2gtMjQuMDQ4Yy00LjcwNSwwLTguNTMyLTMuODI4LTguNTMyLTguNTMyVjEwMy42OTNjMC00LjcwNSwzLjgyOC04LjUzMiw4LjUzMi04LjUzMmgyNC4wNDgNCgkJYzQuNzA1LDAsOC41MzIsMy44MjgsOC41MzIsOC41MzJWNDA4LjMwNnogTTI2NC41MzIsNDAwLjgwNmgtMTcuMDY0di0zMy4wOTdoMTcuMDY0VjQwMC44MDZ6IE0yNjQuNTMyLDM1Mi43MWgtMTcuMDY0VjE1OS4yOTENCgkJaDE3LjA2NFYzNTIuNzF6IE0yNjQuNTMyLDE0NC4yOTFoLTE3LjA2NHYtMzMuMDk3aDE3LjA2NFYxNDQuMjkxeiBNMzIwLjY0NSw0MDguMzA2YzAsNC43MDUtMy44MjgsOC41MzMtOC41MzIsOC41MzNoLTI0LjA0OA0KCQljLTQuNzA1LDAtOC41MzItMy44MjgtOC41MzItOC41MzJWMTAzLjY5M2MwLTQuNzA1LDMuODI4LTguNTMyLDguNTMyLTguNTMyaDI0LjA0OGM0LjcwNSwwLDguNTMyLDMuODI4LDguNTMyLDguNTMyVjQwOC4zMDZ6DQoJCSBNMzM1LjY0NSwxMTEuMTkzaDE3LjA2NHYzMy4wOTdoLTE3LjA2NFYxMTEuMTkzeiBNMzUyLjcwOSw0MDAuODA3aC0xNy4wNjRWMzY3LjcxaDE3LjA2NFY0MDAuODA3eiBNNDA4LjgyMyw0MDguMzA3DQoJCWMwLDQuNzA1LTMuODI4LDguNTMyLTguNTMyLDguNTMyaC0yNC4wNDljLTQuNzA1LDAtOC41MzItMy44MjgtOC41MzItOC41MzJWMTk5Ljg5YzAtNC4xNDItMy4zNTgtNy41LTcuNS03LjUNCgkJYy00LjE0MiwwLTcuNSwzLjM1OC03LjUsNy41djE1Mi44MmgtMTcuMDY0VjE1OS4yOTFoMTcuMDY0djguNTMzYzAsNC4xNDIsMy4zNTgsNy41LDcuNSw3LjVjNC4xNDIsMCw3LjUtMy4zNTgsNy41LTcuNXYtNjQuMTMNCgkJYzAtNC43MDUsMy44MjgtOC41MzIsOC41MzItOC41MzJoMjQuMDQ5YzQuNzA1LDAsOC41MzIsMy44MjgsOC41MzIsOC41MzJWNDA4LjMwN3oiLz4NCjwvZz4NCjwvc3ZnPg0K", + "name": { + "en": "Heating", + "de": "Heizung", + "ru": "Отопление", + "pt": "Aquecimento", + "nl": "Verwarming", + "fr": "Chauffage", + "it": "Riscaldamento", + "es": "Calefacción", + "pl": "Ogrzewanie" + }, + "members": [] + }, + "type": "enum" + }, + "enum.functions.backlight": { + "_id": "enum.functions.backlight", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNTExLjk5MSA1MTEuOTkxIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTEuOTkxIDUxMS45OTE7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwYXRoIGQ9Ik05MS40OSwzMzYuNTcybC0yMS4zMzMsNDIuNjY3Yy0yLjYyNCw1LjI2OS0wLjQ5MSwxMS42NjksNC43NzksMTQuMzE1YzEuNTM2LDAuNzQ3LDMuMTU3LDEuMTA5LDQuNzU3LDEuMTA5DQoJCWMzLjkwNCwwLDcuNjU5LTIuMTc2LDkuNTM2LTUuODg4bDIxLjMzMy00Mi42NjdjMi42NDUtNS4yNjksMC41MTItMTEuNjY5LTQuNzU3LTE0LjMxNQ0KCQlDMTAwLjU1NywzMjkuMTY5LDk0LjE1NywzMzEuMzI0LDkxLjQ5LDMzNi41NzJ6Ii8+DQoJPHBhdGggZD0iTTUwNy41NTQsMTgzLjM1NmMtMi43NzMtMi4wMjctNi4zMzYtMi41MzktOS42LTEuNDkzbC00OS40MDgsMTYuNDY5Yy0xMy4wOTksNC4zNzMtMjEuODg4LDE2LjU3Ni0yMS44ODgsMzAuMzc5djM2Ljg4NQ0KCQljLTI0LjMyLTQuOTQ5LTQyLjY2Ny0yNi40OTYtNDIuNjY3LTUyLjI0NXYtODUuMzMzYzAtMzEuMjMyLTEyLjEzOS02MC43MTUtMzQuMzA0LTgzLjExNQ0KCQljLTIyLjMxNS0yMi4xMDEtNTEuNzk3LTM0LjI0LTgzLjAyOS0zNC4yNGMtNjQuNjgzLDAtMTE3LjMzMyw1Mi42NTEtMTE3LjMzMywxMTcuMzMzdjUzLjMzM2gtMzJjLTE3LjY0MywwLTMyLDE0LjM1Ny0zMiwzMg0KCQl2NDAuMTQ5TDEuMTIyLDQyMS45MDVjLTEuNjQzLDMuMzA3LTEuNDcyLDcuMjMyLDAuNDQ4LDEwLjM2OGMxLjk2MywzLjEzNiw1LjM5Nyw1LjA1Niw5LjA4OCw1LjA1Nmg3NS43NTUNCgkJYzUuMjI3LDM2LjA5NiwzNi4wNzUsNjQsNzMuNTc5LDY0czY4LjM1Mi0yNy45MDQsNzMuNTc5LTY0aDc1Ljc1NWMwLjE0OSwwLDAuMzIsMC4wMjEsMC40MjcsMA0KCQljNS44ODgsMCwxMC42NjctNC43NzksMTAuNjY3LTEwLjY2N2MwLTIuNzUyLTEuMDI0LTUuMjQ4LTIuNzMxLTcuMTI1bC04My4wMjktMTY2LjA1OXYtNDAuMTQ5YzAtMTcuNjQzLTE0LjM1Ny0zMi0zMi0zMmgtMzINCgkJdi01My4zMzNjMC01Mi45MjgsNDMuMDcyLTk2LDk2LTk2YzI1LjU1NywwLDQ5LjcyOCw5Ljk2Myw2Ny45NDcsMjcuOTg5YzE4LjA5MSwxOC4yODMsMjguMDUzLDQyLjQ1MywyOC4wNTMsNjguMDExdjg1LjMzMw0KCQljMCwzNy41MDQsMjcuOTA0LDY4LjM1Miw2NCw3My41Nzl2MzkuMDRjMCwxMy43ODEsOC43ODksMjYuMDA1LDIxLjg2NywzMC4zNTdsNDkuNDA4LDE2LjQ2OQ0KCQljMS4xMzEsMC4zNjMsMi4yNjEsMC41NTUsMy4zOTIsMC41NTVjMi4yMTksMCw0LjM5NS0wLjY4Myw2LjIyOS0yLjAyN2MyLjc5NS0xLjk4NCw0LjQzNy01LjIwNSw0LjQzNy04LjY0VjE5MS45OTYNCgkJQzUxMS45OTEsMTg4LjU2MSw1MTAuMzQ5LDE4NS4zNCw1MDcuNTU0LDE4My4zNTZ6IE0xNTkuOTkxLDQ3OS45OTZjLTI1Ljc0OSwwLTQ3LjMxNy0xOC4zNDctNTIuMjY3LTQyLjY2N2gxMDQuNTMzDQoJCUMyMDcuMzA5LDQ2MS42NDksMTg1Ljc0MSw0NzkuOTk2LDE1OS45OTEsNDc5Ljk5NnogTTI5Mi4wODcsNDE1Ljk5NmgtNjguMDk2aC0xMjhIMjcuOTE3bDc0LjY2Ny0xNDkuMzMzaDExNC44MzcNCgkJTDI5Mi4wODcsNDE1Ljk5NnogTTIwMi42NTgsMjAyLjY2MmM1Ljg2NywwLDEwLjY2Nyw0LjgsMTAuNjY3LDEwLjY2N3YzMkgxMDYuNjU4di0zMmMwLTUuODY3LDQuOC0xMC42NjcsMTAuNjY3LTEwLjY2N0gyMDIuNjU4eg0KCQkgTTQ5MC42NTgsMzQ3Ljg3OGwtMzUuMzcxLTExLjgxOWMtNC4zNzMtMS40MjktNy4yOTYtNS41MDQtNy4yOTYtMTAuMTEydi05Ny4yNTljMC00LjYwOCwyLjkyMy04LjY2MSw3LjI5Ni0xMC4xMTINCgkJbDM1LjM3MS0xMS43OTdWMzQ3Ljg3OHoiLz4NCjwvZz4NCjwvc3ZnPg0K", + "name": { + "en": "Backlight", + "de": "Hintergrundbeleuchtung", + "ru": "Подсветка", + "pt": "Luz de fundo", + "nl": "Backlight", + "fr": "Rétro-éclairage", + "it": "Controluce", + "es": "Iluminar desde el fondo", + "pl": "Podświetlenie" + }, + "members": [] + }, + "type": "enum" + }, + "enum.functions.household": { + "_id": "enum.functions.household", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgJy0vL1czQy8vRFREIFNWRyAxLjEvL0VOJyAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIj4KICA8Zz4KICAgIDxnPgogICAgICA8cGF0aCBkPSJtMjU2LDkyLjNjLTc0LjIsMC0xMjcuOCw1NS4zLTEzNi4zLDExNC43LTUuMywzOS42IDcuNSw3OC4yIDM0LjEsMTA3LjQgMjMuNCwyNSAzNi4yLDU4LjQgMzYuMiw5Mi44bC0uMSw1NC4yYzAsMjEuOSAxOC4xLDM5LjYgNDAuNSwzOS42aDUyLjJjMjIuNCwwIDQwLjUtMTcuNyA0MC41LTM5LjZsLjEtNTQuMmMwLTM1LjQgMTEuNy02Ny44IDM0LjEtOTAuNyAyNC41LTI1IDM3LjMtNTcuMyAzNy4zLTkwLjctMC4xLTc0LjEtNjMtMTMzLjUtMTM4LjYtMTMzLjV6bTQ2LjgsMzY5LjFjMCwxMC40LTguNSwxOC44LTE5LjIsMTguOGgtNTIuMmMtMTAuNywwLTE5LjItOC4zLTE5LjItMTguOHYtMjRoOTAuNXYyNHptMzkuNi0xNTkuNWMtMjYuNiwyNy4xLTQwLjUsNjQuNi00MC41LDEwNS4zdjkuNGgtOTAuNXYtOS40YzAtMzguNi0xNi03Ny4xLTQyLjYtMTA2LjMtMjMuNC0yNS0zMy01Ny4zLTI4LjgtOTAuNyA3LjUtNTAgNTQtOTcgMTE2LjEtOTcgNjUsMCAxMTcuMiw1MS4xIDExNy4yLDExMi42IDAsMjguMS0xMC43LDU1LjItMzAuOSw3Ni4xeiIvPgogICAgICA8cmVjdCB3aWR0aD0iMjEuMyIgeD0iMjQ1LjMiIHk9IjExIiBoZWlnaHQ9IjUwIi8+CiAgICAgIDxwb2x5Z29uIHBvaW50cz0iMzg1LjEsMTA3LjQgNDAwLDEyMi4zIDQzNi41LDg3LjIgNDIxLjUsNzIuMyAgICIvPgogICAgICA8cmVjdCB3aWR0aD0iNTIuMiIgeD0iNDQ4LjgiIHk9IjIzNi4yIiBoZWlnaHQ9IjIwLjkiLz4KICAgICAgPHJlY3Qgd2lkdGg9IjUyLjIiIHg9IjExIiB5PSIyMzYuMiIgaGVpZ2h0PSIyMC45Ii8+CiAgICAgIDxwb2x5Z29uIHBvaW50cz0iOTAuMSw3Mi4yIDc1LjEsODcuMSAxMTEuNiwxMjIuMiAxMjYuNSwxMDcuMyAgICIvPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg==", + "name": { + "en": "Household", + "de": "Haushalt", + "ru": "Домашнее хозяйство", + "pt": "Casa", + "nl": "Huishouden", + "fr": "Ménage", + "it": "Domestico", + "es": "Casa", + "pl": "Gospodarstwo domowe" + }, + "members": [] + }, + "type": "enum" + }, + "enum.functions.audio": { + "_id": "enum.functions.audio", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNDc3LjIxNiA0NzcuMjE2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0NzcuMjE2IDQ3Ny4yMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwYXRoIGQ9Ik00NTMuODU4LDEwNS4xMTZ2LTkxLjZjMC00LjMtMi4xLTguNC01LjUtMTAuOWMtMy41LTIuNS04LTMuMy0xMi4xLTJsLTI3Mi45LDg2LjdjLTUuNiwxLjgtOS40LDctOS40LDEyLjl2OTEuN3YwLjF2MTc1LjMNCgkJYy0xNC4zLTkuOS0zMi42LTE1LjMtNTEuOC0xNS4zYy0yMC4zLDAtMzkuNiw2LjEtNTQuMywxNy4xYy0xNS44LDExLjktMjQuNSwyOC0yNC41LDQ1LjVzOC43LDMzLjYsMjQuNSw0NS41DQoJCWMxNC43LDExLDMzLjksMTcuMSw1NC4zLDE3LjFzMzkuNi02LjEsNTQuMy0xNy4xYzE1LjgtMTEuOSwyNC41LTI4LDI0LjUtNDUuNXYtMjEyLjhsMjQ1LjktNzguMnYxNTYuNg0KCQljLTE0LjMtOS45LTMyLjYtMTUuMy01MS44LTE1LjNjLTIwLjMsMC0zOS42LDYuMS01NC4zLDE3LjFjLTE1LjgsMTEuOS0yNC41LDI4LTI0LjUsNDUuNXM4LjcsMzMuNiwyNC41LDQ1LjUNCgkJYzE0LjcsMTEsMzMuOSwxNy4xLDU0LjMsMTcuMXMzOS42LTYuMSw1NC4zLTE3LjFjMTUuOC0xMS45LDI0LjUtMjgsMjQuNS00NS41di0yMjIuMw0KCQlDNDUzLjg1OCwxMDUuMTE2LDQ1My44NTgsMTA1LjExNiw0NTMuODU4LDEwNS4xMTZ6IE0xMDIuMTU4LDQ1MC4yMTZjLTI4LjEsMC01MS44LTE2LjMtNTEuOC0zNS42YzAtMTkuMywyMy43LTM1LjYsNTEuOC0zNS42DQoJCXM1MS44LDE2LjMsNTEuOCwzNS42QzE1My45NTgsNDM0LjAxNiwxMzAuMjU4LDQ1MC4yMTYsMTAyLjE1OCw0NTAuMjE2eiBNMTgwLjk1OCwxNzMuNDE2di02My40bDI0NS45LTc4LjF2NjMuNEwxODAuOTU4LDE3My40MTZ6DQoJCSBNMzc1LjE1OCwzNjMuMTE2Yy0yOC4xLDAtNTEuOC0xNi4zLTUxLjgtMzUuNmMwLTE5LjMsMjMuNy0zNS42LDUxLjgtMzUuNnM1MS44LDE2LjMsNTEuOCwzNS42DQoJCUM0MjYuODU4LDM0Ni44MTYsNDAzLjE1OCwzNjMuMTE2LDM3NS4xNTgsMzYzLjExNnoiLz4NCjwvZz4NCjwvc3ZnPg0K", + "name": { + "en": "Audio/Music", + "de": "Audio/Musik", + "ru": "Аудио/Музыка", + "pt": "Áudio/Música", + "nl": "Audio/Muziek", + "fr": "Audio/Musique", + "it": "Audio/Musica", + "es": "Audio/Música", + "pl": "Audio/Muzyka" + }, + "members": [] + }, + "type": "enum" + }, + "enum.functions.security": { + "_id": "enum.functions.security", + "common": { + "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTEyIDUxMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZD0iTTI1Niw0MC4wMWMtNS41MzgsMC05Ljk5LDQuNDYtOS45OSw5Ljk5YzAsNS4xNDcsMy44NzIsOS40Myw4Ljk5LDkuOTM4YzUuOTI1LDAuNTg4LDEwLjk5LTQuMDI3LDEwLjk5LTkuOTM4DQoJCUMyNjUuOTksNDQuNDYyLDI2MS41Myw0MC4wMSwyNTYsNDAuMDF6Ii8+DQo8L2c+DQo8Zz4NCgk8cGF0aCBkPSJNNDU2LDYwYy0yNy41NywwLTUwLTIyLjQzLTUwLTUwYzAtNS41MjItNC40NzgtMTAtMTAtMTBIMTE2Yy01LjUyMiwwLTEwLDQuNDc4LTEwLDEwYzAsMjcuNTctMjIuNDMsNTAtNTAsNTANCgkJYy01LjUyMiwwLTEwLDQuNDc4LTEwLDEwdjE2My4xOWMwLDEyNy4xNzcsODIuODk1LDI0MS4xMzUsMjA3LjEyOCwyNzguMzg5YzAuOTM3LDAuMjgsMS45MDQsMC40MjEsMi44NzIsMC40MjENCgkJYzAuOTY4LDAsMS45MzctMC4xNDEsMi44NzMtMC40MjJDMzgwLjQxNSw0NzUuMTE5LDQ2NiwzNjMuMTQ5LDQ2NiwyMzMuMTlWNzBDNDY2LDY0LjQ3OCw0NjEuNTIyLDYwLDQ1Niw2MHogTTQ0NiwyMzMuMTkNCgkJYzAsMTE5LjQ0OC03Ni4yMjEsMjIyLjg5Mi0xOTAsMjU4LjM1M0MxNDMuMTI4LDQ1Ni4zNjUsNjYsMzUxLjk4NCw2NiwyMzMuMTlWNzkuMjg4Qzk2LjYxMSw3NC44OSwxMjAuODksNTAuNjExLDEyNS4yODgsMjANCgkJaDI2MS40MjRDMzkxLjExLDUwLjYxMSw0MTUuMzg5LDc0Ljg5LDQ0Niw3OS4yODhWMjMzLjE5eiIvPg0KPC9nPg0KPGc+DQoJPHBhdGggZD0iTTQyMC4wMTIsOTIuNDljLTIwLjg1NC05LjEzMy0zNy4zNjktMjUuNjQ4LTQ2LjUwMi00Ni41MDJDMzcxLjkxNiw0Mi4zNSwzNjguMzIyLDQwLDM2NC4zNSw0MGgtNjguMzcNCgkJYy01LjUyMiwwLTEwLDQuNDc4LTEwLDEwczQuNDc4LDEwLDEwLDEwaDYyLjA0N2MxMC41NjUsMjAuNjgxLDI3LjI5MiwzNy40MDcsNDcuOTczLDQ3Ljk3M1YyMzMuMTkNCgkJYzAsOTcuODYzLTU4Ljc0OSwxODIuMzAzLTE1MCwyMTYuMTA0Yy05MS4yNTEtMzMuODAyLTE1MC0xMTguMjQxLTE1MC0yMTYuMTA0VjEwNy45NzNDMTI2LjY4MSw5Ny40MDcsMTQzLjQwNyw4MC42ODEsMTUzLjk3Myw2MA0KCQloNjIuMDQ3YzUuNTIyLDAsMTAtNC40NzgsMTAtMTBzLTQuNDc4LTEwLTEwLTEwaC02OC4zN2MtMy45NzIsMC03LjU2NiwyLjM1MS05LjE2LDUuOTg4DQoJCWMtOS4xMzMsMjAuODU0LTI1LjY0OCwzNy4zNjktNDYuNTAyLDQ2LjUwMkM4OC4zNTEsOTQuMDg0LDg2LDk3LjY3OSw4NiwxMDEuNjV2MTMxLjU0YzAsMTA3LjIxMyw2Ni4zMTEsMjAwLjY1MywxNjYuNjY0LDIzNi4xNjYNCgkJYzEuMDc5LDAuMzgyLDIuMjA3LDAuNTczLDMuMzM2LDAuNTczYzEuMTI5LDAsMi4yNTctMC4xOTEsMy4zMzYtMC41NzNDMzU5LjYwNSw0MzMuODczLDQyNiwzNDAuNDksNDI2LDIzMy4xOVYxMDEuNjUNCgkJQzQyNiw5Ny42NzgsNDIzLjY0OSw5NC4wODQsNDIwLjAxMiw5Mi40OXoiLz4NCjwvZz4NCjxnPg0KCTxwYXRoIGQ9Ik0yNTYsMTQ2Yy00OS42MjYsMC05MCw0MC4zNzQtOTAsOTBjMCw0OS42MjYsNDAuMzc0LDkwLDkwLDkwYzQ5LjYyNiwwLDkwLTQwLjM3NCw5MC05MEMzNDYsMTg2LjM3NCwzMDUuNjI2LDE0NiwyNTYsMTQ2eg0KCQkgTTI1NiwzMDZjLTM4LjU5OCwwLTcwLTMxLjQwMi03MC03MGMwLTM4LjU5OCwzMS40MDItNzAsNzAtNzBjMzguNTk4LDAsNzAsMzEuNDAyLDcwLDcwQzMyNiwyNzQuNTk4LDI5NC41OTgsMzA2LDI1NiwzMDZ6Ii8+DQo8L2c+DQo8Zz4NCgk8cGF0aCBkPSJNMzAzLjA3MSwyMDguOTI4Yy0zLjkwNi0zLjkwNC0xMC4yMzYtMy45MDQtMTQuMTQzLDBMMjQ2LDI1MS44NTdsLTEyLjkyOS0xMi45MjhjLTMuOTA2LTMuOTA0LTEwLjIzNi0zLjkwNC0xNC4xNDMsMA0KCQljLTMuOTA1LDMuOTA1LTMuOTA1LDEwLjIzNywwLDE0LjE0M2wyMCwyMEMyNDAuODgyLDI3NS4wMjMsMjQzLjQ0LDI3NiwyNDYsMjc2czUuMTE4LTAuOTc3LDcuMDcxLTIuOTI5bDUwLTUwDQoJCUMzMDYuOTc2LDIxOS4xNjYsMzA2Ljk3NiwyMTIuODM0LDMwMy4wNzEsMjA4LjkyOHoiLz4NCjwvZz4NCg0KPC9zdmc+DQo=", + "name": { + "en": "Security", + "de": "Sicherheit", + "ru": "Безопасность", + "pt": "Segurança", + "nl": "Veiligheid", + "fr": "Sécurité", + "it": "Sicurezza", + "es": "Seguridad", + "pl": "Bezpieczeństwo" + }, + "members": [] + }, + "type": "enum" + } + // todo + } + }; + + var selectId = function () { + if (!that.$grid || !that.$grid.selectId) return; + selectId = that.$grid.selectId.bind(that.$grid); + return that.$grid.selectId.apply(that.$grid, arguments); + }; + + function enumRename(oldId, newId, newCommon, callback) { + if (tasks.length) { + var task = tasks.shift(); + if (task.name === 'delObject') { + that.main.socket.emit(task.name, task.id, function () { + setTimeout(function () { + enumRename(undefined, undefined, undefined, callback); + }, 0); + }); + } else { + that.main.socket.emit(task.name, task.id, task.obj, function () { + setTimeout(function () { + enumRename(undefined, undefined, undefined, callback); + }, 0); + }); + } + } else { + _enumRename(oldId, newId, newCommon, function () { + if (tasks.length) { + enumRename(undefined, undefined, undefined, callback); + } else { + if (callback) callback(); + } + }); + } + } + + function _enumRename(oldId, newId, newCommon, callback) { + //Check if this name exists + if (oldId !== newId && that.main.objects[newId]) { + showMessage(_('Name yet exists!'), true); + that.init(true); + if (callback) callback(); + } else { + if (oldId === newId) { + if (newCommon && (newCommon.name !== undefined || newCommon.icon !== undefined || newCommon.color !== undefined)) { + tasks.push({name: 'extendObject', id: oldId, obj: {common: newCommon}}); + } + if (callback) callback(); + } else if (that.main.objects[oldId] && that.main.objects[oldId].common && that.main.objects[oldId].common.nondeletable) { + showMessage(_('Change of enum\'s id "%s" is not allowed!', oldId), true); + that.init(true); + if (callback) callback(); + } else { + var len = oldId.length + 1; + var children = []; + for (var e = 0; e < that.list.length; e++) { + if (that.list[e].substring(0, len) === oldId + '.') { + children.push(that.list[e]); + } + } + + that.main.socket.emit('getObject', oldId, function (err, obj) { + setTimeout(function () { + if (obj) { + obj._id = newId; + if (obj._rev) delete obj._rev; + if (newCommon && newCommon.name !== undefined) obj.common.name = newCommon.name; + if (newCommon && newCommon.icon !== undefined) obj.common.icon = newCommon.icon; + if (newCommon && newCommon.color !== undefined) obj.common.color = newCommon.color; + tasks.push({name: 'delObject', id: oldId}); + tasks.push({name: 'setObject', id: newId, obj: obj}); + // Rename all children + var count = 0; + for (var i = 0; i < children.length; i++) { + var n = children[i].replace(oldId + '.', newId + '.'); + count++; + _enumRename(children[i], n, null, function () { + if (!--count && callback) callback(); + }); + } + if (!children.length && callback) { + callback(); + } + } + }, 0); + }); + } + } + } + + function enumAddChild(parent, newId, common, callback) { + if (that.main.objects[newId]) { + showMessage(_('Name yet exists!'), true); + return false; + } + + that.main.socket.emit('setObject', newId, { + _id: newId, + common: { + name: common.name, + members: [], + icon: common.icon, + color: common.color + }, + type: 'enum' + }, callback); + return true; + } + + function prepareNewEnum(parent) { + var text = ''; + var id; + if (parent) { + var name = parent.replace(/[.#\\\/&?]+/g, '-'); + + if (standardGroups[parent]) { + for (id in standardGroups[parent]) { + if (standardGroups[parent].hasOwnProperty(id) && that.list.indexOf(id) === -1) { + text += '
  • ' + that.main.getIconFromObj(standardGroups[parent][id]) + getName(standardGroups[parent][id]) + '
  • '; + } + } + } + if (text) { + text += '
  • '; + } + text += '
  • control_point' + _('custom group') + '
  • '; + + that.$gridEnum.find('#btn-new-group-' + name).html(text); + that.$gridEnum.find('.btn-new-group-btn[data-target="btn-new-group-' + name + '"]').dropdown({ + constrainWidth: false + }); + that.$gridEnum.find('#btn-new-group-' + name).find('.new-group-item').off('click').on('click', function () { + var id = $(this).data('id'); + var parent = $(this).data('enum'); + if (!id) { + createOrEditEnum(null, parent); + } else { + var name = parent.replace(/[.#\\\/&?]+/g, '-'); + that.main.saveConfig('enums-active', 'enum-' + name); + that.main.socket.emit('setObject', id, standardGroups[parent][id], function (err) { + if (err) { + that.main.showError(err); + } + }); + } + }); + } else { + for (id in standardEnums) { + if (standardEnums.hasOwnProperty(id) && that.list.indexOf(id) === -1) { + text += '
  • ' + that.main.getIconFromObj(standardEnums[id]) + getName(standardEnums[id]) + '
  • '; + } + } + + if (text) { + text += '
  • '; + } + text += '
  • control_point' + _('custom enum') + '
  • '; + that.$gridEnum.find('#btn-new-enum').html(text); + that.$gridEnum.find('.btn-new-enum-btn').dropdown({ + constrainWidth: false + }); + that.$gridEnum.find('.new-enum-item').off('click').on('click', function () { + var id = $(this).data('id'); + if (!id) { + createOrEditEnum(null); + } else { + var name = id.replace(/[.#\\\/&?]+/g, '-'); + that.main.saveConfig('enums-active', 'enum-' + name); + that.main.socket.emit('setObject', id, standardEnums[id], function (err) { + if (err) { + that.main.showError(err); + } + }); + } + }); + } + } + + this.prepare = function () { + this.isTiles = (this.main.config.enumIsTiles !== undefined && this.main.config.enumIsTiles !== null) ? this.main.config.enumIsTiles : true; + }; + + function getName(objects, id) { + var name; + if (!id) { + name = objects; + } else { + name = objects[id]; + } + if (name && name.common && name.common.name) { + name = translateName(name.common.name); + } else { + var parts = id.split('.'); + name = parts.pop(); + name = name[0].toUpperCase() + name.substring(1).toLowerCase(); + } + return name; + } + + function drawChip(id, group) { + var text = ''; + text += '
    ' + + that.main.getIcon(id) + + '' + + '' + getName(that.main.objects, id) + '' + +// '' + id + '' + + '' + + 'close' + + '
    '; + return text; + } + + function drawEnum(id, $page, scrollTop) { + var obj = that.main.objects[id]; + var name = id.replace(/[.#\\\/&?]+/g, '-'); + var text = + '
    ' + + '
    ' + + ' library_add' + + ' ' + + ' ' + + ' edit' + + ' ' + + ' ' + + ' delete' + + ' ' + + '
    \n' + + ' clear
    \n' + + '
    ' + + '
    '; + + text += '
      '; + + for (var se = 0; se < that.list.length; se++) { + if (that.list[se].substring(0, id.length + 1) === id + '.') { + var en = that.main.objects[that.list[se]]; + var inverted; + var style = ''; + if (en && en.common && en.common.color) { + style = 'background: ' + en.common.color + '; '; + if (that.main.invertColor(en.common.color)) { + inverted = true; + style += 'color: white;'; + } + } + + text += '
    • ' + + that.main.getIcon(that.list[se], null, null, 'icon') + + '' + getName(that.main.objects, that.list[se]) + '' + + '

      ' + that.list[se] + '


      '; + + if (en && en.common && en.common.members && en.common.members.length) { + for (var m = 0; m < en.common.members.length; m++) { + text += drawChip(en.common.members[m], that.list[se]); + } + } + text += 'edit'; + text += 'delete'; + text += '
    • '; + } + } + text += '
    '; + $page.html(text); + prepareNewEnum(id); + scrollTop && $page.find('.enum-collection').scrollTop(scrollTop); + initFilter(id); + } + function applyFilter(id, filter) { + var $tiles = that.$gridList.find('.enum-collection[data-id="' + id + '"] .collection-item'); + if (!filter) { + $tiles.show(); + } else { + filter = filter.toLowerCase(); + $tiles.each(function () { + var $this = $(this); + var eid = $this.data('id'); + var name = getName(that.main.objects, eid); + if (name.toLowerCase().indexOf(filter) !== -1) { + $this.show(); + } else { + if (eid.substring(id.length).toLowerCase().indexOf(filter) !== -1) { + $this.show(); + } else { + $this.hide(); + } + } + }); + } + } + + function initFilter(id) { + var $filter = that.$gridList.find('.enum-buttons[data-id="' + id + '"] .filter-input'); + var data = {}; + for (var se = 0; se < that.list.length; se++) { + var eid = that.list[se]; + if (eid.substring(0, id.length + 1) === id + '.') { + var name = getName(that.main.objects, eid); + var icon = null; + if (that.main.objects[eid] && + that.main.objects[eid].common && + that.main.objects[eid].common.icon) { + icon = that.main.objects[eid].common.icon; + } + + data[name] = icon; + } + } + var $btn = that.$gridList.find('.enum-buttons[data-id="' + id + '"] .filter-clear'); + $filter.mautocomplete({ + data: data, + minLength: 0, + limit: 10 + }).on('change', function () { + var val = $(this).val(); + applyFilter(id, val); + if ($(this).val()) { + $btn.show(); + } else { + $btn.hide(); + } + that.main.saveConfig('filter-' + id, val); + }).on('keyup', function () { + $(this).trigger('change'); + }); + $btn.off('click').on('click', function () { + $filter.val('').trigger('change'); + }); + if (that.main.config['filter-' + id]) { + $filter.val(that.main.config['filter-' + id]).trigger('change'); + } else { + $btn.hide(); + } + } + + function drawEnumsTiles() { + var $tableBody = that.$gridList.find('.tree-table-body'); + that.$gridList.removeClass('tree-table-list').addClass('tree-table-tiles'); + that.$gridList.find('.tree-table-buttons').remove(); + + // create buttons for panels + that.$gridList.prepend('
    \n' + + ' view_list\n' + + ' note_add\n' + + ' \n' + + ' \n' + + ' queue_play_next\n' + + ' \n' + + '
    '); + + + var text = '
    '; + text += '
      '; + var parts; + for (var e = 0; e < that.list.length; e++) { + parts = that.list[e].split('.'); + if (parts.length !== 2) continue; + var name = getName(that.main.objects, that.list[e]); + text += '
    • ' + that.main.getIcon(that.list[e]) + '' + name + '
    • '; + } + text += '
    '; + text += '
    '; + for (var se = 0; se < that.list.length; se++) { + parts = that.list[se].split('.'); + if (parts.length !== 2) continue; + + text += '
    '; + text += '
    '; + } + var scrollTop = {}; + $tableBody.find('.enum-collection').each(function () { + // remember actual offset + scrollTop[$(this).data('id')] = $(this).scrollTop(); + }); + + // destroy droppable + try { + var $items = that.$gridEnum.find('.tree-table-body .collection').find('.collection-item'); + try { + if ($items.droppable('instance')) { + $items.droppable('destroy'); + } + } catch (e) { + console.error(e); + } + } catch (e) { + + } + + $tableBody.html(text); + + if ($tableBody.find('.tabs li').length > 0) { + $tableBody.find('.tabs').mtabs({ + onShow: function (tab) { + that.main.saveConfig('enums-active', $(tab).attr('id')); + } + }); + if (that.main.config['enums-active'] && !that.main.noSelect) { + $tableBody.find('.tabs').mtabs('select', that.main.config['enums-active']); + } + } + + + $tableBody.find('.page').each(function () { + drawEnum($(this).data('id'), $(this), scrollTop[$(this).data('id')]); + }); + $tableBody.find('.btn-new-category').on('click', function () { + createOrEditEnum(null, $(this).data('id')); + }); + $tableBody.find('.btn-edit-category').on('click', function () { + createOrEditEnum($(this).data('id')); + }); + $tableBody.find('.btn-del-category').on('click', function () { + deleteEnum($(this).data('id')); + }); + $tableBody.find('.edit-content').on('click', function () { + createOrEditEnum($(this).data('id')); + }); + $tableBody.find('.delete-content').on('click', function () { + deleteEnum($(this).data('id')); + }); + $tableBody.find('.close').on('click', function () { + removeMember($(this).data('id'), $(this).data('enum')); + }); + + that.$gridList.find('.btn-edit').off('click').on('click', function () { + switchEditMode(!that.editMode); + }); + + that.$gridList.find('.btn-switch-tiles').off('click').on('click', function () { + that.isTiles = false; + that.main.saveConfig('enumIsTiles', that.isTiles); + + setTimeout(function () { + drawEnumsTable(); + }, 50); + }); + prepareNewEnum(); + + var $collection = that.$gridEnum.find('.tree-table-body .collection'); + setupDroppableTiles($collection); + } + + function drawEnumsTable() { + try { + var $items = that.$gridEnum.find('.tree-table-main').find('tbody>tr.treetable-enum'); + if ($items.droppable('instance')) { + $items.droppable('destroy'); + } + } catch (e) { + console.log(e); + } + // extract all enums + that.$gridList.html('').removeClass('tree-table-tiles').addClass('tree-table-list'); + + that.$gridList.treeTable({ + objects: that.main.objects, + root: 'enum', + columns: ['title', 'name'], + members: true, + colors: true, + icons: true, + widths: ['calc(100% - 250px)', '250px'], + //classes: ['', 'treetable-center'], + name: 'enums', + buttonsWidth: '40px', + buttons: [ + { + text: false, + icons: { + primary:'ui-icon-trash' + }, + click: function (id, children, parent) { + if (that.main.objects[id]) { + if (that.main.objects[id].type === 'enum') { + if (children) { + // ask if only object must be deleted or just this one + that.main.confirmMessage(_('All sub-enums of %s will be deleted too?', id), null, 'help', function (result) { + // If all + if (result) { + that.main._delObjects(id, true, function (err) { + if (!err) { + showMessage(_('Deleted')); + } else { + showMessage(_('Error: %s', err), true); + } + }); + } // else do nothing + }); + } else { + that.main.confirmMessage(_('Are you sure to delete %s?', id), null, 'help', function (result) { + // If all + if (result) that.main._delObjects(id, true, function (err) { + if (!err) { + showMessage(_('Deleted')); + } else { + showMessage(_('Error: %s', err), true); + } + }); + }); + } + } else { + removeMember(id, parent); + } + } else { + if (that.main.objects[parent] && that.main.objects[parent].type === 'enum') { + removeMember(id, parent); + } else { + showMessage(_('Object "%s" does not exists. Update the page.', id)); + } + } + }, + width: 26, + height: 20 + }, { + text: false, + icons: { + primary:'ui-icon-pencil' + }, + match: function (id) { + return that.main.objects[id] && that.main.objects[id].type === 'enum'; + }, + click: function (id, children, parent) { + createOrEditEnum(id); + }, + width: 26, + height: 20 + } + ], + panelButtons: [ + { + id: 'tab-enums-btn-switch-tiles', + title: _('change view mode'), + icon: 'view_module', + click: function () { + that.isTiles = true; + that.main.saveConfig('enumIsTiles', that.isTiles); + setTimeout(function () { + drawEnumsTiles(); + }, 50); + } + }, + { + id: 'tab-enums-list-new-enum', + title: _('New enum'), + icon: 'note_add', + click: function () { + createOrEditEnum(null); + } + }, + { + id: 'tab-enums-list-new-category', + title: _('New category'), + icon: 'library_add', + click: function () { + createOrEditEnum(null, that.enumEdit); + } + }, + { + id: 'tab-enums-list-edit', + title: _('Edit'), + icon: 'edit', + click: function () { + switchEditMode(!that.editMode); + } + } + ], + onChange: function (id, oldId) { + if (id !== oldId) { + that.enumEdit = id; + var obj = that.main.objects[id]; + if (obj && obj.type === 'enum') { + $('#tab-enums-list-new-enum').removeClass('disabled').attr('title', _('Create new enum, like %s', 'enum.newCategory')); + var parts = id.split('.'); + if (parts.length === 2) { + that.$gridList.find('#tab-enums-list-new-category').removeClass('disabled').attr('title', _('Create new category, like %s', id + '.newEnum')); + } else { + that.$gridList.find('#tab-enums-list-new-category').addClass('disabled'); + } + } else { + that.$gridList.find('#tab-enums-list-new-enum').addClass('disabled'); + that.$gridList.find('#tab-enums-list-new-category').addClass('disabled'); + } + } + }, + onReady: setupDroppableTable + });//.treeTable('show', currentEnum); + that.$gridList.find('.tree-table-buttons a').addClass('btn-small'); + that.$gridList.find('#tab-enums-list-new-enum').addClass('disabled'); + that.$gridList.find('#tab-enums-list-new-category').addClass('disabled'); + } + + function getEnumsChildren(id) { + var parts = id.split('.'); + var items = []; + var regex = new RegExp('^' + id.replace(/\./g, '\\.') + '\\.'); + for (var se = 0; se < that.list.length; se++) { + var _parts = that.list[se].split('.'); + if (_parts.length === parts.length + 1 && regex.test(that.list[se])) { + items.push(that.list[se]); + } + } + return items; + } + + function deleteEnum(id) { + if (that.main.objects[id].type === 'enum') { + var children = getEnumsChildren(id); + + if (children && children.length) { + // ask if only object must be deleted or just this one + that.main.confirmMessage(_('All sub-enums of %s will be deleted too?', id), null, 'help', function (result) { + // If all + if (result) { + that.main._delObjects(id, true, function (err) { + if (!err) { + showMessage(_('Deleted')); + } else { + showMessage(_('Error: %s', err), true); + } + }); + } // else do nothing + }); + } else { + that.main.confirmMessage(_('Are you sure to delete %s?', id), null, 'help', function (result) { + // If all + if (result) that.main._delObjects(id, true, function (err) { + if (!err) { + showMessage(_('Deleted')); + } else { + showMessage(_('Error: %s', err), true); + } + }); + }); + } + } + } + + function removeMember(id, parent) { + that.main.socket.emit('getObject', parent, function (err, obj) { + if (obj && obj.common && obj.common.members) { + var pos = obj.common.members.indexOf(id); + if (pos !== -1) { + obj.common.members.splice(pos, 1); + that.main.socket.emit('setObject', obj._id, obj, function (err) { + if (!err) { + showMessage(_('Removed')); + } else { + showMessage(_('Error: %s', err), true); + } + }); + } else { + showMessage(_('%s is not in the list')); + } + } + }); + } + + function addMember(id, parent) { + that.main.socket.emit('getObject', parent, function (err, obj) { + if (obj && obj.common) { + obj.common.members = obj.common.members || []; + var pos = obj.common.members.indexOf(id); + if (pos === -1) { + obj.common.members.push(id); + obj.common.members.sort(); + that.main.socket.emit('setObject', obj._id, obj, function (err) { + if (!err) { + showMessage(_('%s added to %s', id, obj._id)); + } else { + showMessage(_('Error: %s', err), true); + } + }); + } else { + showMessage(_('Is yet in the list')); + } + } + }); + } + + function showMessage(text, duration, isError) { + if (typeof duration === 'boolean') { + isError = duration; + duration = 3000; + } + that.main.showToast(that.$gridEnum.find('.tree-table-buttons'), text, null, duration, isError); + } + + function setupDraggable() { + var $trs = that.$gridEnum.find('.fancytree-container>tbody'); + try { + if ($trs.sortable('instance')) { + $trs.sortable('destroy'); + } + } catch (e) { + console.error(e); + } + + that.$gridEnum.find('.fancytree-container>tbody') + .sortable({ + connectWith: '#tab-enums .tab-enums-list .tree-table-main.treetable', + items: '.fancytree-type-draggable', + appendTo: that.$gridEnum, + refreshPositions: true, + helper: function (e, $target) { + return $('
    ' + $target.find('.fancytree-title').text() + '
    '); + }, + zIndex: 999990, + revert: false, + scroll: false, + start: function (e, ui) { + var $prev = ui.item.prev(); + // place this item back where it was + ui.item.data('prev', $prev); + that.$gridEnum.addClass('dragging'); + }, + stop: function (e, ui) { + that.$gridEnum.removeClass('dragging'); + }, + update: function (event, ui) { + // place this item back where it was + var $prev = ui.item.data('prev'); + if (!$prev || !$prev.length) { + $(this).prepend(ui.item); + } else { + $($prev).after(ui.item); + } + } + }) + .disableSelection(); + } + + this._initObjectTree = function () { + var settings = { + objects: main.objects, + noDialog: true, + draggable: ['device', 'channel', 'state'], + name: 'enum-objects', + expertModeRegEx: /^system\.|^yunkong2\.|^_|^[\w-]+$|^enum\.|^[\w-]+\.admin|^script\./, + texts: { + select: _('Select'), + cancel: _('Cancel'), + all: _('All'), + id: _('ID'), + ID: _('ID'), + name: _('Name'), + role: _('Role'), + room: _('Room'), + 'function': _('Function'), + value: _('Value'), + type: _('Type'), + selectid: _('Select ID'), + from: _('From'), + lc: _('Last changed'), + ts: _('Time stamp'), + wait: _('Processing...'), + ack: _('Acknowledged'), + edit: _('Edit'), + push: _('Trigger event'), + ok: _('Ok'), + with: _('With'), + without: _('Without'), + copyToClipboard: _('Copy to clipboard'), + expertMode: _('Toggle expert mode'), + refresh: _('Update'), + sort: _('Sort alphabetically'), + button: _('Settings'), + noData: _('No data') + }, + filter: { + type: 'state' + }, + columns: ['ID', 'name', 'type', 'role'] + }; + + selectId('init', settings) + .selectId('show'); + + setupDraggable(); + }; + + function setupDroppableTable($treetable) { + if (!that.editMode) return; + + if (!$treetable) { + $treetable = that.$gridEnum.find('.tree-table-main'); + } + + $treetable.find('tbody>tr.treetable-enum').droppable({ + accept: '.fancytree-type-draggable', + over: function (e, ui) { + $(this).addClass('tab-accept-item'); + if ($(this).hasClass('not-empty') && !$(this).hasClass('expanded')) { + var id = $(this).data('tt-id'); + var timer; + if ((timer = $(this).data('timer'))) { + clearTimeout(timer); + } + $(this).data('timer', setTimeout(function () { + that.$gridList.treeTable('expand', $(this).data('tt-id')); + }, 1000)); + } + }, + out: function (e, ui) { + $(this).removeClass('tab-accept-item'); + var timer; + if ((timer = $(this).data('timer'))) { + clearTimeout(timer); + $(this).data('timer', null); + } + }, + tolerance: 'pointer', + drop: function (e, ui) { + $(this).removeClass('tab-accept-item'); + var id = ui.draggable.data('id'); + var enumId = $(this).data('tt-id'); + + addMember(id, enumId); + } + }); + } + + function setupDroppableTiles($collection) { + if (!that.editMode) return; + + $collection = $collection || that.$gridEnum.find('.tree-table-body .collection'); + + var $items = $collection.find('.collection-item'); + try { + if ($items.droppable('instance')) { + $items.droppable('destroy'); + } + } catch (e) { + console.error(e); + } + + $items.droppable({ + accept: '.fancytree-type-draggable', + over: function (e, ui) { + $(this).addClass('tab-accept-item'); + }, + out: function (e, ui) { + $(this).removeClass('tab-accept-item'); + }, + tolerance: 'pointer', + drop: function (e, ui) { + $(this).removeClass('tab-accept-item'); + var id = ui.draggable.data('id'); + var enumId = $(this).data('id'); + addMember(id, enumId); + } + }); + } + + function createOrEditEnum(id, parentId) { + var idChanged = false; + var $dialog = that.$gridEnum.find('#tab-enums-dialog-new'); + var oldId = ''; + + var nameVal = ''; + var idVal = ''; + var originalIdVal = ''; + var iconVal = ''; + var colorVal = ''; + + var isIdEditable = true; + + installFileUpload($dialog, 50000, function (err, text) { + if (err) { + showMessage(err, true); + } else { + if (!text.match(/^data:image\//)) { + showMessage(_('Unsupported image format'), true); + return; + } + $dialog.find('.tab-enums-dialog-create').removeClass('disabled'); + iconVal = text; + + $dialog.find('.tab-enums-dialog-new-icon').show().html(''); + $dialog.find('.tab-enums-dialog-new-icon img').attr('src', text); + $dialog.find('.tab-enums-dialog-new-icon-clear').show(); + } + }); + + if (id) { + if (that.main.objects[id] && that.main.objects[id].common) { + nameVal = translateName(that.main.objects[id].common.name); + iconVal = that.main.objects[id].common.icon; + colorVal = that.main.objects[id].common.color; + } + oldId = id; + idVal = id; + $dialog.find('#tab-enums-dialog-preserve-id').prop('checked', true); + isIdEditable = false; + } else { + $dialog.find('#tab-enums-dialog-preserve-id').prop('checked', false); + isIdEditable = true; + } + + $dialog.find('.tab-enums-dialog-new-title').text(parentId ? _('Create new category') : (idVal ? _('Rename') : _('Create new enum'))); + + if (idVal) { + var parts = idVal.split('.'); + if (parts.length <= 2) { + id = true; + } + idVal = parts.pop(); + parentId = parts.join('.'); + originalIdVal = idVal; + } + + $dialog.find('#tab-enums-dialog-new-name') + .val(nameVal) + .off('change') + .on('change', function () { + var $id = $dialog.find('#tab-enums-dialog-new-id'); + var id = $id.val(); + var val = $(this).val(); + val = val.replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_').trim().toLowerCase(); + if (isIdEditable && (!id || !idChanged)) { + $id.val(val); + $dialog.find('#tab-enums-dialog-new-preview').val((parentId || 'enum') + '.' + (val || '#')); + // detect materialize + M.updateTextFields('#tab-enums-dialog-new'); + } + if ($id.val() && !$id.val().match(/[.\s]/)) { + $dialog.find('.tab-enums-dialog-create').removeClass('disabled'); + $id.removeClass('wrong'); + } else { + $dialog.find('.tab-enums-dialog-create').addClass('disabled'); + $id.addClass('wrong'); + } + }).off('keyup').on('keyup', function () { + $(this).trigger('change'); + }); + + $dialog.find('#tab-enums-dialog-new-id') + .val(idVal) + .off('change') + .on('change', function () { + if ($dialog.find('#tab-enums-dialog-preserve-id').prop('checked')) return; + idChanged = true; + var val = $(this).val(); + $dialog.find('#tab-enums-dialog-new-preview').val((parentId || 'enum') + '.' + ($(this).val() || '#')); + M.updateTextFields('#tab-enums-dialog-new'); + + if (val && !val.match(/[.\s]/)) { + $dialog.find('.tab-enums-dialog-create').removeClass('disabled'); + $(this).removeClass('wrong'); + } else { + $dialog.find('.tab-enums-dialog-create').addClass('disabled'); + $(this).addClass('wrong'); + } + }).off('keyup').on('keyup', function () { + $(this).trigger('change'); + }); + + $dialog.find('#tab-enums-dialog-new-id').prop('disabled', !isIdEditable); + + $dialog.find('.tab-enums-dialog-create') + .addClass('disabled') + .off('click') + .text(oldId ? _('Change') : _('Create')) + .on('click', function () { + if (oldId) { + var name; + if ($dialog.find('#tab-enums-dialog-preserve-id').prop('checked')) { + if (typeof that.main.objects[oldId].common.name === 'object') { + name = that.main.objects[oldId].common.name; + } else { + name = {'en': oldId.split('.').pop()}; + } + name[systemLang] = $dialog.find('#tab-enums-dialog-new-name').val(); + } else { + name = $dialog.find('#tab-enums-dialog-new-name').val(); + } + enumRename( + oldId, + parentId + '.' + $dialog.find('#tab-enums-dialog-new-id').val(), + { + name: name, + icon: iconVal, + color: colorVal + }, + function (err) { + if (err) { + showMessage(_('Error: %s', err), true); + } else { + showMessage(_('Updated')); + } + } + ); + } else { + enumAddChild( + parentId, + (parentId || 'enum') + '.' + $dialog.find('#tab-enums-dialog-new-id').val(), + { + name: $dialog.find('#tab-enums-dialog-new-name').val(), + icon: iconVal, + color: colorVal + }, + function (err) { + if (err) { + showMessage(_('Error: %s', err), true, 5000); + } else { + showMessage(_('Updated')); + } + }); + } + }); + + $dialog.find('#tab-enums-dialog-new-preview').val((parentId || 'enum') + '.' + (idVal || '#')); + + if (iconVal) { + $dialog.find('.tab-enums-dialog-new-icon').show().html(that.main.getIcon(oldId)); + $dialog.find('.tab-enums-dialog-new-icon-clear').show(); + } else { + $dialog.find('.tab-enums-dialog-new-icon').hide(); + $dialog.find('.tab-enums-dialog-new-icon-clear').hide(); + } + colorVal = colorVal || false; + if (colorVal) { + $dialog.find('.tab-enums-dialog-new-color').val(colorVal); + } else { + $dialog.find('.tab-enums-dialog-new-color').val(); + } + + M.updateTextFields('#tab-enums-dialog-new'); + that.main.showToast($dialog, _('Drop the icons here')); + + $dialog.find('.tab-enums-dialog-new-upload').off('click').on('click', function () { + $dialog.find('.drop-file').trigger('click'); + }); + $dialog.find('.tab-enums-dialog-new-icon-clear').off('click').on('click', function () { + if (iconVal) { + iconVal = ''; + $dialog.find('.tab-enums-dialog-new-icon').hide(); + $dialog.find('.tab-enums-dialog-create').removeClass('disabled'); + $dialog.find('.tab-enums-dialog-new-icon-clear').hide(); + } + }); + $dialog.find('.tab-enums-dialog-new-color-clear').off('click').on('click', function () { + if (colorVal) { + $dialog.find('.tab-enums-dialog-create').removeClass('disabled'); + $dialog.find('.tab-enums-dialog-new-color-clear').hide(); + $dialog.find('.tab-enums-dialog-new-colorpicker').colorpicker({ + component: '.btn', + color: colorVal, + container: $dialog.find('.tab-enums-dialog-new-colorpicker') + }).colorpicker('setValue', ''); + colorVal = ''; + } + }); + var time = Date.now(); + try { + $dialog.find('.tab-enums-dialog-new-colorpicker').colorpicker('destroy'); + } catch (e) { + + } + $dialog.find('.tab-enums-dialog-new-colorpicker').colorpicker({ + component: '.btn', + color: colorVal, + container: $dialog.find('.tab-enums-dialog-new-colorpicker') + }).colorpicker('setValue', colorVal).on('showPicker.colorpicker', function (/* event */) { + //$dialog.find('.tab-enums-dialog-new-colorpicker')[0].scrollIntoView(false); + var $modal = $dialog.find('.modal-content'); + $modal[0].scrollTop = $modal[0].scrollHeight; + }).on('changeColor.colorpicker', function (event){ + if (Date.now() - time > 100) { + colorVal = event.color.toHex(); + $dialog.find('.tab-enums-dialog-create').removeClass('disabled'); + $dialog.find('.tab-enums-dialog-new-icon-clear').show(); + } + }); + if (colorVal) { + $dialog.find('.tab-enums-dialog-new-color-clear').show(); + } else { + $dialog.find('.tab-enums-dialog-new-color-clear').hide(); + } + + $dialog.find('#tab-enums-dialog-preserve-id').off('change').on('change', function () { + if ($(this).prop('checked')) { + $dialog.find('#tab-enums-dialog-new-id').prop('disabled', true) + .val(originalIdVal); + idVal = originalIdVal; + isIdEditable = false; + } else { + if (that.main.objects[id] && that.main.objects[id].common) { + isIdEditable = !that.main.objects[id].common['object-non-deletable'] && !that.main.objects[id].common.dontDelete; + } + if (isIdEditable) { + idVal = $dialog.find('#tab-enums-dialog-new-name').val(); + idVal = idVal.replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_').trim().toLowerCase(); + $dialog.find('#tab-enums-dialog-new-id').prop('disabled', false) + .val(idVal); + } + } + idChanged = false; + $dialog.find('#tab-enums-dialog-new-preview').val((parentId || 'enum') + '.' + (idVal || '#')); + }); + + // workaround for materialize checkbox problem + $dialog.find('input[type="checkbox"]+span').off('click').on('click', function () { + var $input = $(this).prev(); + if (!$input.prop('disabled')) { + $input.prop('checked', !$input.prop('checked')).trigger('change'); + } + }); + + $dialog.modal().modal('open'); + } + + function switchEditMode(isEnabled) { + that.editMode = isEnabled; + var $editButton = that.$gridEnum.find('#tab-enums-list-edit'); + + if (that.editMode) { + $editButton.removeClass('blue').addClass('red'); + that.$gridEnum.addClass('tab-enums-edit'); + that._initObjectTree(); + showMessage(_('You can drag&drop the devices, channels and states to enums')); + if (that.isTiles) { + setupDroppableTiles(); + } else { + setupDroppableTable(); + } + } else { + selectId('destroy'); + try { + var _$items = that.$gridEnum.find('.collection-item'); + if (_$items.droppable('instance')) { + _$items.droppable('destroy'); + } + } catch (e) { + console.error(e); + } + try { + var $trs = that.$gridEnum.find('tbody>tr.treetable-enum'); + if ($trs.droppable('instance')) { + $trs.droppable('destroy'); + } + } catch (e) { + console.error(e); + } + + $editButton.removeClass('red').addClass('blue'); + that.$gridEnum.removeClass('tab-enums-edit'); + } + } + + this._postInit = function () { + if (typeof this.$gridList !== 'undefined') { + if (!this.main.objects['enum.rooms']) { + this.main.objects['enum.rooms'] = { + "_id": "enum.rooms", + "common": { + "icon": "home", + "name": { + "en": "Rooms", + "de": "Räume", + "ru": "Комнаты", + "pt": "Quartos", + "nl": "Kamers", + "fr": "Pièces", + "it": "Camere", + "es": "Habitaciones", + "pl": "Pokoje" + }, + "desc": { + "en": "List of the rooms", + "de": "Liste der Räumen", + "ru": "Список комнат", + "pt": "Lista dos quartos", + "nl": "Lijst met kamers", + "fr": "Liste des chambres", + "it": "Elenco delle stanze", + "es": "Lista de las habitaciones", + "pl": "Lista pokoi" + }, + "members": [], + "dontDelete": true + }, + "type": "enum", + "acl": { + "owner": "system.user.admin", + "ownerGroup": "system.group.administrator", + "permissions": 1911 + } + }; + that.main.socket.emit('setObject', 'enum.rooms', this.main.objects['enum.rooms']); + this.list.unshift('enum.rooms'); + } + if (!this.main.objects['enum.functions']) { + this.main.objects['enum.functions'] = { + "_id": "enum.functions", + "common": { + "icon": "lightbulb_outline", + "name": { + "en": "Functions", + "de": "Funktionen", + "ru": "функции", + "pt": "Funções", + "nl": "functies", + "fr": "Les fonctions", + "it": "funzioni", + "es": "Funciones", + "pl": "Funkcje" + }, + "desc": { + "en": "List of the functions", + "de": "Liste der Funktionen", + "ru": "Список функций", + "pt": "Lista das funções", + "nl": "Lijst met functies", + "fr": "Liste des fonctions", + "it": "Elenco delle funzioni", + "es": "Lista de las funciones", + "pl": "Lista funkcji" + }, + "members": [], + "dontDelete": true + }, + "type": "enum", + "acl": { + "owner": "system.user.admin", + "ownerGroup": "system.group.administrator", + "permissions": 1911 + } + }; + this.list.unshift('enum.functions'); + that.main.socket.emit('setObject', 'enum.functions', this.main.objects['enum.functions']); + } + + if (this.isTiles) { + drawEnumsTiles(); + } else { + drawEnumsTable(); + } + if (this.editMode) { + this._initObjectTree(); + } else { + selectId('destroy'); + } + } + }; + + this.init = function (update) { + if (this.inited && !update) { + return; + } + if (!this.main || !this.main.objectsLoaded) { + setTimeout(function () { + that.init(update); + }, 250); + return; + } + + this._postInit(); + + if (!this.inited) { + this.inited = true; + this.main.subscribeObjects('enum.*'); + } + }; + + this.destroy = function () { + if (this.inited) { + this.inited = false; + // subscribe objects and states + this.main.unsubscribeObjects('enum.*'); + } + switchEditMode(false); + this.$gridList.treeTable('destroy'); + }; + + this.objectChange = function (id, obj, action) { + //Update enums + if (id.match(/^enum\./)) { + if (obj) { + if (this.list.indexOf(id) === -1) this.list.push(id); + } else { + var j = this.list.indexOf(id); + if (j !== -1) this.list.splice(j, 1); + } + + if (this.updateTimers) clearTimeout(this.updateTimers); + + this.updateTimers = setTimeout(function () { + that.updateTimers = null; + that._postInit(); + }, 200); + } + + if (this.$grid) selectId('object', id, obj, action); + }; +} diff --git a/src/js/adminEvents.js b/src/js/adminEvents.js new file mode 100644 index 0000000..8ecb4ce --- /dev/null +++ b/src/js/adminEvents.js @@ -0,0 +1,347 @@ +function Events(main) { + 'use strict'; + + var that = this; + this.main = main; + this.$tab = $('#tab-events'); // body + var isRemote = location.hostname === 'yunkong2.net' || location.hostname === 'yunkong2.pro'; + + var list = { + count: 0, + start: 0, + limit: 500 //const + }; + var timeout = null; + + var pause = { + list: [], + mode: false, + counter: 0, + overflow: false, + $counterSpan: null + }; + + var $header; + var hdr; + var $table; + var $outer; + var $pause; + + var columnResizeInit = { + done: false, + timer: null + }; + + this.prepare = function () { + $outer = this.$tab.find('#event-outer'); + $table = this.$tab.find('#event-table'); + $pause = this.$tab.find('#event-pause'); + + // install header + $header = this.$tab.find('#events-table-tr'); + hdr = new IobListHeader($header, {list: $outer, colWidthOffset: 1, prefix: 'event-filter'}); + hdr.doFilter = filterEvents; + + hdr.add('combobox', 'type'); + hdr.add('edit', 'id', 'ID'); + //hdr.add('edit', 'val', 'Value'); + hdr.add('edit', 'val', 'value'); + hdr.add('combobox', 'ack', 'ack', [ + {val: '', name: 'all'}, + {val: 'true', name: 'ack'}, + {val: 'false', name: 'not ack'} + ]); + hdr.add('combobox', 'from', 'from'); + hdr.add('text', 'ts'); + hdr.add('text', 'lc'); + + Object.defineProperty(hdr, 'getValues', { + value: function () { + hdr.ID.selectedVal = hdr.ID.selectedVal.toLocaleLowerCase(); + if (hdr.ack.selectedVal === 'true') hdr.ack.selectedVal = true; + if (hdr.ack.selectedVal === 'false') hdr.ack.selectedVal = false; + }, + enumerateble: false + }); + + $pause.on('click', function () { + that.pause(); + }); + + // bind "clear events" button + var $eventClear = this.$tab.find('#event-clear'); + $eventClear + .off('click').on('click', function () { + list.count = 0; + list.start = 0; + that.$tab.find('#event-table').html(''); + }); + }; + + this.init = function () { + if (isRemote) { + that.$tab.find('#grid-events').html(_('You can\'t see events via cloud') + '
    cloud_off').addClass('no-cloud-events'); + return; + } + if (!hdr) return; + + if (this.inited) { + return; + } + + installColResize(); + + this.inited = true; + this.main.subscribeObjects('*'); + this.main.subscribeStates('*'); + }; + + this.destroy = function () { + if (this.inited) { + this.inited = false; + this.main.unsubscribeObjects('*'); + this.main.unsubscribeStates('*'); + } + }; + + var widthSet = false; + + function installColResize() { + if (!$.fn.colResizable) return; + if ($outer.is(':visible')) { + $outer.colResizable({ + liveDrag: true, + + partialRefresh: true, + marginLeft: 5, + postbackSafe:true, + + onResize: function (event) { + return hdr.syncHeader(); + // // read width of data.$tree and set the same width for header + // var thDest = $('#log-outer-header >thead>tr>th'); //if table headers are specified in its semantically correct tag, are obtained + // var thSrc = $outer.find('>tbody>tr:first>td'); + // for (var i = 1; i < thSrc.length; i++) { + // $(thDest[i]).attr('width', $(thSrc[i]).width()); + // } + } + }); + hdr.syncHeader(); + } else { + setTimeout(function () { + installColResize(); + }, 200) + } + } + + function updateResizersHeight() { + columnResizeInit.timer = null; + $(window).trigger('resize.JColResizer'); + } + + // ----------------------------- Show events ------------------------------------------------ + this.add = function (id, stateOrObj, isMessage, isState) { + if (isRemote) return; + + var type = isState ? 'stateChange' : (isMessage ? 'message' : 'objChange'); + var value; + var ack; + var from = ''; + var ts; + var lc; + if (hdr) { + if (hdr.getValues) { + hdr.getValues(); + } + + hdr.type.checkAddOption(type); + } + if (!columnResizeInit.done) { + // if the height not 100%, the column resizer is too short. Wait till the table will be really full and reinit resizer. + // update resizer once and remeber it if the table has full height + if (!columnResizeInit.timer) { + columnResizeInit.timer = setTimeout(updateResizersHeight, 1000); + } + + if (list.count > 20) { + columnResizeInit.done = true; + } + } + + if (!pause.mode) { + if (list.count >= that.limit) { + list.start++; + var e = document.getElementById('event_' + list.start); + if (e) e.outerHTML = ''; + } else { + list.count++; + } + } + + // if Object + if (!isMessage && !isState) { + if (!stateOrObj) { + value = 'deleted'; + ts = main.formatDate(new Date()); + } else { + value = JSON.stringify(stateOrObj, '\x0A', 2); + if (value !== undefined && value.length > 30) { + value = '' + value.substring(0, 30) + '...'; + } + ts = main.formatDate(stateOrObj.ts); + } + } else + // if state + if (isState) { + if (!stateOrObj) { + value = 'deleted'; + ts = main.formatDate(new Date()); + } else { + stateOrObj.from = stateOrObj.from || ''; + stateOrObj.from = stateOrObj.from.replace('system.adapter.', ''); + stateOrObj.from = stateOrObj.from.replace('system.', ''); + + hdr && hdr.from.checkAddOption(stateOrObj.from, function (o) { + return {val: o.replace(/\./g, '-'), name: o}; + }); + + from = stateOrObj.from; + + value = JSON.stringify(stateOrObj.val); + if (value !== undefined && value.length > 30) { + value = '
    ' + value.substring(0, 30) + '...
    '; + } + ack = stateOrObj.ack ? 'true' : 'false'; + ts = main.formatDate(stateOrObj.ts); + lc = main.formatDate(stateOrObj.lc); + } + } else + // if message + if (isMessage) { + // todo + } + + var visible = true; + if (hdr) { + if (hdr.type.selectedVal && hdr.type.selectedVal !== type) { + visible = false; + } else if (hdr.ID.selectedVal && id.toLocaleLowerCase().indexOf(hdr.ID.selectedVal) === -1) { + visible = false; + } else if (hdr.value.selectedVal !== '' && value !== null && value !== undefined && value.indexOf(hdr.value.selectedVal) === -1) { + visible = false; + } else if (hdr.ack.selectedVal !== '' && hdr.ack.selectedVal !== ack) { + visible = false; + } else if (hdr.from.selectedVal && hdr.from.selectedVal !== from) { + visible = false; + } + } + + + var text = ''; + text += '' + type + ''; + text += '' + id + ''; + if (isNaN(value)) { + text += '' + (value || '') + ''; + } else { + text += '' + (value || '') + ''; + } + text += '' + (ack || '') + ''; + text += '' + (from || '') + ''; + text += '' + (ts || '') + ''; + text += '' + (lc || '') + ''; + text += ''; + + if (pause.mode) { + pause.list.push(text); + pause.counter++; + + if (pause.counter > list.limit) { + if (!pause.overflow) { + $pause.addClass('red lighten3') + .attr('title', _('Message buffer overflow. Losing oldest')); + pause.overflow = true; + } + pause.list.shift(); + } + pause.$counterSpan.html(pause.counter); + } else if ($table) { + $table.prepend(text); + if (!widthSet && (window.location.hash === '#tab-events' || window.location.hash === '#events')) { + hdr && hdr.syncHeader(); + widthSet = true; + } + } + }; + + /*this.onSelected = function () { + hdr && hdr.syncHeader(); + };*/ + + function filterEvents() { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + if (hdr.getValues) { + hdr.getValues(); + } + + $table && $table.find('.event-line').each(function (index) { + var isShow = true; + var $this = $(this); + if (hdr.type.selectedVal && !$this.hasClass('event-type-' + hdr.type.selectedVal)) { + isShow = false; + } else + if (hdr.from.selectedVal && !$this.hasClass('event-from-' + hdr.from.selectedVal)) { + isShow = false; + } else + if (hdr.ack.selectedVal !== '' && !$this.hasClass('event-ack-' + hdr.ack.selectedVal)) { + isShow = false; + } else + if (hdr.ID.selectedVal && $(this).find('td.event-column-id').text().toLocaleLowerCase().indexOf(hdr.ID.selectedVal) === -1) { + isShow = false; + } else + if (hdr.value.selectedVal !== '' && $(this).find('td.event-column-value').text().indexOf(hdr.value.selectedVal) === -1) { + isShow = false; + } + + if (isShow) { + $this.show(); + } else { + $this.hide(); + } + }); + } + + this.pause = function () { + if (!pause.mode) { + $pause.addClass('yellow btn-pause-button-active'); + + pause.$counterSpan = $pause; + pause.$counterSpan.html('0'); + pause.counter = 0; + pause.mode = true; + } else { + pause.mode = false; + for (var i = 0; i < pause.list.length; i++) { + if (list.count >= 500) { + list.start++; + var e = document.getElementById('event_' + list.start); + if (e) e.outerHTML = ''; + } else { + list.count++; + } + $table.prepend(pause.list[i]); + } + pause.overflow = false; + pause.list = []; + pause.counter = 0; + + $pause + .removeClass('yellow btn-pause-button-active') + .html('pause'); + } + }; +} + + diff --git a/src/js/adminFileUtils.js b/src/js/adminFileUtils.js new file mode 100644 index 0000000..0b93d6f --- /dev/null +++ b/src/js/adminFileUtils.js @@ -0,0 +1,49 @@ +function fileHandler(event) { + event.preventDefault(); + var file = event.dataTransfer ? event.dataTransfer.files[0] : event.target.files[0]; + + var $dz = $(this).find('.drop-zone'); + var callback = $(this).data('drop-zone-cb'); + var limit = $(this).data('limit'); + if (file.size > (limit || 10000)) { + callback && callback(_('File is too big!')); + $dz.hide(); + return false; + } + $dz.show(); + var reader = new FileReader(); + reader.onload = function (evt) { + $dz.hide(); + callback && callback(null, evt.target.result); + }; + reader.readAsDataURL(file); +} + +/** + * Install file upload on some div + * @param {object} $dropZone is jquery object of the div (DOM element) where the drop zone must be installed + * @param {number} limit is maximal size of file in bytes + * @param {function} callback is callback in form function (err, fileDataBase64) {} +*/ +function installFileUpload($dropZone, limit, callback) { + if (typeof window.FileReader !== 'undefined' && !$dropZone.data('installed')) { + $dropZone.data('installed', true); + $dropZone.prepend(''); + var $dz = $dropZone.find('.drop-zone'); + $dropZone[0].ondragover = function() { + $dz.off('click'); + $dz.show(); + return false; + }; + + $dz[0].ondragleave = function() { + $dz.hide(); + return false; + }; + + $dz[0].ondrop = fileHandler.bind($dropZone[0]); + } + $dropZone.data('drop-zone-cb', callback); + $dropZone.data('limit', limit); + $dropZone.find('.drop-file').on('change', fileHandler.bind($dropZone[0])); +} \ No newline at end of file diff --git a/src/js/adminHosts.js b/src/js/adminHosts.js new file mode 100644 index 0000000..d3aca93 --- /dev/null +++ b/src/js/adminHosts.js @@ -0,0 +1,663 @@ +function Hosts(main) { + 'use strict'; + + var that = this; + this.main = main; + this.list = []; + this.$tab = $('#tab-hosts'); + this.$grid = this.$tab.find('#hosts'); + this.$table = this.$tab.find('#grid-hosts'); + this.inited = false; + this.isTiles = true; + this.words = {}; + + this.prepare = function () { + this.isTiles = (this.main.config.hostsIsTiles !== undefined && this.main.config.hostsIsTiles !== null) ? this.main.config.hostsIsTiles : true; + + // fix for IE + if (this.main.browser === 'ie' && this.main.browserVersion <= 10) { + this.isTiles = false; + this.$tab.find('.btn-switch-tiles').hide(); + } + + this.$tab.find('.btn-reload') + .attr('title', _('Update')) + .on('click', function () { + that.init(true); + }); + + this.$tab.find('.btn-switch-tiles').off('click').on('click', function () { + that.isTiles = !that.isTiles; + + if (that.isTiles) { + $(this).find('i').text('view_list'); + } else { + $(this).find('i').text('view_module'); + } + + that.main.saveConfig('hostsIsTiles', that.isTiles); + + setTimeout(function () { + that._postInit(); + }, 50); + }); + + if (this.isTiles) { + this.$tab.find('.btn-switch-tiles').find('i').text('view_list'); + } else { + this.$tab.find('.btn-switch-tiles').find('i').text('view_module'); + } + + this.$tab.find('.filter-clear').on('click', function () { + that.$tab.find('.filter-input').val('').trigger('change'); + }); + + var $hostsFilter = this.$tab.find('.filter-input'); + $hostsFilter.on('change', function () { + var filter = $(this).val(); + if (filter) { + $(this).addClass('input-not-empty'); + that.$tab.find('.filter-clear').show(); + } else { + that.$tab.find('.filter-clear').hide(); + $(this).removeClass('input-not-empty'); + } + + that.main.saveConfig('hostsFilter', filter); + applyFilter(filter); + }).on('keyup', function () { + if (that.filterTimeout) clearTimeout(that.filterTimeout); + that.filterTimeout = setTimeout(function () { + that.$tab.find('.filter-input').trigger('change'); + }, 300); + }); + + if (this.main.config.hostsFilter && this.main.config.hostsFilter[0] !== '{') { + $hostsFilter.val(that.main.config.hostsFilter).addClass('input-not-empty'); + this.$tab.find('.filter-clear').show(); + } else { + this.$tab.find('.filter-clear').hide(); + } + + // cache translations + this.words['Title'] = _('Title'); + this.words['OS'] = _('OS'); + this.words['Available'] = _('Available'); + this.words['Installed'] = _('Installed'); + this.words['Events'] = _('Events'); + this.words['Title'] = _('Title'); + that.words['Type'] = _('Type'); + }; + + // ----------------------------- Hosts show and Edit ------------------------------------------------ + this.initButtons = function (id) { + var selector = id ? '[data-host-id="' + id + '"]' : ''; + + this.$tab.find('.host-update-submit' + selector).off('click').on('click', function () { + that.main.cmdExec($(this).attr('data-host-name'), 'upgrade self', function (exitCode) { + if (!exitCode) that.init(true); + }); + }); + + this.$tab.find('.host-restart-submit' + selector).off('click').on('click', function () { + that.main.waitForRestart = true; + that.main.cmdExec($(this).attr('data-host-name'), '_restart'); + }); + this.$tab.find('.host-delete' + selector).off('click').on('click', function () { + that.main.cmdExec(that.main.currentHost, 'host remove ' + $(this).attr('data-host-name')); + }); + + this.$tab.find('.host-edit' + selector).off('click').on('click', function () { + editHost($(this).attr('data-host-id')); + }); + + this.$tab.find('.host-update-hint-submit' + selector).off('click').on('click', function () { + var infoTimeout = setTimeout(function () { + showUpdateInfo(); + infoTimeout = null; + }, 1000); + + that.main.socket.emit('sendToHost', $(this).attr('data-host-name'), 'getLocationOnDisk', null, function (data) { + if (infoTimeout) clearTimeout(infoTimeout); + infoTimeout = null; + showUpdateInfo(data); + }); + }); + + }; + + function showUpdateInfo(data) { + var $dialog = $('#dialog-host-update'); + if (data) { + var path = data.path; + path = path.replace(/\\/g, '/'); + var parts = path.split('/'); + parts.pop(); // js-controller + parts.pop(); // node_modules + + if (data.platform === 'linux' || data.platform === 'darwin' || data.platform === 'freebsd' || data.platform === 'lin') { + // linux + $dialog.find('#dialog-host-update-instructions').val('cd ' + parts.join('/') + '\nsudo yunkong2 stop\nsudo yunkong2 update\nsudo yunkong2 upgrade self\nsudo yunkong2 start') + } else { + // windows + $dialog.find('#dialog-host-update-instructions').val('cd ' + parts.join('\\') + '\nyunkong2 stop\nyunkong2 update\nyunkong2 upgrade self\nyunkong2 start') + } + } else { + $dialog.find('#dialog-host-update-instructions').val('cd /opt/yunkong2\nsudo yunkong2 stop\nsudo yunkong2 update\nsudo yunkong2 upgrade self\nsudo yunkong2 start') + } + + if (!$dialog.data('inited')) { + $dialog.data('inited', true); + $dialog.modal(); + } + $dialog.modal('open'); + } + + function applyFilter(filter) { + filter = (filter || '').toLowerCase().trim(); + + if (!filter) { + that.$tab.find('.hosts-host').show(); + that.$tab.find('.hosts-host-filtered-out').hide(); + } else { + var someVisible = false; + that.$tab.find('.hosts-host').each(function () { + var text = $(this).data('host-filter'); + if (text.toLowerCase().indexOf(filter) !== -1) { + $(this).show(); + someVisible = true; + } else { + $(this).hide(); + } + }); + if (!someVisible) { + that.$tab.find('.hosts-host-filtered-out').show(); + } else { + that.$tab.find('.hosts-host-filtered-out').hide(); + } + } + } + + function showOneHostRow(index) { + var obj = that.main.objects[that.list[index].id]; + var alive = that.main.states[obj._id + '.alive'] && that.main.states[obj._id + '.alive'].val && that.main.states[obj._id + '.alive'].val !== 'null'; + obj.common = obj.common || {}; + obj.native = obj.native || {}; + + var text = ''; + //LED + text += '
    '; + // icon + text += '' + that.main.getHostIcon(obj) + ''; + // name + text += '' + obj.common.hostname + ''; + // type + text += '' + obj.common.type + ''; + var title = obj.common.titleLang || obj.common.title; + if (typeof title === 'object') { + title = title[systemLang] || title.en; + } + // description + text += '' + title + ''; + // platform + // text += '' + obj.common.platform + ''; // actually only one platform + // OS + text += '' + (obj.native.os ? obj.native.os.platform : _('unknown')) + ''; + // Available + text += '' + + '' + + '' + + ''; + + // installed + text += '' + obj.common.installedVersion + ''; + + // event rates + if (that.main.states[obj._id + '.inputCount']) { + text += '⇥' + that.main.states[obj._id + '.inputCount'].val + ' / ↦' + that.main.states[obj._id + '.outputCount'].val + ''; + } else { + text += ' / '; + } + + // restart button + text += ''; + + text += ''; + + return text; + } + + function showOneHostTile(index) { + var obj = that.main.objects[that.list[index].id]; + var alive = that.main.states[obj._id + '.alive'] && that.main.states[obj._id + '.alive'].val && that.main.states[obj._id + '.alive'].val !== 'null'; + obj.common = obj.common || {}; + obj.native = obj.native || {}; + + var color; + if (obj.common.color) { + color = that.main.invertColor(obj.common.color); + } + + var text = '
    '+ + '
    ' + + '
    '+ + ' ' + that.main.getHostIcon(obj, ' ') + + '
    ' + + '
    '+ + '
    ' + + ' ' + obj.common.hostname + '' + + '
      '+ + '
    • ' + that.words['Type'] + ': ' + obj.common.type + '
    • ' + + '
    • ' + that.words['Title'] + ': ' + obj.common.title + '
    • ' + + '
    • ' + that.words['OS'] + ': ' + (obj.native.os ? obj.native.os.platform : _('unknown')) + '
    • ' + + '
    • ' + that.words['Available'] + ':
    • ' + + '
    • ' + that.words['Installed'] + ': ' + obj.common.installedVersion + '
    • '; + + if (that.main.states[obj._id + '.inputCount']) { + text += '
    • ' + that.words['Events'] + ': ⇥' + that.main.states[obj._id + '.inputCount'].val + ' / ↦' + that.main.states[obj._id + '.outputCount'].val + '
    • '; + } else { + text += '
    • ' + that.words['Events'] + ': /
    • '; + } + + text += '
    '+ + '
    '+ + '
    '+ + ' edit' + + ' autorenew'; + if (obj.common.hostname !== that.main.currentHost) { + text += ' delete'; + } + text += ' ' + + ' ' + + '
    '+ + '
    '+ + '
    '; + + return text; + } + + function editHost(id) { + var $dialog = $('#tab-host-dialog-edit'); + + var titleVal = ''; + var iconVal = ''; + var colorVal = ''; + + installFileUpload($dialog, 50000, function (err, text) { + if (err) { + that.main.showToast($dialog, err); + } else { + if (!text.match(/^data:image\//)) { + that.main.showToast($dialog, _('Unsupported image format')); + return; + } + $dialog.find('.tab-host-dialog-ok').removeClass('disabled'); + iconVal = text; + + $dialog.find('.tab-host-dialog-edit-icon').show().html(''); + $dialog.find('.tab-host-dialog-edit-icon img').attr('src', text); + $dialog.find('.tab-host-dialog-edit-icon-clear').show(); + } + }); + + if (that.main.objects[id] && that.main.objects[id].common) { + titleVal = that.main.objects[id].common.title; + if (typeof titleVal === 'object') { + titleVal = titleVal[systemLang] || titleVal.en; + } + iconVal = that.main.objects[id].common.icon; + colorVal = that.main.objects[id].common.color; + } + + $dialog.find('#tab-host-dialog-edit-title') + .val(titleVal) + .off('change') + .on('change', function () { + $dialog.find('.tab-host-dialog-ok').removeClass('disabled'); + }).off('keyup').on('keyup', function () { + $(this).trigger('change'); + }); + + $dialog.find('.tab-host-dialog-ok') + .addClass('disabled') + .off('click') + .on('click', function () { + var obj = JSON.parse(JSON.stringify(that.main.objects[id])); + obj.common.title = $dialog.find('#tab-host-dialog-edit-title').val(); + obj.common.icon = iconVal; + obj.common.color = colorVal; + if (JSON.stringify(obj) !== JSON.stringify(that.main.objects[id])) { + that.main.socket.emit('setObject', obj._id, obj, function (err) { + that.main.showToast($dialog, _('Updated')); + }); + } else { + that.main.showToast($dialog, _('Nothing changed')); + } + }); + + if (iconVal) { + $dialog.find('.tab-host-dialog-edit-icon').show().html(that.main.getIcon(id)); + $dialog.find('.tab-host-dialog-edit-icon-clear').show(); + } else { + $dialog.find('.tab-host-dialog-edit-icon').hide(); + $dialog.find('.tab-host-dialog-edit-icon-clear').hide(); + } + + colorVal = colorVal || false; + + if (colorVal) { + $dialog.find('.tab-host-dialog-edit-color').val(colorVal); + } else { + $dialog.find('.tab-host-dialog-edit-color').val(); + } + + M.updateTextFields('#tab-host-dialog-edit'); + that.main.showToast($dialog, _('Drop the icons here')); + + $dialog.find('.tab-host-dialog-edit-upload').off('click').on('click', function () { + $dialog.find('.drop-file').trigger('click'); + }); + + $dialog.find('.tab-host-dialog-edit-icon-clear').off('click').on('click', function () { + if (iconVal) { + iconVal = ''; + $dialog.find('.tab-host-dialog-edit-icon').hide(); + $dialog.find('.tab-host-dialog-ok').removeClass('disabled'); + $dialog.find('.tab-host-dialog-edit-icon-clear').hide(); + } + }); + $dialog.find('.tab-host-dialog-edit-color-clear').off('click').on('click', function () { + if (colorVal) { + $dialog.find('.tab-host-dialog-ok').removeClass('disabled'); + $dialog.find('.tab-host-dialog-edit-color-clear').hide(); + $dialog.find('.tab-host-dialog-edit-colorpicker').colorpicker({ + component: '.btn', + color: colorVal, + container: $dialog.find('.tab-host-dialog-edit-colorpicker') + }).colorpicker('setValue', ''); + colorVal = ''; + } + }); + var time = Date.now(); + try { + $dialog.find('.tab-host-dialog-edit-colorpicker').colorpicker('destroy'); + } catch (e) { + + } + $dialog.find('.tab-host-dialog-edit-colorpicker').colorpicker({ + component: '.btn', + color: colorVal, + container: $dialog.find('.tab-host-dialog-edit-colorpicker') + }).colorpicker('setValue', colorVal).on('showPicker.colorpicker', function (/* event */) { + //$dialog.find('.tab-host-dialog-edit-colorpicker')[0].scrollIntoView(false); + var $modal = $dialog.find('.modal-content'); + $modal[0].scrollTop = $modal[0].scrollHeight; + }).on('changeColor.colorpicker', function (event){ + if (Date.now() - time > 100) { + colorVal = event.color.toHex(); + $dialog.find('.tab-host-dialog-ok').removeClass('disabled'); + $dialog.find('.tab-host-dialog-edit-icon-clear').show(); + } + }); + if (colorVal) { + $dialog.find('.tab-host-dialog-edit-color-clear').show(); + } else { + $dialog.find('.tab-host-dialog-edit-color-clear').hide(); + } + + $dialog.modal().modal('open'); + } + + function showHostsTile() { + var text = ''; + for (var i = 0; i < that.list.length; i++) { + text += showOneHostTile(i); + } + that.$table.html(''); + that.$tab.find('.hosts-table').hide(); + that.$grid.html(text).show(); + that.$grid.append('
    ' + _('Filtered out') + '
    '); + } + + function showHostsTable() { + var text = ''; + for (var i = 0; i < that.list.length; i++) { + text += showOneHostRow(i); + } + that.$grid.html('').hide(); + that.$table.html(text); + that.$tab.find('.hosts-table').show(); + that.$table.append('' + _('Filtered out') + ''); + } + + this.updateCounter = function (counter) { + if (counter === undefined) { + this.main.tabs.adapters.getAdaptersInfo(this.main.currentHost, false, false, function (repository, installedList) { + var hostsToUpdate = 0; + if (!installedList || !installedList.hosts) return; + + for (var id in installedList.hosts) { + if (!installedList.hosts.hasOwnProperty(id)) continue; + var obj = that.main.objects['system.host.' + id]; + if (!obj || !obj.common) continue; + var installedVersion = obj.common.installedVersion; + var availableVersion = obj.common ? (repository && repository[obj.common.type] ? repository[obj.common.type].version : '') : ''; + + if (installedVersion && availableVersion && !that.main.upToDate(availableVersion, installedVersion)) { + id = 'system.host.' + id.trim().replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_'); + if (that.main.states[id + '.alive'] && that.main.states[id + '.alive'].val && that.main.states[id + '.alive'].val !== 'null') { + hostsToUpdate++; + } + } + } + + that.updateCounter(hostsToUpdate); + }); + } else if (counter) { + var $updates = $('#updates-for-hosts'); + if ($updates.length) { + $updates.text(counter); + } else { + $('' + counter + '').appendTo('.admin-sidemenu-items[data-tab="tab-hosts"] a'); + } + } else { + $('#updates-for-hosts').remove(); + } + }; + + this._postInit = function () { + if (typeof that.$grid !== 'undefined') { + if (this.isTiles) { + showHostsTile(); + } else { + showHostsTable() + } + applyFilter(this.$tab.find('.filter-input').val()); + + var timer = setTimeout(function () { + console.warn('Timeout for repository'); + timer = null; + that.initButtons(); + }, 2000); + + var host = that.main.currentHost; + if (!host) { + // find alive host + for (var i = 0; i < that.list.length; i++) { + if (that.main.states[that.list[i].id + '.alive'] && that.main.states[that.list[i].id + '.alive'].val) { + host = that.list[i].id; + break; + } + } + } + + that.main.tabs.adapters.getAdaptersInfo(host, true, false, function (repository, installedList) { + if (!installedList || !installedList.hosts) return; + + for (var id in installedList.hosts) { + if (!installedList.hosts.hasOwnProperty(id)) continue; + var obj = that.main.objects['system.host.' + id]; + var installed = installedList.hosts[id].version; + if (installed !== installedList.hosts[id].runningVersion) installed += '(' + _('Running: ') + installedList.hosts[id].runningVersion + ')'; + if (!installed && obj.common && obj.common.installedVersion) installed = obj.common.installedVersion; + + id = 'system.host.' + id.trim().replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_'); + that.$tab.find('.hosts-version-installed[data-host-id="' + id + '"]').html(installed); + } + + that.$tab.find('.hosts-host').each(function () { + var id = $(this).data('host-id'); + var obj = that.main.objects[id]; + var installedVersion = obj.common.installedVersion; + var availableVersion = obj.common ? (repository && repository[obj.common.type] ? repository[obj.common.type].version : '') : ''; + if (installedVersion && availableVersion) { + if (!that.main.upToDate(availableVersion, installedVersion)) { + // show button + if (that.main.states[id + '.alive'] && that.main.states[id + '.alive'].val && that.main.states[id + '.alive'].val !== 'null') { + $(this).find('.host-update-submit').show(); + $(this).find('.host-update-hint-submit').show(); + $(this).find('.hosts-version-installed').addClass('updateReady'); + $(this).find('.hosts-version-available').addClass('hosts-version-available-updatable'); + } + } + } + if (availableVersion) { + $(this).find('.hosts-version-available').html(availableVersion); + } + }); + + if (timer) { + clearTimeout(timer); + timer = null; + } + that.initButtons(); + }); + } + }; + + this.init = function (update) { + if (this.inited && !update) { + return; + } + + this.getHosts(function () { + that._postInit(); + }); + if (!this.inited) { + this.inited = true; + this.main.subscribeObjects('system.host.*'); + this.main.subscribeStates('system.host.*'); + } + }; + + this.destroy = function () { + if (this.inited) { + this.inited = false; + this.main.unsubscribeObjects('system.host.*'); + this.main.unsubscribeStates('system.host.*'); + } + }; + + this.addHost = function (obj) { + var addr = null; + // Find first non internal IP and use it as identifier + if (obj.native.hardware && obj.native.hardware.networkInterfaces) { + for (var eth in obj.native.hardware.networkInterfaces) { + if (!obj.native.hardware.networkInterfaces.hasOwnProperty(eth)) continue; + for (var num = 0; num < obj.native.hardware.networkInterfaces[eth].length; num++) { + if (!obj.native.hardware.networkInterfaces[eth][num].internal) { + addr = obj.native.hardware.networkInterfaces[eth][num].address; + break; + } + } + if (addr) break; + } + } + if (addr) { + this.list.push({name: obj.common.hostname, address: addr, id: obj._id}); + } else { + this.list.push({name: obj.common.hostname, address: '127.0.0.1', id: obj._id}); + } + }; + + this.getHosts = function (callback) { + this.main.socket.emit('getForeignObjects', 'system.host.*', 'state', function (err, res) { + for (var id in res) { + if (!res.hasOwnProperty(id)) continue; + that.main.objects[id] = res[id]; + } + that.main.socket.emit('getForeignStates', 'system.host.*', function (err, res) { + for (var id in res) { + if (!res.hasOwnProperty(id)) continue; + that.main.states[id] = res[id]; + } + that.main.socket.emit('getForeignObjects', 'system.host.*', 'host', function (err, res) { + that.list = []; + for (var id in res) { + if (!res.hasOwnProperty(id)) continue; + var obj = res[id]; + + that.main.objects[id] = obj; + + if (obj.type === 'host') { + that.addHost(obj); + } + } + main.initHostsList(); + if (callback) callback(); + }); + }); + }); + }; + + this.objectChange = function (id, obj, action) { + // Update hosts + if (id.match(/^system\.host\.[-\w]+$/)) { + var found = false; + var i; + for (i = 0; i < this.list.length; i++) { + if (this.list[i].id === id) { + found = true; + break; + } + } + + if (obj) { + if (!found) this.list.push({id: id, address: obj.common.address ? obj.common.address[0] : '', name: obj.common.name}); + } else { + if (found) this.list.splice(i, 1); + } + + if (this.updateTimer) clearTimeout(this.updateTimer); + + this.updateTimer = setTimeout(function () { + that.updateTimer = null; + that._postInit(); + }, 200); + } + }; + + this.stateChange = function (id, state) { + if (id.match(/^system\.host\..+\.alive$/)) { + id = id.substring(0, id.length - 6); + if (state && state.val) { + this.$tab.find('.hosts-led[data-host-id="' + id + '"]').removeClass('led-red').addClass('led-green'); + } else { + this.$tab.find('.hosts-led[data-host-id="' + id + '"]').removeClass('led-green').addClass('led-red'); + this.$tab.find('.host-update-submit[data-host-id="' + id + '"]').hide(); + this.$tab.find('.host-update-hint-submit[data-host-id="' + id + '"]').hide(); + this.$tab.find('.host-restart-submit[data-host-id="' + id + '"]').hide(); + this.$tab.find('.hosts-version-available[data-host-id="' + id + '"]').removeClass('hosts-version-available-updatable'); + } + } else if (id.match(/^system\.host\..+\.outputCount$/)) { + id = id.substring(0, id.length - 12); + + this.$tab.find('.host-out[data-host-id="' + id + '"]').html('↦' + state.val + ''); + } else if (id.match(/^system\.host\..+\.inputCount$/)) { + id = id.substring(0, id.length - 11); + + this.$tab.find('.host-in[data-host-id="' + id + '"]').html('↦' + state.val + ''); + } + }; +} + diff --git a/src/js/adminInstances.js b/src/js/adminInstances.js new file mode 100644 index 0000000..032ab3c --- /dev/null +++ b/src/js/adminInstances.js @@ -0,0 +1,1448 @@ +function Instances(main) { + 'use strict'; + + var that = this; + + this.$tab = $('#tab-instances'); + this.$grid = $('#grid-instances'); + this.$gridHead = $('#grid-instances-head'); + + this.inited = false; + this.main = main; + this.list = []; + this.hostsText = null; + this.filterHost = false; + this.memState = 'memAvailable'; + + if (!window.tdp) { + window.tdp = function (x, nachkomma) { + return isNaN(x) ? '' : x.toFixed(nachkomma || 0).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, "."); + } + } + + function getLinkVar(_var, obj, attr, link, instance) { + if (attr === 'protocol') attr = 'secure'; + + if (_var === 'ip') { + link = link.replace('%' + _var + '%', location.hostname); + } else + if (_var === 'instance') { + link = link.replace('%' + _var + '%', instance); + } else { + if (obj) { + if (attr.match(/^native_/)) attr = attr.substring(7); + + var val = obj.native[attr]; + if (_var === 'bind' && (!val || val === '0.0.0.0')) val = location.hostname; + + if (attr === 'secure') { + link = link.replace('%' + _var + '%', val ? 'https' : 'http'); + } else { + if (link.indexOf('%' + _var + '%') === -1) { + link = link.replace('%native_' + _var + '%', val); + } else { + link = link.replace('%' + _var + '%', val); + } + } + } else { + if (attr === 'secure') { + link = link.replace('%' + _var + '%', 'http'); + } else { + if (link.indexOf('%' + _var + '%') === -1) { + link = link.replace('%native_' + _var + '%', ''); + } else { + link = link.replace('%' + _var + '%', ''); + } + } + } + } + return link; + } + + function resolveLink(link, adapter, instance) { + var vars = link.match(/%(\w+)%/g); + var _var; + var v; + var parts; + if (vars) { + // first replace simple patterns + for (v = vars.length - 1; v >= 0; v--) { + _var = vars[v]; + _var = _var.replace(/%/g, ''); + + parts = _var.split('_'); + // like "port" + if (_var.match(/^native_/)) { + link = getLinkVar(_var, that.main.objects['system.adapter.' + adapter + '.' + instance], _var, link, instance); + vars.splice(v, 1); + } else + if (parts.length === 1) { + link = getLinkVar(_var, that.main.objects['system.adapter.' + adapter + '.' + instance], parts[0], link, instance); + vars.splice(v, 1); + } else + // like "web.0_port" + if (parts[0].match(/\.[0-9]+$/)) { + link = getLinkVar(_var, that.main.objects['system.adapter.' + parts[0]], parts[1], link, instance); + vars.splice(v, 1); + } + } + var links = {}; + var instances; + var adptr = parts[0]; + // process web_port + for (v = 0; v < vars.length; v++) { + _var = vars[v]; + _var = _var.replace(/%/g, ''); + if (_var.match(/^native_/)) _var = _var.substring(7); + + parts = _var.split('_'); + if (!instances) { + instances = []; + for (var inst = 0; inst < 10; inst++) { + if (that.main.objects['system.adapter.' + adptr + '.' + inst]) instances.push(inst); + } + } + + for (var i = 0; i < instances.length; i++) { + links[adptr + '.' + i] = { + instance: adptr + '.' + i, + link: getLinkVar(_var, that.main.objects['system.adapter.' + adptr + '.' + i], parts[1], links[adptr + '.' + i] ? links[adptr + '.' + i].link : link, i) + }; + } + } + + var result; + if (instances) { + result = {}; + var count = 0; + var firtsLink = ''; + for (var d in links) { + if (links.hasOwnProperty(d)) { + result[links[d].instance] = links[d].link; + if (!firtsLink) firtsLink = links[d].link; + count++; + } + } + if (count < 2) { + link = firtsLink; + result = null; + } + } + } + return result || link; + } + + this.replaceInLink = function (link, adapter, instance) { + if (typeof link === 'object') { + var links = JSON.parse(JSON.stringify(link)); + var first; + for (var v in links) { + if (links.hasOwnProperty(v)) { + links[v] = resolveLink(links[v], adapter, instance); + if (!first) first = links[v]; + } + } + links.__first = first; + return links; + } else { + return resolveLink(link, adapter, instance); + } + }; + + function updateLed(instanceId) { + var tmp = instanceId.split('.'); + var adapter = tmp[2]; + var instance = tmp[3]; + + var $led = that.$tab.find('.instance-led[data-instance-id="' + instanceId + '"]'); + + var common = that.main.objects[instanceId] ? that.main.objects[instanceId].common || {} : {}; + var state = (common.mode === 'daemon') ? 'green' : 'blue'; + var title = ''; + if (common.enabled && (!common.webExtension || !that.main.objects[instanceId].native.webInstance)) { + title = ''; + title += ''; + + if (that.main.states[adapter + '.' + instance + '.info.connection'] || that.main.objects[adapter + '.' + instance + '.info.connection']) { + title += ''; + } + title += '
    ' + _('Connected to host: ') + ''; + + if (!that.main.states[instanceId + '.connected'] || !that.main.states[instanceId + '.connected'].val) { + title += ((common.mode === 'daemon') ? '' + _('false') + '' : _('false')); + state = (common.mode === 'daemon') ? 'red' : 'blue'; + } else { + title += '' + _('true') + ''; + } + title += '
    ' + _('Heartbeat: ') + ''; + + if (!that.main.states[instanceId + '.alive'] || !that.main.states[instanceId + '.alive'].val) { + title += ((common.mode === 'daemon') ? '' + _('false') + '' : _('false')); + state = (common.mode === 'daemon') ? 'red' : 'blue'; + } else { + title += '' + _('true') + ''; + } + title += '
    ' + _('Connected to %s: ', adapter) + ''; + var val = that.main.states[adapter + '.' + instance + '.info.connection'] ? that.main.states[adapter + '.' + instance + '.info.connection'].val : false; + if (!val) { + state = state === 'red' ? 'red' : 'orange'; + title += '' + _('false') + ''; + } else { + if (val === true) { + title += '' + _('true') + ''; + } else { + title += '' + val + ''; + } + } + title += '
    '; + } else { + state = (common.mode === 'daemon') ? 'gray' : 'blue'; + title = ''; + title += ''; + + title += ''; + + if (that.main.states[adapter + '.' + instance + '.info.connection'] || that.main.objects[adapter + '.' + instance + '.info.connection']) { + title += ''; + } + title += '
    ' + _('Connected to host: ') + ''; + + if (!that.main.states[instanceId + '.connected'] || !that.main.states[instanceId + '.connected'].val) { + title += _('false'); + } else { + title += '' + _('true') + ''; + } + title += '
    ' + _('Heartbeat: ') + ''; + if (!that.main.states[instanceId + '.alive'] || !that.main.states[instanceId + '.alive'].val) { + title += _('false'); + } else { + title += '' + _('true') + ''; + } + title += '
    ' + _('Connected to %s: ', adapter) + ''; + var val = that.main.states[adapter + '.' + instance + '.info.connection'] ? that.main.states[adapter + '.' + instance + '.info.connection'].val : false; + if (!val) { + title += _('false'); + } else { + if (val === true) { + title += '' + _('true') + ''; + } else { + title += '' + val + ''; + } + } + title += '
    '; + } + + state = (state === 'blue') ? '' : state; + + $led.removeClass('led-red led-green led-orange led-blue').addClass('led-' + state).data('title', title); + + if (!$led.data('inited') && state !== 'gray') { + $led.data('inited', true); + + $led.hover(function () { + var text = '
    ' + $(this).data('title') + '
    '; + var $big = $(text); + + $big.insertAfter($(this)); + $(this).data('big', $big[0]); + var h = parseFloat($big.height()); + var top = Math.round($(this).position().top - ((h - parseFloat($(this).height())) / 2)); + if (h + top > (window.innerHeight || document.documentElement.clientHeight)) { + top = (window.innerHeight || document.documentElement.clientHeight) - h; + } + if (top < 0) { + top = 0; + } + $big.css({top: top}).on('click', function () { + var big = $(this).data('big'); + $(big).remove(); + $(this).data('big', undefined); + }); + }, function () { + var big = $(this).data('big'); + $(big).remove(); + $(this).data('big', undefined); + }).on('click', function () { + $(this).trigger('hover'); + }); + } + } + + /*function _createHead() { + var text = ''; + // _('name'), _('instance'), _('title'), _('enabled'), _('host'), _('mode'), _('schedule'), '', _('platform'), _('loglevel'), _('memlimit'), _('alive'), _('connected')], + text += ''; + //text += ''; + text += ''; + text += '' + _('instance') + ''; + text += ''; + text += '' + _('title') + ''; + + if (that.main.tabs.hosts.list.length > 1) { + text += '' + _('host') + ''; + } + + text += '' + _('schedule_group') + ''; + + if (that.main.config.expertMode) { + text += '' + _('restart') + ''; + text += '' + _('loglevel') + ''; + text += '' + _('memlimit') + ''; + text += '' + _('events') + ''; + } + text += '' + _('RAM usage') + ''; + that.$gridHead.html(text); + }*/ + + function createHead() { + var text = ''; + // _('name'), _('instance'), _('title'), _('enabled'), _('host'), _('mode'), _('schedule'), '', _('platform'), _('loglevel'), _('memlimit'), _('alive'), _('connected')], + text += '' + + '' + _('instance') + '' + + ''; + text += ''; + text += ''; + // disabled, because no one use it + if (false && that.main.config.expertMode) { + text += '' + _('actions') + ''; + } else { + text += '' + _('actions') + ''; + } + text += '' + _('title') + ''; + + if (that.main.tabs.hosts.list.length > 1) { + text += '' + _('host') + ''; + } + + text += '' + _('schedule_group') + ''; + + if (that.main.config.expertMode) { + text += '' + _('restart') + ''; + text += '' + _('loglevel') + ''; + text += '' + _('memlimit') + ''; + text += '' + _('events') + ''; + } + text += '' + _('RAM usage') + ''; + that.$gridHead.html(text); + } + + function calculateTotalRam() { + var host = that.main.states['system.host.' + that.main.currentHost + '.memRss']; + var processes = 1; + var mem = host ? host.val : 0; + for (var i = 0; i < that.list.length; i++) { + var obj = that.main.objects[that.list[i]]; + if (!obj || !obj.common) continue; + if (obj.common.host !== that.main.currentHost) continue; + if (obj.common.enabled && obj.common.mode === 'daemon') { + var m = that.main.states[obj._id + '.memRss']; + mem += m ? m.val : 0; + processes++; + } + } + mem = Math.round(mem); + var $totalRam = that.$tab.find('#totalRam'); + if (mem.toString() !== $totalRam.text()) { + $totalRam.html('' + mem + ''); + } + var text = _('%s processes', processes); + var $running_processes = that.$tab.find('#running_processes'); + if (text !== $running_processes.text()) { + $running_processes.html('' + text + '') + } + } + + function calculateFreeMem() { + if (that.main.states['system.host.' + that.main.currentHost + '.memAvailable']) { + that.memState = 'memAvailable'; + } else if (that.main.states['system.host.' + that.main.currentHost + '.freemem']) { + that.memState = 'freemem'; + } + + var host = that.main.states['system.host.' + that.main.currentHost + '.' + that.memState]; + if (host) { + that.totalmem = that.totalmem || (that.main.objects['system.host.' + that.main.currentHost].native.hardware.totalmem / (1024 * 1024)); + var percent = Math.round((host.val / that.totalmem) * 100); + var $freeMem = that.$tab.find('#freeMem'); + var strVal = tdp(host.val); + if (strVal !== $freeMem.text()) { + $freeMem.html('' + strVal + ''); + that.$tab.find('#freeMemPercent').html('' + percent + '%'); + } + } else { + that.$tab.find('.free-mem-label').hide(); + } + } + + function calculateDiskMem() { + var diskSize = that.main.states['system.host.' + that.main.currentHost + '.diskSize']; + var diskFree = that.main.states['system.host.' + that.main.currentHost + '.diskFree']; + var diskWarning = that.main.states['system.host.' + that.main.currentHost + '.diskWarning']; + + if (diskFree && diskFree.val && diskSize && diskSize.val) { + if (diskWarning) { + diskWarning = parseFloat(diskWarning.val); + } else { + diskWarning = 5; + } + + var $diskFree = that.$tab.find('#diskFree'); + var size = (Math.round((diskFree.val / diskSize.val) * 1000) / 10); + $diskFree.html('' + size + ''); + $diskFree.parent().attr('title', _('Size: %s, Free: %s', that.main.formatBytes(diskSize.val * 1024 * 1024), that.main.formatBytes(diskFree.val * 1024 * 1024))); + } else { + that.$tab.find('.tab-instances-info-disk').hide(); + } + } + + function calculateRam(instanceId) { + var mem; + var common = that.main.objects[instanceId] ? that.main.objects[instanceId].common || {} : {}; + if (common.enabled && common.mode === 'daemon' && that.main.states[instanceId + '.memRss']) { + mem = that.main.states[instanceId + '.memRss'].val; + mem = parseFloat(mem) || 0; + + if (common.memoryLimitMB && common.memoryLimitMB <= mem) { + mem = '' + mem.toFixed(1) + ' MB'; + } else { + mem = mem.toFixed(1) + ' MB' + } + } else { + mem = ''; + } + return mem; + } + + function showOneAdapter(rootElem, instanceId, form, justContent) { + var text; + var common = that.main.objects[instanceId] ? that.main.objects[instanceId].common || {} : {}; + var tmp = instanceId.split('.'); + var adapter = tmp[2]; + var instance = tmp[3]; + + if (form === 'tile') { + text = justContent ? '' : '
    '; + text += justContent ? '' : '
    '; + } else { + // table + text = justContent ? '' : ''; + + var link = common.localLinks || common.localLink || ''; + var url = link ? that.replaceInLink(link, adapter, instance) : ''; + if (link) { + if (typeof url === 'object') { + link = ''; + } else { + link = ''; + } + } + + // State - + // red - adapter is not connected or not alive, + // orange - adapter is connected and alive, but device is not connected, + // green - adapter is connected and alive, device is connected or no device, + text += '
    '; + + // icon + text += '' + (common.icon ? link + '' : '') + (link ? '
    ': '') + ''; + + // name and instance + text += '' + adapter + '.' + instance + ''; + + var isRun = common.onlyWWW || common.enabled; + // buttons + text += '' + + '' + + '' + + ''; + // disable, because no one use it + if (false && that.main.config.expertMode) { + text += ''; + } + text += '' + + (url ? '' : '') + + ''; + + var title = common.titleLang || common.title; + if (typeof title === 'object') { + title = title[systemLang] || title.en; + } + + // title + text += '' + (title || '') + ''; + + // host - hide it if only one host + if (that.main.tabs.hosts.list.length > 1) { + if (!that.hostsText) { + that.hostsText = ''; + for (var h = 0; h < that.main.tabs.hosts.list.length; h++) { + var host = that.main.tabs.hosts.list[h] || ''; + that.hostsText += (that.hostsText ? ';' : '') + host.name; + } + } + text += '' + (common.host || '') + ''; + } + + // schedule + text += '' + (common.mode === 'schedule' ? (common.schedule || '') : '') + ''; + + // scheduled restart (only experts) + if (that.main.config.expertMode) { + text += '' + (common.restartSchedule || '') + ''; + // debug level (only experts) + text += '' + (common.loglevel || '') + ''; + // Max RAM (only experts) + text += '' + (common.memoryLimitMB || '') + ''; + // Max RAM (only experts) + if (isRun && that.main.states[instanceId + '.inputCount'] && that.main.states[instanceId + '.outputCount']) { + text += '⇥' + that.main.states[instanceId + '.inputCount'].val + ' / ↦' + that.main.states[instanceId + '.outputCount'].val + ''; + } else { + text += ' / '; + } + } + + text += '' + calculateRam(instanceId) + ''; + + text += justContent ? '' : ''; + } + if (!justContent) { + rootElem.append(text); + } else { + that.$tab.find('.instance-adapter[data-instance-id="' + instanceId + '"]').html(text); + } + // init buttons + that.initButtons(instanceId, url); + updateLed(instanceId); + // init links + that.$tab.find('.instance-editable[data-instance-id="' + instanceId + '"]') + .on('click', onQuickEditField) + .addClass('select-id-quick-edit'); + + // init schedule editor + that.$tab.find('.instance-schedule[data-instance-id="' + instanceId + '"]').each(function () { + if (!$(this).find('button').length) { + $(this).append(''); + $(this).find('button').on('click', function () { + var attr = $(this).data('name'); + var _instanceId = $(this).data('instance-id'); + showCronDialog(that.main.objects[_instanceId].common[attr] || '', function (newValue) { + if (newValue !== null) { + var obj = {common: {}}; + obj.common[attr] = newValue; + that.main.socket.emit('extendObject', _instanceId, obj, function (err) { + if (err) that.main.showError(err); + }); + } + }) + }); + } + }); + + that.$tab.find('.instance-name[data-instance-id="' + instanceId + '"]').on('click', function () { + var $btn = that.$tab.find('.instance-settings[data-instance-id="' + $(this).data('instance-id') + '"]'); + if (!$btn.hasClass('small-button-empty')) { + $btn.trigger('click'); + } + }).css('cursor', 'pointer'); + } + + function applyFilter(filter) { + if (filter === undefined) { + filter = that.$tab.find('.instances-filter').val(); + } + var invisible = []; + if (filter) { + var reg = new RegExp(filter); + + for (var i = 0; i < that.list.length; i++) { + var obj = that.main.objects[that.list[i]]; + if (!obj || !obj.common) { + that.$grid.find('.instance-adapter[data-instance-id="' + that.list[i] + '"]').hide(); + continue; + } + var isShow = 'hide'; + var title = obj.common.titleLang || obj.common.title; + if (typeof title === 'object') { + title = title[systemLang] || title.en; + } + + if (obj.common.name && reg.test(obj.common.name)) { + isShow = 'show'; + } else + if (title && reg.test(title)) { + isShow = 'show'; + } else + if (filter === 'true') { + isShow = that.$grid.find('.instance-adapter[data-instance-id="' + that.list[i] + '"]').find('instance-led').hasClass('led-green') ? 'show' : 'hide'; + } else + if (filter === 'false') { + isShow = that.$grid.find('.instance-adapter[data-instance-id="' + that.list[i] + '"]').find('instance-led').hasClass('led-green') ? 'hide' : 'show'; + } + + if (isShow === 'show' && that.filterHost && obj.common.host !== that.main.currentHost) isShow = 'hide'; + + if (isShow === 'hide') invisible.push(that.list[i]); + that.$grid.find('.instance-adapter[data-instance-id="' + that.list[i] + '"]')[isShow](); + } + } else { + if (that.filterHost) { + for (var j = 0; j < that.list.length; j++) { + var _obj = that.main.objects[that.list[j]]; + if (!_obj || !_obj.common) { + that.$grid.find('.instance-adapter[data-instance-id="' + that.list[j] + '"]').hide(); + continue; + } + var _isShow = 'hide'; + if (_obj.common.host === that.main.currentHost) _isShow = 'show'; + that.$grid.find('.instance-adapter[data-instance-id="' + that.list[j] + '"]')[_isShow](); + } + } else { + that.$grid.find('.instance-adapter').show(); + } + } + var anyVisible = false; + that.$grid.find('.instance-adapter').each(function () { + if ($(this).is(':visible')) { + anyVisible = true; + return false; + } + }); + + if (anyVisible) { + that.$grid.find('.filtered-out').hide(); + } else { + that.$grid.find('.filtered-out').show(); + } + } + + function onQuickEditField(e) { + var $this = $(this); + var id = $this.data('instance-id'); + var attr = $this.data('name'); + var options = $this.data('options'); + var oldVal = $this.data('value'); + var innerHTML = this.innerHTML; + var textAlign = $this.css('text-align'); + $this.css('text-align', 'left'); + + $this.off('click').removeClass('select-id-quick-edit').css('position', 'relative'); + + var css = 'cursor: pointer; position: absolute;width: 16px; height: 16px; top: 2px; border-radius: 6px; z-index: 3; background-color: lightgray'; + var type = 'text'; + var text; + + if (options) { + var opt = options.split(';'); + text = ''; + } + text = text || ''; + + var timeout = null; + + $this.html(text + + '
    ' + + '
    '); + + var $input = (options) ? $this.find('select') : $this.find('input'); + + $this.find('.select-id-quick-edit-cancel').off('click').on('click', function (e) { + if (timeout) clearTimeout(timeout); + timeout = null; + e.preventDefault(); + e.stopPropagation(); + $this.html(innerHTML) + .off('click') + .on('click', onQuickEditField) + .addClass('select-id-quick-edit') + .css('text-align', textAlign); + }); + + $this.find('.select-id-quick-edit-ok').off('click').on('click', function () { + $this.trigger('blur'); + }); + + $input.val(oldVal); + + $input.blur(function () { + if (timeout) clearTimeout(timeout); + + timeout = setTimeout(function () { + timeout = null; + var val = $(this).val(); + + if (JSON.stringify(val) !== JSON.stringify(oldVal)) { + that.main.socket.emit('getObject', id, function (err, obj) { + if (obj) { + obj.common = obj.common || {}; + obj.common[attr] = val; + if (attr === 'title' && obj.common.titleLang) { + delete obj.common.titleLang; + } + that.main.socket.emit('setObject', obj._id, obj, function (err) { + if (err) that.main.showError(err); + }); + } else { + console.log('Object ' + id + ' does not exist: ' + err); + } + }); + oldVal = '' + oldVal + ''; + } else { + oldVal = innerHTML; + } + $this.html(oldVal) + .off('click') + .on('click', onQuickEditField) + .addClass('select-id-quick-edit') + .css('text-align', textAlign); + }.bind(this), 100); + }).on('keyup', function (e) { + if (e.which === 13) $(this).trigger('blur'); + if (e.which === 27) { + if (oldVal === undefined) oldVal = ''; + $this.html(oldVal) + .off('click') + .on('click', onQuickEditField) + .addClass('select-id-quick-edit') + .css('text-align', textAlign); + } + }); + + if (typeof e === 'object') { + e.preventDefault(); + e.stopPropagation(); + } + + setTimeout(function () { + $input.focus(); + }, 100); + } + + function showCronDialog(value, cb) { + value = (value || '').replace(/"/g, '').replace(/'/g, ''); + try { + setupCron(value, cb); + } catch (e) { + alert(_('Cannot parse value as cron')); + } + } + + this.prepare = function () { + /*this.$dialogCron.dialog({ + autoOpen: false, + modal: true, + width: 700, + height: 550, + resizable: false, + title: _('Cron expression'), + buttons: [ + { + id: 'dialog_cron_insert', + text: _('Insert'), + click: function () { + var val = $('#div-cron').cron('value'); + that.$dialogCron.dialog('close'); + that.editor.insert('"' + val + '"'); + that.editor.focus(); + } + }, + { + id: 'dialog_cron_clear', + text: _('Clear'), + click: function () { + $('#div-cron').cron('value', ''); + } + }, + { + id: 'dialog_cron_callback', + text: _('Set CRON'), + click: function () { + } + }, + { + text: _('Cancel'), + click: function () { + that.$dialogCron.dialog('close'); + } + } + ] + }); + + $('#div-cron').cron({value: ''}); +*/ + var $filter = that.$tab.find('.instances-filter'); + var $filterClear = that.$tab.find('.instances-filter-clear'); + + $filter.on('change', function () { + var val = $(this).val(); + if (val) { + $(this).addClass('input-not-empty'); + $filterClear.show(); + } else { + $(this).removeClass('input-not-empty'); + $filterClear.hide(); + } + that.main.saveConfig('instancesFilter', val); + applyFilter(val); + }).on('keyup', function () { + if (that.filterTimeout) clearTimeout(that.filterTimeout); + that.filterTimeout = setTimeout(function () { + $filter.trigger('change'); + }, 300); + }); + if (that.main.config.instancesFilter && that.main.config.instancesFilter[0] !== '{') { + $filter.addClass('input-not-empty').val(that.main.config.instancesFilter); + $filterClear.show(); + } else { + $filterClear.hide(); + } + + //$('#load_grid-instances').show(); + that.$tab.find('.btn-instances-expert-mode').on('click', function () { + that.main.config.expertMode = !that.main.config.expertMode; + that.main.saveConfig('expertMode', that.main.config.expertMode); + that.updateExpertMode(); + that.main.tabs.adapters.updateExpertMode(); + }); + + if (that.main.config.expertMode) { + that.$tab.find('.btn-instances-expert-mode').addClass('red lighten-3'); + } + + that.$tab.find('.btn-instances-reload').on('click', function () { + that.init(true, true); + }); + + /*that.$grid.find('#btn-instances-form').button({ + icons: {primary: 'ui-icon-refresh'}, + text: false + }).css({width: '1.5em', height: '1.5em'}).attr('title', _('reload')).on('click', function () { + that.main.config.instanceForm = that.main.config.instanceForm === 'tile' ? 'list' : 'tile'; + that.main.saveCell('expertMode', that.main.config.expertMode); + that.init(true); + });*/ + + $filterClear.on('click', function () { + $filter.val('').trigger('change'); + }); + + this.$tab.find('.btn-instances-host').off('click').on('click', function () { + that.filterHost = !that.filterHost; + if (that.filterHost) { + that.$tab.find('.btn-instances-host').addClass('red lighten-3'); + } else { + that.$tab.find('.btn-instances-host').removeClass('red lighten-3'); + } + that.main.saveConfig('instancesFilterHost', that.filterHost); + + setTimeout(function () { + applyFilter(); + }, 50); + }); + + this.filterHost = this.main.config.instancesFilterHost || false; + + if (this.filterHost) { + this.$tab.find('.btn-instances-host').addClass('red lighten-3'); + } else { + this.$tab.find('.btn-instances-host').removeClass('red lighten-3'); + } + }; + + this.updateExpertMode = function () { + that.init(true); + if (that.main.config.expertMode) { + that.$tab.find('.btn-instances-expert-mode').addClass('red lighten-3'); + } else { + that.$tab.find('.btn-instances-expert-mode').removeClass('red lighten-3'); + } + }; + + this.replaceLink = function (_var, adapter, instance, elem) { + _var = _var.replace(/%/g, ''); + if (_var.match(/^native_/)) _var = _var.substring(7); + // like web.0_port + var parts; + if (_var.indexOf('_') === -1) { + parts = [ + adapter + '.' + instance, + _var + ] + } else { + parts = _var.split('_'); + // add .0 if not defined + if (!parts[0].match(/\.[0-9]+$/)) parts[0] += '.0'; + } + + if (parts[1] === 'protocol') parts[1] = 'secure'; + + if (_var === 'instance') { + setTimeout(function () { + var link; + if (elem) { + link = that.$tab.find('#' + elem).data('src'); + } else { + link = that.$tab.find('#a_' + adapter + '_' + instance).attr('href'); + } + + link = link.replace('%instance%', instance); + if (elem) { + that.$tab.find('#' + elem).data('src', link); + } else { + that.$tab.find('#a_' + adapter + '_' + instance).attr('href', link); + } + }, 0); + return; + } + + this.main.socket.emit('getObject', 'system.adapter.' + parts[0], function (err, obj) { + if (obj) { + setTimeout(function () { + var link; + if (elem) { + link = that.$tab.find('#' + elem).data('src'); + } else { + link = that.$tab.find('#a_' + adapter + '_' + instance).attr('href'); + } + if (link) { + if (parts[1] === 'secure') { + link = link.replace('%' + _var + '%', obj.native[parts[1]] ? 'https' : 'http'); + } else { + if (link.indexOf('%' + _var + '%') === -1) { + link = link.replace('%native_' + _var + '%', obj.native[parts[1]]); + } else { + link = link.replace('%' + _var + '%', obj.native[parts[1]]); + } + } + if (elem) { + that.$tab.find('#' + elem).data('src', link); + } else { + that.$tab.find('#a_' + adapter + '_' + instance).attr('href', link); + } + } + }, 0); + } + }); + }; + + /*this.replaceLinks = function (vars, adapter, instance, elem) { + if (typeof vars !== 'object') vars = [vars]; + for (var t = 0; t < vars.length; t++) { + this.replaceLink(vars[t], adapter, instance, elem); + } + };*/ + + this._replaceLink = function (link, _var, adapter, instance, callback) { + // remove %% + _var = _var.replace(/%/g, ''); + + if (_var.match(/^native_/)) _var = _var.substring(7); + // like web.0_port + var parts; + if (_var.indexOf('_') === -1) { + parts = [adapter + '.' + instance, _var]; + } else { + parts = _var.split('_'); + // add .0 if not defined + if (!parts[0].match(/\.[0-9]+$/)) parts[0] += '.0'; + } + + if (parts[1] === 'protocol') parts[1] = 'secure'; + + this.main.socket.emit('getObject', 'system.adapter.' + parts[0], function (err, obj) { + if (obj && link) { + if (parts[1] === 'secure') { + link = link.replace('%' + _var + '%', obj.native[parts[1]] ? 'https' : 'http'); + } else { + if (link.indexOf('%' + _var + '%') === -1) { + link = link.replace('%native_' + _var + '%', obj.native[parts[1]]); + } else { + link = link.replace('%' + _var + '%', obj.native[parts[1]]); + } + } + } else { + console.log('Cannot get link ' + parts[1]); + link = link.replace('%' + _var + '%', ''); + } + setTimeout(function () { + callback(link, adapter, instance); + }, 0); + }); + }; + + this._replaceLinks = function (link, adapter, instance, arg, callback) { + if (!link) { + return callback(link, adapter, instance, arg); + } + var vars = link.match(/%(\w+)%/g); + if (!vars) { + return callback(link, adapter, instance, arg); + } + if (vars[0] === '%ip%') { + link = link.replace('%ip%', location.hostname); + this._replaceLinks(link, adapter, instance, arg, callback); + return; + } + if (vars[0] === '%instance%') { + link = link.replace('%instance%', instance); + this._replaceLinks(link, adapter, instance, arg, callback); + return; + } + this._replaceLink(link, vars[0], adapter, instance, function (link, adapter, instance) { + this._replaceLinks(link, adapter, instance, arg, callback); + }.bind(this)); + }; + + this._postInit = function (update, showTip) { + if (this.main.currentHost && typeof this.$grid !== 'undefined' && (!this.$grid.data('inited') || update)) { + this.$grid.data('inited', true); + this.list.sort(); + var onlyWWW = []; + // move all adapters with not onlyWWW and noConfig to the bottom + for (var l = this.list.length - 1; l >= 0; l--) { + if (this.main.objects[this.list[l]] && + this.main.objects[this.list[l]].common && + !this.main.objects[this.list[l]].common.localLink && + !this.main.objects[this.list[l]].common.localLinks && + this.main.objects[this.list[l]].common.noConfig + ) { + onlyWWW.push(this.list[l]); + this.list.splice(l, 1); + } + } + this.list.sort(); + onlyWWW.sort(); + for (l = 0; l < onlyWWW.length; l++) { + this.list.push(onlyWWW[l]); + } + + createHead(); + this.$grid.html(''); + var ts = Date.now(); + for (var i = 0; i < this.list.length; i++) { + var obj = this.main.objects[this.list[i]]; + if (!obj) continue; + showOneAdapter(this.$grid, this.list[i], this.main.config.instanceForm); + } + console.log(Date.now() - ts); + + this.$grid.append('' + _('Filtered out') + ''); + + that.$tab.find('#currentHost').html(this.main.currentHost); + + if (that.main.tabs.hosts.list.length > 1) { + this.$tab.find('.btn-instances-host').show(); + } else { + this.$tab.find('.btn-instances-host').hide(); + this.filterHost = false; + } + + applyFilter(); + + calculateTotalRam(); + calculateFreeMem(); + calculateDiskMem(); + that.restoreScroll(); + if (showTip) { + that.main.showToast(that.$tab.find('.main-toolbar-table'), _('Updated')); + } + } + }; + + this.getInstances = function (callback) { + this.main.socket.emit('getForeignObjects', 'system.adapter.*', 'state', function (err, res) { + for (var id in res) { + if (!res.hasOwnProperty(id)) continue; + that.main.objects[id] = res[id]; + } + that.main.socket.emit('getForeignStates', '*.info.connection', function (err, res) { + for (var id in res) { + if (!res.hasOwnProperty(id)) continue; + that.main.states[id] = res[id]; + } + + that.main.socket.emit('getForeignStates', 'system.adapter.*', function (err, res) { + for (var id in res) { + if (!res.hasOwnProperty(id)) continue; + that.main.states[id] = res[id]; + } + + that.main.socket.emit('getForeignObjects', 'system.adapter.*', 'instance', function (err, res) { + that.main.instances.splice(0, that.main.instances.length); // because of pointer in admin.main + for (var id in res) { + if (!res.hasOwnProperty(id)) continue; + var obj = res[id]; + that.main.objects[id] = obj; + + if (obj.type === 'instance') { + that.main.instances.push(id); + } + } + if (callback) callback(); + }); + + }); + }); + }); + }; + + this.init = function (update, showMessage) { + if (this.inited && !update) { + return; + } + if (!this.main.objectsLoaded) { + setTimeout(function () { + that.init(update, showMessage); + }, 250); + return; + } + var count = 0; + + count++; + this.getInstances(function () { + if (!--count) that._postInit(update, showMessage); + }); + count++; + this.main.tabs.hosts.getHosts(function () { + if (!--count) that._postInit(update, showMessage); + }); + + if (!this.inited) { + this.inited = true; + // subscribe objects and states + this.main.subscribeObjects('system.adapter.*'); + this.main.subscribeStates('system.adapter.*'); + this.main.subscribeObjects('system.host.*'); + this.main.subscribeStates('system.host.*'); + this.main.subscribeStates('*.info.connection'); + } + }; + + this.saveScroll = function () { + this.scrollTop = this.$tab.find('.grid-main-div').scrollTop(); + }; + this.restoreScroll = function () { + if (this.scrollTop) { + this.$tab.find('.grid-main-div').scrollTop(this.scrollTop); + } + }; + this.destroy = function () { + if (this.inited) { + this.saveScroll(); + this.$grid.data('inited', false); + this.inited = false; + // subscribe objects and states + this.main.unsubscribeObjects('system.adapter.*'); + this.main.unsubscribeStates('system.host.*'); + this.main.unsubscribeObjects('system.host.*'); + this.main.unsubscribeStates('system.adapter.*'); + this.main.unsubscribeStates('*.info.connection'); + } + }; + + this.stateChange = function (id, state) { + this.main.states[id] = state; + if (this.$grid) { + var parts = id.split('.'); + var last = parts.pop(); + id = parts.join('.'); + + if (state) { + if (last === 'diskFree' || last === 'diskWarning') { + // update disk size + calculateDiskMem(); + } else if (last === that.memState) { + // update total ram + calculateFreeMem(); + } else if (last === 'memRss') { + // update total ram + calculateTotalRam(); + // update instance ram + var $mem = that.$tab.find('.memUsage[data-instance-id="' + id + '"]'); + var mem = calculateRam(id); + if ($mem.length && $mem.text() !== mem) { + $mem.html('' + mem + ''); + } + } else if (last === 'outputCount') { + // update total ram + that.$tab.find('.instance-out[data-instance-id="' + id + '"]').html('↦' + state.val + ''); + } else if (last === 'inputCount') { + that.$tab.find('.instance-in[data-instance-id="' + id + '"]').html('⇥' + state.val + ''); + } + + if (this.list.indexOf(id) !== -1) { + if (last === 'alive' || last === 'connected') { + updateLed(id); + } + return; + } + id = 'system.adapter.' + parts[0] + '.' + parts[1]; + if (this.list.indexOf(id) !== -1 && last === 'connection') { + updateLed(id); + } + } + } + }; + + this.objectChange = function (id, obj, action) { + // Update Instance Table + if (id.match(/^system\.adapter\.[-\w]+\.[0-9]+$/)) { + if (obj) { + if (this.list.indexOf(id) === -1) { + // add new instance + this.list.push(id); + + if (this.updateTimer) clearTimeout(this.updateTimer); + + this.updateTimer = setTimeout(function () { + that.updateTimer = null; + that.init(true); + }, 200); + + // open automatically config dialog + if (!obj.common.noConfig) { + setTimeout(function () { + if (window.location.hash.indexOf('/config/') === -1) { + // open configuration dialog + that.main.navigate({ + tab: 'instances', + dialog: 'config', + params: id + }); + } + }, 2000); + } + } else { + if (id.indexOf('.web.') !== -1) { + if (this.updateTimer) clearTimeout(this.updateTimer); + + this.updateTimer = setTimeout(function () { + that.updateTimer = null; + that.init(true); + }, 200); + } else { + // update just one line or + this.$grid.find('.instance-adapter[data-instance-id="' + id + '"]').html(showOneAdapter(this.$grid, id, this.main.config.instanceForm, true)); + } + } + } else { + var i = this.list.indexOf(id); + if (i !== -1) { + this.list.splice(i, 1); + this.$grid.find('.instance-adapter[data-instance-id="' + id + '"]').remove(); + } + } + } else + // update list if some host changed + if (id.match(/^system\.host\.[-\w]+$/)) { + if (this.updateTimer) clearTimeout(this.updateTimer); + + this.updateTimer = setTimeout(function () { + that.updateTimer = null; + that.init(true); + }, 200); + } + }; + + this.initButtons = function (id, url) { + id = id ? '[data-instance-id="' + id + '"]' : ''; + + var $e = that.$grid.find('.instance-edit' + id).off('click').on('click', function () { + that.onEdit($(this).attr('data-instance-id')); + }); + + //var buttonSize = {width: '2em', height: '2em'} + + if (!$e.find('.ui-button-icon-primary').length) { + $e/*.button({ + icons: {primary: 'ui-icon-pencil'}, + text: false + }).css({width: '2em', height: '2em'})*/.attr('title', _('edit')); + } + + $e = that.$grid.find('.instance-settings' + id).off('click') + .on('click', function () { + that.main.navigate({ + tab: 'instances', + dialog: 'config', + params: $(this).data('instance-id') + }); + }); + /*if (!$e.find('.ui-button-icon-primary').length) { + $e.button({icons: {primary: 'ui-icon-note'}, text: false}).css({width: '2em', height: '2em'}).attr('title', _('config')); + }*/ + $e.each(function () { + var _id = $(this).attr('data-instance-id'); + if (main.objects[_id] && main.objects[_id].common && main.objects[_id].common.noConfig) { + //$(this).button('disable'); + $(this).addClass('disabled'); + } + }); + + $e = that.$grid.find('.instance-reload' + id).off('click') + .on('click', function () { + that.main.socket.emit('extendObject', $(this).attr('data-instance-id'), {}, function (err) { + if (err) that.main.showError(err); + }); + }); + /*if (!$e.find('.ui-button-icon-primary').length) { + $e.button({icons: {primary: 'ui-icon-refresh'}, text: false}).attr('title', _('reload')); + }*/ + + $e = that.$grid.find('.instance-del' + id).off('click') + .on('click', function () { + var id = $(this).attr('data-instance-id'); + if (that.main.objects[id] && that.main.objects[id].common && that.main.objects[id].common.host) { + var name = id.replace(/^system\.adapter\./, ''); + that.main.confirmMessage(_('Are you sure you want to delete the instance %s?', name), null, 'help', function (result) { + if (result) { + that.main.cmdExec(that.main.objects[id].common.host, 'del ' + id.replace('system.adapter.', ''), function (exitCode) { + if (!exitCode) that.main.tabs.adapters.init(true); + }); + } + }); + } + }); + + /*if (!$e.find('.ui-button-icon-primary').length) { + $e.button({icons: {primary: 'ui-icon-trash'}, text: false}).attr('title', _('delete')); + } else { + //$e.button('enable'); + $e.removeClass('disabled'); + }*/ + $e = that.$grid.find('.instance-issue' + id).off('click') + .on('click', function () { + that.main.navigate({ + tab: 'instances', + dialog: 'issue', + params: $(this).data('instance-id') + }); + }); + /*if (!$e.find('.ui-button-icon-primary').length) { + //$e.button({icons: {primary: 'ui-icon-pin-s'}, text: false}).css({width: '2em', height: '2em'}).attr('title', _('bug')); + //Material-Hack + $e.button().attr('title', _('bug')).empty().append('bug_report'); + }*/ + + that.$grid.find('.instance-image' + id).each(function () { + if (!$(this).data('installed')) { + $(this).data('installed', true); + $(this).hover(function () { + var text = '
    '; + var $big = $(text); + $big.insertAfter($(this)); + $(this).data('big', $big[0]); + var h = parseFloat($big.height()); + var top = Math.round($(this).position().top - ((h - parseFloat($(this).height())) / 2)); + if (h + top > (window.innerHeight || document.documentElement.clientHeight)) { + top = (window.innerHeight || document.documentElement.clientHeight) - h; + } + if (top < 0) { + top = 0; + } + $big.css({top: top}); + }, function () { + var big = $(this).data('big'); + $(big).remove(); + $(this).data('big', undefined); + }); + } + }); + $e = that.$grid.find('.instance-stop-run' + id).off('click') + .on('click', function () { + var id = $(this).attr('data-instance-id'); + //$(this).button('disable'); + $(this).addClass('disabled'); + that.main.socket.emit('extendObject', id, {common: {enabled: !that.main.objects[id].common.enabled}}, function (err) { + if (err) that.main.showError(err); + }); + }); + + if (!$e.find('.ui-button-icon-primary').length) { + $e.each(function () { + var id = $(this).attr('data-instance-id'); + var enabled = that.main.objects[id].common.enabled; + + if (enabled) { + $e + .addClass('instance-running') + .attr('title', _('Activated. Click to stop.')) + .find('i') + .html('pause'); + } else { + $e + .removeClass('instance-running') + .attr('title', _('Deactivated. Click to start.')) + .find('i') + .html('play_arrow'); + } + + /*$e.button({icons: {primary: enabled ? 'ui-icon-pause': 'ui-icon-play'}, text: false}) + //.css({'background-color': enabled ? 'lightgreen' : '#FF9999'}) + //.css({'background-color': enabled ? 'rgba(0, 255, 0, 0.15)' : 'rgba(255, 0, 0, 0.15)'}) + .css({'background-color': enabled ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)'}) + .attr('title', enabled ? _('Activated. Click to stop.') : _('Deactivated. Click to start.'));*/ + }); + } + + $e = that.$grid.find('.instance-web' + id).off('click') + .on('click', function () { + var _link = $(this).data('link'); + if (typeof _link === 'object') { + var menu = ''; + for (var m in _link) { + if (!_link.hasOwnProperty(m)) continue; + if (m === '__first') continue; + var port = _link[m].match(/^https?:\/\/[-.\w]+:(\d+)\/?/); + var https = _link[m].match(/^https:\/\//); + + menu += ''; + } + menu += ''; + + var $instancesMenu = $('#instances-menu'); + if ($instancesMenu.data('inited')) $instancesMenu.menu('destroy'); + + var pos = $(this).position(); + $instancesMenu.html(menu); + if (!$instancesMenu.data('inited')) { + $instancesMenu.data('inited', true); + $instancesMenu.mouseleave(function () { + $(this).hide(); + }); + } + + $instancesMenu.menu().css({ + left: pos.left, + top: pos.top + }).show(); + + $instancesMenu.find('.instances-menu-link').off('click').on('click', function () { + if ($(this).data('link')) window.open($(this).data('link'), $(this).data('instance-id')); + $('#instances-menu').hide(); + }); + + } else { + window.open($(this).data('link'), $(this).data('instance-id')); + } + }); + if (typeof url === 'object') $e.data('link', url); + + /*if (!$e.find('.ui-button-icon-primary').length) { + $e.button({icons: {primary: 'ui-icon-image'}, text: false}).attr('title', _('open web page')); + } else { + $e.removeClass('disabled'); + //$e.button('enable'); + }*/ + }; + + this.resize = function (width, height) { + //this.$grid.setGridHeight(height - 150).setGridWidth(width); + }; +} diff --git a/src/js/adminIntro.js b/src/js/adminIntro.js new file mode 100644 index 0000000..d6857f7 --- /dev/null +++ b/src/js/adminIntro.js @@ -0,0 +1,587 @@ +function Intro(main) { + 'use strict'; + + var that = this; + + this.$tab = $('#tab-intro'); + this.$tiles = this.$tab.find('.tab-intro-cards'); + this.main = main; + this.inited = false; + this.$template = $('#intro-template'); + + function readInstances(callback) { + that.main.socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, doc) { + if (err) { + if (callback) callback (err, []); + } else { + if (doc.rows.length === 0) { + if (callback) callback (err, []); + } else { + that.main.instances = []; + for (var i = 0; i < doc.rows.length; i++) { + that.main.instances.push(doc.rows[i].id); + that.main.objects[doc.rows[i].id] = doc.rows[i].value; + } + if (callback) callback(err, that.main.instances); + } + } + }); + } + + /** + * Format number in seconds to time text + * @param {!number} seconds + * @returns {String} + */ + function formatSeconds(seconds) { + var days = Math.floor(seconds / (3600 * 24)); + seconds %= 3600 * 24; + var hours = Math.floor(seconds / 3600); + if (hours < 10) { + hours = '0' + hours; + } + seconds %= 3600; + var minutes = Math.floor(seconds / 60); + if (minutes < 10) { + minutes = '0' + minutes; + } + seconds %= 60; + seconds = Math.floor(seconds); + if (seconds < 10) { + seconds = '0' + seconds; + } + var text = ''; + if (days) { + text += days + ' ' + _('daysShortText') + ' '; + } + text += hours + ':' + minutes + ':' + seconds; + + return text; + } + + /** + * Format bytes to MB or GB + * @param {!number} bytes + * @returns {String} + */ + function formatRam(bytes) { + var GB = Math.floor(bytes / (1024 * 1024 * 1024) * 10) / 10; + bytes %= (1024 * 1024 * 1024); + var MB = Math.floor(bytes / (1024 * 1024) * 10) / 10; + var text = ''; + if (GB > 1) { + text += GB + ' GB '; + } else { + text += MB + ' MB '; + } + + return text; + } + + function formatSpeed(mhz) { + return mhz + ' MHz'; + } + + /** + * FormatObject for host informations + * @type type + */ + var formatInfo = { + 'Uptime': formatSeconds, + 'System uptime': formatSeconds, + 'RAM': formatRam, + 'Speed': formatSpeed, + 'Disk size': that.main.formatBytes, + 'Disk free': that.main.formatBytes + }; + + function copyToClipboard(e) { + var $input = $('",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:k.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("