iobroker.javascript
Version:
Rules Engine for ioBroker
657 lines (606 loc) • 26.7 kB
HTML
<html>
<head>
<!-- Materialze style -->
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="adminWords.js"></script>
<!-- you have to define 2 functions in the global scope: -->
<script type="text/javascript">
var gSettings;
var mapTimer;
var mapLoaded;
var useOpenLayers = true;
var OSM = null;
/**
* List of forbidden Locations for a mirror directory
* relative to the default data directory
* @type {*[]}
*/
var forbiddenMirrorLocations = [
'backup-objects',
'files',
'backitup',
'../backups',
'../node_modules',
'../log'
];
var ioBrokerDataDir = '/opt/iobroker'; // Use as default because most systems are linux
var pathSep = '/'; // Use as default because most systems are linux
function setValue(id, value, onChange) {
var $value = $('#' + id + '.value');
if ($value.attr('type') === 'checkbox') {
$value.prop('checked', value).on('change', function () {
var id = $(this).attr('id');
if (id === 'useSystemGPS') {
showHideSettings();
updateMap();
}
gSettings[id] = $(this).prop('checked');
onChange();
});
} else {
$value.val(value).on('change', function () {
var id = $(this).attr('id');
gSettings[id] = $(this).val();
if (id === 'longitude' || id === 'latitude') {
updateMap();
}
onChange();
}).on('keyup', function() {
$(this).trigger('change');
});
}
}
function initMap() {
mapLoaded = true;
updateMap(true);
}
function positionReady(position) {
$('#latitude').val(position.coords.latitude.toFixed(8));
$('#longitude').val(position.coords.longitude.toFixed(8)).trigger('change');
M.updateTextFields();
updateMap();
}
function updateMap(immediately) {
var lng;
var lat;
if ($('#useSystemGPS').prop('checked')) {
if (systemConfig && systemConfig.common && (systemConfig.common.longitude || systemConfig.common.latitude)) {
lng = systemConfig.common.longitude;
lat = systemConfig.common.latitude;
}
} else {
lng = $('#longitude').val();
lat = $('#latitude').val();
if (!lng || !lat) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(positionReady);
}
}
}
lng = lng || 0;
lat = lat || 0;
if (useOpenLayers) {
// OPEN STREET MAPS
if (typeof ol === 'undefined') {
return setTimeout(updateMap, 200);
}
var point = ol.proj.fromLonLat([parseFloat(lng), parseFloat(lat)]);
if (!OSM) {
OSM = {};
OSM.markerSource = new ol.source.Vector();
OSM.markerStyle = new ol.style.Style({
image: new ol.style.Icon(/** @type {olx.style.IconOptions} */ ({
anchor: [0.5, 49],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
opacity: 0.75,
src: './pin.png'
}))
});
OSM.oMap = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({source: new ol.source.OSM()}),
new ol.layer.Vector({
source: OSM.markerSource,
style: OSM.markerStyle,
})
],
view: new ol.View({center: point, zoom: 17})
});
OSM.marker = new ol.Feature({
geometry: new ol.geom.Point(point),
name: _('Your home')
});
OSM.markerSource.addFeature(OSM.marker);
OSM.oMap.on('singleclick', function (event){
if (!$('#useSystemGPS').prop('checked')) {
var lonLat = ol.proj.toLonLat(event.coordinate);
$('#longitude').val(Math.round(lonLat[0] * 1000000) / 1000000);
$('#latitude').val(Math.round(lonLat[1] * 1000000) / 1000000).trigger('change');
M.updateTextFields();
}
});
}
var zoom = OSM.oMap.getView().getZoom();
OSM.marker.setGeometry(new ol.geom.Point(point));
OSM.oMap.setView(new ol.View({center: point, zoom: zoom}));
} else {
// GOOGLE MAPS
if (!mapLoaded) return;
if (!immediately) {
clearTimeout(mapTimer);
mapTimer = setTimeout(function () {
updateMap(true);
}, 1000);
return;
}
if (mapTimer) {
clearTimeout(mapTimer);
mapTimer = null;
}
if (lat || lng) {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 14,
center: {lat: parseFloat(lat), lng: parseFloat(lng)}
});
var marker = new google.maps.Marker({
position: {lat: parseFloat(lat), lng: parseFloat(lng)},
map: map,
title: _('Your home')
});
}
}
}
function showHideSettings() {
if ($('#useSystemGPS').prop('checked')) {
$('.local-coords').hide();
} else {
$('.local-coords').show();
}
}
function load(settings, onChange) {
if (!useOpenLayers) {
var key1 = 'AIzaSyCIrBRZfZAE';
var key2 = '_0C1OplAUy7OXhiWLoZc3eY';
var key = key1 + key2;
// load google API
$.ajax({
// please do not misuse this api key!
url: 'https://maps.googleapis.com/maps/api/js?key=' + key + '&signed_in=true&callback=initMap',
dataType: 'script',
cache: true
});
} else {
mapLoaded = true;
// load OSM
$.ajax({
url: '../../lib/js/ol.js',
dataType: 'script',
cache: true
}).done(function () {
//setTimeout(updateMap, 500);
});
$.ajax({
url: '../../lib/css/ol.css',
success: function (data) {
$('head').append('<style>' + data + '</style>');
}
});
}
if (settings.sunriseEvent === undefined) {
settings.sunriseEvent = 'nightEnd';
}
if (settings.sunriseOffset === undefined) {
settings.sunriseOffset = 0;
}
if (settings.sunriseLimitStart === undefined) {
settings.sunriseLimitStart = '06:00';
}
if (settings.sunriseLimitEnd === undefined) {
settings.sunriseLimitEnd = '12:00';
}
if (settings.sunsetEvent === undefined) {
settings.sunsetEvent = 'dusk';
}
if (settings.sunsetOffset === undefined) {
settings.sunsetOffset = 0;
}
if (settings.sunsetLimitStart === undefined) {
settings.sunsetLimitStart = '18:00';
}
if (settings.sunsetLimitEnd === undefined) {
settings.sunsetLimitEnd = '23:00';
}
if (settings.useSystemGPS === undefined) {
settings.useSystemGPS = (systemConfig.common.longitude && systemConfig.common.latitude);
}
if (settings.enableSendToHost === undefined) {
settings.enableSendToHost = false;
}
if (settings.enableExec === undefined) {
settings.enableExec = false;
}
if (settings.mirrorInstance === undefined) {
settings.mirrorInstance = 0;
}
if (settings.maxSetStatePerMinute === undefined) {
settings.maxSetStatePerMinute = 1000;
}
$('.astro-calc').on('change', function () {
updateAstroTimes();
}).on('keyup', function () {
$(this).attr('type') === 'text' && $(this).trigger('change');
});
getAdapterInstances('javascript', function (res) {
if (!res) return;
var $mirrorInstance = $('#mirrorInstance');
for (var t = 0; t < res.length; t++) {
$mirrorInstance.append('<option value="' + res[t]._id.split('.').pop() + '">' + res[t]._id.replace('system.adapter.', '') + '</option>');
}
$mirrorInstance.val(settings.mirrorInstance);
$mirrorInstance.select();
});
// get the iobroker data dir and path separator
sendTo(null, 'getIoBrokerDataDir', null, function (response) {
if (!response) {
return;
}
if (response.dataDir) {
ioBrokerDataDir = response.dataDir;
}
if (response.sep) {
pathSep = response.sep;
}
console.log('Update ioBroker Directory to ' + ioBrokerDataDir + ' with pathSeparator ' + pathSep);
});
gSettings = settings;
$('.value').each(function () {
var id = $(this).attr('id');
setValue(id, settings[id] || '', onChange);
});
showHideSettings();
list2chips('.libraries', settings.libraries || '', onChange);
list2chips('.libraryTypings', settings.libraryTypings || '', onChange);
$('.tab-astro').on('click', function () {
updateMap();
setTimeout(function() {
OSM && OSM.oMap && OSM.oMap.updateSize();
}, 400);
});
updateAstroTimes();
onChange(false);
M.updateTextFields();
}
function list2chips(selector, list, onChange) {
var chips = list.split(/[,;\s]+/);
var data = [];
for (var c = 0; c < chips.length; c++) {
if (chips[c] && chips[c].trim()) {
data.push({tag: chips[c].trim()});
}
}
$(selector).chips({
data: data,
placeholder: _('Module names'),
secondaryPlaceholder: _('Add module'),
onChipAdd: onChange,
onChipDelete: onChange
});
}
function chips2list(selector) {
var data = $(selector).chips('getData');
var text = [];
for (var lib = 0; lib < data.length; lib++) {
text.push(data[lib].tag);
}
return text.join(' ');
}
function convertGrad(obj, attr) {
// Extract ° " and '
var m = obj[attr].match(/([0-9]+)°(\s*)(([0-9]+)')?(\s*)(([0-9]+)")?(\s*)([A-Z]+)?/);
if (m) {
var grad = m[1];
var min = m[4];
var sec = m[7];
var dir = m[9];
if (grad !== undefined && grad !== null) {
// Graddez = Grad + ( Minuten * 60 + Sekunden ) / 3600
var result = parseInt(grad, 10);
if (min !== undefined && min !== null) {
result += parseInt(min, 10) / 60;
if (sec !== undefined && sec !== null) {
result += parseInt(sec, 10) / 3600;
}
}
if (dir) {
dir = dir.toLowerCase();
if (dir === 's' || dir === 'w') {
result = (-1) * result;
}
}
obj[attr] = result.toFixed(6);
return true;
} else {
return false;
}
} else {
showMessage(_('Invalid format. Use A°B\'C"D'));
return false;
}
}
function formatTime(date) {
const h = String(date.getHours());
const m = String(date.getMinutes());
const s = String(date.getSeconds());
return `${h.padStart(2, '0')}:${m.padStart(2, '0')}:${s.padStart(2, '0')}`;
}
function updateAstroTimes() {
const obj = {};
const useSystem = $('#useSystemGPS').prop('checked');
$('.astro-calc').each(function () {
var id = $(this).attr('id');
var val = $(this).val();
if (!useSystem || (id !== 'longitude' && id !== 'latitude')) {
obj[id] = val;
}
});
sendTo(null, 'calcAstro', obj, function (times) {
const nextSunrise = formatTime(new Date(times.nextSunrise.date));
const nextSunset = formatTime(new Date(times.nextSunset.date));
if (nextSunrise !== times.nextSunrise.serverTime) {
$('#nextSunrise').html(`${times.nextSunrise.serverTime} (Local time: ${nextSunrise})`);
} else {
$('#nextSunrise').html(times.nextSunrise.serverTime);
}
if (nextSunset !== times.nextSunset.serverTime) {
$('#nextSunset').html(`${times.nextSunset.serverTime} (Local time: ${nextSunset})`);
} else {
$('#nextSunset').html(times.nextSunset.serverTime);
}
});
}
function pathJoin(parts, sep){
var separator = sep || '/';
var replace = new RegExp(separator + '{1,}', 'g');
return parts.join(separator).replace(replace, separator);
}
function save(callback) {
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();
}
});
obj.libraries = chips2list('.libraries');
obj.libraryTypings = chips2list('.libraryTypings');
if (obj.latitude.indexOf('°') !== -1) {
if (!convertGrad(obj, 'latitude')) {
return;
}
$('#latitude').val(obj.latitude);
}
if (obj.longitude.indexOf('°') !== -1) {
if (!convertGrad(obj, 'longitude')) {
return;
}
$('#longitude').val(obj.longitude);
}
if (obj.mirrorPath) {
var ioBDataDir = ioBrokerDataDir + pathSep;
for (var dir of forbiddenMirrorLocations) {
dir = pathJoin([ioBDataDir, dir], pathSep) + pathSep;
if (dir.includes(obj.mirrorPath) || obj.mirrorPath.startsWith(dir)) {
showError('This path is not allowed for mirroring. Please change it.');
return;
}
}
}
callback(obj);
}
</script>
<style>
.top-padding {
padding: 1rem;
}
#map {
width: 100%;
height: calc(100% - 60px);
}
</style>
</head>
<body>
<!-- you have to put your config page in a div with id adapter-container -->
<div class="m adapter-container">
<div class="row">
<div class="col s12">
<ul class="tabs">
<li class="tab col s5"><a href="#tab-main" class="translate active">Main settings</a></li>
<li class="tab col s5 tab-astro"><a href="#tab-astro" class="translate">Astro settings</a></li>
</ul>
</div>
<div id="tab-main" class="col s12 page">
<div class="row">
<div class="col s6 m4 l2">
<img src="javascript.png" class="logo" alt="logo"/>
</div>
</div>
<div class="row">
<div class="col s12">
<label class="translate">Additional npm modules:</label>
<div class="chips libraries"></div>
</div>
</div>
<div class="row">
<div class="col s12">
<label class="translate">Activate syntax help for these npm modules:</label>
<div class="chips libraryTypings"></div>
</div>
</div>
<div class="row">
<div class="col s8">
<input id="mirrorPath" class="value" type="text"/>
<label for="mirrorPath" class="translate">Mirror scripts to file path</label>
</div>
<div class="col s4">
<select id="mirrorInstance" class="value"></select>
<label for="mirrorInstance" class="translate">Instance, that do mirroring</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m6">
<input id="enableSetObject" class="value" type="checkbox"/>
<span for="enableSetObject" class="translate">Enable command "setObject":</span>
</div>
<div class="input-field col s12 m6">
<input id="enableSendToHost" class="value" type="checkbox"/>
<span for="enableSendToHost" class="translate">Enable command "sendToHost":</span>
</div>
<div class="input-field col s12 m6">
<input id="enableExec" class="value" type="checkbox"/>
<span for="enableExec" class="translate">Enable command "exec":</span>
</div>
<div class="input-field col s12 m6">
<input type="checkbox" id="subscribe" class="value" />
<span for="subscribe" class="translate">Do not subscribe all states on start:</span>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input class="value validate" placeholder="1000" id="maxSetStatePerMinute" type="number" min="20" max="100000">
<label for="maxSetStatePerMinute" class="translate">Maximum setState requests per Minute per Script</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input class="value validate" placeholder="100" id="maxTriggersPerScript" type="number" min="20" max="100000">
<label for="maxTriggersPerScript" class="translate">Maximum triggers per Script (until warning)</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="checkbox" id="allowSelfSignedCerts" class="value" />
<span for="allowSelfSignedCerts" class="translate">Allow self-signed certificates for URL requests</span>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input class="value validate" id="gptKey" type="text">
<label for="gptKey" class="translate">ChatGPT API key</label>
</div>
</div>
</div>
<div id="tab-astro" class="col s12 page">
<div class="row top-padding">
<div class="col s12 m6">
<div class="row">
<div class="input-field col s12">
<input id="useSystemGPS" type="checkbox" class="value filled-in" />
<span for="useSystemGPS" class="translate">Use system settings:</span>
</div>
</div>
<div class="row local-coords">
<div class="input-field col s12">
<input id="latitude" type="text" class="value astro-calc" />
<label for="latitude" class="translate">Latitude °:</label>
</div>
</div>
<div class="row local-coords">
<div class="input-field col s12">
<input id="longitude" type="text" class="value astro-calc" />
<label for="longitude" class="translate">Longitude °:</label>
</div>
</div>
<div class="row local-coords">
<div class="col s12">
<p class="translate">Help</p>
</div>
</div>
<div class="row">
<h6 class="translate">Day time settings</h6>
</div>
<div class="row">
<div class="input-field col s12">
<div class="input-field col s4">
<select id="sunriseEvent" class="value astro-calc" >
<option value="nightEnd" class="translate">sch_astro_nightEnd</option>
<option value="nauticalDawn" class="translate">sch_astro_nauticalDawn</option>
<option value="dawn" class="translate">sch_astro_dawn</option>
<option value="sunrise" class="translate">sch_astro_sunrise</option>
<option value="sunriseEnd" class="translate">sch_astro_sunriseEnd</option>
<option value="goldenHourEnd" class="translate">sch_astro_goldenHourEnd</option>
</select>
<span for="sunriseEvent" class="translate">Used as start of the daytime</span>
</div>
<div class="input-field col s2">
<input id="sunriseOffset" type="number" class="value astro-calc" />
<label for="sunriseOffset" class="translate">Offset</label>
<span>± </span><span class="translate">in minutes</span>
</div>
<div class="input-field col s3">
<input id="sunriseLimitStart" type="text" class="value astro-calc" />
<label for="sunriseLimitStart" class="translate">But not earlier</label>
</div>
<div class="input-field col s3">
<input id="sunriseLimitEnd" type="text" class="value astro-calc" />
<label for="sunriseLimitEnd" class="translate">And not later</label>
</div>
</div>
<div><span class="translate">Next sunrise:</span> <span id="nextSunrise"></span></div>
</div>
<div class="row">
<div class="input-field col s12">
<div class="input-field col s4">
<select id="sunsetEvent" class="value astro-calc">
<option value="goldenHour" class="translate">sch_astro_goldenHour</option>
<option value="sunsetStart" class="translate">sch_astro_sunsetStart</option>
<option value="sunset" class="translate">sch_astro_sunset</option>
<option value="dusk" class="translate">sch_astro_dusk</option>
<option value="nauticalDusk" class="translate">sch_astro_nauticalDusk</option>
<option value="night" class="translate">sch_astro_night</option>
</select>
<span for="sunsetEvent" class="translate">Used as end of the daytime</span>
</div>
<div class="input-field col s2">
<input id="sunsetOffset" type="number" class="value astro-calc" />
<label for="sunsetOffset" class="translate">Offset</label>
<span>± </span><span class="translate">in minutes</span>
</div>
<div class="input-field col s3">
<input id="sunsetLimitStart" type="text" class="value astro-calc" />
<label for="sunsetLimitStart" class="translate">But not earlier</label>
</div>
<div class="input-field col s3">
<input id="sunsetLimitEnd" type="text" class="value astro-calc" />
<label for="sunsetLimitEnd" class="translate">And not later</label>
</div>
</div>
</div>
<div class="row">
<div><span class="translate">Next sunset:</span> <span id="nextSunset"></span></div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="checkbox" id="createAstroStates" class="value" />
<span for="createAstroStates" class="translate">Create states for all astro times</span>
</div>
</div>
</div>
<div class="col s12 m6 map">
<div id="map"></div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>