aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gui/README.md9
-rw-r--r--gui/muxrc.py104
-rwxr-xr-xgui/odr-dabmux-gui.py34
-rw-r--r--gui/static/intercooler-1.0.1.min.js2
-rw-r--r--gui/static/script.js12
-rw-r--r--gui/static/style.css9
-rw-r--r--gui/views/index.tpl39
-rw-r--r--gui/views/rcparam.tpl22
-rw-r--r--src/RemoteControl.cpp15
-rw-r--r--src/RemoteControl.h8
10 files changed, 217 insertions, 37 deletions
diff --git a/gui/README.md b/gui/README.md
index 06869e9..fc0311d 100644
--- a/gui/README.md
+++ b/gui/README.md
@@ -13,17 +13,20 @@ Usage
-----
Launch ODR-DabMux with your preferred multiplex, and enable the statistics and
-management server in the configuration file to port 12720.
+management server in the configuration file to port 12720, and the zeromq RC on
+tcp://lo:12722
Start the gui/odr-dabmux-gui.py script on the same machine
Connect to http://localhost:8000
-Admire the fabulously well-designed presentation of the configuration.
+Admire the fabulously well-designed presentation of the configuration. In the
+remote control tab, you can interact with the ODR-DabMux RC to get an set
+parameters.
Expect more features to come: Better design; integrated statistics, dynamically
updated information, configuration upload and download, less ridiculous README,
and much more. We can even start dreaming about live multiplex reconfiguration.
-2015-03-07 mpb
+2016-10-07 mpb
diff --git a/gui/muxrc.py b/gui/muxrc.py
new file mode 100644
index 0000000..3bda046
--- /dev/null
+++ b/gui/muxrc.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2016
+# Matthias P. Braendli, matthias.braendli@mpb.li
+#
+# http://www.opendigitalradio.org
+#
+# This file is part of ODR-DabMux.
+#
+# ODR-DabMux is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ODR-DabMux is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+import zmq
+import json
+
+class RCParameter(object):
+ def __init__(self, param, value):
+ self.param = param
+ self.value = value
+
+class RCModule(object):
+ """Container object for RC module"""
+ def __init__(self, name):
+ self.name = name
+ self.parameters = []
+
+class MuxRemoteControl(object):
+ """Interact with ODR-DabMux using the ZMQ RC"""
+
+ def __init__(self, mux_host, mux_port=12722):
+ self._host = mux_host
+ self._port = mux_port
+ self._ctx = zmq.Context()
+
+ self.module_list = []
+
+ def zRead(self, message_parts):
+ sock = zmq.Socket(self._ctx, zmq.REQ)
+ sock.setsockopt(zmq.LINGER, 0)
+ sock.connect("tcp://{}:{}".format(self._host, self._port))
+
+ for i, part in enumerate(message_parts):
+ if i == len(message_parts) - 1:
+ f = 0
+ else:
+ f = zmq.SNDMORE
+
+ print("Send {} {}".format(i, part))
+ sock.send(part, flags=f)
+
+ print("Poll")
+
+ # use poll for timeouts:
+ poller = zmq.Poller()
+ poller.register(sock, zmq.POLLIN)
+ if poller.poll(5*1000): # 5s timeout in milliseconds
+ recv = sock.recv_multipart()
+ print("RX {}".format(recv))
+ sock.close()
+ return recv
+ else:
+ raise IOError("Timeout processing ZMQ request")
+
+ def load(self):
+ """Load the list of RC modules"""
+ module_names = self.zRead([b'list'])
+
+ self.module_list = []
+
+ for name in module_names:
+ mod = RCModule(name)
+ module_params = self.zRead([b'show', name])
+
+ for param in module_params:
+ p, v = param.split(': ')
+ mod.parameters.append(RCParameter(p, v))
+
+ self.module_list.append(mod)
+
+ def get_modules(self):
+ return self.module_list
+
+ def get_param_value(self, module, param):
+ value = self.zRead([b'get', module, param])
+ if value[0] == b'fail':
+ raise ValueError("Error getting param: {}".format(value[1]))
+ else:
+ return value[0]
+
+ def set_param_value(self, module, param, value):
+ ret = self.zRead([b'set', module, param, value])
+ if ret[0] == b'fail':
+ raise ValueError("Error getting param: {}".format(ret[1]))
+
diff --git a/gui/odr-dabmux-gui.py b/gui/odr-dabmux-gui.py
index 85098f8..2f028e4 100755
--- a/gui/odr-dabmux-gui.py
+++ b/gui/odr-dabmux-gui.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2015
+# Copyright (C) 2016
# Matthias P. Braendli, matthias.braendli@mpb.li
#
# http://www.opendigitalradio.org
@@ -28,6 +28,7 @@
# along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
from muxconfig import *
+from muxrc import *
from bottle import route, run, template, static_file, request
import json
@@ -77,16 +78,40 @@ def config_json_get():
'config': conf.get_full_configuration() }
+@route('/rc/<module>/<param>', method="GET")
+def rc_get(module, param):
+ rc.load()
+
+ value = rc.get_param_value(module, param)
+
+ return template('rcparam',
+ module = module,
+ param = param,
+ value = value)
+
+@route('/rc/<module>/<param>', method="POST")
+def rc_post(module, param):
+ value = request.forms.get('newvalue')
+
+ rc.set_param_value(module, param, value)
+
+ value = rc.get_param_value(module, param)
+
+ return """<p>Parameter is now {}</p>
+<p><a href="/#rcmodules">Return</a></p>""".format(value)
+
@route('/')
def index():
conf.load()
+ rc.load()
return template('index',
version = conf.get_mux_version(),
g = conf.get_general_options(),
services = conf.get_services(),
subchannels = conf.get_subchannels(),
- components = conf.get_components())
+ components = conf.get_components(),
+ rcmodules = rc.get_modules())
@route('/services')
def index():
@@ -119,10 +144,13 @@ if __name__ == '__main__':
parser.add_argument('--host', default='127.0.0.1', help='socket host (default: 127.0.0.1)',required=False)
parser.add_argument('--port', default='8000', help='socket port (default: 8000)',required=False)
parser.add_argument('--mhost', default='127.0.0.1', help='mux host (default: 127.0.0.1)',required=False)
- parser.add_argument('--mport', default='12720', help='mux port (default: 12720)',required=False)
+ parser.add_argument('--mport', default='12720', help='mux management server port (default: 12720)',required=False)
+ parser.add_argument('--rcport', default='12722', help='mux zmq rc port (default: 12722)',required=False)
cli_args = parser.parse_args()
conf = ConfigurationHandler(cli_args.mhost, cli_args.mport)
+ rc = MuxRemoteControl(cli_args.mhost, cli_args.rcport)
+
run(host=cli_args.host, port=int(cli_args.port), debug=True, reloader=False)
diff --git a/gui/static/intercooler-1.0.1.min.js b/gui/static/intercooler-1.0.1.min.js
new file mode 100644
index 0000000..c89fdb9
--- /dev/null
+++ b/gui/static/intercooler-1.0.1.min.js
@@ -0,0 +1,2 @@
+/*! intercooler 1.0.1 2016-09-13 */
+!function(a,b){"function"==typeof define&&define.amd?define("intercooler",["jquery"],function(c){return a.Intercooler=b(c)}):"object"==typeof exports?module.exports=b(require("jquery")):a.Intercooler=b(jQuery)}(this,function($){var Intercooler=Intercooler||function(){"use strict";function remove(a){a.remove()}function showIndicator(a){a.closest(".ic-use-transition").length>0?(a.data("ic-use-transition",!0),a.removeClass("ic-use-transition")):a.show()}function hideIndicator(a){a.data("ic-use-transition")?(a.data("ic-use-transition",null),a.addClass("ic-use-transition")):a.hide()}function fixICAttributeName(a){return USE_DATA?"data-"+a:a}function getICAttribute(a,b){return a.attr(fixICAttributeName(b))}function setICAttribute(a,b,c){a.attr(fixICAttributeName(b),c)}function prepend(a,b){try{a.prepend(b)}catch(b){log(a,formatError(b),"ERROR")}if(getICAttribute(a,"ic-limit-children")){var c=parseInt(getICAttribute(a,"ic-limit-children"));a.children().length>c&&a.children().slice(c,a.children().length).remove()}}function append(a,b){try{a.append(b)}catch(b){log(a,formatError(b),"ERROR")}if(getICAttribute(a,"ic-limit-children")){var c=parseInt(getICAttribute(a,"ic-limit-children"));a.children().length>c&&a.children().slice(0,a.children().length-c).remove()}}function log(a,b,c){if(null==a&&(a=$("body")),a.trigger("log.ic",[b,c,a]),"ERROR"==c){window.console&&window.console.log("Intercooler Error : "+b);var d=closestAttrValue($("body"),"ic-post-errors-to");d&&$.post(d,{error:b})}}function uuid(){return _UUID++}function icSelectorFor(a){return getICAttributeSelector("ic-id='"+getIntercoolerId(a)+"'")}function parseInterval(a){return log(null,"POLL: Parsing interval string "+a,"DEBUG"),"null"==a||"false"==a||""==a?null:a.lastIndexOf("ms")==a.length-2?parseFloat(a.substr(0,a.length-2)):a.lastIndexOf("s")==a.length-1?1e3*parseFloat(a.substr(0,a.length-1)):1e3}function getICAttributeSelector(a){return"["+fixICAttributeName(a)+"]"}function initScrollHandler(){null==_scrollHandler&&(_scrollHandler=function(){$(getICAttributeSelector("ic-trigger-on='scrolled-into-view'")).each(function(){isScrolledIntoView(getTriggeredElement($(this)))&&1!=$(this).data("ic-scrolled-into-view-loaded")&&($(this).data("ic-scrolled-into-view-loaded",!0),fireICRequest($(this)))})},$(window).scroll(_scrollHandler))}function currentUrl(){return window.location.pathname+window.location.search+window.location.hash}function createDocument(a){var b=null;return/<(html|body)/i.test(a)?(b=document.documentElement.cloneNode(),b.innerHTML=a):(b=document.documentElement.cloneNode(!0),b.querySelector("body").innerHTML=a),$(b)}function getTarget(a){var b=$(a).closest(getICAttributeSelector("ic-target")),c=getICAttribute(b,"ic-target");return"this"==c?b:c&&0!=c.indexOf("this.")?0==c.indexOf("closest ")?a.closest(c.substr(8)):0==c.indexOf("find ")?a.find(c.substr(5)):$(c):a}function processHeaders(elt,xhr){elt.trigger("beforeHeaders.ic",[elt,xhr]),log(elt,"response headers: "+xhr.getAllResponseHeaders(),"DEBUG");var target=null;if(xhr.getResponseHeader("X-IC-Title")&&(document.title=xhr.getResponseHeader("X-IC-Title")),xhr.getResponseHeader("X-IC-Refresh")){var pathsToRefresh=xhr.getResponseHeader("X-IC-Refresh").split(",");log(elt,"X-IC-Refresh: refreshing "+pathsToRefresh,"DEBUG"),$.each(pathsToRefresh,function(a,b){refreshDependencies(b.replace(/ /g,""),elt)})}if(xhr.getResponseHeader("X-IC-Script")&&(log(elt,"X-IC-Script: evaling "+xhr.getResponseHeader("X-IC-Script"),"DEBUG"),eval(xhr.getResponseHeader("X-IC-Script"))),xhr.getResponseHeader("X-IC-Redirect")&&(log(elt,"X-IC-Redirect: redirecting to "+xhr.getResponseHeader("X-IC-Redirect"),"DEBUG"),window.location=xhr.getResponseHeader("X-IC-Redirect")),"true"==xhr.getResponseHeader("X-IC-CancelPolling")&&cancelPolling($(elt).closest(getICAttributeSelector("ic-poll"))),"true"==xhr.getResponseHeader("X-IC-ResumePolling")){var pollingElt=$(elt).closest(getICAttributeSelector("ic-poll"));setICAttribute(pollingElt,"ic-pause-polling",null),startPolling(pollingElt)}if(xhr.getResponseHeader("X-IC-SetPollInterval")){var pollingElt=$(elt).closest(getICAttributeSelector("ic-poll"));cancelPolling(pollingElt),setICAttribute(pollingElt,"ic-poll",xhr.getResponseHeader("X-IC-SetPollInterval")),startPolling(pollingElt)}xhr.getResponseHeader("X-IC-Open")&&(log(elt,"X-IC-Open: opening "+xhr.getResponseHeader("X-IC-Open"),"DEBUG"),window.open(xhr.getResponseHeader("X-IC-Open")));var triggerValue=xhr.getResponseHeader("X-IC-Trigger");if(triggerValue)if(log(elt,"X-IC-Trigger: found trigger "+triggerValue,"DEBUG"),target=getTarget(elt),xhr.getResponseHeader("X-IC-Trigger-Data")){var triggerArgs=$.parseJSON(xhr.getResponseHeader("X-IC-Trigger-Data"));target.trigger(triggerValue,triggerArgs)}else triggerValue.indexOf("{")>=0?$.each($.parseJSON(triggerValue),function(a,b){target.trigger(a,b)}):target.trigger(triggerValue,[]);var localVars=xhr.getResponseHeader("X-IC-Set-Local-Vars");return localVars&&$.each($.parseJSON(localVars),function(a,b){localStorage.setItem(a,b)}),xhr.getResponseHeader("X-IC-Remove")&&elt&&(target=getTarget(elt),log(elt,"X-IC-Remove header found.","DEBUG"),remove(target)),elt.trigger("afterHeaders.ic",[elt,xhr]),!0}function beforeRequest(a){a.addClass("disabled"),a.data("ic-request-in-flight",!0)}function requestCleanup(a,b){a.length>0&&hideIndicator(a),b.removeClass("disabled"),b.data("ic-request-in-flight",!1),b.data("ic-next-request")&&(b.data("ic-next-request")(),b.data("ic-next-request",null))}function replaceOrAddMethod(a,b){if("string"===$.type(a)){var c=/(&|^)_method=[^&]*/,d="&_method="+b;return c.test(a)?a.replace(c,d):a+d}return a.append("_method",b),a}function globalEval(a){return window.eval.call(window,a)}function closestAttrValue(a,b){var c=$(a).closest(getICAttributeSelector(b));return c.length>0?getICAttribute(c,b):null}function formatError(a){var b=a.toString()+"\n";try{b+=a.stack}catch(a){}return b}function handleRemoteRequest(a,b,c,d,e){beforeRequest(a),d=replaceOrAddMethod(d,b);var f=findIndicator(a);f.length>0&&showIndicator(f);var g,h=uuid(),i=new Date;g=USE_ACTUAL_HTTP_METHOD?b:"GET"==b?"GET":"POST";var j={type:g,url:c,data:d,dataType:"text",headers:{Accept:"text/html-partial, */*; q=0.9","X-IC-Request":!0,"X-HTTP-Method-Override":b},beforeSend:function(e,f){a.trigger("beforeSend.ic",[a,d,f,e,h]),log(a,"before AJAX request "+h+": "+b+" to "+c,"DEBUG");var g=closestAttrValue(a,"ic-on-beforeSend");g&&globalEval("(function (data, settings, xhr) {"+g+"})")(d,f,e)},success:function(b,c,d){a.trigger("success.ic",[a,b,c,d,h]),log(a,"AJAX request "+h+" was successful.","DEBUG");var g=closestAttrValue(a,"ic-on-success");if(!g||0!=globalEval("(function (data, textStatus, xhr) {"+g+"})")(b,c,d)){var i=new Date;try{if(processHeaders(a,d)){log(a,"Processed headers for request "+h+" in "+(new Date-i)+"ms","DEBUG");var j=new Date;if(d.getResponseHeader("X-IC-PushURL")||"true"==closestAttrValue(a,"ic-push-url"))try{requestCleanup(f,a);var k=d.getResponseHeader("X-IC-PushURL")||closestAttrValue(a,"ic-src");if(!_history)throw"History support not enabled";_history.snapshotForHistory(k)}catch(b){log(a,"Error during history snapshot for "+h+": "+formatError(b),"ERROR")}e(b,c,a,d),log(a,"Process content for request "+h+" in "+(new Date-j)+"ms","DEBUG")}a.trigger("after.success.ic",[a,b,c,d,h])}catch(b){log(a,"Error processing successful request "+h+" : "+formatError(b),"ERROR")}}},error:function(b,d,e){a.trigger("error.ic",[a,d,e,b]);var f=closestAttrValue(a,"ic-on-error");f&&globalEval("(function (status, str, xhr) {"+f+"})")(d,e,b),log(a,"AJAX request "+h+" to "+c+" experienced an error: "+e,"ERROR")},complete:function(b,c){log(a,"AJAX request "+h+" completed in "+(new Date-i)+"ms","DEBUG"),requestCleanup(f,a);try{$.contains(document,a[0])?$(a).trigger("complete.ic",[a,d,c,b,h]):$("body").trigger("complete.ic",[a,d,c,b,h])}catch(b){log(a,"Error during complete.ic event for "+h+" : "+formatError(b),"ERROR")}var e=closestAttrValue(a,"ic-on-complete");e&&globalEval("(function (xhr, status) {"+e+"})")(b,c)}};"string"!=$.type(d)&&(j.dataType=null,j.processData=!1,j.contentType=!1),$(document).trigger("beforeAjaxSend.ic",j),$.ajax(j)}function findIndicator(a){var b=null;if(getICAttribute($(a),"ic-indicator"))b=$(getICAttribute($(a),"ic-indicator")).first();else if(b=$(a).find(".ic-indicator").first(),0==b.length){var c=closestAttrValue(a,"ic-indicator");c?b=$(c).first():$(a).next().is(".ic-indicator")&&(b=$(a).next())}return b}function processIncludes(a,b){if(0==$.trim(b).indexOf("{")){var c=$.parseJSON(b);$.each(c,function(b,c){a=appendData(a,b,c)})}else $(b).each(function(){var b=$(this).serializeArray();$.each(b,function(b,c){a=appendData(a,c.name,c.value)})});return a}function processLocalVars(a,b){return $(b.split(",")).each(function(){var b=$.trim(this),c=localStorage.getItem(b);c&&(a=appendData(a,b,c))}),a}function appendData(a,b,c){return"string"===$.type(a)?a+"&"+b+"="+encodeURIComponent(c):(a.append(b,c),a)}function getParametersForElement(a,b,c){var d=getTarget(b),e=null;b.is("form")&&"multipart/form-data"==b.attr("enctype")?(e=new FormData(b[0]),e=appendData(e,"ic-request",!0)):(e="ic-request=true",e+="GET"!=a&&b.closest("form").length>0?"&"+b.closest("form").serialize():"&"+b.serialize());var f=closestAttrValue(b,"ic-prompt");if(f){var g=prompt(f);if(!g)return null;var h=closestAttrValue(b,"ic-prompt-name")||"ic-prompt-value";e=appendData(e,h,g)}b.attr("id")&&(e=appendData(e,"ic-element-id",b.attr("id"))),b.attr("name")&&(e=appendData(e,"ic-element-name",b.attr("name"))),getICAttribute(d,"ic-id")&&(e=appendData(e,"ic-id",getICAttribute(d,"ic-id"))),d.attr("id")&&(e=appendData(e,"ic-target-id",d.attr("id"))),c&&c.attr("id")&&(e=appendData(e,"ic-trigger-id",c.attr("id"))),c&&c.attr("name")&&(e=appendData(e,"ic-trigger-name",c.attr("name")));var i=closestAttrValue(b,"ic-include");i&&(e=processIncludes(e,i));var j=closestAttrValue(b,"ic-local-vars");return j&&(e=processLocalVars(e,j)),$(getICAttributeSelector("ic-global-include")).each(function(){e=processIncludes(e,getICAttribute($(this),"ic-global-include"))}),e=appendData(e,"ic-current-url",currentUrl()),log(b,"request parameters "+e,"DEBUG"),e}function maybeSetIntercoolerInfo(a){var b=getTarget(a);getIntercoolerId(b),1!=a.data("elementAdded.ic")&&(a.data("elementAdded.ic",!0),a.trigger("elementAdded.ic"))}function getIntercoolerId(a){return getICAttribute(a,"ic-id")||setICAttribute(a,"ic-id",uuid()),getICAttribute(a,"ic-id")}function processNodes(a){a.length>1?a.each(function(){processNodes($(this))}):(processMacros(a),processSources(a),processPolling(a),processTriggerOn(a),processRemoveAfter(a),processAddClasses(a),processRemoveClasses(a))}function fireReadyStuff(a){a.trigger("nodesProcessed.ic"),$.each(_readyHandlers,function(b,c){try{c(a)}catch(b){log(a,formatError(b),"ERROR")}})}function autoFocus(a){a.find("[autofocus]:last").focus()}function processMacros(a){$.each(_MACROS,function(b,c){0==$(a).closest(".ic-ignore").length&&($(a).is("["+c+"]")&&processMacro(c,$(a)),$(a).find("["+c+"]").each(function(){0==$(this).closest(".ic-ignore").length&&processMacro(c,$(this))}))})}function processSources(a){0==$(a).closest(".ic-ignore").length&&($(a).is(getICAttributeSelector("ic-src"))&&maybeSetIntercoolerInfo($(a)),$(a).find(getICAttributeSelector("ic-src")).each(function(){0==$(this).closest(".ic-ignore").length&&maybeSetIntercoolerInfo($(this))}))}function processPolling(a){0==$(a).closest(".ic-ignore").length&&($(a).is(getICAttributeSelector("ic-poll"))&&(maybeSetIntercoolerInfo($(a)),startPolling(a)),$(a).find(getICAttributeSelector("ic-poll")).each(function(){0==$(this).closest(".ic-ignore").length&&(maybeSetIntercoolerInfo($(this)),startPolling($(this)))}))}function processTriggerOn(a){0==$(a).closest(".ic-ignore").length&&(handleTriggerOn(a),$(a).find(getICAttributeSelector("ic-trigger-on")).each(function(){0==$(this).closest(".ic-ignore").length&&handleTriggerOn($(this))}))}function processRemoveAfter(a){0==$(a).closest(".ic-ignore").length&&(handleRemoveAfter(a),$(a).find(getICAttributeSelector("ic-remove-after")).each(function(){0==$(this).closest(".ic-ignore").length&&handleRemoveAfter($(this))}))}function processAddClasses(a){0==$(a).closest(".ic-ignore").length&&(handleAddClasses(a),$(a).find(getICAttributeSelector("ic-add-class")).each(function(){0==$(this).closest(".ic-ignore").length&&handleAddClasses($(this))}))}function processRemoveClasses(a){0==$(a).closest(".ic-ignore").length&&(handleRemoveClasses(a),$(a).find(getICAttributeSelector("ic-remove-class")).each(function(){0==$(this).closest(".ic-ignore").length&&handleRemoveClasses($(this))}))}function startPolling(a){if(null==a.data("ic-poll-interval-id")&&"true"!=getICAttribute($(a),"ic-pause-polling")){var b=parseInterval(getICAttribute(a,"ic-poll"));if(null!=b){var c=icSelectorFor(a),d=parseInt(getICAttribute(a,"ic-poll-repeats"))||-1,e=0;log(a,"POLL: Starting poll for element "+c,"DEBUG");var f=setInterval(function(){var b=$(c);a.trigger("onPoll.ic",b),0==b.length||e==d||a.data("ic-poll-interval-id")!=f?(log(a,"POLL: Clearing poll for element "+c,"DEBUG"),clearTimeout(f)):fireICRequest(b),e++},b);a.data("ic-poll-interval-id",f)}}}function cancelPolling(a){null!=a.data("ic-poll-interval-id")&&(clearTimeout(a.data("ic-poll-interval-id")),a.data("ic-poll-interval-id",null))}function refreshDependencies(a,b){log(b,"refreshing dependencies for path "+a,"DEBUG"),$(getICAttributeSelector("ic-src")).each(function(){var c=!1;"GET"==verbFor($(this))&&"ignore"!=getICAttribute($(this),"ic-deps")&&"undefined"==typeof getICAttribute($(this),"ic-poll")&&(isDependent(a,getICAttribute($(this),"ic-src"))?null!=b&&$(b)[0]==$(this)[0]||(fireICRequest($(this)),c=!0):(isDependent(a,getICAttribute($(this),"ic-deps"))||"*"==getICAttribute($(this),"ic-deps"))&&(null!=b&&$(b)[0]==$(this)[0]||(fireICRequest($(this)),c=!0))),c&&log($(this),"depends on path "+a+", refreshing...","DEBUG")})}function isDependent(a,b){return!!_isDependentFunction(a,b)}function verbFor(a){return getICAttribute(a,"ic-verb")?getICAttribute(a,"ic-verb").toUpperCase():"GET"}function eventFor(a,b){return"default"==a?$(b).is("button")?"click":$(b).is("form")?"submit":$(b).is(":input")?"change":"click":a}function preventDefault(a,b){return a.is("form")||a.is(":submit")&&1==a.closest("form").length||a.is("a")&&a.is("[href]")&&0!=a.attr("href").indexOf("#")}function handleRemoveAfter(a){if(getICAttribute($(a),"ic-remove-after")){var b=parseInterval(getICAttribute($(a),"ic-remove-after"));setTimeout(function(){remove(a)},b)}}function parseAndApplyClass(a,b,c){var d="",e=50;if(a.indexOf(":")>0){var f=a.split(":");d=f[0],e=parseInterval(f[1])}else d=a;setTimeout(function(){b[c](d)},e)}function handleAddClasses(a){if(getICAttribute($(a),"ic-add-class"))for(var b=getICAttribute($(a),"ic-add-class").split(","),c=b.length,d=0;d<c;d++)parseAndApplyClass($.trim(b[d]),a,"addClass")}function handleRemoveClasses(a){if(getICAttribute($(a),"ic-remove-class"))for(var b=getICAttribute($(a),"ic-remove-class").split(","),c=b.length,d=0;d<c;d++)parseAndApplyClass($.trim(b[d]),a,"removeClass")}function getTriggeredElement(elt){var triggerFrom=getICAttribute(elt,"ic-trigger-from");return triggerFrom?$($.inArray(triggerFrom,["document","window"])>=0?eval(triggerFrom):triggerFrom):elt}function handleTriggerOn(a){if(getICAttribute($(a),"ic-trigger-on"))if("load"==getICAttribute($(a),"ic-trigger-on"))fireICRequest(a);else if("scrolled-into-view"==getICAttribute($(a),"ic-trigger-on"))initScrollHandler(),setTimeout(function(){$(window).trigger("scroll")},100);else{var b=getICAttribute($(a),"ic-trigger-on").split(" ");$(getTriggeredElement(a)).on(eventFor(b[0],$(a)),function(c){var d=closestAttrValue(a,"ic-on-beforeTrigger");if(d&&0==globalEval("(function (evt, elt) {"+d+"})")(c,$(a)))return log($(a),"ic-trigger cancelled by ic-on-beforeTrigger","DEBUG"),!1;if("changed"==b[1]){var e=$(a).val(),f=$(a).data("ic-previous-val");$(a).data("ic-previous-val",e),e!=f&&fireICRequest($(a))}else fireICRequest($(a));return!preventDefault(a,c)||(c.preventDefault(),!1)})}}function macroIs(a,b){return a==fixICAttributeName(b)}function processMacro(a,b){macroIs(a,"ic-post-to")&&(setIfAbsent(b,"ic-src",getICAttribute(b,"ic-post-to")),setIfAbsent(b,"ic-verb","POST"),setIfAbsent(b,"ic-trigger-on","default"),setIfAbsent(b,"ic-deps","ignore")),macroIs(a,"ic-put-to")&&(setIfAbsent(b,"ic-src",getICAttribute(b,"ic-put-to")),setIfAbsent(b,"ic-verb","PUT"),setIfAbsent(b,"ic-trigger-on","default"),setIfAbsent(b,"ic-deps","ignore")),macroIs(a,"ic-patch-to")&&(setIfAbsent(b,"ic-src",getICAttribute(b,"ic-patch-to")),setIfAbsent(b,"ic-verb","PATCH"),setIfAbsent(b,"ic-trigger-on","default"),setIfAbsent(b,"ic-deps","ignore")),macroIs(a,"ic-get-from")&&(setIfAbsent(b,"ic-src",getICAttribute(b,"ic-get-from")),setIfAbsent(b,"ic-trigger-on","default"),setIfAbsent(b,"ic-deps","ignore")),macroIs(a,"ic-delete-from")&&(setIfAbsent(b,"ic-src",getICAttribute(b,"ic-delete-from")),setIfAbsent(b,"ic-verb","DELETE"),setIfAbsent(b,"ic-trigger-on","default"),setIfAbsent(b,"ic-deps","ignore")),macroIs(a,"ic-action")&&setIfAbsent(b,"ic-trigger-on","default");var c=null,d=null;if(macroIs(a,"ic-style-src")){c=getICAttribute(b,"ic-style-src").split(":");var e=c[0];d=c[1],setIfAbsent(b,"ic-src",d),setIfAbsent(b,"ic-target","this.style."+e)}if(macroIs(a,"ic-attr-src")){c=getICAttribute(b,"ic-attr-src").split(":");var f=c[0];d=c[1],setIfAbsent(b,"ic-src",d),setIfAbsent(b,"ic-target","this."+f)}macroIs(a,"ic-prepend-from")&&setIfAbsent(b,"ic-src",getICAttribute(b,"ic-prepend-from")),macroIs(a,"ic-append-from")&&setIfAbsent(b,"ic-src",getICAttribute(b,"ic-append-from"))}function setIfAbsent(a,b,c){null==getICAttribute(a,b)&&setICAttribute(a,b,c)}function isScrolledIntoView(a){var b=$(window).scrollTop(),c=b+$(window).height(),d=$(a).offset().top,e=d+$(a).height();return e>=b&&d<=c&&e<=c&&d>=b}function maybeScrollToTarget(a,b){if("false"!=closestAttrValue(a,"ic-scroll-to-target")&&("true"==closestAttrValue(a,"ic-scroll-to-target")||"true"==closestAttrValue(b,"ic-scroll-to-target"))){var c=-50;closestAttrValue(a,"ic-scroll-offset")?c=parseInt(closestAttrValue(a,"ic-scroll-offset")):closestAttrValue(b,"ic-scroll-offset")&&(c=parseInt(closestAttrValue(b,"ic-scroll-offset")));var d=b.offset().top,e=$(window).scrollTop(),f=e+window.innerHeight;(d<e||d>f)&&(c+=d,$("html,body").animate({scrollTop:c},400))}}function getTransitionDuration(a,b){var c=closestAttrValue(a,"ic-transition-duration");if(c)return parseInterval(c);if(c=closestAttrValue(b,"ic-transition-duration"))return parseInterval(c);var d=0,e=$(b).css("transition-duration");e&&(d+=parseInterval(e));var f=$(b).css("transition-delay");return f&&(d+=parseInterval(f)),d}function processICResponse(a,b,c){if(a&&""!=a&&" "!=a){log(b,"response content: \n"+a,"DEBUG");var d=getTarget(b),e=maybeFilter(a,closestAttrValue(b,"ic-select-from-response")),f=function(){if("true"==closestAttrValue(b,"ic-replace-target")){try{d.replaceWith(e)}catch(a){log(b,formatError(a),"ERROR")}processNodes(e),fireReadyStuff($(d)),autoFocus($(d))}else{if(b.is(getICAttributeSelector("ic-prepend-from")))prepend(d,e),processNodes(e),fireReadyStuff($(d)),autoFocus($(d));else if(b.is(getICAttributeSelector("ic-append-from")))append(d,e),processNodes(e),fireReadyStuff($(d)),autoFocus($(d));else{try{d.empty().append(e)}catch(a){log(b,formatError(a),"ERROR")}$(d).children().each(function(){processNodes($(this))}),fireReadyStuff($(d)),autoFocus($(d))}1!=c&&maybeScrollToTarget(b,d)}};if(0==d.length)return void log(b,"Invalid target for element: "+getICAttribute($(b).closest(getICAttributeSelector("ic-target")),"ic-target"),"ERROR");var g=getTransitionDuration(b,d);d.addClass("ic-transitioning"),setTimeout(function(){try{f()}catch(a){log(b,"Error during content swaop : "+formatError(a),"ERROR")}setTimeout(function(){try{d.removeClass("ic-transitioning"),_history&&_history.updateHistory(),d.trigger("complete_transition.ic",[d])}catch(a){log(b,"Error during transition complete : "+formatError(a),"ERROR")}},20)},g)}else log(b,"Empty response, nothing to do here.","DEBUG")}function maybeFilter(a,b){var c=$.parseHTML(a,null,!0),d=$(c);return b?d.filter(b).add(d.find(b)).contents():d}function getStyleTarget(a){var b=closestAttrValue(a,"ic-target");return b&&0==b.indexOf("this.style.")?b.substr(11):null}function getAttrTarget(a){var b=closestAttrValue(a,"ic-target");return b&&0==b.indexOf("this.")?b.substr(5):null}function fireICRequest(a,b){var c=a;a.is(getICAttributeSelector("ic-src"))||void 0!=getICAttribute(a,"ic-action")||(a=a.closest(getICAttributeSelector("ic-src")));var d=closestAttrValue(a,"ic-confirm");if((!d||confirm(d))&&a.length>0){var e=uuid();a.data("ic-event-id",e);var f=function(){if(1==a.data("ic-request-in-flight"))return void a.data("ic-next-request",f);if(a.data("ic-event-id")==e){var d=getStyleTarget(a),g=d?null:getAttrTarget(a),h=verbFor(a),i=getICAttribute(a,"ic-src");if(i){var j=b||function(b){d?a.css(d,b):g?a.attr(g,b):(processICResponse(b,a),"GET"!=h&&refreshDependencies(getICAttribute(a,"ic-src"),a))},k=getParametersForElement(h,a,c);k&&handleRemoteRequest(a,h,i,k,j)}var l=getICAttribute(a,"ic-action");l&&invokeLocalAction(a,l)}},g=closestAttrValue(a,"ic-trigger-delay");g?setTimeout(f,parseInterval(g)):f()}}function invokeLocalAction(a,b){var c=getTarget(a),d=b.split(";"),e=[],f=0;$.each(d,function(a,b){var d=$.trim(b),g=d,h=[];d.indexOf(":")>0&&(g=d.substr(0,d.indexOf(":")),h=computeArgs(d.substr(d.indexOf(":")+1,d.length))),""==g||("delay"==g?(null==f&&(f=0),f+=parseInterval(h[0]+"")):(null==f&&(f=420),e.push([f,makeApplyAction(c,g,h)]),f=null))}),f=0,$.each(e,function(a,b){f+=b[0],setTimeout(b[1],f)})}function computeArgs(args){try{return eval("["+args+"]")}catch(a){return[$.trim(args)]}}function makeApplyAction(a,b,c){return function(){var d=a[b]||window[b];d?d.apply(a,c):log(a,"Action "+b+" was not found","ERROR")}}function newIntercoolerHistory(a,b,c,d){function e(a){return null==a||a.slotLimit!=c||a.historyVersion!=d||null==a.lruList}function f(){for(var b=[],e=0;e<a.length;e++)0==a.key(e).indexOf(s)&&b.push(a.key(e));for(var f=0;f<b.length;f++)a.removeItem(b[f]);a.removeItem(r),t={slotLimit:c,historyVersion:d,lruList:[]}}function g(b){var c=t.lruList,d=c.indexOf(b),e=n($("body"));if(d>=0)log(e,"URL found in LRU list, moving to end","INFO"),c.splice(d,1),c.push(b);else if(log(e,"URL not found in LRU list, adding","INFO"),c.push(b),c.length>t.slotLimit){var f=c.shift();log(e,"History overflow, removing local history for "+f,"INFO"),a.removeItem(s+f)}return a.setItem(r,JSON.stringify(t)),c}function h(b){var d=JSON.stringify(b);try{a.setItem(b.id,d)}catch(e){try{f(),a.setItem(b.id,d)}catch(a){log(n($("body")),"Unable to save intercooler history with entire history cleared, is something else eating local storage? History Limit:"+c,"ERROR")}}}function i(a,b,c){var d={url:c,id:s+c,content:a,yOffset:b,timestamp:(new Date).getTime()};return g(c),h(d),d}function j(a){if(null==a.onpopstate||1!=a.onpopstate["ic-on-pop-state-handler"]){var b=a.onpopstate;a.onpopstate=function(a){n($("body")).trigger("handle.onpopstate.ic"),m(a)||b&&b(a),n($("body")).trigger("pageLoad.ic")},a.onpopstate["ic-on-pop-state-handler"]=!0}}function k(){u&&(l(u.newUrl,currentUrl(),u.oldHtml,u.yOffset),u=null)}function l(a,c,d,e){var f=i(d,e,c);b.replaceState({"ic-id":f.id},"","");var g=n($("body")),h=i(g.html(),window.pageYOffset,a);b.pushState({"ic-id":h.id},"",a),g.trigger("pushUrl.ic",[g,h])}function m(b){var c=b.state;if(c&&c["ic-id"]){var d=JSON.parse(a.getItem(c["ic-id"]));if(d)return processICResponse(d.content,n($("body")),!0),d.yOffset&&window.scrollTo(0,d.yOffset),!0;$.get(currentUrl(),{"ic-restore-history":!0},function(a,b){var c=createDocument(a),d=n(c).html();processICResponse(d,n($("body")),!0)})}return!1}function n(a){var b=a.find(getICAttributeSelector("ic-history-elt"));return b.length>0?b:a}function o(a){var b=n($("body"));b.trigger("beforeHistorySnapshot.ic",[b]),u={newUrl:a,oldHtml:b.html(),yOffset:window.pageYOffset}}function p(){var b="",c=[];for(var d in a)c.push(d);c.sort();var e=0;for(var f in c){var g=2*a[c[f]].length;e+=g,b+=c[f]+"="+(g/1024/1024).toFixed(2)+" MB\n"}return b+"\nTOTAL LOCAL STORAGE: "+(e/1024/1024).toFixed(2)+" MB"}function q(){return t}var r="ic-history-support",s="ic-hist-elt-",t=JSON.parse(a.getItem(r)),u=null;return e(t)&&(log(n($("body")),"Intercooler History configuration changed, clearing history","INFO"),f()),null==t&&(t={slotLimit:c,historyVersion:d,lruList:[]}),{clearHistory:f,updateHistory:k,addPopStateHandler:j,snapshotForHistory:o,_internal:{addPopStateHandler:j,supportData:q,dumpLocalStorage:p,updateLRUList:g}}}function getSlotLimit(){return 20}function refresh(a){return"string"==typeof a||a instanceof String?refreshDependencies(a):fireICRequest(a),Intercooler}function init(){var a=$("body");processNodes(a),fireReadyStuff(a),_history&&_history.addPopStateHandler(window),location.search&&location.search.indexOf("ic-launch-debugger=true")>=0&&Intercooler.debug()}var USE_DATA="true"==$('meta[name="intercoolerjs:use-data-prefix"]').attr("content"),USE_ACTUAL_HTTP_METHOD="true"==$('meta[name="intercoolerjs:use-actual-http-method"]').attr("content"),_MACROS=$.map(["ic-get-from","ic-post-to","ic-put-to","ic-patch-to","ic-delete-from","ic-style-src","ic-attr-src","ic-prepend-from","ic-append-from","ic-action"],function(a){return fixICAttributeName(a)}),_scrollHandler=null,_UUID=1,_readyHandlers=[],_isDependentFunction=function(a,b){if(!a||!b)return!1;var c=a.split(/[\?#]/,1)[0].split("/").filter(function(a){return""!=a}),d=b.split(/[\?#]/,1)[0].split("/").filter(function(a){return""!=a});return""!=c&&""!=d&&(d.slice(0,c.length).join("/")==c.join("/")||c.slice(0,d.length).join("/")==d.join("/"))},_history=null;try{_history=newIntercoolerHistory(localStorage,window.history,getSlotLimit(),.1)}catch(a){log($("body"),"Could not initialize history","WARN")}return $.ajaxTransport("text",function(a,b){if("#"==b.url[0]){var c=fixICAttributeName("ic-local-"),d=$(b.url),e=[],f=200,g="OK";d.each(function(a,b){$.each(b.attributes,function(a,b){if(b.name.substr(0,c.length)==c){var d=b.name.substring(c.length);if("status"==d){var h=b.value.match(/(\d+)\s?(.*)/);null!=h?(f=h[1],g=h[2]):(f="500",g="Attribute Error")}else e.push(d+": "+b.value)}})});var h=d.length>0?d.html():"";return{send:function(a,b){b(f,g,{html:h},e.join("\n"))},abort:function(){}}}return null}),$(function(){init()}),{refresh:refresh,history:_history,triggerRequest:fireICRequest,processNodes:processNodes,closestAttrValue:closestAttrValue,verbFor:verbFor,isDependent:isDependent,getTarget:getTarget,processHeaders:processHeaders,setIsDependentFunction:function(a){_isDependentFunction=a},ready:function(a){_readyHandlers.push(a)},debug:function(){var a=closestAttrValue("body","ic-debugger-url")||"https://intercoolerreleases-leaddynocom.netdna-ssl.com/intercooler-debugger.js";$.getScript(a).fail(function(a,b,c){log($("body"),formatError(c),"ERROR")})},_internal:{init:init,replaceOrAddMethod:replaceOrAddMethod}}}();return Intercooler}); \ No newline at end of file
diff --git a/gui/static/script.js b/gui/static/script.js
index 231aa03..a585f9f 100644
--- a/gui/static/script.js
+++ b/gui/static/script.js
@@ -1,16 +1,4 @@
$(document).ready(function() {
- // Only show first tab
- $('#info div:not(:first)').hide();
-
- // Handle clicks on tabs to change visiblity of panes
- $('#info-nav li').click(function(event) {
- event.preventDefault();
- $('#info div').hide();
- $('#info-nav .current').removeClass("current");
- $(this).addClass('current');
- var clicked = $(this).find('a:first').attr('href');
- $('#info ' + clicked).fadeIn('fast');
- }).eq(0).addClass('current');
});
diff --git a/gui/static/style.css b/gui/static/style.css
index 46dccc0..43176f8 100644
--- a/gui/static/style.css
+++ b/gui/static/style.css
@@ -8,6 +8,12 @@ p {
padding: 5px;
}
+div.cadre{
+ border: 1px solid #999;
+ padding: 0 10px;
+ margin: 5px;
+}
+
#info{
width: 600px;
border: 1px solid #999;
@@ -34,10 +40,9 @@ p {
#info-nav li a:hover{
color:#d15600;
}
-#info-nav li.current{
+#info-nav li.current{
background: #fff;
padding-bottom: 4px;
-
}
#celebs {
diff --git a/gui/views/index.tpl b/gui/views/index.tpl
index c38d827..4538df8 100644
--- a/gui/views/index.tpl
+++ b/gui/views/index.tpl
@@ -14,11 +14,12 @@
<li><a href="#servicelist">Services</a></li>
<li><a href="#subchannels">Subchannels</a></li>
<li><a href="#components">Components</a></li>
+ <li><a href="#rcmodules">RC Modules</a></li>
</ul>
<div id="info">
- <div id="general">
- <p>General Multiplex Options</p>
+ <div id="general"><div class="cadre">
+ <h2>General Multiplex Options</h2>
<ul>
<li>Number of frames to encode: {{g.nbframes}}</li>
<li>Statistics server port: {{g.statsserverport}}</li>
@@ -27,31 +28,45 @@
<li>DAB Mode: {{g.dabmode}}</li>
<li>Log to syslog: {{g.syslog}}</li>
</ul>
- </div>
- <div id="servicelist">
- <p>Services</p>
+ </div></div>
+ <div id="servicelist"><div class="cadre">
+ <h2>Services</h2>
<ul>
% for s in services:
<li>{{s.name}}: <i>{{s.label}} ({{s.shortlabel}})</i> &mdash; id = {{s.id}}</li>
% end
</ul>
- </div>
- <div id="subchannels">
- <p>Subchannels</p>
+ </div></div>
+ <div id="subchannels"><div class="cadre">
+ <h2>Subchannels</h2>
<ul>
% for s in subchannels:
<li>{{s.name}}: <i>{{s.type}}</i> &mdash; {{s.inputfile}}; {{s.bitrate}}kbps</li>
% end
</ul>
- </div>
- <div id="components">
- <p>Components</p>
+ </div></div>
+ <div id="components"><div class="cadre">
+ <h2>Components</h2>
<ul>
% for s in components:
<li>{{s.name}}: <i>{{s.label}} ({{s.shortlabel}})</i> &mdash; service {{s.service}}; subchannel {{s.subchannel}}; figtype {{s.figtype}}</li>
% end
</ul>
- </div>
+ </div></div>
+ <div id="rcmodules"><div class="cadre">
+ <h2>RC Modules</h2>
+ <ul>
+ % for m in rcmodules:
+ <li>{{m.name}}
+ <ul>
+ % for p in m.parameters:
+ <li><a href="/rc/{{m.name}}/{{p.param}}">{{p.param}}</a> : {{p.value}}</li>
+ % end
+ </ul>
+ </li>
+ % end
+ </ul>
+ </div></div>
</div>
</body>
</html>
diff --git a/gui/views/rcparam.tpl b/gui/views/rcparam.tpl
new file mode 100644
index 0000000..955086b
--- /dev/null
+++ b/gui/views/rcparam.tpl
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>ODR-DabMux Configuration</title>
+ <link rel="stylesheet" href="/static/style.css" type="text/css" media="screen" charset="utf-8"/>
+ <script type="text/javascript" src="/static/jquery-1.10.2.min.js"></script>
+ <script type="text/javascript" src="/static/intercooler-1.0.1.min.js"></script>
+</head>
+<body>
+ <h1>Remote-Control of module {{module}}</h1>
+
+ <form ic-post-to="/rc/{{module}}/{{param}}">
+ <div class="form-group">
+ <label>Parameter <i>{{param}}</i> value: </label>
+ <input class="form-control" name="newvalue" type="text" value="{{value}}">
+ </div>
+ <button class="btn btn-default">Update value</button>
+ </form>
+
+</body>
+</html>
+
diff --git a/src/RemoteControl.cpp b/src/RemoteControl.cpp
index ac9f087..31e63f2 100644
--- a/src/RemoteControl.cpp
+++ b/src/RemoteControl.cpp
@@ -57,7 +57,9 @@ std::list<std::string> RemoteControllable::get_supported_parameters() const {
return parameterlist;
}
-RemoteControllable* RemoteControllers::get_controllable_(const std::string& name) {
+RemoteControllable* RemoteControllers::get_controllable_(
+ const std::string& name)
+{
auto rc = std::find_if(controllables.begin(), controllables.end(),
[&](RemoteControllable* r) { return r->get_rc_name() == name; });
@@ -69,6 +71,17 @@ RemoteControllable* RemoteControllers::get_controllable_(const std::string& name
}
}
+void RemoteControllers::set_param(
+ const std::string& name,
+ const std::string& param,
+ const std::string& value)
+{
+ etiLog.level(info) << "RC: Setting " << name << " " << param
+ << " to " << value;
+ RemoteControllable* controllable = get_controllable_(name);
+ return controllable->set_parameter(param, value);
+}
+
// This runs in a separate thread, because
// it would take too long to be done in the main loop
// thread.
diff --git a/src/RemoteControl.h b/src/RemoteControl.h
index 5b735a8..fe8ac42 100644
--- a/src/RemoteControl.h
+++ b/src/RemoteControl.h
@@ -177,10 +177,10 @@ class RemoteControllers {
return controllable->get_parameter(param);
}
- void set_param(const std::string& name, const std::string& param, const std::string& value) {
- RemoteControllable* controllable = get_controllable_(name);
- return controllable->set_parameter(param, value);
- }
+ void set_param(
+ const std::string& name,
+ const std::string& param,
+ const std::string& value);
std::list<RemoteControllable*> controllables;