OVMS3/OVMS.V3/components/ovms_webserver/assets/script.js

2212 lines
199 KiB
JavaScript
Raw Normal View History

/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function A
}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=n._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}}),function(){var a;l.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,e;return c=d.getElementsByTagName("body")[0],c&&c.style?(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(d.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(e),a):void 0}}();var T=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,U=new RegExp("^(?:([+-])=|)("+T+")([a-z%]*)$","i"),V=["Top","Right","Bottom","Left"],W=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function X(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNum
marginLeft:0},function(){return a.getBoundingClientRect().left}):0))+"px":void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+V[d]+b]=f[d]||f[d-2]||f[0];return e}},Na.test(a)||(n.cssHooks[a+b].set=db)}),n.fn.extend({css:function(a,b){return Y(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ra(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return cb(this,!0)},hide:function(){return cb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){W(this)?n(this).show():n(this).hide()})}});function gb(a,b,c,d,e){return new gb.prototype.init(a,b,c,d,e)}n.Tween=gb,gb.prototype={constructor:gb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=gb.propHooks[this.prop];return a&&a.get?a.get(this):gb.propHooks._default.get(this)},run:function(a){var b,c=gb.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):gb.propHooks._default.set(this),this}},gb.prototype.init.prototype=gb.prototype,gb.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},gb.propHooks.scrollTop=gb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=gb.prototype.init,n.fx.step={};var hb,ib,jb=/^(?:toggle|show|hide)$/,kb=/queueHooks$/;function lb(){return a.setTimeout(function(){hb=void 0}),hb=n.now()}function mb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=V[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function nb(a,b,c){for(var d,e=(qb.tweeners[b]||[]).concat(qb.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ob(a,b,c){var d,e,f,g,h,i,j,k,m=this,o={},p=a.style,q=a.nodeType&&W(a),r=n._data(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,m.always(function(){m.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=n.css(a,"display"),k="none"===j?n._data(a,"olddisplay")||Ma(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(l.inlineBlockNeedsLayout&&"inline"!==Ma(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",l.shrinkWrapBlocks()||m.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],jb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(o))"inline"===("none"===j?Ma(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=n._data(a,"fxshow",{}),f&&(r.hidden=!q),q?n(a).show():m.done(function(){n(a).hide()}),m.done(function(){var b;n._removeData(a,"fxshow");for(b in o)n.style(a,b,o[b])});for(d in o)g=nb(q?r[d]:0,d,m),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function pb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expa
padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return Y(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var nc=a.jQuery,oc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=oc),b&&a.jQuery===n&&(a.jQuery=nc),n},b||(a.jQuery=a.$=n),n});
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under the MIT license
*/
if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.D
this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e<c&&"top";if("bottom"==this.affixed)return null!=c?!(e+this.unpin<=f.top)&&"bottom":!(e+g<=a-d)&&"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&e<=c?"top":null!=d&&i+j>=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>
*
* 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.
*/
(function(global, undefined) { "use strict";
var POW_2_24 = 5.960464477539063e-8,
POW_2_32 = 4294967296,
POW_2_53 = 9007199254740992;
function encode(value) {
var data = new ArrayBuffer(256);
var dataView = new DataView(data);
var lastLength;
var offset = 0;
function prepareWrite(length) {
var newByteLength = data.byteLength;
var requiredLength = offset + length;
while (newByteLength < requiredLength)
newByteLength <<= 1;
if (newByteLength !== data.byteLength) {
var oldDataView = dataView;
data = new ArrayBuffer(newByteLength);
dataView = new DataView(data);
var uint32count = (offset + 3) >> 2;
for (var i = 0; i < uint32count; ++i)
dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));
}
lastLength = length;
return dataView;
}
function commitWrite() {
offset += lastLength;
}
function writeFloat64(value) {
commitWrite(prepareWrite(8).setFloat64(offset, value));
}
function writeUint8(value) {
commitWrite(prepareWrite(1).setUint8(offset, value));
}
function writeUint8Array(value) {
var dataView = prepareWrite(value.length);
for (var i = 0; i < value.length; ++i)
dataView.setUint8(offset + i, value[i]);
commitWrite();
}
function writeUint16(value) {
commitWrite(prepareWrite(2).setUint16(offset, value));
}
function writeUint32(value) {
commitWrite(prepareWrite(4).setUint32(offset, value));
}
function writeUint64(value) {
var low = value % POW_2_32;
var high = (value - low) / POW_2_32;
var dataView = prepareWrite(8);
dataView.setUint32(offset, high);
dataView.setUint32(offset + 4, low);
commitWrite();
}
function writeTypeAndLength(type, length) {
if (length < 24) {
writeUint8(type << 5 | length);
} else if (length < 0x100) {
writeUint8(type << 5 | 24);
writeUint8(length);
} else if (length < 0x10000) {
writeUint8(type << 5 | 25);
writeUint16(length);
} else if (length < 0x100000000) {
writeUint8(type << 5 | 26);
writeUint32(length);
} else {
writeUint8(type << 5 | 27);
writeUint64(length);
}
}
function encodeItem(value) {
var i;
if (value === false)
return writeUint8(0xf4);
if (value === true)
return writeUint8(0xf5);
if (value === null)
return writeUint8(0xf6);
if (value === undefined)
return writeUint8(0xf7);
switch (typeof value) {
case "number":
if (Math.floor(value) === value) {
if (0 <= value && value <= POW_2_53)
return writeTypeAndLength(0, value);
if (-POW_2_53 <= value && value < 0)
return writeTypeAndLength(1, -(value + 1));
}
writeUint8(0xfb);
return writeFloat64(value);
case "string":
var utf8data = [];
for (i = 0; i < value.length; ++i) {
var charCode = value.charCodeAt(i);
if (charCode < 0x80) {
utf8data.push(charCode);
} else if (charCode < 0x800) {
utf8data.push(0xc0 | charCode >> 6);
utf8data.push(0x80 | charCode & 0x3f);
} else if (charCode < 0xd800) {
utf8data.push(0xe0 | charCode >> 12);
utf8data.push(0x80 | (charCode >> 6) & 0x3f);
utf8data.push(0x80 | charCode & 0x3f);
} else {
charCode = (charCode & 0x3ff) << 10;
charCode |= value.charCodeAt(++i) & 0x3ff;
charCode += 0x10000;
utf8data.push(0xf0 | charCode >> 18);
utf8data.push(0x80 | (charCode >> 12) & 0x3f);
utf8data.push(0x80 | (charCode >> 6) & 0x3f);
utf8data.push(0x80 | charCode & 0x3f);
}
}
writeTypeAndLength(3, utf8data.length);
return writeUint8Array(utf8data);
default:
var length;
if (Array.isArray(value)) {
length = value.length;
writeTypeAndLength(4, length);
for (i = 0; i < length; ++i)
encodeItem(value[i]);
} else if (value instanceof Uint8Array) {
writeTypeAndLength(2, value.length);
writeUint8Array(value);
} else {
var keys = Object.keys(value);
length = keys.length;
writeTypeAndLength(5, length);
for (i = 0; i < length; ++i) {
var key = keys[i];
encodeItem(key);
encodeItem(value[key]);
}
}
}
}
encodeItem(value);
if ("slice" in data)
return data.slice(0, offset);
var ret = new ArrayBuffer(offset);
var retView = new DataView(ret);
for (var i = 0; i < offset; ++i)
retView.setUint8(i, dataView.getUint8(i));
return ret;
}
function decode(data, tagger, simpleValue) {
// OVMS extension: if data is passed as a string, assume 8 bit stream,
// to allow decoding in browser as simple as encoding in Duktape:
if (typeof(data) == "string") {
var u8 = new Uint8Array(data.length);
for (var i = 0; i < data.length; i++)
u8[i] = data.charCodeAt(i) & 0xff;
data = u8.buffer;
}
var dataView = new DataView(data);
var offset = 0;
if (typeof tagger !== "function")
tagger = function(value) { return value; };
if (typeof simpleValue !== "function")
simpleValue = function() { return undefined; };
function commitRead(length, value) {
offset += length;
return value;
}
function readArrayBuffer(length) {
return commitRead(length, new Uint8Array(data, offset, length));
}
function readFloat16() {
var tempArrayBuffer = new ArrayBuffer(4);
var tempDataView = new DataView(tempArrayBuffer);
var value = readUint16();
var sign = value & 0x8000;
var exponent = value & 0x7c00;
var fraction = value & 0x03ff;
if (exponent === 0x7c00)
exponent = 0xff << 10;
else if (exponent !== 0)
exponent += (127 - 15) << 10;
else if (fraction !== 0)
return (sign ? -1 : 1) * fraction * POW_2_24;
tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13);
return tempDataView.getFloat32(0);
}
function readFloat32() {
return commitRead(4, dataView.getFloat32(offset));
}
function readFloat64() {
return commitRead(8, dataView.getFloat64(offset));
}
function readUint8() {
return commitRead(1, dataView.getUint8(offset));
}
function readUint16() {
return commitRead(2, dataView.getUint16(offset));
}
function readUint32() {
return commitRead(4, dataView.getUint32(offset));
}
function readUint64() {
return readUint32() * POW_2_32 + readUint32();
}
function readBreak() {
if (dataView.getUint8(offset) !== 0xff)
return false;
offset += 1;
return true;
}
function readLength(additionalInformation) {
if (additionalInformation < 24)
return additionalInformation;
if (additionalInformation === 24)
return readUint8();
if (additionalInformation === 25)
return readUint16();
if (additionalInformation === 26)
return readUint32();
if (additionalInformation === 27)
return readUint64();
if (additionalInformation === 31)
return -1;
throw "Invalid length encoding";
}
function readIndefiniteStringLength(majorType) {
var initialByte = readUint8();
if (initialByte === 0xff)
return -1;
var length = readLength(initialByte & 0x1f);
if (length < 0 || (initialByte >> 5) !== majorType)
throw "Invalid indefinite length element";
return length;
}
function appendUtf16Data(utf16data, length) {
for (var i = 0; i < length; ++i) {
var value = readUint8();
if (value & 0x80) {
if (value < 0xe0) {
value = (value & 0x1f) << 6
| (readUint8() & 0x3f);
length -= 1;
} else if (value < 0xf0) {
value = (value & 0x0f) << 12
| (readUint8() & 0x3f) << 6
| (readUint8() & 0x3f);
length -= 2;
} else {
value = (value & 0x0f) << 18
| (readUint8() & 0x3f) << 12
| (readUint8() & 0x3f) << 6
| (readUint8() & 0x3f);
length -= 3;
}
}
if (value < 0x10000) {
utf16data.push(value);
} else {
value -= 0x10000;
utf16data.push(0xd800 | (value >> 10));
utf16data.push(0xdc00 | (value & 0x3ff));
}
}
}
function decodeItem() {
var initialByte = readUint8();
var majorType = initialByte >> 5;
var additionalInformation = initialByte & 0x1f;
var i;
var length;
if (majorType === 7) {
switch (additionalInformation) {
case 25:
return readFloat16();
case 26:
return readFloat32();
case 27:
return readFloat64();
}
}
length = readLength(additionalInformation);
if (length < 0 && (majorType < 2 || 6 < majorType))
throw "Invalid length";
switch (majorType) {
case 0:
return length;
case 1:
return -1 - length;
case 2:
if (length < 0) {
var elements = [];
var fullArrayLength = 0;
while ((length = readIndefiniteStringLength(majorType)) >= 0) {
fullArrayLength += length;
elements.push(readArrayBuffer(length));
}
var fullArray = new Uint8Array(fullArrayLength);
var fullArrayOffset = 0;
for (i = 0; i < elements.length; ++i) {
fullArray.set(elements[i], fullArrayOffset);
fullArrayOffset += elements[i].length;
}
return fullArray;
}
return readArrayBuffer(length);
case 3:
var utf16data = [];
if (length < 0) {
while ((length = readIndefiniteStringLength(majorType)) >= 0)
appendUtf16Data(utf16data, length);
} else
appendUtf16Data(utf16data, length);
return String.fromCharCode.apply(null, utf16data);
case 4:
var retArray;
if (length < 0) {
retArray = [];
while (!readBreak())
retArray.push(decodeItem());
} else {
retArray = new Array(length);
for (i = 0; i < length; ++i)
retArray[i] = decodeItem();
}
return retArray;
case 5:
var retObject = {};
for (i = 0; i < length || length < 0 && !readBreak(); ++i) {
var key = decodeItem();
retObject[key] = decodeItem();
}
return retObject;
case 6:
return tagger(decodeItem(), length);
case 7:
switch (length) {
case 20:
return false;
case 21:
return true;
case 22:
return null;
case 23:
return undefined;
default:
return simpleValue(length);
}
}
}
var ret = decodeItem();
if (offset !== data.byteLength)
throw "Remaining bytes";
return ret;
}
var obj = { encode: encode, decode: decode };
if (typeof define === "function" && define.amd)
define("cbor/cbor", obj);
else if (typeof module !== "undefined" && module.exports)
module.exports = obj;
else if (!global.CBOR)
global.CBOR = obj;
})(this);
/* ovms.js | (c) Michael Balzer | https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3 */
const monthnames = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints;
if (window.loggedin == undefined)
window.loggedin = false;
/**
* Utilities
*/
function after(seconds, fn) {
return window.setTimeout(fn, seconds*1000);
}
function now() {
return Math.floor((new Date()).getTime() / 1000);
}
function encode_html(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/'/g, '&apos;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function unwrapLogLine(s) {
return String(s)
.replace(/(\S)\|+(.)/g, "$1\n……: $2");
}
function fix_minheight($el) {
if ($el.css("resize") != "none") return;
var mh = parseInt($el.css("max-height")), h = $el.outerHeight();
$el.css("min-height", mh ? Math.min(h, mh) : h);
}
function getPathURL(path) {
// TODO: use actual http.server config
if (path.startsWith('/sd/'))
return path.substr(3);
else
return '';
}
/**
* Hexadecimal encoding & decoding
* based on: https://stackoverflow.com/a/54099484 by Aaron Watters
*/
var HEX = {
to_hex_array: [],
to_byte_map: {},
// Init lookup tables:
init: function () {
for (var ord=0; ord<=0xff; ord++) {
var s = ord.toString(16);
if (s.length < 2) {
s = "0" + s;
}
this.to_hex_array.push(s);
this.to_byte_map[s] = ord;
}
},
// Encode ArrayBuffer to hexadecimal string:
// Usage example: HEX.encode(CBOR.encode({a: 42})) = "a16161182a"
encode: function (arraybuffer) {
if (!this.to_hex_array.length) this.init();
var buffer = new Uint8Array(arraybuffer);
var hex_array = [];
for (var i=0; i<buffer.length; i++) {
hex_array.push(this.to_hex_array[buffer[i]]);
}
return hex_array.join('');
},
// Decode hexadecimal string into ArrayBuffer:
// Usage example: CBOR.decode(HEX.decode("a16161182a")) = {a: 42}
decode: function (s) {
if (!this.to_hex_array.length) this.init();
var length2 = s.length;
if ((length2 % 2) != 0) {
console.error("HEX.decode: string must have length a multiple of 2");
return null;
}
var length = length2 / 2;
var buffer = new Uint8Array(length);
for (var i=0; i<length; i++) {
var i2 = i * 2;
var b = s.substring(i2, i2 + 2);
buffer[i] = this.to_byte_map[b];
}
return buffer.buffer;
}
};
/**
* AJAX Pages & Commands
*/
var page = {
uri: null,
path: null,
search: null,
params: {}
};
function setPage(uri) {
page.uri = uri;
var uriparts = uri.split("?");
page.path = uriparts[0];
page.search = uriparts[1];
page.params = {};
if (page.search) {
page.search.split("&").map(function(kv) {
var v = kv.split("=");
page.params[decodeURIComponent(v[0])] = (v[1] != null) ? decodeURIComponent(v[1]) : true;
});
}
if (page.params["nm"] == 1) $("body").addClass("night");
else if (page.params["nm"] == 0) $("body").removeClass("night");
}
function updateLocation(reload) {
page.search = '';
for (v in page.params)
page.search += "&" + encodeURIComponent(v) + "=" + encodeURIComponent(page.params[v]);
page.search = page.search.slice(1);
var hash = "#" + page.path + (page.search ? "?" + page.search : "");
if (!reload) $("#main").data("uri", hash.substr(1));
location.hash = hash;
}
function readLocation() {
var uri = location.hash.substr(1);
if (!uri.match("^/?[a-zA-Z0-9_]"))
uri = "/home";
return uri;
}
function loadPage(uri) {
if (typeof uri != "string")
uri = readLocation();
if ($("#main").data("uri") != uri) {
loaduri("#main", "get", uri, {});
}
}
function reloadpage() {
var uri = readLocation();
loaduri("#main", "get", uri, {});
}
function reloadmenu() {
$("#menu").load("/menu");
}
function login(dsturi) {
if (!dsturi)
dsturi = page.uri || "/home";
loadPage("/login?uri=" + encodeURIComponent(dsturi));
}
function logout() {
loadPage("/logout");
}
function xhrErrorInfo(request, textStatus, errorThrown) {
var txt = "";
if (request.status == 401 || request.status == 403)
txt = "Unauthorized. <a class=\"btn btn-sm btn-default\" href=\"javascript:login()\">Login</a>";
else if (request.status >= 400)
txt = "Error " + request.status + " " + request.statusText;
else if (textStatus)
txt = "Request " + textStatus + ", please retry";
else if (errorThrown)
txt = errorThrown;
return txt;
}
function setcontent(tgt, uri, text){
if (!tgt || !tgt.length) return;
tgt.find(".receiver").unsubscribe();
tgt.chart("destroy");
tgt.table("destroy");
if (tgt[0].id == "main") {
$("#nav .dropdown.open .dropdown-toggle").dropdown("toggle");
$("#nav .navbar-collapse").collapse("hide");
$("#nav li").removeClass("active");
var mi = $("#nav [href='"+uri+"']");
mi.parents("li").addClass("active");
if (tgt[0].id == "main")
window.scrollTo(0,0);
else
tgt[0].scrollIntoView();
tgt.html(text);
var $p = tgt.find(">.panel");
if ($p.length == 1) $p.addClass("panel-single");
var moduleid = $("title").data("moduleid") || "OVMS";
if (mi.length > 0)
document.title = moduleid + " " + (mi.attr("title") || mi.text());
else
document.title = moduleid + " Console";
} else {
if (tgt[0].id == "main")
window.scrollTo(0,0);
else
tgt[0].scrollIntoView();
tgt.html(text);
}
tgt.find(".get-window-resize").trigger('window-resize');
tgt.find(".receiver").subscribe().trigger("msg:metrics", metrics);
tgt.trigger("load");
}
function loaduri(target, method, uri, data){
var tgt = $(target), cont;
if (tgt.length != 1)
return false;
tgt.data("uri", uri);
if (tgt[0].id == "main") {
cont = $("html");
location.hash = "#" + uri;
setPage(uri);
} else {
cont = tgt.closest(".modal") || tgt.closest("form") || tgt;
}
$.ajax({ "type": method, "url": uri, "data": data,
"timeout": 15000,
"beforeSend": function(){
cont.addClass("loading");
},
"complete": function(){
cont.removeClass("loading");
},
"success": function(response){
setcontent(tgt, uri, response);
},
"error": function(response, xhrerror, httperror){
var text = response.responseText || httperror+"\n" || xhrerror+"\n";
if (text.search("alert") == -1) {
text = '<div id="alert" class="alert alert-danger alert-dismissable">'
+ '<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>'
+ '<strong>' + text + '</strong>'
+ '</div>';
}
setcontent(tgt, uri, text);
},
});
return true;
}
$.fn.loaduri = function(method, uri, data) {
return this.each(function() {
loaduri(this, method, uri, data);
});
};
function standardTextFilter(msg) {
if (msg.error)
return '<div class="bg-danger">'+msg.error+'</div>';
else
return $('<div/>').text(msg.text).html();
}
function loadjs(command, target, filter, timeout) {
if (!command) return null;
var args = {};
if (typeof command == "object") {
args = command;
} else {
args.command = command;
}
args.type = "js";
return loadcmd(args, target, filter, timeout);
}
function loadcmd(command, target, filter, timeout) {
var $output, outmode = "", args = {};
if (!command) return null;
if (typeof command == "object") {
args = command;
} else {
args.command = command;
}
if (typeof filter == "number") {
timeout = filter; filter = null;
}
if (typeof target == "function") {
filter = target; target = null;
}
if (target == null) {
$output = $(null);
}
else if (typeof target == "object") {
$output = target;
}
else if (target.startsWith("+")) {
outmode = "+";
$output = $(target.substr(1));
} else {
$output = $(target);
}
if (!filter)
filter = standardTextFilter;
if (!timeout) {
if (args["type"] != "js" && /^(test |ota |copen .* scan)/.test(args.command))
timeout = 300;
else
timeout = 20;
}
var lastlen = 0, xhr, timeouthd;
var checkabort = function() {
if (xhr.readyState != 4)
xhr.abort("timeout");
};
var add_output = function(addhtml) {
if (addhtml == null || !$output.length) {
outmode = "+";
return;
}
if (outmode == "") { $output.empty(); outmode = "+"; }
var autoscroll = ($output.get(0).scrollTop + $output.innerHeight()) >= $output.get(0).scrollHeight;
$output.append(addhtml);
$output.closest('.get-window-resize').trigger('window-resize');
if (autoscroll) $output.scrollTop($output.get(0).scrollHeight);
};
xhr = $.ajax({ "type": "post", "url": "/api/execute", "data": args,
"timeout": 0,
"beforeSend": function() {
if ($output.length) {
$output.addClass("loading");
fix_minheight($output);
}
timeouthd = window.setTimeout(checkabort, timeout*1000);
},
"complete": function() {
window.clearTimeout(timeouthd);
if ($output.length) {
$output.removeClass("loading");
fix_minheight($output);
}
},
"xhrFields": {
onprogress: function(ev) {
var request = ev.currentTarget;
if (request.status != 200) return;
var addtext = request.response.substring(lastlen);
lastlen = request.response.length;
add_output(filter({ "request": request, "text": addtext }));
window.clearTimeout(timeouthd);
timeouthd = window.setTimeout(checkabort, timeout*1000);
},
},
"success": function(response, textStatus, request) {
var addtext = response.substring(lastlen);
add_output(filter({ "request": request, "text": addtext }));
},
"error": function(request, textStatus, errorThrown) {
var cmdinfo = (args.command.length > 200) ? args.command.substr(0,200)+"[…]" : args.command;
console.log("loadcmd '" + cmdinfo + "' ERROR: status=" + textStatus + ", httperror=" + errorThrown);
var txt = xhrErrorInfo(request, textStatus, errorThrown);
add_output(filter({ "request": request, "error": txt }));
},
});
return xhr;
}
$.fn.loadcmd = function(command, filter, timeout) {
return this.each(function() {
loadcmd(command, $(this), filter, timeout);
});
};
/**
* WebSocket Connection
*/
var monitorTimer, last_monotonic = 0;
var ws, ws_inhibit = 0;
var metrics = {};
var shellhist = [""], shellhpos = 0;
var loghist = [];
const loghist_maxsize = 100;
function initSocketConnection(){
if (location.protocol == "https:") {
ws = new WebSocket('wss://' + location.host + '/msg');
} else {
ws = new WebSocket('ws://' + location.host + '/msg');
}
ws.onopen = function(ev) {
console.log("WebSocket OPENED", ev);
$(".receiver").subscribe();
};
ws.onerror = function(ev) { console.log("WebSocket ERROR", ev); };
ws.onclose = function(ev) { console.log("WebSocket CLOSED", ev); };
ws.onmessage = function(ev) {
var msg;
try {
msg = JSON.parse(ev.data);
} catch (e) {
console.error("WebSocket msg: " + e + ": " + ev.data);
return;
}
for (msgtype in msg) {
if (msgtype == "event") {
$(".receiver").trigger("msg:event", msg.event);
$(".monitor[data-events]").each(function(){
var cmd = $(this).data("updcmd");
var js = $(this).data("updjs");
var evf = $(this).data("events");
if ((cmd || js) && evf && msg.event.match(evf)) {
$(this).data("updlast", now());
loadcmd({ command: js ? js : cmd, type: js ? "js" : "cmd" }, $(this));
}
});
}
else if (msgtype == "metrics") {
$.extend(metrics, msg.metrics);
$(".receiver").trigger("msg:metrics", msg.metrics);
}
else if (msgtype == "notify") {
processNotification(msg.notify);
$(".receiver").trigger("msg:notify", msg.notify);
}
else if (msgtype == "log") {
loghist.push(msg.log);
if (loghist.length > loghist_maxsize) loghist.shift();
$(".receiver").trigger("msg:log", msg.log);
}
}
};
}
function monitorInit(force){
$(".monitor").each(function(){
var cmd = $(this).data("updcmd");
var js = $(this).data("updjs");
var txt = $(this).text();
if ((cmd || js) && (force || !txt)) {
$(this).data("updlast", now());
loadcmd({ command: js ? js : cmd, type: js ? "js" : "cmd" }, $(this));
}
});
}
function monitorUpdate(){
if (!ws || ws.readyState == ws.CLOSED){
if (ws_inhibit != 0)
--ws_inhibit;
if (ws_inhibit == 0)
initSocketConnection();
}
var new_monotonic = parseInt(metrics["m.monotonic"]) || 0;
if (new_monotonic < last_monotonic)
location.reload();
else
last_monotonic = new_monotonic;
$(".monitor").each(function(){
var cnt = $(this).data("updcnt");
var int = $(this).data("updint");
var last = $(this).data("updlast");
var cmd = $(this).data("updcmd");
var js = $(this).data("updjs");
if (!cnt || (!cmd && !js) || (now()-last) < int)
return;
$(this).data("updcnt", cnt-1);
$(this).data("updlast", now());
loadcmd({ command: js ? js : cmd, type: js ? "js" : "cmd" }, $(this));
});
}
function processNotification(msg) {
var opts = { timeout: 0 };
if (msg.type == "info") {
opts.title = '<span class="lead text-info"><i>ⓘ</i> ' + msg.subtype + ' Info</span>';
opts.timeout = 60;
}
else if (msg.type == "alert") {
opts.title = '<span class="lead text-danger"><i>⚠</i> ' + msg.subtype + ' Alert</span>';
}
else if (msg.type == "error") {
opts.title = '<span class="lead text-warning"><i>⛍</i> ' + msg.subtype + ' Error</span>';
}
else
return;
opts.body = '<pre>' + msg.value + '</pre>';
confirmdialog(opts.title, opts.body, ["OK"], opts.timeout);
}
$.fn.subscribe = function(topics) {
return this.each(function() {
var subscriptions = $(this).data("subscriptions");
if (!topics) {
// init from data attr:
topics = subscriptions;
subscriptions = "";
}
var subs = subscriptions ? subscriptions.split(' ') : [];
var tops = topics ? topics.split(' ') : [];
for (var i = 0; i < tops.length; i++) {
if (tops[i] && !subs.includes(tops[i])) {
try {
console.log("subscribe " + tops[i]);
if (ws) ws.send("subscribe " + tops[i]);
subs.push(tops[i]);
} catch (e) {
console.log(e);
}
}
}
$(this).data("subscriptions", subs.join(' '));
});
};
$.fn.unsubscribe = function(topics) {
return this.each(function() {
var subscriptions = $(this).data("subscriptions");
if (!topics) {
// cleanup:
topics = subscriptions;
}
var subs = subscriptions ? subscriptions.split(' ') : [];
var tops = topics ? topics.split(' ') : [];
var i, j;
for (i = 0; i < tops.length; i++) {
if (tops[i] && (j = subs.indexOf(tops[i])) >= 0) {
try {
console.log("unsubscribe " + tops[i]);
if (ws) ws.send("unsubscribe " + tops[i]);
subs.splice(j, 1);
} catch (e) {
console.log(e);
}
}
}
$(this).data("subscriptions", subs.join(' '));
});
};
$.fn.reconnectTicker = function(msg) {
$("html").addClass("loading disabled");
ws_inhibit = 10;
if (ws) ws.close();
window.setInterval(function(){
if (ws && ws.readyState == ws.OPEN)
location.reload();
else
$("#reconnectTickerDots").append("•");
}, 1000);
return this.append(
(msg ? msg : '<p class="lead">Rebooting now…</p>') +
'<p>The window will automatically reload when the browser reconnects to the module.</p>' +
'<p id="reconnectTickerDots">•</p>');
};
/**
* UI Widgets
*/
// Plugin Maker
// credits: https://www.bitovi.com/blog/writing-the-perfect-jquery-plugin
$.pluginMaker = function(plugin) {
$.fn[plugin.prototype.cname] = function(options) {
var args = $.makeArray(arguments), after = args.slice(1);
var ismethod = (typeof options == "string");
var result;
this.each(function() {
// see if we have an instance
var instance = $.data(this, plugin.prototype.cname);
if (instance) {
if (ismethod) {
// call a method on the instance
if ($.isFunction(instance[options]))
result = instance[options].apply(instance, after);
else
throw "UndefinedMethod: " + plugin.prototype.cname + "." + options;
} else if (instance.update) {
// call update on the instance
instance.update.apply(instance, args);
}
} else {
// create the plugin
new plugin(this, options);
}
});
return (ismethod && result != undefined) ? result : this;
};
};
// OVMS namespace:
var ovms = {uid:0};
// Widget root class:
ovms.Widget = function(el, options) {
if (el) this.init(el, options);
}
$.extend(ovms.Widget.prototype, {
cname: "widget",
options: {},
init: function(el, options) {
this.uid = ++ovms.uid;
this.$el = $(el);
this.$el.data(this.cname, this);
this.$el.addClass(this.cname);
var dataoptions = this.$el.data("options");
if (dataoptions != null && typeof dataoptions != "object") {
console.error("Invalid JSON syntax: " + this.$el.data("options"));
dataoptions = null;
}
this.options = $.extend({}, this.options, dataoptions, options);
},
update: function(options) {
$.extend(this.options, options);
},
});
// Dialog:
ovms.Dialog = function(el, options) {
if (el) this.init(el, options);
}
$.extend(ovms.Dialog.prototype, ovms.Widget.prototype, {
cname: "dialog",
options: {
title: '',
body: '',
show: false,
remote: false,
backdrop: true,
keyboard: true,
transition: 'fade',
size: '',
contentClass: '',
onShow: null,
onHide: null,
onShown: null,
onHidden: null,
onUpdate: null,
buttons: [{}],
timeout: 0,
input: null,
},
init: function(el, options) {
if ($(el).parent().length == 0) {
options = $.extend(options, { show: true, isDynamic: true });
}
ovms.Widget.prototype.init.call(this, el, options);
this.input = options.input ? options.input : {};
this.data = { showing: false };
this.$buttons = [];
// convert element to modal if not predefined by user:
if (this.$el.children().length == 0) {
this.$el.html('<div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><h4 class="modal-title"></h4></div><div class="modal-body"></div><div class="modal-footer"></div></div></div></div>');
this.$el.addClass("modal");
}
this.$el.on('show.bs.modal', $.proxy(this.onShow, this));
this.$el.on('hide.bs.modal', $.proxy(this.onHide, this));
this.$el.on('shown.bs.modal', $.proxy(this.onShown, this));
this.$el.on('hidden.bs.modal', $.proxy(this.onHidden, this));
if (this.options.isDynamic) this.$el.appendTo('body');
this.update(this.options);
},
update: function(options) {
$.extend(this.options, options);
// configure modal:
this.$el.removeClass('fade').addClass(this.options.transition);
this.$el.find('.modal-dialog').attr("class", "modal-dialog " + (this.options.size ? ("modal-" + this.options.size) : ""));
this.$el.find('.modal-content').attr("class", "modal-content " + (this.options.contentClass || ""));
if (options.title != null)
this.$el.find('.modal-title').html(options.title);
if (options.body != null)
this.$el.find('.modal-body').html(options.body);
if (options.buttons != null) {
var footer = this.$el.find('.modal-footer');
footer.empty();
this.$buttons = [];
for (var i = 0; i < this.options.buttons.length; i++) {
var btn = $.extend({ label: 'Close', btnClass: 'default', autoHide: true, action: null }, this.options.buttons[i], { index: i });
this.options.buttons[i] = btn;
this.$buttons[i] = $('<button type="button" class="btn btn-'+btn.btnClass+'" value="'+btn.index+'">'+btn.label+'</button>')
.appendTo(footer).on('click', $.proxy(this.onClick, this, btn));
}
}
if (this.options.onUpdate)
this.options.onUpdate.call(this.$el, this.input);
this.$el.modal(this.options);
},
show: function(options) {
if (options) this.update(options);
this.$el.modal('show');
},
hide: function() {
this.$el.modal('hide');
},
onShow: function() {
if (this.options.onShow)
this.options.onShow.call(this.$el, this.input);
},
onHide: function() {
if (this.options.onHide)
this.options.onHide.call(this.$el, this.input);
},
onShown: function() {
this.data.showing = true;
this.input.button = null;
if (!supportsTouch) this.$el.find('.form-control, .btn').first().focus();
if (this.options.onShown)
this.options.onShown.call(this.$el, this.input);
if (this.options.timeout)
after(this.options.timeout, $.proxy(this.hide, this));
},
onHidden: function() {
this.data.showing = false;
if (this.options.isDynamic)
this.$el.detach();
if (this.options.onHidden)
this.options.onHidden.call(this.$el, this.input);
if (this.input.button != null && this.input.button.action) {
this.input.button.action.call(this.$el, this.input);
}
},
isShowing: function() {
return this.data.showing;
},
triggerButton: function(button) {
var btn;
if (typeof button == "number") {
btn = this.$buttons[button];
}
else if (typeof button == "string") {
for (var i = 0; i < this.options.buttons.length; i++) {
if (this.options.buttons[i].label == button) {
btn = this.$buttons[i];
break;
}
}
}
if (btn && !btn.prop("disabled")) {
btn.trigger('click');
return true;
}
return false;
},
onClick: function(button) {
this.input.button = button;
if (button.autoHide)
this.hide();
else if (button.action) {
button.action.call(this.$el, this.input);
this.input.button = null;
}
},
});
$.pluginMaker(ovms.Dialog);
// Dialog utility wrappers:
$.fn.confirmdialog = function(_title, _body, _buttons, _action, _timeout) {
if (typeof _action == "number") { _timeout = _action; _action = null; }
var options = { show: true, title: _title, body: _body, buttons: [], timeout: _timeout };
if (_buttons) {
for (var i = 0; i < _buttons.length; i++) {
options.buttons.push({ label: _buttons[i],
btnClass: (_buttons.length <= 2 && i==_buttons.length-1) ? "primary" : "default" });
}
}
if (_action) {
options.onHidden = function(input) { _action(input.button ? input.button.index : null); };
}
return this.dialog(options);
};
var confirmdialog = function() { return $.fn.confirmdialog.apply($('<div />'), arguments); };
$.fn.promptdialog = function(_type, _title, _prompt, _buttons, _action) {
var options = { show: true, title: _title, buttons: [] };
var uid = ++ovms.uid;
options.body = '<label for="prompt'+uid+'">'+_prompt+'</label><input id="prompt'+uid+'" type="'+_type+'" class="form-control">';
if (_buttons) {
for (var i = 0; i < _buttons.length; i++) {
options.buttons.push({ label: _buttons[i],
btnClass: (_buttons.length <= 2 && i==_buttons.length-1) ? "primary" : "default" });
}
}
options.onUpdate = function(input) {
var footer = $(this).find('.modal-footer');
$(this).find('input').val(input.text).on('keydown', function(ev) {
if (ev.which == 13) footer.find('.btn-primary').trigger('click');
});
};
options.onShown = function(input) {
$(this).find('input').first().focus();
}
options.onHidden = function(input) {
if (input.button) input.text = $(this).find('input').val();
if (_action) _action(input.button ? input.button.index : null, input.text);
};
return this.dialog(options);
};
var promptdialog = function() { return $.fn.promptdialog.apply($('<div />'), arguments); };
// FileBrowser:
ovms.FileBrowser = function(el, options) {
if (el) this.init(el, options);
}
$.extend(ovms.FileBrowser.prototype, ovms.Widget.prototype, {
cname: "filebrowser",
options: {
path: '',
quicknav: ['/sd/', '/store/'],
filter: null,
sortBy: null,
sortDir: 1,
onUpdate: null,
onPathChange: null,
onAction: null,
input: null,
},
init: function(el, options) {
ovms.Widget.prototype.init.call(this, el, options);
this.input = options.input ? options.input : {};
$.extend(this.input, {
path: '',
dir: '',
file: '',
noload: false,
});
this.data = {
lastpath: '',
lastdir: '',
listdir: '',
listsel: '',
};
this.xhr = null;
if (this.$el.children().length == 0) {
this.$el.html('<div class="fb-pathbox form-group"><label class="control-label" for="input-path-${uid}">Path (trailing slash = dir):</label><div class="input-group"><input type="text" class="form-control font-monospace" name="path" id="input-path-${uid}" value=""><div class="input-group-btn"><button type="button" class="btn btn-default fb-path-stop" disabled title="Stop">&times;</button><button type="button" class="btn btn-default fb-path-reload" title="Reload">⟲</button><button type="button" class="btn btn-default fb-path-up" title="Up">↰</button></div></div></div><div class="fb-quicknav form-group"/><div class="fb-files"><table class="table table-condensed table-hover table-scrollable font-monospace get-window-resize"><thead><tr><th class="col-xs-3 col-sm-2" data-key="size">Size</th><th class="hidden-xs col-sm-4" data-key="date">Date</th><th class="col-xs-9 col-sm-6" data-key="name">Name</th></tr></thead><tbody/></table></div>'.replace(/\$\{uid\}/g, this.uid));
}
this.$pathinput = this.$el.find("input[name=path]");
this.$pathinput.on('change', $.proxy(this.setPath, this)).on('keydown', $.proxy(function(ev) {
if (ev.which == 13) {
var lastdir = this.input.dir;
this.setPath();
if (this.input.file || this.input.dir == lastdir)
this.onAction();
ev.preventDefault();
}
else if (ev.which == 27) {
this.stopLoad();
}
}, this));
this.$el.find('.fb-path-up').on('click', $.proxy(function(ev) {
var parent = this.input.dir.replace(/\/[^/]+$/, '');
if (parent) this.setPath(parent+'/');
else this.setPath(this.input.dir+'/');
}, this));
this.$el.find('.fb-path-reload').on('click', $.proxy(function(ev) {
this.setPath(this.input.path, true);
}, this));
this.$btnstop = this.$el.find('.fb-path-stop');
this.$btnstop.on('click', $.proxy(function(ev) { this.stopLoad(); }, this));
this.$quicknav = this.$el.find(".fb-quicknav");
this.$filetable = this.$el.find(".fb-files>table");
this.$filecols = this.$filetable.find("thead th");
this.$filecols.on('click', $.proxy(function(ev) {
var by = $(ev.currentTarget).data("key");
var dir = $(ev.currentTarget).hasClass('sort-down') ? -1 : 1;
if (by == this.options.sortBy) dir = -dir;
this.sortList(by, dir);
if (!supportsTouch) this.$pathinput.focus();
}, this));
this.$filebody = this.$filetable.find("tbody");
this.$filebody.on('click', 'tr', $.proxy(function(ev) {
this.data.listsel = $(ev.currentTarget).data("name");
this.setPath(this.data.listdir + "/" + this.data.listsel);
ev.preventDefault();
}, this)).on('dblclick', 'tr', $.proxy(this.onAction, this));
this.update(this.options);
},
update: function(options) {
$.extend(this.options, options);
this.$quicknav.empty();
for (var i = 0; i < this.options.quicknav.length; i++) {
this.$quicknav.append('<button type="button" class="btn btn-sm btn-default"><code>' + this.options.quicknav[i] + '</code></button>');
}
this.$quicknav.find("button").on('click', $.proxy(function(ev) {
this.setPath($(ev.delegateTarget).text());
}, this));
if (this.options.onUpdate)
this.options.onUpdate.call(this.$el, this.input);
this.sortList(this.options.sortBy, this.options.sortDir);
if (options.path !== undefined) {
this.setPath(options.path);
}
else if (options.filter !== undefined) {
this.loadDir();
}
},
getInput: function() {
return this.input;
},
setPath: function(newpath, reload) {
if (typeof newpath == "string") {
this.input.path = newpath;
this.$pathinput.val(newpath);
} else {
this.input.path = this.$pathinput.val();
}
this.input.dir = this.input.path.replace(/\/[^/]*$/, '');
this.input.file = this.input.path.substr(this.input.dir.length+1);
var fn = "";
if (this.input.dir == this.data.listdir)
fn = this.input.path.substr(this.data.listdir.length+1);
this.$filebody.children().each(function() {
if ($(this).data("name") === fn) $(this).addClass("active");
else $(this).removeClass("active");
});
if (reload)
this.data.lastdir = '';
if (this.input.path != this.data.lastpath) {
this.input.noload = false;
if (this.options.onPathChange)
this.options.onPathChange.call(this.$el, this.input);
this.data.lastpath = this.input.path;
}
if (this.input.dir != this.data.lastdir) {
if (this.input.noload) {
this.stopLoad();
this.data.lastdir = '';
this.$filebody.empty();
this.data.listdir = '';
this.data.listsel = '';
} else {
this.loadDir();
this.data.lastdir = this.input.dir;
}
}
if (!supportsTouch) this.$pathinput.focus();
},
loadDir: function() {
var self = this;
var fbuf = "";
this.stopLoad();
this.$filebody.empty();
this.data.listdir = this.input.dir;
this.data.listsel = '';
if (!this.data.listdir)
return;
this.$btnstop.prop('disabled', false);
this.xhr = loadcmd("vfs ls '" + this.input.dir + "'", this.$filebody, function(msg) {
if (msg.error) {
if (msg.error.startsWith("Request abort"))
return '<div class="bg-info">Stopped.</div>';
else
return '<div class="bg-danger">'+msg.error+'</div>';
}
fbuf += msg.text;
var lines = fbuf.split("\n");
if (!lines || lines.length < 2) return "";
fbuf = lines[lines.length-1];
var res = '', f = {};
for (var i = 0; i < lines.length-1; i++) {
if (!lines[i]) continue;
if (lines[i].startsWith("Error"))
return '<div class="bg-danger">' + lines[i] + '</div>';
f.size = lines[i].substring(0, 10).trim();
f.date = lines[i].substring(10, 29).trim();
f.name = lines[i].substring(29).trim();
if (!f.name) {
console.log("FileBrowser.loadDir: can't parse line: '" + lines[i] + "'");
} else {
f.path = self.data.listdir + '/' + f.name;
f.isdir = (f.size == '[DIR]');
f.bytes = self.sizeToBytes(f.size);
f.isodate = self.dateToISO(f.date);
f.class = "";
if (self.options.filter && (
(typeof self.options.filter == "string" && !f.isdir && !f.name.match(self.options.filter)) ||
(typeof self.options.filter == "function" && !self.options.filter(f)))) {
continue;
}
if (f.name == self.input.file) {
f.class += " active";
self.data.listsel = f.name;
} else {
f.class = "";
}
res += '<tr data-name="' + encode_html(f.name) + '" data-size="' + f.bytes +
'" data-date="' + f.isodate + '" class="' + f.class + '">' +
'<td class="col-xs-3 col-sm-2">' + f.size +
'</td><td class="hidden-xs col-sm-4">' + f.date +
'</td><td class="col-xs-9 col-sm-6">' + encode_html(f.name) + '</td></tr>';
}
}
if (!self.options.sortBy)
return res;
if (res) {
var scrollpos = self.$filebody.get(0).scrollTop;
self.$filebody.detach().append(res);
self.sortList();
self.$filebody.appendTo(self.$filetable).scrollTop(scrollpos);
self.$filetable.trigger('window-resize');
}
return null;
}).always($.proxy(function() {
this.$btnstop.prop('disabled', true);
}, this));
},
sizeToBytes: function(size) {
if (size == "[DIR]") return -1;
var unit = size[size.length-1], val = parseFloat(size);
if (unit == 'k') return val*1024;
else if (unit == 'M') return val*1048576;
else if (unit == 'G') return val*1073741824;
else return val;
},
dateToISO: function(date) {
var month = monthnames.indexOf(date.substr(3,3)) + 1;
return date.substr(7,4)+'-'+(month<10?'0':'')+month+'-'+date.substr(0,2)+' '+date.substr(12);
},
stopLoad: function() {
this.$btnstop.prop('disabled', true);
if (this.xhr)
this.xhr.abort();
if (!supportsTouch) this.$pathinput.focus();
},
sortList: function(by, dir) {
if (by == null) {
by = this.options.sortBy;
dir = this.options.sortDir;
} else {
dir = dir || 1;
this.options.sortBy = by;
this.options.sortDir = dir;
this.$filecols.removeClass('sort-up sort-down')
.filter('[data-key="'+by+'"]').addClass((dir<0) ? 'sort-down' : 'sort-up');
}
if (!by) return;
var rows = $.makeArray(this.$filebody.children());
rows.sort(function(a,b){
if (!a.dataset[by]) return 1; if (!b.dataset[by]) return -1;
var pri = a.dataset[by].localeCompare(b.dataset[by]);
var sec = (by!="name") ? a.dataset["name"].localeCompare(b.dataset["name"]) : 0;
return dir * (pri ? pri : sec);
});
this.$filebody.html(rows);
},
newDir: function() {
this.stopLoad();
var path = this.input.dir + "/";
var self = this;
promptdialog("text", "Create new directory", path + "… (empty = create this dir)", ["Cancel", "Create"], function(create, dirname) {
if (create) {
path = (path + dirname).replace(/\/+$/, "");
$.post("/api/execute", { "command": "vfs mkdir '" + path + "'" }, function(result) {
if (result.startsWith("Error"))
confirmdialog("Error", result, ["OK"]);
else
self.setPath(path + "/", path == self.input.dir);
}).fail(function(request, textStatus, errorThrown){
confirmdialog("Error", xhrErrorInfo(request, textStatus, errorThrown), ["OK"]);
});
}
});
},
onAction: function() {
if (this.options.onAction)
this.options.onAction.call(this.$el, this.input);
},
});
$.pluginMaker(ovms.FileBrowser);
// FileDialog:
ovms.FileDialog = function(el, options) {
if (el) this.init(el, options);
}
$.extend(ovms.FileDialog.prototype, ovms.Widget.prototype, {
cname: "filedialog",
options: {
title: 'Select file',
submit: 'Select',
onSubmit: null,
onCancel: null,
path: '',
quicknav: ['/sd/', '/store/'],
filter: null,
sortBy: null,
sortDir: 1,
select: 'f',
showNewDir: true,
backdrop: true,
keyboard: true,
transition: 'fade',
size: 'lg',
onUpdate: null,
show: false,
},
init: function(el, options) {
ovms.Widget.prototype.init.call(this, el, options);
this.input = {};
this.$fb = null;
this.$el.dialog({
input: this.input,
body: '<div class="filebrowser"/>',
onHide: $.proxy(this.onHide, this),
onHidden: $.proxy(this.onHidden, this),
});
this.$fb = this.$el.find('.filebrowser').filebrowser({
input: this.input,
onPathChange: $.proxy(this.onPathChange, this),
onAction: $.proxy(this.onAction, this),
});
this.update(this.options);
},
update: function(options) {
$.extend(this.options, options);
var newbtns = [];
if (this.options.showNewDir) newbtns.push(
{ label: "New dir", btnClass: "default pull-left", autoHide: false, action: $.proxy(this.newDir, this) });
newbtns.push(
{ label: "Cancel" },
{ label: this.options.submit, btnClass: "primary" });
this.$el.dialog({
title: this.options.title,
buttons: newbtns,
backdrop: this.options.backdrop,
keyboard: this.options.keyboard,
transition: this.options.transition,
size: this.options.size,
});
this.$btnsubmit = this.$el.find(".modal-footer .btn-primary");
this.$fb.filebrowser({
path: (options.path != null) ? options.path : this.input.path,
quicknav: this.options.quicknav,
filter: this.options.filter,
sortBy: this.options.sortBy,
sortDir: this.options.sortDir,
});
this.onPathChange();
if (this.options.onUpdate)
this.options.onUpdate.call(this.$el, this.input);
if (options.show != null) {
var showing = this.$el.dialog('isShowing');
if (options.show === true && !showing)
this.$el.dialog('show');
else if (options.show === false && showing)
this.$el.dialog('hide');
}
},
show: function(options) {
if (options) this.update(options);
this.$el.dialog('show');
},
hide: function() {
this.$el.dialog('hide');
},
setPath: function(newpath, reload) {
this.$fb.filebrowser('setPath', newpath, reload);
},
newDir: function() {
this.$fb.filebrowser('newDir');
},
onPathChange: function() {
var ok = (
(this.options.select == 'f' && this.input.file) ||
(this.options.select == 'd' && !this.input.file));
this.$btnsubmit.prop("disabled", !ok);
},
onAction: function() {
this.$el.dialog('triggerButton', this.options.submit);
},
onHide: function() {
this.$fb.filebrowser('stopLoad');
},
onHidden: function(input) {
if (input.button && input.button.label == this.options.submit) {
if (this.options.onSubmit)
this.options.onSubmit.call(this.$el, this.input);
}
else {
if (this.options.onCancel)
this.options.onCancel.call(this.$el, this.input);
}
},
});
$.pluginMaker(ovms.FileDialog);
// Template list editor:
$.fn.listEditor = function(op, data){
return this.each(function(){
if (op) {
$(this).trigger('list:'+op, data);
} else {
// init:
var $this = $(this);
var el_itemid = $this.find('.list-item-id');
var el_body = $this.find('.list-items');
var el_template = $this.find('template').html();
$this.on('list:addItem', function(evt, data) {
var id = Number(el_itemid.val()) + 1;
el_itemid.val(id);
data = $.extend({ MODE: "upd" }, data);
var txt = el_template.replace(/ITEM_ID/g, id).replace(/ITEM_MODE/g, data.MODE);
for (key in data)
txt = txt.replace(new RegExp('ITEM_'+key,'g'), encode_html(data[key]));
txt = txt.replace(/ITEM_\w+/g, '');
var $el = $(txt).appendTo(el_body);
$el.find("option[data-value]").each(function(){
$(this).prop("selected", $(this).val() == $(this).data("value"));
});
$el.find("input[data-value]").each(function(){
var sel = $(this).val() == $(this).data("value");
$(this).prop("checked", sel);
if (sel) $(this).parent().addClass("active");
else $(this).parent().removeClass("active");
});
var discl = (data.MODE == "add") ? "add-disabled" : "list-disabled";
$el.find("select."+discl).each(function(){
$(this).prop("disabled", true).append($('<input type="hidden">')
.attr("name", $(this).attr("name")).attr("value", $(this).val()));
});
$el.find("input."+discl).prop("readonly", true);
$el.find("button."+discl).prop("disabled", true);
$this.trigger('list:validate');
});
$this.on('change keyup', 'input,select,textarea', function(ev) {
$this.trigger('list:validate');
});
$this.on('click', '.list-item-add', function(evt) {
var data = $.extend({ MODE: "add" }, $(this).data('preset'));
$(this).trigger('list:addItem', data);
});
$this.on('click', '.list-item-del', function(evt) {
$(this).closest('.list-item').remove();
$this.trigger('list:validate');
});
}
});
};
/**
* Slider widget plugin
*/
$.fn.slider = function(options) {
return this.each(function() {
var $sld = $(this).closest('.slider'), data = $.extend({ checked: true }, $sld.data());
var opts = (typeof options == "object") ? options : data;
// init?
if ($sld.children().length == 0) {
var id = $sld.attr('id');
$sld.html('\
<div class="slider-control form-inline">\
<input class="slider-enable" type="checkbox" checked>\
<input class="form-control slider-value" type="number" id="input-ID" name="ID">\
<span class="slider-unit">UNIT</span>\
<input class="btn btn-default slider-down" type="button" value="">\
<input class="btn btn-default slider-set" type="button" value="◈">\
<input class="btn btn-default slider-up" type="button" value="">\
</div>\
<input class="slider-input" type="range">'
.replace(/ID/g, id).replace(/UNIT/g, opts.unit||''));
}
// update:
var $inp = $sld.find('.slider-value, .slider-input'), $cb = $sld.find('.slider-enable'),
$bt = $sld.find('input[type=button]'), $sb = $bt.filter('.slider-set'),
oldchk = data.checked, chk = (opts.checked != null) ? opts.checked : oldchk,
dis = (opts.disabled != null) ? opts.disabled : ($sld.prop('disabled')==true);
$.extend(data, opts);
if (opts.unit != null) $sld.find('.slider-unit').html(opts.unit);
if (opts.min != null) $inp.attr('min', opts.min);
if (opts.max != null) $inp.attr('max', opts.max);
if (opts.step != null) $inp.attr('step', opts.step);
if (opts.default != null) {
if ($sb.length == 1)
$sb.data('set', opts.default);
if (!chk)
$inp.val(opts.default);
data.default = Math.max(data.min, Math.min(data.max, 1*opts.default));
}
if (opts.value !== undefined) {
if (opts.value === null)
data.value = data.uservalue = data.default;
else
data.value = Math.max(data.min, Math.min(data.max, 1*opts.value));
if (chk)
$inp.attr('value', data.value).val(data.value);
if (chk || data.uservalue == null)
data.uservalue = data.value;
}
if (chk != oldchk) {
$cb.prop('checked', chk);
if (chk) {
if (opts.reset || data.reset)
data.value = data.default;
else
data.value = data.uservalue;
} else {
data.uservalue = data.value;
data.value = data.default;
}
$inp.val(data.value);
}
$cb.prop('disabled', dis);
$bt.prop('disabled', !chk || dis);
$inp.prop('disabled', !chk || dis).prop('checked', chk);
if (dis)
$sld.addClass('disabled').prop('disabled', true).attr('disabled', true);
else
$sld.removeClass('disabled').prop('disabled', false).attr('disabled', false);
$sld.data(data);
});
};
/**
* Highcharts
*/
var highchartsLoader;
$.fn.chart = function(options) {
if (this.length == 0)
return this;
var $this = this;
if (options === "destroy") {
// destroy:
var $cl = this.find(".has-chart").add(this.filter(".has-chart"));
$cl.each(function() {
var chart = $(this).data("chart");
if (typeof chart == "object") {
$(this).data("chart", null);
chart.destroy();
}
});
} else {
// init:
function init_charts() {
$this.each(function() {
var chart = Highcharts.chart(this, options, function() {
if (this.userOptions && this.userOptions.onUpdate)
this.userOptions.onUpdate.call(this, metrics);
});
$(this).data("chart", chart).addClass("has-chart get-window-resize").on("window-resize", function() {
$(this).data("chart").reflow();
});
$(this).closest(".metric.chart").data("chart", chart);
});
}
if (window.Highcharts) {
init_charts();
} else if (highchartsLoader) {
highchartsLoader.then(init_charts);
} else {
highchartsLoader = $.ajax({
url: (window.assets && window.assets["charts_js"]) || "/assets/charts.js?v=6.0.7",
dataType: "script",
cache: true,
success: function(){ init_charts(); }
});
}
}
return this;
};
/**
* DataTables
*/
var datatablesLoader;
$.fn.table = function(options) {
if (this.length == 0)
return this;
var $this = this;
if (options === "destroy") {
// destroy:
var $tl = this.find(".has-dataTable").add(this.filter(".has-dataTable"));
$tl.each(function() {
var table = $(this).data("dataTable");
if (typeof table == "object") {
$(this).data("dataTable", null);
table.destroy();
}
});
} else {
// init:
function init_tables() {
$this.each(function() {
$(this).one("init.dt", function(ev, settings) {
if (settings && settings.oInstance && settings.oInit && settings.oInit.onUpdate)
settings.oInit.onUpdate.call(settings.oInstance.api(), metrics);
});
var table = $(this).DataTable(options);
$(this).data("dataTable", table).addClass("has-dataTable");
$(this).closest(".metric.table").data("dataTable", table);
});
}
if ($.fn.DataTables) {
init_tables();
} else if (datatablesLoader) {
datatablesLoader.then(init_tables);
} else {
datatablesLoader = $.ajax({
url: (window.assets && window.assets["tables_js"]) || "/assets/tables.js?v=1.10.18",
dataType: "script",
cache: true,
success: function(){ init_tables(); }
});
}
}
return this;
};
/**
* Framework Init
*/
$(function(){
// Toggle night mode:
$('body').on('click', '.toggle-night', function(event){
var nm = $('body').toggleClass("night").hasClass("night");
page.params["nm"] = 0+nm;
updateLocation();
event.stopPropagation();
return false;
});
// Toggle fullscreen mode:
document.fullScreenMode = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
$(document).on('mozfullscreenchange webkitfullscreenchange fullscreenchange', function() {
this.fullScreenMode = !this.fullScreenMode;
if (this.fullScreenMode) {
$('body').addClass("fullscreened");
} else {
$('body').removeClass("fullscreened");
}
$(window).trigger("resize");
});
$('body').on('click', '.toggle-fullscreen', function(evt) {
element = document.body;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
return false;
});
// AJAX page links, forms & buttons:
$('body').on('click', 'a[target^="#"], form[target^="#"] .btn[type="submit"]', function(event){
var method = $(this).data("method") || "get";
var uri = $(this).attr("href");
var target = $(this).attr("target");
var data = {};
if (method.toLowerCase() == "post") {
var p = uri.split("?");
if (p.length == 2) {
uri = p[0];
data = p[1];
}
if (uri == "" || uri == "#")
uri = $("#main").data("uri");
}
if (!uri) {
var frm = $(this.form);
method = frm.attr("method") || "get";
uri = frm.attr("action");
target = frm.attr("target");
data = frm.serialize();
if (this.name)
data += (data?"&":"") + encodeURI(this.name+"="+(this.value||"1"));
}
if ($(this).data("dismiss") == "modal")
$(this).closest(".modal").removeClass("fade").modal("hide");
if (!loaduri(target, method, uri, data))
return true;
event.preventDefault();
event.stopPropagation();
return false;
});
$('body').on('submit', 'form[target^="#"]', function(event) {
var $frm = $(this);
var method = $frm.attr("method") || "get";
var uri = $frm.attr("action");
var target = $frm.attr("target");
var data = $frm.serialize();
var $btn = $frm.find('input[type="submit"], button[type="submit"]').first();
if ($btn.length && $btn.attr("name"))
data += (data?"&":"") + encodeURI($btn.attr("name") +"="+ ($btn.val()||"1"));
if ($frm.data("dismiss") == "modal" || $btn.data("dismiss") == "modal")
$frm.closest(".modal").removeClass("fade").modal("hide");
if (!loaduri(target, method, uri, data))
return true;
event.preventDefault();
event.stopPropagation();
return false;
});
// AJAX command links & buttons:
$('body').on('click', '.btn[data-cmd]', function(event){
var btn = $(this);
var cmd = btn.data("cmd");
var tgt = btn.data("target");
var updcnt = btn.data("watchcnt") || 0;
var updint = btn.data("watchint") || 2;
btn.prop("disabled", true);
$(tgt).data("updcnt", 0);
loadcmd(cmd, tgt).then(function(){
btn.prop("disabled", false);
$(tgt).data("updcnt", updcnt);
$(tgt).data("updint", updint);
$(tgt).data("updlast", now());
}, function(){
btn.prop("disabled", false);
});
event.stopPropagation();
return false;
});
$('body').on('click', '.btn[data-js]', function(event){
var btn = $(this);
var js = btn.data("js");
var tgt = btn.data("target");
var updcnt = btn.data("watchcnt") || 0;
var updint = btn.data("watchint") || 2;
btn.prop("disabled", true);
$(tgt).data("updcnt", 0);
loadjs(js, tgt).then(function(){
btn.prop("disabled", false);
$(tgt).data("updcnt", updcnt);
$(tgt).data("updint", updint);
$(tgt).data("updlast", now());
}, function(){
btn.prop("disabled", false);
});
event.stopPropagation();
return false;
});
// Long touch buttons:
var longtouchTimeout, $longtouchProgress;
$('body').on('touchstart', '.btn-longtouch .btn, .btn.btn-longtouch', function(ev) {
var $this = $(this);
if ($this.prop('disabled')) return;
var action = $this.attr("title") || $this.text();
if (navigator.vibrate) navigator.vibrate([100,400,100,400,100,400]);
$longtouchProgress = $('<div class="hover-progress longtouch"><div class="hover-progress-body"><div class="info">Hold touch for/to</div><div class="action">'+encode_html(action)+'</div><div class="progress"><div class="progress-bar progress-bar-info" style="width:0%"></div></div></div></div>').appendTo("body").find(".progress-bar");
window.getComputedStyle($longtouchProgress.get(0)).width;
$longtouchProgress.css("width", "100%");
longtouchTimeout = window.setTimeout(function() {
if (navigator.vibrate) navigator.vibrate(1000);
if ($longtouchProgress) $longtouchProgress.closest(".hover-progress").remove();
longtouchTimeout = null;
$longtouchProgress = null;
$this.trigger('click');
}, 1500);
ev.preventDefault();
}).on('touchcancel touchend', '.btn-longtouch .btn, .btn.btn-longtouch', function(ev) {
window.clearTimeout(longtouchTimeout);
if (navigator.vibrate) navigator.vibrate(0);
if ($longtouchProgress) $longtouchProgress.closest(".hover-progress").remove();
longtouchTimeout = null;
$longtouchProgress = null;
ev.preventDefault();
}).on('contextmenu', function(ev) {
if ($longtouchProgress) {
ev.preventDefault();
ev.stopPropagation();
ev.stopImmediatePropagation();
return false;
}
});
// Slider widget event handling:
$("body").on("change", ".slider-enable", function(evt) {
var $this = $(this), $sld = $this.closest(".slider"), $inp = $sld.find(".slider-input, .slider-value");
$this.slider({ checked: this.checked });
$inp.trigger("input", true).trigger("change", true);
});
$("body").on("input change", ".slider-value", function(evt, noprop) {
var $this = $(this), $sld = $this.closest(".slider"), $inp = $sld.find(".slider-input");
if (!noprop) {
$this.slider({ value: this.value });
$inp.trigger(evt.type, true);
}
});
$("body").on("input change", ".slider-input", function(evt, noprop) {
var $this = $(this), $sld = $this.closest(".slider"), $inp = $sld.find(".slider-value");
if (!noprop) {
$this.slider({ value: this.value });
$inp.trigger(evt.type, true);
}
});
$("body").on("click", ".slider-up", function(evt) {
$(this).closest(".slider").find(".slider-input").val(function() {
return Math.min(1*this.value + (1*this.step||1), this.max);
}).trigger("input").trigger("change");
});
$("body").on("click", ".slider-down", function(evt) {
$(this).closest(".slider").find(".slider-input").val(function() {
return Math.max(1*this.value - (1*this.step||1), this.min);
}).trigger("input").trigger("change");
});
$("body").on("click", ".slider-set", function(evt) {
var $inp = $(this).closest(".slider").find(".slider-input");
$inp.val($(this).data("set")).trigger("input").trigger("change");
});
// data-toggle="filefialog":
$("body").on('click', '.btn[data-toggle="filedialog"]', function(evt) {
var $this = $(this);
var $tgt = $($this.data("target"));
var $inp = $($this.data("input"));
if ($tgt.length && $inp.length) {
var val = $inp.val();
var opt = {
show: true,
onSubmit: function(input) { $inp.val(input.path); }
};
if (val) opt.path = val;
$tgt.filedialog(opt);
}
});
// Metrics displays:
$("body").on('msg:metrics', '.receiver', function(e, update) {
$(this).find(".metric").each(function() {
var $el = $(this), metric = $el.data("metric"), prec = $el.data("prec"), scale = $el.data("scale");
if (!metric) return;
// filter:
var keys = metric.split(","), val;
for (var i=0; i<keys.length; i++) {
if ((val = update[keys[i]]) != null) break;
}
if (val == null) return;
// process:
if ($el.hasClass("text")) {
$el.children(".value").text(val);
} else if ($el.hasClass("number")) {
var vf = val;
if (scale != null) vf = Number(vf) * scale;
if (prec != null) vf = Number(vf).toFixed(prec);
$el.children(".value").text(vf);
} else if ($el.hasClass("progress")) {
var vf = val;
if (scale != null) vf = Number(vf) * scale;
if (prec != null) vf = Number(vf).toFixed(prec);
var $pb = $(this.firstElementChild), min = $pb.attr("aria-valuemin"), max = $pb.attr("aria-valuemax");
var vp = (val-min) / (max-min) * 100;
$pb.css("width", vp+"%").attr("aria-valuenow", vp).find(".value").text(vf);
var lw = 0; $pb.find("span").each(function(){ lw += $(this).width(); });
if (($pb.parent().width()*vp/100) < lw) $pb.addClass("value-low"); else $pb.removeClass("value-low");
} else if ($el.hasClass("chart")) {
var ch = $(this).data("chart");
if (ch && ch.userOptions && ch.userOptions.onUpdate)
ch.userOptions.onUpdate.call(ch, update);
} else if ($el.hasClass("table")) {
var dt = $(this).data("dataTable");
if (dt && dt.settings() && dt.settings()[0] && dt.settings()[0].oInit && dt.settings()[0].oInit.onUpdate)
dt.settings()[0].oInit.onUpdate.call(dt, update);
} else {
$el.text(val);
}
});
});
// Modal autoclear:
$("body").on("hidden.bs.modal", ".modal", function(evt) {
$(this).find(".modal-autoclear").empty();
});
// Proxy window resize:
$(window).on("resize", function(event){
$(".get-window-resize").trigger("window-resize");
});
$("body").on("window-resize", ".table-scrollable", function(evt) {
var bw = $(this).find('>tbody').get(0).scrollWidth;
if (bw) $(this).find('>thead').css('width', bw);
});
// Monitor timer:
if (!monitorTimer)
monitorTimer = window.setInterval(monitorUpdate, 1000);
// AJAX page init:
window.onpopstate = loadPage;
loadPage();
});