Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
/.idea
|
||||
/tmp
|
||||
10
.npmignore
Normal file
10
.npmignore
Normal file
@@ -0,0 +1,10 @@
|
||||
Gruntfile.js
|
||||
tasks
|
||||
node_modules
|
||||
.idea
|
||||
.git
|
||||
/node_modules
|
||||
test
|
||||
tmp
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
23
.travis.yml
Normal file
23
.travis.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
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
|
||||
- npm install winston@2.3.1
|
||||
- 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production'
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
222
Gruntfile.js
Normal file
222
Gruntfile.js
Normal file
@@ -0,0 +1,222 @@
|
||||
// To use this file in WebStorm, right click on the file name in the Project Panel (normally left) and select "Open Grunt Console"
|
||||
|
||||
/** @namespace __dirname */
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
"use strict";
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
var srcDir = __dirname + '/';
|
||||
var pkg = grunt.file.readJSON('package.json');
|
||||
var adaptName = pkg.name.substring('iobroker.'.length);
|
||||
var iopackage = grunt.file.readJSON('io-package.json');
|
||||
var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
|
||||
var newname = grunt.option('name');
|
||||
var author = grunt.option('author') || '@@Author@@';
|
||||
var email = grunt.option('email') || '@@email@@';
|
||||
var fs = require('fs');
|
||||
|
||||
// check arguments
|
||||
if (process.argv[2] == 'rename') {
|
||||
console.log('Try to rename to "' + newname + '"');
|
||||
if (!newname) {
|
||||
console.log('Please write the new template name, like: "grunt rename --name=mywidgetset" --author="Author Name"');
|
||||
process.exit();
|
||||
}
|
||||
if (newname.indexOf(' ') != -1) {
|
||||
console.log('Name may not have space in it.');
|
||||
process.exit();
|
||||
}
|
||||
if (newname.toLowerCase() != newname) {
|
||||
console.log('Name must be lower case.');
|
||||
process.exit();
|
||||
}
|
||||
if (fs.existsSync(__dirname + '/admin/template-rest.png')) {
|
||||
fs.renameSync(__dirname + '/admin/template-rest.png', __dirname + '/admin/' + newname + '.png');
|
||||
}
|
||||
if (fs.existsSync(__dirname + '/widgets/template-rest.html')) {
|
||||
fs.renameSync(__dirname + '/widgets/template-rest.html', __dirname + '/widgets/' + newname + '.html');
|
||||
}
|
||||
if (fs.existsSync(__dirname + '/widgets/template/js/template-rest.js')) {
|
||||
fs.renameSync(__dirname + '/widgets/template/js/template-rest.js', __dirname + '/widgets/template/js/' + newname + '.js');
|
||||
}
|
||||
if (fs.existsSync(__dirname + '/widgets/template-rest')) {
|
||||
fs.renameSync(__dirname + '/widgets/template-rest', __dirname + '/widgets/' + newname);
|
||||
}
|
||||
}
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: pkg,
|
||||
|
||||
replace: {
|
||||
version: {
|
||||
options: {
|
||||
patterns: [
|
||||
{
|
||||
match: /version: *"[\.0-9]*"/,
|
||||
replacement: 'version: "' + version + '"'
|
||||
},
|
||||
{
|
||||
match: /"version": *"[\.0-9]*",/g,
|
||||
replacement: '"version": "' + version + '",'
|
||||
},
|
||||
{
|
||||
match: /version: *"[\.0-9]*",/g,
|
||||
replacement: 'version: "' + version + '",'
|
||||
}
|
||||
]
|
||||
},
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'package.json',
|
||||
srcDir + 'io-package.json'
|
||||
],
|
||||
dest: srcDir
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'widgets/' + adaptName + '.html'
|
||||
],
|
||||
dest: srcDir + 'widgets'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'widgets/' + adaptName + '/js/' + adaptName + '.js'
|
||||
],
|
||||
dest: srcDir + 'widgets/' + adaptName + '/js/'
|
||||
}
|
||||
]
|
||||
},
|
||||
name: {
|
||||
options: {
|
||||
patterns: [
|
||||
{
|
||||
match: /template\-rest/g,
|
||||
replacement: newname
|
||||
},
|
||||
{
|
||||
match: /Template\-rest/g,
|
||||
replacement: newname ? (newname[0].toUpperCase() + newname.substring(1)) : 'Template-rest'
|
||||
},
|
||||
{
|
||||
match: /@@Author@@/g,
|
||||
replacement: author
|
||||
},
|
||||
{
|
||||
match: /@@email@@/g,
|
||||
replacement: email
|
||||
}
|
||||
]
|
||||
},
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'io-package.json',
|
||||
srcDir + 'LICENSE',
|
||||
srcDir + 'package.json',
|
||||
srcDir + 'README.md',
|
||||
srcDir + 'io-package.json',
|
||||
srcDir + 'main.js',
|
||||
srcDir + 'Gruntfile.js'
|
||||
],
|
||||
dest: srcDir
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'widgets/' + newname +'.html'
|
||||
],
|
||||
dest: srcDir + 'widgets'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'admin/index.html'
|
||||
],
|
||||
dest: srcDir + 'admin'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'widgets/' + newname + '/js/' + newname +'.js'
|
||||
],
|
||||
dest: srcDir + 'widgets/' + newname + '/js'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: [
|
||||
srcDir + 'widgets/' + newname + '/css/*.css'
|
||||
],
|
||||
dest: srcDir + 'widgets/' + newname + '/css'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
// Javascript code styler
|
||||
jscs: require(__dirname + '/tasks/jscs.js'),
|
||||
// Lint
|
||||
jshint: require(__dirname + '/tasks/jshint.js'),
|
||||
|
||||
http: {
|
||||
get_hjscs: {
|
||||
options: {
|
||||
url: 'https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/tasks/jscs.js'
|
||||
},
|
||||
dest: 'tasks/jscs.js'
|
||||
},
|
||||
get_jshint: {
|
||||
options: {
|
||||
url: 'https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/tasks/jshint.js'
|
||||
},
|
||||
dest: 'tasks/jshint.js'
|
||||
},
|
||||
get_jscsRules: {
|
||||
options: {
|
||||
url: 'https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/tasks/jscsRules.js'
|
||||
},
|
||||
dest: 'tasks/jscsRules.js'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-replace');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-jscs');
|
||||
grunt.loadNpmTasks('grunt-http');
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'http',
|
||||
'replace:version',
|
||||
'jshint',
|
||||
'jscs'
|
||||
]);
|
||||
|
||||
grunt.registerTask('prepublish', [
|
||||
'http',
|
||||
'replace:version'
|
||||
]);
|
||||
|
||||
grunt.registerTask('p', [
|
||||
'http',
|
||||
'replace:version'
|
||||
]);
|
||||
|
||||
grunt.registerTask('rename', [
|
||||
'replace:name'
|
||||
]);
|
||||
};
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 '@@Author@@' <'@@email@@'>
|
||||
|
||||
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.
|
||||
90
README.md
Normal file
90
README.md
Normal file
@@ -0,0 +1,90 @@
|
||||

|
||||
# ioBroker.template-rest
|
||||
=================
|
||||
|
||||
This adapter is a template for the creation of an ioBroker adapter. You do not need it, at least that you plan developing your own adapter.
|
||||
|
||||
This is extension of [ioBroker.template](https://github.com/ioBroker/ioBroker.template) adapter with REST service.
|
||||
By default the WEB server will be started to serve HTTP GET/POST requests. It can be started as secure(https) or unsecure(http) and with authentication or without.
|
||||
|
||||
Additionally is shown how to poll some other URL for JSON data and parse them.
|
||||
|
||||
##Steps
|
||||
1. download and unpack this packet from github ```https://github.com/ioBroker/ioBroker.template/archive/master.zip```
|
||||
or clone git repository ```git clone https://github.com/ioBroker/ioBroker.template.git```
|
||||
|
||||
2. download required npm packets. Write in ioBroker.template directory:
|
||||
|
||||
```npm install```
|
||||
|
||||
3. set name of this template. Call
|
||||
|
||||
```grunt rename --name=mynewname --email=email@mail.com --author="Author Name"```
|
||||
|
||||
*mynewname* must be **lower** case and with no spaces.
|
||||
|
||||
If grunt is not available, install grunt globally:
|
||||
|
||||
```npm install -g grunt-cli```
|
||||
|
||||
4. rename directory from *ioBroker.template* (can be *ioBroker.template-master*) to *iobroker.mynewname*
|
||||
|
||||
5. to use this template you should copy it into *.../iobroker/node_modules* directory and then create an instance for it with iobroker.admin
|
||||
|
||||
6. create your adapter:
|
||||
|
||||
* you might want to start with main.js (code running within iobroker) and admin/index.html (the adapter settings page).
|
||||
|
||||
* [Adapter-Development-Documentation](https://github.com/ioBroker/ioBroker/wiki/Adapter-Development-Documentation),
|
||||
|
||||
* [Installation, setup and first steps with an ioBroker Development Environment](https://github.com/ioBroker/ioBroker/wiki/Installation,-setup-and-first-steps-with-an-ioBroker-Development-Environment)
|
||||
|
||||
* [Write and debug vis widgets](https://github.com/ioBroker/ioBroker/wiki/How-to-debug-vis-and-to-write-own-widget-set)
|
||||
|
||||
* files under the www folders are made available under http://<iobrokerIP>:8082/<adapter-name>/
|
||||
* for this to work the iobroker.vis adapter has to be installed
|
||||
* delete this folder if you do not plan to export any files this way
|
||||
* call ```iobroker upload <adapter-name>``` after you change files in the www folder to get the new files uploaded to vis
|
||||
* the widget folder contains an example of a vis widget
|
||||
* you might want to start with *widget/<adapter-name>.html* and *widget/js/<adapter-name>.js*
|
||||
* call ```iobroker visdebug <adapter-name>``` to enable debugging and upload widget to "vis". (This works only from V0.7.15 of js-controller)
|
||||
* If you do not plan to export any widget then delete the whole widget folder and remove the ```"restartAdapters": ["vis"]``` statement from *io-package.json*
|
||||
* After admin/index.html is changed you must execute ```iobroker upload mynewname``` to see changes in admin console. The same is valid for any files in *admin* and *www* directory
|
||||
|
||||
7. change version: edit package.json and then call ```grunt p``` in your adapter directory.
|
||||
|
||||
8. share it with the community
|
||||
|
||||
## Usage
|
||||
After the adapter is started, you can check following requests:
|
||||
|
||||
http://ip:9090/api/system.adapter.admin.0.memHeapTotal
|
||||
http://ip:9090/api/plain/system.adapter.admin.0.memHeapTotal
|
||||
|
||||
## Changelog
|
||||
|
||||
#### 0.1.0
|
||||
* (bluefox) initial release
|
||||
|
||||
## License
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 @@Author@@ <@@email@@>
|
||||
|
||||
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.
|
||||
212
admin/index.html
Normal file
212
admin/index.html
Normal file
@@ -0,0 +1,212 @@
|
||||
<html>
|
||||
|
||||
<!-- these 4 files always have to be included -->
|
||||
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/>
|
||||
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
|
||||
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
|
||||
|
||||
|
||||
<!-- optional: use jqGrid
|
||||
<link rel="stylesheet" type="text/css" href="../../lib/css/jqGrid/ui.jqgrid-4.5.4.css"/>
|
||||
<script type="text/javascript" src="../../lib/js/jqGrid/jquery.jqGrid-4.5.4.min.js"></script>
|
||||
<script type="text/javascript" src="../../lib/js/jqGrid/i18n/grid.locale-all.js"></script>
|
||||
-->
|
||||
|
||||
<!-- optional: use multiselect
|
||||
<link rel="stylesheet" type="text/css" href="../../lib/css/jquery.multiselect-1.13.css"/>
|
||||
<script type="text/javascript" src="../../lib/js/jquery.multiselect-1.13.min.js"></script>
|
||||
-->
|
||||
|
||||
<!-- these two file always have to be included -->
|
||||
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
|
||||
<script type="text/javascript" src="../../js/translate.js"></script>
|
||||
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
|
||||
|
||||
|
||||
<!-- you have to define 2 functions in the global scope: -->
|
||||
<script type="text/javascript">
|
||||
|
||||
// Dictionary (systemDictionary is global variable from adapter-settings.js)
|
||||
systemDictionary = {
|
||||
"template-rest adapter settings": {"de": "Beispiel", "ru": "Пример"},
|
||||
"test1": {"en": "Test 1", "de": "Test 1", "ru": "Тест 1"},
|
||||
"test2": {"en": "Test 2", "de": "Test 2", "ru": "Тест 2"},
|
||||
|
||||
"Run as:": {"de": "Laufen unter Anwender:", "ru": "Запустить от пользователя:"},
|
||||
"IP:": {"de": "IP:", "ru": "IP:"},
|
||||
"Port:": {"de": "Port:", "ru": "Порт:"},
|
||||
"Secure(HTTPS):": {"de": "Verschlüsselung(HTTPS):", "ru": "Шифрование(HTTPS):"},
|
||||
"Authentication:": {"de": "Authentifikation:", "ru": "Аутентификация:"},
|
||||
"Listen on all IPs": {"en": "Listen on all IPs", "de": "An allen IP Adressen hören", "ru": "Открыть сокет на всех IP адресах"},
|
||||
"help_tip": {
|
||||
"en": "On save the adapter restarts with new configuration immediately",
|
||||
"de": "Beim Speichern von Einstellungen der Adapter wird sofort neu gestartet.",
|
||||
"ru": "Сразу после сохранения настроек драйвер перезапуститься с новыми значениями"
|
||||
},
|
||||
"Public certificate:": {"en": "Public certificate:", "de": "Publikzertifikat:", "ru": "'Public' сертификат:"},
|
||||
"Private certificate:": {"en": "Private certificate:", "de": "Privatzertifikat:", "ru": "'Private' сертификат:"},
|
||||
"Chained certificate:": {"en": "Chained certificate:", "de": "Kettenzertifikat:", "ru": "'Chained' сертификат:"},
|
||||
"Let's Encrypt settings": {
|
||||
"en": "Let's Encrypt settings",
|
||||
"de": "Einstellungen Let's Encrypt",
|
||||
"ru": "Настройкт Let's Encrypt"
|
||||
},
|
||||
"Use Lets Encrypt certificates:": {
|
||||
"en": "Use Let's Encrypt certificates:",
|
||||
"de": "Benutzen Let's Encrypt Zertifikate:",
|
||||
"ru": "Использовать сертификаты Let's Encrypt:"
|
||||
},
|
||||
"Use this instance for automatic update:": {
|
||||
"en": "Use this instance for automatic update:",
|
||||
"de": "Benutze diese Instanz für automatische Updates:",
|
||||
"ru": "Обновлять сертификаты в этом драйвере:"
|
||||
},
|
||||
"Port to check the domain:": {
|
||||
"en": "Port to check the domain:",
|
||||
"de": "Port um die Domain zu prüfen:",
|
||||
"ru": "Порт для проверки доменного имени:"
|
||||
}
|
||||
};
|
||||
|
||||
function showHideSettings() {
|
||||
if ($('#secure').prop('checked')) {
|
||||
$('#_certPublic').show();
|
||||
$('#_certPrivate').show();
|
||||
$('#_certChained').show();
|
||||
$('.le-settings').show();
|
||||
|
||||
if ($('#leEnabled').prop('checked')) {
|
||||
$('.le-sub-settings').show();
|
||||
if ($('#leUpdate').prop('checked')) {
|
||||
$('.le-sub-settings-update').show();
|
||||
} else {
|
||||
$('.le-sub-settings-update').hide();
|
||||
}
|
||||
} else {
|
||||
$('.le-sub-settings').hide();
|
||||
}
|
||||
} else {
|
||||
$('#_certPublic').hide();
|
||||
$('#_certPrivate').hide();
|
||||
$('#_certChained').hide();
|
||||
$('#auth').prop('checked', false);
|
||||
$('.le-settings').hide();
|
||||
}
|
||||
if ($('#auth').prop('checked')) {
|
||||
$('#secure').prop('checked', true);
|
||||
$('#defaultUser').val('admin');
|
||||
$('.defaultUser').hide();
|
||||
$('#_ttl').show();
|
||||
} else {
|
||||
$('.defaultUser').show();
|
||||
$('#_ttl').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// the function loadSettings has to exist ...
|
||||
function load(settings, onChange) {
|
||||
if (!settings) return;
|
||||
|
||||
// this functions are loaded from library
|
||||
getIPs(function(ips) {
|
||||
for (var i = 0; i < ips.length; i++) {
|
||||
$('#bind').append('<option value="' + ips[i].address + '">' + ips[i].name + '</option>');
|
||||
}
|
||||
$('#bind.value').val(settings.bind);
|
||||
});
|
||||
|
||||
|
||||
// example: select elements with id=key and class=value and insert value
|
||||
$('.value').each(function () {
|
||||
var key = $(this).attr('id');
|
||||
// example: select elements with id=key and class=value and insert value
|
||||
if ($('#' + key + '.value').attr('type') === 'checkbox') {
|
||||
$('#' + key + '.value').prop('checked', settings[key]).change(function() {
|
||||
onChange();
|
||||
});
|
||||
} else {
|
||||
$('#' + key + '.value').val(settings[key]).change(function() {
|
||||
onChange();
|
||||
}).keyup(function() {
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
});
|
||||
// Signal to admin, that no changes yet
|
||||
onChange(false);
|
||||
|
||||
// this functions are loaded from library
|
||||
fillSelectCertificates('#certPublic', 'public', settings.certPublic);
|
||||
fillSelectCertificates('#certPrivate', 'private', settings.certPrivate);
|
||||
fillSelectCertificates('#certChained', 'chained', settings.certChained);
|
||||
fillUsers('#defaultUser', settings.defaultUser);
|
||||
|
||||
$('#auth').change(function () {
|
||||
if ($(this).prop('checked')) $('#secure').prop('checked', true);
|
||||
showHideSettings();
|
||||
});
|
||||
$('#secure').change(function () {
|
||||
showHideSettings();
|
||||
});
|
||||
showHideSettings();
|
||||
}
|
||||
|
||||
// ... and the function save has to exist.
|
||||
// you have to make sure the callback is called with the settings object as first param!
|
||||
function save(callback) {
|
||||
// example: select elements with class=value and build settings object
|
||||
var obj = {};
|
||||
$('.value').each(function () {
|
||||
var $this = $(this);
|
||||
if ($this.attr('type') === 'checkbox') {
|
||||
obj[$this.attr('id')] = $this.prop('checked');
|
||||
} else {
|
||||
obj[$this.attr('id')] = $this.val();
|
||||
}
|
||||
});
|
||||
callback(obj);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- you have to put your config page in a div with id adapter-container -->
|
||||
<div id="adapter-container">
|
||||
|
||||
<table><tr>
|
||||
<td><img src="template-rest.png"/></td>
|
||||
<td><h3 class="translate">template-rest adapter settings</h3></td>
|
||||
</tr></table>
|
||||
<table>
|
||||
<tr><td class="translate">test1</td><td> <input class="value" id="test1"/></td></tr>
|
||||
<tr><td class="translate">test2</td><td> <input class="value" id="test2"/></td></tr>
|
||||
<tr><td colspan="2"><h4 class="translate">Server settings</h4></td></tr>
|
||||
<tr><td class="translate">IP:</td><td> <select class="value" id="bind"></select></td></tr>
|
||||
<tr><td class="translate">Port:</td><td> <input class="value" id="port" size="5" maxlength="5"/></td></tr>
|
||||
<tr><td class="translate">Secure(HTTPS):</td><td> <input class="value" id="secure" type="checkbox" /></td></tr>
|
||||
<tr><td class="translate">Authentication:</td><td><input class="value" id="auth" type="checkbox" /></td></tr>
|
||||
<tr id="_certPublic">
|
||||
<td class="translate">Public certificate:</td>
|
||||
<td><select id="certPublic" class="value"></select></td>
|
||||
</tr>
|
||||
<tr id="_certPrivate">
|
||||
<td class="translate">Private certificate:</td>
|
||||
<td><select id="certPrivate" class="value"></select></td>
|
||||
</tr>
|
||||
<tr id="_certChained">
|
||||
<td class="translate">Chained certificate:</td>
|
||||
<td><select id="certChained" class="value"></select></td>
|
||||
</tr>
|
||||
<tr class="defaultUser"><td class="translate">Run as:</td><td><select class="value" id="defaultUser" /></td></tr>
|
||||
<tr><td colspan="2"><h4 class="translate">Polling settings</h4></td></tr>
|
||||
<tr><td class="translate">URL:</td><td> <input class="value" id="pollURL"/></td></tr>
|
||||
<tr><td class="translate">Interval:</td><td> <input class="value" id="interval" size="7" maxlength="7"/></td></tr>
|
||||
<tr><td colspan="2"> </td></tr>
|
||||
<tr class="le-settings"><td colspan="2"><h3 class="translate">Let's Encrypt settings</h3></tr>
|
||||
<tr class="le-settings"><td><label for="leEnabled" class="translate">Use Lets Encrypt certificates:</label></td><td><input class="value" id="leEnabled" type="checkbox" /></td></tr>
|
||||
<tr class="le-settings le-sub-settings"><td><label for="leUpdate" class="translate">Use this instance for automatic update:</label></td><td><input class="value" id="leUpdate" type="checkbox" /></td></tr>
|
||||
<tr class="le-settings le-sub-settings le-sub-settings-update"><td><label for="lePort" class="translate">Port to check the domain:</label></td><td><input class="value number" id="lePort" type="number" size="5" maxlength="5" /></td></tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</html>
|
||||
BIN
admin/template-rest.png
Normal file
BIN
admin/template-rest.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
25
appveyor.yml
Normal file
25
appveyor.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
- npm install winston@2.3.1
|
||||
- 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production'
|
||||
test_script:
|
||||
- echo %cd%
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm test
|
||||
build: 'off'
|
||||
65
io-package.json
Normal file
65
io-package.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"common": {
|
||||
"name": "template-rest",
|
||||
"version": "0.1.0",
|
||||
"title": "Javascript/Node.js based template-rest adapter",
|
||||
"desc": {
|
||||
"en": "ioBroker template-rest Adapter",
|
||||
"de": "ioBroker template-rest Adapter",
|
||||
"ru": "ioBroker template-rest драйвер как образец"
|
||||
},
|
||||
"news": {
|
||||
"0.1.0" :{
|
||||
"en": "initial checkin",
|
||||
"de": "initial checkin",
|
||||
"ru": "первая версия"
|
||||
},
|
||||
"0.0.9" :{
|
||||
"en": "previous version",
|
||||
"de": "vorherige Version",
|
||||
"ru": "предыдущая версия"
|
||||
},
|
||||
"0.0.8" :{
|
||||
"en": "older version (up to 5 versions)",
|
||||
"de": "noch vorherige Version (bis zu 5 Versionen)",
|
||||
"ru": "более предыдущая версия (до 5ти версий)"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
"my Name <my@email.com>"
|
||||
],
|
||||
"platform": "Javascript/Node.js",
|
||||
"mode": "daemon",
|
||||
"icon": "template-rest.png",
|
||||
"enabled": true,
|
||||
"extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.template-rest/master/admin/template-rest.png",
|
||||
"keywords": ["template-rest", "REST"],
|
||||
"readme": "https://github.com/ioBroker/ioBroker.template-rest/blob/master/README.md",
|
||||
"loglevel": "info",
|
||||
"type": "communication",
|
||||
"dependencies": [{"js-controller": ">=0.12.0"}]
|
||||
},
|
||||
"native": {
|
||||
"test1": true,
|
||||
"test2": 42,
|
||||
|
||||
"port": 9090,
|
||||
"auth": false,
|
||||
"secure": false,
|
||||
"bind": "0.0.0.0",
|
||||
"defaultUser": "admin",
|
||||
"certPublic": "defaultPublic",
|
||||
"certPrivate": "defaultPrivate",
|
||||
"certChained": "",
|
||||
|
||||
"pollURL": "",
|
||||
"interval": 30000,
|
||||
|
||||
"leEnabled": false,
|
||||
"leUpdate": false,
|
||||
"leCheckPort": 80
|
||||
},
|
||||
"objects": [
|
||||
|
||||
]
|
||||
}
|
||||
83
lib/utils.js
Normal file
83
lib/utils.js
Normal file
@@ -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 = [
|
||||
'iobroker.js-controller',
|
||||
'ioBroker.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 == null) {
|
||||
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;
|
||||
318
main.js
Normal file
318
main.js
Normal file
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
*
|
||||
* template adapter
|
||||
*
|
||||
*
|
||||
* file io-package.json comments:
|
||||
*
|
||||
* {
|
||||
* "common": {
|
||||
* "name": "template", // name has to be set and has to be equal to adapters folder name and main file name excluding extension
|
||||
* "version": "0.0.0", // use "Semantic Versioning"! see http://semver.org/
|
||||
* "title": "Node.js template Adapter", // Adapter title shown in User Interfaces
|
||||
* "authors": [ // Array of authord
|
||||
* "name <mail@template.com>"
|
||||
* ]
|
||||
* "desc": "template adapter", // Adapter description shown in User Interfaces. Can be a language object {de:"...",ru:"..."} or a string
|
||||
* "platform": "Javascript/Node.js", // possible values "javascript", "javascript/Node.js" - more coming
|
||||
* "mode": "daemon", // possible values "daemon", "schedule", "subscribe"
|
||||
* "schedule": "0 0 * * *" // cron-style schedule. Only needed if mode=schedule
|
||||
* "loglevel": "info" // Adapters Log Level
|
||||
* },
|
||||
* "native": { // the native object is available via adapter.config in your adapters code - use it for configuration
|
||||
* "test1": true,
|
||||
* "test2": 42,
|
||||
*
|
||||
* "port": 9090, // port where the REST service will be started. It should has name "port", because it can be changed from console.
|
||||
* "auth": false, // if basic authentication is required. If enabled, the secure connection should be used.
|
||||
* "secure": false, // If SSL communication must be used. Should has the name "secure", because it can be changed from console.
|
||||
* "bind": "0.0.0.0", // Specific address, where the server will be started. "0.0.0.0" means that it will be available for all addresses. Should has the name "bind", because it can be changed from console.
|
||||
* "defaultUser": "admin", // If authentication is disabled, here can be specified as which user the requests will be done to iobroker DB.
|
||||
* "certPublic": "defaultPublic",// Required for SSL communication: name of some public certificate from iobroker DB
|
||||
* "certPrivate": "defaultPrivate"// Required for SSL communication: name of some private key from iobroker DB
|
||||
*
|
||||
* "pollURL": "", // Request all 30 seconds the JSON from this URL, parse it and store in ioBroker
|
||||
* "interval": 30000 // polling interval
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* You can read here, how REST API could be implementer: https://scotch.io/tutorials/build-a-restful-api-using-node-and-express-4
|
||||
*/
|
||||
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// you have to require the utils module and call adapter function
|
||||
var utils = require(__dirname + '/lib/utils'); // Get common adapter utils
|
||||
|
||||
// load additional libraries
|
||||
var express = require('express'); // call express
|
||||
var request = null; // will be initialized later if polling enabled
|
||||
|
||||
// you have to call the adapter function and pass a options object
|
||||
// name has to be set and has to be equal to adapters folder name and main file name excluding extension
|
||||
// adapter will be restarted automatically every time as the configuration changed, e.g system.adapter.template.0
|
||||
var adapter = utils.Adapter('template-rest');
|
||||
var LE = require(utils.controllerDir + '/lib/letsencrypt.js');
|
||||
|
||||
// REST server
|
||||
var webServer = null;
|
||||
var app = null;
|
||||
var router = null;
|
||||
var timer = null;
|
||||
|
||||
// is called when adapter shuts down - callback has to be called under any circumstances!
|
||||
adapter.on('unload', function (callback) {
|
||||
try {
|
||||
adapter.log.info('cleaned everything up...');
|
||||
if (webServer) {
|
||||
webServer.close();
|
||||
webServer = null;
|
||||
}
|
||||
if (timer) clearInterval(timer);
|
||||
callback();
|
||||
} catch (e) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
// is called if a subscribed object changes
|
||||
adapter.on('objectChange', function (id, obj) {
|
||||
// Warning, obj can be null if it was deleted
|
||||
adapter.log.info('objectChange ' + id + ' ' + JSON.stringify(obj));
|
||||
});
|
||||
|
||||
// is called if a subscribed state changes
|
||||
adapter.on('stateChange', function (id, state) {
|
||||
// Warning, state can be null if it was deleted
|
||||
adapter.log.info('stateChange ' + id + ' ' + JSON.stringify(state));
|
||||
|
||||
// you can use the ack flag to detect if it is status (true) or command (false)
|
||||
if (state && !state.ack) {
|
||||
adapter.log.info('ack is not set!');
|
||||
}
|
||||
});
|
||||
|
||||
// Some message was sent to adapter instance over message box. Used by email, pushover, text2speech, ...
|
||||
adapter.on('message', function (obj) {
|
||||
if (typeof obj == 'object' && obj.message) {
|
||||
if (obj.command == 'send') {
|
||||
// e.g. send email or pushover or whatever
|
||||
console.log('send command');
|
||||
|
||||
// Send response in callback if required
|
||||
if (obj.callback) adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// is called when databases are connected and adapter received configuration.
|
||||
// start here!
|
||||
adapter.on('ready', function () {
|
||||
main();
|
||||
});
|
||||
|
||||
function addRoutes(_router) {
|
||||
// test route to make sure everything is working (accessed at GET http://localhost:9090/api)
|
||||
_router.get('/', function (req, res) {
|
||||
res.json({message: 'Welcome to our JSON REST api!'});
|
||||
});
|
||||
|
||||
_router.get('/plain/', function(req, res) {
|
||||
res.send('Welcome to our text REST api!');
|
||||
});
|
||||
|
||||
// on routes that end in /plain/:id
|
||||
// ----------------------------------------------------
|
||||
_router.route('/plain/:id')
|
||||
// get the bear with that id (accessed at GET http://localhost:8080/api/plain/:id)
|
||||
.get(function (req, res) {
|
||||
adapter.getForeignState(req.params.id, {user: req.user}, function (err, state) {
|
||||
if (err) {
|
||||
res.status(500).send(err);
|
||||
} else if (!state) {
|
||||
res.status(500).send('not found');
|
||||
} else {
|
||||
res.send('Value: ' + state.val);
|
||||
}
|
||||
});
|
||||
})// create a handler for post (accessed at POST http://localhost:8080/api/bears)
|
||||
.post(function (req, res) {
|
||||
adapter.setForeignState(req.params.id, req.body, {user: req.user}, function (err, state) {
|
||||
if (err) {
|
||||
res.status(500).send(err);
|
||||
} else if (!state) {
|
||||
res.status(500).send('not found');
|
||||
} else {
|
||||
res.send('Value used');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// handler for get over JSON
|
||||
_router.route('/:id')
|
||||
// get the bear with that id (accessed at GET http://localhost:8080/api/plain/:id)
|
||||
.get(function (req, res) {
|
||||
adapter.getForeignState(req.params.id, {user: req.user}, function (err, state) {
|
||||
if (err) {
|
||||
res.status(500).send({error: err});
|
||||
} else {
|
||||
res.json(state);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initWebServer(settings) {
|
||||
app = express();
|
||||
router = express.Router();
|
||||
|
||||
// install authentication
|
||||
app.get('/', function (req, res) {
|
||||
if (settings.auth) {
|
||||
var b64auth = (req.headers.authorization || '').split(' ')[1] || '';
|
||||
var loginPass = new Buffer(b64auth, 'base64').toString().split(':');
|
||||
var login = loginPass[0];
|
||||
var password = loginPass[1];
|
||||
|
||||
// Check in ioBroker user and password
|
||||
adapter.checkPassword(login, password, function (result) {
|
||||
if (!result) {
|
||||
adapter.log.error('Wrong user or password: ' + login);
|
||||
res.set('WWW-Authenticate', 'Basic realm="nope"');
|
||||
res.status(401).send('You shall not pass.');
|
||||
} else {
|
||||
req.user = login;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
req.user = settings.defaultUser;
|
||||
}
|
||||
});
|
||||
|
||||
// add route cases
|
||||
addRoutes(router);
|
||||
|
||||
// REGISTER OUR ROUTES -------------------------------
|
||||
// all of our routes will be prefixed with /api
|
||||
app.use('/api', router);
|
||||
|
||||
if (settings.port) {
|
||||
if (settings.secure) {
|
||||
if (!adapter.config.certificates) {
|
||||
adapter.log.error('certificates missing');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
webServer = LE.createServer(app, adapter.config, adapter.config.certificates, adapter.config.leConfig, adapter.log);
|
||||
|
||||
adapter.getPort(settings.port, function (port) {
|
||||
if (port != settings.port && !adapter.config.findNextPort) {
|
||||
adapter.log.error('port ' + settings.port + ' already in use');
|
||||
process.exit(1);
|
||||
}
|
||||
webServer.listen(port, settings.bind, function() {
|
||||
adapter.log.info('Server listening on http' + (settings.secure ? 's' : '') + '://' + settings.bind + ':' + port);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
adapter.log.error('port missing');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function pollData() {
|
||||
// you can read about module "request" here: https://www.npmjs.com/package/request
|
||||
request = request || require('request'); // load library
|
||||
request(adapter.config.pollURL, function (error, response, body) {
|
||||
if (error || response.statusCode !== 200) {
|
||||
adapter.log.error(error || response.statusCode);
|
||||
} else {
|
||||
// try to parse answer
|
||||
try {
|
||||
var data = JSON.parse(body);
|
||||
// do something with data
|
||||
adapter.log.info(JSON.parse(data));
|
||||
|
||||
} catch (e) {
|
||||
adapter.log.error('Cannot parse answer');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
// The adapters config (in the instance object everything under the attribute "native") is accessible via
|
||||
// adapter.config:
|
||||
adapter.log.info('config test1: ' + adapter.config.test1);
|
||||
adapter.log.info('config test1: ' + adapter.config.test2);
|
||||
|
||||
/**
|
||||
*
|
||||
* For every state in the system there has to be also an object of type state
|
||||
*
|
||||
* Here a simple template for a boolean variable named "testVariable"
|
||||
*
|
||||
* Because every adapter instance uses its own unique namespace variable names can't collide with other adapters variables
|
||||
*
|
||||
*/
|
||||
|
||||
adapter.setObject('testVariable', {
|
||||
type: 'state',
|
||||
common: {
|
||||
name: 'testVariable',
|
||||
type: 'boolean',
|
||||
role: 'indicator'
|
||||
},
|
||||
native: {}
|
||||
});
|
||||
|
||||
// in this template all states changes inside the adapters namespace are subscribed
|
||||
adapter.subscribeStates('*');
|
||||
|
||||
|
||||
/**
|
||||
* setState examples
|
||||
*
|
||||
* you will notice that each setState will cause the stateChange event to fire (because of above subscribeStates cmd)
|
||||
*
|
||||
*/
|
||||
|
||||
// the variable testVariable is set to true as command (ack=false)
|
||||
adapter.setState('testVariable', true);
|
||||
|
||||
// same thing, but the value is flagged "ack"
|
||||
// ack should be always set to true if the value is received from or acknowledged from the target system
|
||||
adapter.setState('testVariable', {val: true, ack: true});
|
||||
|
||||
// same thing, but the state is deleted after 30s (getState will return null afterwards)
|
||||
adapter.setState('testVariable', {val: true, ack: true, expire: 30});
|
||||
|
||||
|
||||
// try to load certificates
|
||||
if (adapter.config.secure) {
|
||||
// Load certificates
|
||||
// Load certificates
|
||||
adapter.getCertificates(function (err, certificates, leConfig) {
|
||||
adapter.config.certificates = certificates;
|
||||
adapter.config.leConfig = leConfig;
|
||||
initWebServer(adapter.config);
|
||||
});
|
||||
} else {
|
||||
initWebServer(adapter.config);
|
||||
}
|
||||
|
||||
// Convert port to number
|
||||
adapter.config.interval = parseInt(adapter.config.interval, 10);
|
||||
|
||||
// If interval and URl are set => poll it every X milliseconds
|
||||
if (adapter.config.interval && adapter.config.pollURL) {
|
||||
// initial poll
|
||||
pollData();
|
||||
// poll every x milliseconds
|
||||
timer = setInterval(pollData, adapter.config.interval);
|
||||
}
|
||||
}
|
||||
47
package.json
Normal file
47
package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "iobroker.template-rest",
|
||||
"version": "0.1.0",
|
||||
"description": "ioBroker template-rest Adapter",
|
||||
"author": {
|
||||
"name": "@@Author@@",
|
||||
"email": "@@email@@"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "@@Author@@",
|
||||
"email": "@@email@@"
|
||||
}
|
||||
],
|
||||
"homepage": "https://github.com/ioBroker/ioBroker.template-rest",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"ioBroker",
|
||||
"template-rest",
|
||||
"REST API"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ioBroker/ioBroker.template-rest"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.13.4",
|
||||
"request": "^2.72.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-replace": "^1.0.1",
|
||||
"grunt-contrib-jshint": "^1.1.0",
|
||||
"grunt-jscs": "^3.0.1",
|
||||
"grunt-http": "^2.2.0",
|
||||
"mocha": "^4.1.0",
|
||||
"chai": "^4.1.2"
|
||||
},
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "node node_modules/mocha/bin/mocha --exit"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ioBroker/ioBroker.template-rest/issues"
|
||||
},
|
||||
"readmeFilename": "README.md"
|
||||
}
|
||||
17
tasks/jscs.js
Normal file
17
tasks/jscs.js
Normal file
@@ -0,0 +1,17 @@
|
||||
var srcDir = __dirname + "/../";
|
||||
|
||||
module.exports = {
|
||||
all: {
|
||||
src: [
|
||||
srcDir + "*.js",
|
||||
srcDir + "lib/*.js",
|
||||
srcDir + "adapter/example/*.js",
|
||||
srcDir + "tasks/**/*.js",
|
||||
srcDir + "www/**/*.js",
|
||||
'!' + srcDir + "www/lib/**/*.js",
|
||||
'!' + srcDir + 'node_modules/**/*.js',
|
||||
'!' + srcDir + 'adapter/*/node_modules/**/*.js'
|
||||
],
|
||||
options: require('./jscsRules.js')
|
||||
}
|
||||
};
|
||||
36
tasks/jscsRules.js
Normal file
36
tasks/jscsRules.js
Normal file
@@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
force: true,
|
||||
"requireCurlyBraces": ["else", "for", "while", "do", "try", "catch"], /*"if",*/
|
||||
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireParenthesesAroundIIFE": true,
|
||||
"disallowSpacesInFunctionDeclaration": {"beforeOpeningRoundBrace": true},
|
||||
"disallowSpacesInNamedFunctionExpression": {"beforeOpeningRoundBrace": true},
|
||||
"requireSpacesInFunctionExpression": {"beforeOpeningCurlyBrace": true},
|
||||
"requireSpacesInAnonymousFunctionExpression": {"beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true},
|
||||
"requireSpacesInNamedFunctionExpression": {"beforeOpeningCurlyBrace": true},
|
||||
"requireSpacesInFunctionDeclaration": {"beforeOpeningCurlyBrace": true},
|
||||
"disallowMultipleVarDecl": true,
|
||||
"requireBlocksOnNewline": true,
|
||||
"disallowEmptyBlocks": true,
|
||||
"disallowSpacesInsideObjectBrackets": true,
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"disallowSpaceAfterObjectKeys": true,
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
//"requireAlignedObjectValues": "all",
|
||||
"requireOperatorBeforeLineBreak": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
// "disallowLeftStickedOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
// "requireRightStickedOperators": ["!"],
|
||||
// "requireSpaceAfterBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
//"disallowSpaceAfterBinaryOperators": [","],
|
||||
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
|
||||
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
|
||||
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
|
||||
"requireSpaceAfterBinaryOperators": ["?", ">", ",", ">=", "<=", "<", "+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
|
||||
//"validateIndentation": 4,
|
||||
//"validateQuoteMarks": { "mark": "\"", "escape": true },
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowKeywordsOnNewLine": ["else", "catch"]
|
||||
|
||||
};
|
||||
17
tasks/jshint.js
Normal file
17
tasks/jshint.js
Normal file
@@ -0,0 +1,17 @@
|
||||
var srcDir = __dirname + "/../";
|
||||
|
||||
module.exports = {
|
||||
options: {
|
||||
force: true
|
||||
},
|
||||
all: [
|
||||
srcDir + "*.js",
|
||||
srcDir + "lib/*.js",
|
||||
srcDir + "adapter/example/*.js",
|
||||
srcDir + "tasks/**/*.js",
|
||||
srcDir + "www/**/*.js",
|
||||
'!' + srcDir + "www/lib/**/*.js",
|
||||
'!' + srcDir + 'node_modules/**/*.js',
|
||||
'!' + srcDir + 'adapter/*/node_modules/**/*.js'
|
||||
]
|
||||
};
|
||||
728
test/lib/setup.js
Normal file
728
test/lib/setup.js
Normal file
@@ -0,0 +1,728 @@
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
// check if tmp directory exists
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var child_process = require('child_process');
|
||||
var rootDir = path.normalize(__dirname + '/../../');
|
||||
var pkg = require(rootDir + 'package.json');
|
||||
var debug = typeof v8debug === 'object';
|
||||
pkg.main = pkg.main || 'main.js';
|
||||
|
||||
var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
|
||||
adapterName = adapterName[adapterName.length - 2];
|
||||
var adapterStarted = false;
|
||||
|
||||
function getAppName() {
|
||||
var parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 3].split('.')[0];
|
||||
}
|
||||
|
||||
var appName = getAppName().toLowerCase();
|
||||
|
||||
var objects;
|
||||
var states;
|
||||
|
||||
var pid = null;
|
||||
|
||||
function copyFileSync(source, target) {
|
||||
|
||||
var targetFile = target;
|
||||
|
||||
//if target is a directory a new file with the same name will be created
|
||||
if (fs.existsSync(target)) {
|
||||
if ( fs.lstatSync( target ).isDirectory() ) {
|
||||
targetFile = path.join(target, path.basename(source));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(targetFile, fs.readFileSync(source));
|
||||
}
|
||||
catch (err) {
|
||||
console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)");
|
||||
}
|
||||
}
|
||||
|
||||
function copyFolderRecursiveSync(source, target, ignore) {
|
||||
var files = [];
|
||||
|
||||
var base = path.basename(source);
|
||||
if (base === adapterName) {
|
||||
base = pkg.name;
|
||||
}
|
||||
//check if folder needs to be created or integrated
|
||||
var targetFolder = path.join(target, base);
|
||||
if (!fs.existsSync(targetFolder)) {
|
||||
fs.mkdirSync(targetFolder);
|
||||
}
|
||||
|
||||
//copy
|
||||
if (fs.lstatSync(source).isDirectory()) {
|
||||
files = fs.readdirSync(source);
|
||||
files.forEach(function (file) {
|
||||
if (ignore && ignore.indexOf(file) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var curSource = path.join(source, file);
|
||||
var curTarget = path.join(targetFolder, file);
|
||||
if (fs.lstatSync(curSource).isDirectory()) {
|
||||
// ignore grunt files
|
||||
if (file.indexOf('grunt') !== -1) return;
|
||||
if (file === 'chai') return;
|
||||
if (file === 'mocha') return;
|
||||
copyFolderRecursiveSync(curSource, targetFolder, ignore);
|
||||
} else {
|
||||
copyFileSync(curSource, curTarget);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(rootDir + 'tmp')) {
|
||||
fs.mkdirSync(rootDir + 'tmp');
|
||||
}
|
||||
|
||||
function storeOriginalFiles() {
|
||||
console.log('Store original files...');
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
var f = fs.readFileSync(dataDir + 'objects.json');
|
||||
var objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) {
|
||||
objects['system.adapter.admin.0'].common.enabled = false;
|
||||
}
|
||||
if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) {
|
||||
objects['system.adapter.admin.1'].common.enabled = false;
|
||||
}
|
||||
|
||||
fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects));
|
||||
try {
|
||||
f = fs.readFileSync(dataDir + 'states.json');
|
||||
fs.writeFileSync(dataDir + 'states.json.original', f);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('no states.json found - ignore');
|
||||
}
|
||||
}
|
||||
|
||||
function restoreOriginalFiles() {
|
||||
console.log('restoreOriginalFiles...');
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
var f = fs.readFileSync(dataDir + 'objects.json.original');
|
||||
fs.writeFileSync(dataDir + 'objects.json', f);
|
||||
try {
|
||||
f = fs.readFileSync(dataDir + 'states.json.original');
|
||||
fs.writeFileSync(dataDir + 'states.json', f);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('no states.json.original found - ignore');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkIsAdapterInstalled(cb, counter, customName) {
|
||||
customName = customName || pkg.name.split('.').pop();
|
||||
counter = counter || 0;
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
console.log('checkIsAdapterInstalled...');
|
||||
|
||||
try {
|
||||
var f = fs.readFileSync(dataDir + 'objects.json');
|
||||
var objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.' + customName + '.0']) {
|
||||
console.log('checkIsAdapterInstalled: ready!');
|
||||
setTimeout(function () {
|
||||
if (cb) cb();
|
||||
}, 100);
|
||||
return;
|
||||
} else {
|
||||
console.warn('checkIsAdapterInstalled: still not ready');
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
if (counter > 20) {
|
||||
console.error('checkIsAdapterInstalled: Cannot install!');
|
||||
if (cb) cb('Cannot install');
|
||||
} else {
|
||||
console.log('checkIsAdapterInstalled: wait...');
|
||||
setTimeout(function() {
|
||||
checkIsAdapterInstalled(cb, counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsControllerInstalled(cb, counter) {
|
||||
counter = counter || 0;
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
console.log('checkIsControllerInstalled...');
|
||||
try {
|
||||
var f = fs.readFileSync(dataDir + 'objects.json');
|
||||
var objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.admin.0']) {
|
||||
console.log('checkIsControllerInstalled: installed!');
|
||||
setTimeout(function () {
|
||||
if (cb) cb();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
if (counter > 20) {
|
||||
console.log('checkIsControllerInstalled: Cannot install!');
|
||||
if (cb) cb('Cannot install');
|
||||
} else {
|
||||
console.log('checkIsControllerInstalled: wait...');
|
||||
setTimeout(function() {
|
||||
checkIsControllerInstalled(cb, counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function installAdapter(customName, cb) {
|
||||
if (typeof customName === 'function') {
|
||||
cb = customName;
|
||||
customName = null;
|
||||
}
|
||||
customName = customName || pkg.name.split('.').pop();
|
||||
console.log('Install adapter...');
|
||||
var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js';
|
||||
// make first install
|
||||
if (debug) {
|
||||
child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
checkIsAdapterInstalled(function (error) {
|
||||
if (error) console.error(error);
|
||||
console.log('Adapter installed.');
|
||||
if (cb) cb();
|
||||
});
|
||||
} else {
|
||||
// add controller
|
||||
var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
|
||||
waitForEnd(_pid, function () {
|
||||
checkIsAdapterInstalled(function (error) {
|
||||
if (error) console.error(error);
|
||||
console.log('Adapter installed.');
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function waitForEnd(_pid, cb) {
|
||||
if (!_pid) {
|
||||
cb(-1, -1);
|
||||
return;
|
||||
}
|
||||
_pid.on('exit', function (code, signal) {
|
||||
if (_pid) {
|
||||
_pid = null;
|
||||
cb(code, signal);
|
||||
}
|
||||
});
|
||||
_pid.on('close', function (code, signal) {
|
||||
if (_pid) {
|
||||
_pid = null;
|
||||
cb(code, signal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function installJsController(cb) {
|
||||
console.log('installJsController...');
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') ||
|
||||
!fs.existsSync(rootDir + 'tmp/' + appName + '-data')) {
|
||||
// try to detect appName.js-controller in node_modules/appName.js-controller
|
||||
// travis CI installs js-controller into node_modules
|
||||
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) {
|
||||
console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"');
|
||||
// copy all
|
||||
// stop controller
|
||||
console.log('Stop controller if running...');
|
||||
var _pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
_pid = child_process.exec('node ' + appName + '.js stop', {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
_pid = child_process.fork(appName + '.js', ['stop'], {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
|
||||
waitForEnd(_pid, function () {
|
||||
// copy all files into
|
||||
if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp');
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules');
|
||||
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){
|
||||
console.log('Copy js-controller...');
|
||||
copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/');
|
||||
}
|
||||
|
||||
console.log('Setup js-controller...');
|
||||
var __pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
_pid = child_process.exec('node ' + appName + '.js setup first --console', {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
__pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
waitForEnd(__pid, function () {
|
||||
checkIsControllerInstalled(function () {
|
||||
// change ports for object and state DBs
|
||||
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
console.log('Setup finished.');
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(function () {
|
||||
storeOriginalFiles();
|
||||
if (cb) cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// check if port 9000 is free, else admin adapter will be added to running instance
|
||||
var client = new require('net').Socket();
|
||||
client.connect(9000, '127.0.0.1', function() {
|
||||
console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
client.destroy();
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) {
|
||||
console.log('installJsController: no js-controller => install from git');
|
||||
|
||||
child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', {
|
||||
cwd: rootDir + 'tmp/',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
console.log('Setup js-controller...');
|
||||
var __pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
child_process.exec('node ' + appName + '.js setup first', {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
child_process.fork(appName + '.js', ['setup', 'first'], {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// let npm install admin and run setup
|
||||
checkIsControllerInstalled(function () {
|
||||
var _pid;
|
||||
|
||||
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) {
|
||||
_pid = child_process.fork(appName + '.js', ['stop'], {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
|
||||
waitForEnd(_pid, function () {
|
||||
// change ports for object and state DBs
|
||||
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(function () {
|
||||
storeOriginalFiles();
|
||||
if (cb) cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
console.log('installJsController: js-controller installed');
|
||||
if (cb) cb(false);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function copyAdapterToController() {
|
||||
console.log('Copy adapter...');
|
||||
// Copy adapter to tmp/node_modules/appName.adapter
|
||||
copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']);
|
||||
console.log('Adapter copied.');
|
||||
}
|
||||
|
||||
function clearControllerLog() {
|
||||
var dirPath = rootDir + 'tmp/log';
|
||||
var files;
|
||||
try {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
console.log('Clear controller log...');
|
||||
files = fs.readdirSync(dirPath);
|
||||
} else {
|
||||
console.log('Create controller log directory...');
|
||||
files = [];
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Cannot read "' + dirPath + '"');
|
||||
return;
|
||||
}
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Controller log cleared');
|
||||
} catch (err) {
|
||||
console.error('cannot clear log: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearDB() {
|
||||
var dirPath = rootDir + 'tmp/iobroker-data/sqlite';
|
||||
var files;
|
||||
try {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
console.log('Clear sqlite DB...');
|
||||
files = fs.readdirSync(dirPath);
|
||||
} else {
|
||||
console.log('Create controller log directory...');
|
||||
files = [];
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Cannot read "' + dirPath + '"');
|
||||
return;
|
||||
}
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Clear sqlite DB');
|
||||
} catch (err) {
|
||||
console.error('cannot clear DB: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupController(cb) {
|
||||
installJsController(function (isInited) {
|
||||
clearControllerLog();
|
||||
clearDB();
|
||||
|
||||
if (!isInited) {
|
||||
restoreOriginalFiles();
|
||||
copyAdapterToController();
|
||||
}
|
||||
// read system.config object
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
var objs;
|
||||
try {
|
||||
objs = fs.readFileSync(dataDir + 'objects.json');
|
||||
objs = JSON.parse(objs);
|
||||
}
|
||||
catch (e) {
|
||||
console.log('ERROR reading/parsing system configuration. Ignore');
|
||||
objs = {'system.config': {}};
|
||||
}
|
||||
if (!objs || !objs['system.config']) {
|
||||
objs = {'system.config': {}};
|
||||
}
|
||||
|
||||
if (cb) cb(objs['system.config']);
|
||||
});
|
||||
}
|
||||
|
||||
function startAdapter(objects, states, callback) {
|
||||
if (adapterStarted) {
|
||||
console.log('Adapter already started ...');
|
||||
if (callback) callback(objects, states);
|
||||
return;
|
||||
}
|
||||
adapterStarted = true;
|
||||
console.log('startAdapter...');
|
||||
if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) {
|
||||
try {
|
||||
if (debug) {
|
||||
// start controller
|
||||
pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
// start controller
|
||||
pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(JSON.stringify(error));
|
||||
}
|
||||
} else {
|
||||
console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main);
|
||||
}
|
||||
if (callback) callback(objects, states);
|
||||
}
|
||||
|
||||
function startController(isStartAdapter, onObjectChange, onStateChange, callback) {
|
||||
if (typeof isStartAdapter === 'function') {
|
||||
callback = onStateChange;
|
||||
onStateChange = onObjectChange;
|
||||
onObjectChange = isStartAdapter;
|
||||
isStartAdapter = true;
|
||||
}
|
||||
|
||||
if (onStateChange === undefined) {
|
||||
callback = onObjectChange;
|
||||
onObjectChange = undefined;
|
||||
}
|
||||
|
||||
if (pid) {
|
||||
console.error('Controller is already started!');
|
||||
} else {
|
||||
console.log('startController...');
|
||||
adapterStarted = false;
|
||||
var isObjectConnected;
|
||||
var isStatesConnected;
|
||||
|
||||
var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer');
|
||||
objects = new Objects({
|
||||
connection: {
|
||||
"type" : "file",
|
||||
"host" : "127.0.0.1",
|
||||
"port" : 19001,
|
||||
"user" : "",
|
||||
"pass" : "",
|
||||
"noFileCache": false,
|
||||
"connectTimeout": 2000
|
||||
},
|
||||
logger: {
|
||||
silly: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
debug: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
info: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
warn: function (msg) {
|
||||
console.warn(msg);
|
||||
},
|
||||
error: function (msg) {
|
||||
console.error(msg);
|
||||
}
|
||||
},
|
||||
connected: function () {
|
||||
isObjectConnected = true;
|
||||
if (isStatesConnected) {
|
||||
console.log('startController: started!');
|
||||
if (isStartAdapter) {
|
||||
startAdapter(objects, states, callback);
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(objects, states);
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
change: onObjectChange
|
||||
});
|
||||
|
||||
// Just open in memory DB itself
|
||||
var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer');
|
||||
states = new States({
|
||||
connection: {
|
||||
type: 'file',
|
||||
host: '127.0.0.1',
|
||||
port: 19000,
|
||||
options: {
|
||||
auth_pass: null,
|
||||
retry_max_delay: 15000
|
||||
}
|
||||
},
|
||||
logger: {
|
||||
silly: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
debug: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
info: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
warn: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
error: function (msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
},
|
||||
connected: function () {
|
||||
isStatesConnected = true;
|
||||
if (isObjectConnected) {
|
||||
console.log('startController: started!!');
|
||||
if (isStartAdapter) {
|
||||
startAdapter(objects, states, callback);
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(objects, states);
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
change: onStateChange
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stopAdapter(cb) {
|
||||
if (!pid) {
|
||||
console.error('Controller is not running!');
|
||||
if (cb) {
|
||||
setTimeout(function () {
|
||||
cb(false);
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
adapterStarted = false;
|
||||
pid.on('exit', function (code, signal) {
|
||||
if (pid) {
|
||||
console.log('child process terminated due to receipt of signal ' + signal);
|
||||
if (cb) cb();
|
||||
pid = null;
|
||||
}
|
||||
});
|
||||
|
||||
pid.on('close', function (code, signal) {
|
||||
if (pid) {
|
||||
if (cb) cb();
|
||||
pid = null;
|
||||
}
|
||||
});
|
||||
|
||||
pid.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
|
||||
function _stopController() {
|
||||
if (objects) {
|
||||
objects.destroy();
|
||||
objects = null;
|
||||
}
|
||||
if (states) {
|
||||
states.destroy();
|
||||
states = null;
|
||||
}
|
||||
}
|
||||
|
||||
function stopController(cb) {
|
||||
var timeout;
|
||||
if (objects) {
|
||||
console.log('Set system.adapter.' + pkg.name + '.0');
|
||||
objects.setObject('system.adapter.' + pkg.name + '.0', {
|
||||
common:{
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopAdapter(function () {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
|
||||
_stopController();
|
||||
|
||||
if (cb) {
|
||||
cb(true);
|
||||
cb = null;
|
||||
}
|
||||
});
|
||||
|
||||
timeout = setTimeout(function () {
|
||||
timeout = null;
|
||||
console.log('child process NOT terminated');
|
||||
|
||||
_stopController();
|
||||
|
||||
if (cb) {
|
||||
cb(false);
|
||||
cb = null;
|
||||
}
|
||||
pid = null;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Setup the adapter
|
||||
function setAdapterConfig(common, native, instance) {
|
||||
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
if (common) objects[id].common = common;
|
||||
if (native) objects[id].native = native;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects));
|
||||
}
|
||||
|
||||
// Read config of the adapter
|
||||
function getAdapterConfig(instance) {
|
||||
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
return objects[id];
|
||||
}
|
||||
|
||||
if (typeof module !== undefined && module.parent) {
|
||||
module.exports.getAdapterConfig = getAdapterConfig;
|
||||
module.exports.setAdapterConfig = setAdapterConfig;
|
||||
module.exports.startController = startController;
|
||||
module.exports.stopController = stopController;
|
||||
module.exports.setupController = setupController;
|
||||
module.exports.stopAdapter = stopAdapter;
|
||||
module.exports.startAdapter = startAdapter;
|
||||
module.exports.installAdapter = installAdapter;
|
||||
module.exports.appName = appName;
|
||||
module.exports.adapterName = adapterName;
|
||||
module.exports.adapterStarted = adapterStarted;
|
||||
}
|
||||
77
test/testApi.js
Normal file
77
test/testApi.js
Normal file
@@ -0,0 +1,77 @@
|
||||
var expect = require('chai').expect;
|
||||
var setup = require(__dirname + '/lib/setup');
|
||||
var request = require('request');
|
||||
|
||||
var objects = null;
|
||||
var states = null;
|
||||
|
||||
process.env.NO_PROXY = '127.0.0.1';
|
||||
|
||||
describe('Test RESTful API', function() {
|
||||
before('Test RESTful API: Start js-controller', function (_done) {
|
||||
this.timeout(600000); // because of first install from npm
|
||||
var brokerStarted = false;
|
||||
setup.setupController(function () {
|
||||
var config = setup.getAdapterConfig();
|
||||
// enable adapter
|
||||
config.common.enabled = true;
|
||||
config.common.loglevel = 'debug';
|
||||
config.native.port = 18183;
|
||||
setup.setAdapterConfig(config.common, config.native);
|
||||
|
||||
setup.startController(function (_objects, _states) {
|
||||
objects = _objects;
|
||||
states = _states;
|
||||
// give some time to start server
|
||||
setTimeout(function () {
|
||||
_done();
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API: get /api/plain - must return welcome text', function (done) {
|
||||
request('http://127.0.0.1:18183/api/plain', function (error, response, body) {
|
||||
console.log('get /api/plain => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
expect(body).to.be.equal('Welcome to our text REST api!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API: get /api - must return welcome text', function (done) {
|
||||
request('http://127.0.0.1:18183/api', function (error, response, body) {
|
||||
console.log('get /api => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
expect(body).to.be.equal('{"message":"Welcome to our JSON REST api!"}');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API: get /api/id - must return value', function (done) {
|
||||
request('http://127.0.0.1:18183/api/system.adapter.template-rest.0.alive', function (error, response, body) {
|
||||
console.log('get /api => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
var data = JSON.parse(body);
|
||||
expect(data.val).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API: get /api/plain/id - must return value', function (done) {
|
||||
request('http://127.0.0.1:18183/api/plain/system.adapter.template-rest.0.alive', function (error, response, body) {
|
||||
console.log('get /api => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
expect(body).to.be.equal('Value: true');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after('Test RESTful API: Stop js-controller', function (done) {
|
||||
this.timeout(6000);
|
||||
setup.stopController(function (normalTerminated) {
|
||||
console.log('Adapter normal terminated: ' + normalTerminated);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
91
test/testPackageFiles.js
Normal file
91
test/testPackageFiles.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/* jshint -W097 */
|
||||
/* jshint strict:false */
|
||||
/* jslint node: true */
|
||||
/* jshint expr: true */
|
||||
var expect = require('chai').expect;
|
||||
var fs = require('fs');
|
||||
|
||||
describe('Test package.json and io-package.json', function() {
|
||||
it('Test package files', function (done) {
|
||||
console.log();
|
||||
|
||||
var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8');
|
||||
var ioPackage = JSON.parse(fileContentIOPackage);
|
||||
|
||||
var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8');
|
||||
var npmPackage = JSON.parse(fileContentNPMPackage);
|
||||
|
||||
expect(ioPackage).to.be.an('object');
|
||||
expect(npmPackage).to.be.an('object');
|
||||
|
||||
expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist;
|
||||
expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist;
|
||||
|
||||
expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version);
|
||||
|
||||
if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) {
|
||||
console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!');
|
||||
console.log();
|
||||
}
|
||||
|
||||
expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist;
|
||||
expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist;
|
||||
|
||||
if (ioPackage.common.name.indexOf('template') !== 0) {
|
||||
if (Array.isArray(ioPackage.common.authors)) {
|
||||
expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0);
|
||||
if (ioPackage.common.authors.length === 1) {
|
||||
expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('WARNING: Testing for set authors field in io-package skipped because template adapter');
|
||||
console.log();
|
||||
}
|
||||
expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true;
|
||||
if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') {
|
||||
console.log('WARNING: titleLang is not existing in io-package.json. Please add');
|
||||
console.log();
|
||||
}
|
||||
if (
|
||||
ioPackage.common.title.indexOf('iobroker') !== -1 ||
|
||||
ioPackage.common.title.indexOf('ioBroker') !== -1 ||
|
||||
ioPackage.common.title.indexOf('adapter') !== -1 ||
|
||||
ioPackage.common.title.indexOf('Adapter') !== -1
|
||||
) {
|
||||
console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.');
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (ioPackage.common.name.indexOf('vis-') !== 0) {
|
||||
if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) {
|
||||
console.log('WARNING: Admin3 support is missing! Please add it');
|
||||
console.log();
|
||||
}
|
||||
if (ioPackage.common.materialize) {
|
||||
expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true;
|
||||
}
|
||||
}
|
||||
|
||||
var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE');
|
||||
var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8');
|
||||
if (fileContentReadme.indexOf('## Changelog') === -1) {
|
||||
console.log('Warning: The README.md should have a section ## Changelog');
|
||||
console.log();
|
||||
}
|
||||
expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true;
|
||||
if (!licenseFileExists) {
|
||||
console.log('Warning: The License should also exist as LICENSE file');
|
||||
console.log();
|
||||
}
|
||||
if (fileContentReadme.indexOf('## License') === -1) {
|
||||
console.log('Warning: The README.md should also have a section ## License to be shown in Admin3');
|
||||
console.log();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
81
test/testSsl.js
Normal file
81
test/testSsl.js
Normal file
@@ -0,0 +1,81 @@
|
||||
var expect = require('chai').expect;
|
||||
var setup = require(__dirname + '/lib/setup');
|
||||
var request = require('request');
|
||||
|
||||
var objects = null;
|
||||
var states = null;
|
||||
|
||||
process.env.NO_PROXY = '127.0.0.1';
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
describe('Test RESTful API SSL', function() {
|
||||
before('Test RESTful API SSL: Start js-controller', function (_done) {
|
||||
this.timeout(600000); // because of first install from npm
|
||||
var brokerStarted = false;
|
||||
setup.setupController(function () {
|
||||
var config = setup.getAdapterConfig();
|
||||
// enable adapter
|
||||
config.common.enabled = true;
|
||||
config.common.loglevel = 'debug';
|
||||
config.native.port = 18184;
|
||||
config.native.auth = true;
|
||||
config.native.secure = true;
|
||||
|
||||
setup.setAdapterConfig(config.common, config.native);
|
||||
|
||||
setup.startController(function (_objects, _states) {
|
||||
objects = _objects;
|
||||
states = _states;
|
||||
// give some time to start server
|
||||
setTimeout(function () {
|
||||
_done();
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API SSL: get /api/plain - must return welcome text', function (done) {
|
||||
request('https://admin:iobroker@127.0.0.1:18184/api/plain', function (error, response, body) {
|
||||
console.log('get /api/plain => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
expect(body).to.be.equal('Welcome to our text REST api!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API SSL: get /api - must return welcome text', function (done) {
|
||||
request('https://admin:iobroker@127.0.0.1:18184/api', function (error, response, body) {
|
||||
console.log('get /api => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
expect(body).to.be.equal('{"message":"Welcome to our JSON REST api!"}');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API SSL: get /api/id - must return value', function (done) {
|
||||
request('https://admin:iobroker@127.0.0.1:18184/api/system.adapter.template-rest.0.alive', function (error, response, body) {
|
||||
console.log('get /api => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
var data = JSON.parse(body);
|
||||
expect(data.val).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Test RESTful API SSL: get /api/plain/id - must return value', function (done) {
|
||||
request('https://admin:iobroker@127.0.0.1:18184/api/plain/system.adapter.template-rest.0.alive', function (error, response, body) {
|
||||
console.log('get /api => ' + body);
|
||||
expect(error).to.be.not.ok;
|
||||
expect(body).to.be.equal('Value: true');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after('Test RESTful API SSL: Stop js-controller', function (done) {
|
||||
this.timeout(6000);
|
||||
setup.stopController(function (normalTerminated) {
|
||||
console.log('Adapter normal terminated: ' + normalTerminated);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user