diff --git a/src/webui/extjs_dvb.c b/src/webui/extjs_dvb.c index 0ceaa584..200465e3 100644 --- a/src/webui/extjs_dvb.c +++ b/src/webui/extjs_dvb.c @@ -100,6 +100,7 @@ extjs_idnode_filter return 1; } +// TODO: move this static htsmsg_t * extjs_idnode_class ( const idclass_t *idc ) { @@ -112,7 +113,7 @@ extjs_idnode_class ( const idclass_t *idc ) } static int -extjs_mpegts_services +extjs_mpegts_service (http_connection_t *hc, const char *remain, void *opaque) { //char buf[256]; @@ -166,7 +167,7 @@ extjs_mpegts_services static int -extjs_mpegts_muxes +extjs_mpegts_mux (http_connection_t *hc, const char *remain, void *opaque) { char buf[256]; @@ -223,7 +224,7 @@ extjs_mpegts_muxes } static int -extjs_mpegts_networks +extjs_mpegts_network (http_connection_t *hc, const char *remain, void *opaque) { mpegts_network_t *mn; @@ -278,6 +279,73 @@ extjs_mpegts_networks return 0; } +static int +extjs_mpegts_input + (http_connection_t *hc, const char *remain, void *opaque) +{ + extern const idclass_t mpegts_input_class; + mpegts_input_t *mi; + mpegts_network_t *mn; + htsbuf_queue_t *hq = &hc->hc_reply; + const char *op = http_arg_get(&hc->hc_req_args, "op"); + htsmsg_t *out = htsmsg_create_map(); + extjs_grid_conf_t conf; + int total = 0; + + if (!op) return 404; + + if (!strcmp(op, "list")) { + htsmsg_t *list = htsmsg_create_list(); + extjs_grid_conf(&hc->hc_req_args, &conf); + + pthread_mutex_lock(&global_lock); + LIST_FOREACH(mi, &mpegts_input_all, mi_global_link) { + if (conf.filter && !extjs_idnode_filter(&mi->mi_id, conf.filter)) + continue; + total++; + if (conf.start-- > 0) + continue; + if (conf.limit != 0) { + if (conf.limit > 0) conf.limit--; + htsmsg_t *e = htsmsg_create_map(); + htsmsg_add_str(e, "uuid", idnode_uuid_as_str(&mi->mi_id)); + idnode_save(&mi->mi_id, e); + htsmsg_add_msg(list, NULL, e); + } + } + pthread_mutex_unlock(&global_lock); + htsmsg_add_msg(out, "entries", list); + htsmsg_add_u32(out, "total", total); + } else if (!strcmp(op, "class")) { + htsmsg_t *list= extjs_idnode_class(&mpegts_input_class); + htsmsg_add_msg(out, "entries", list); + } else if (!strcmp(op, "network_class")) { + const char *uuid = http_arg_get(&hc->hc_req_args, "uuid"); + if (!uuid) return 404; + mpegts_input_t *mi = idnode_find(uuid, &mpegts_input_class); + if (!mi) return 404; + htsmsg_t *list= extjs_idnode_class(mi->mi_network_class(mi)); + htsmsg_add_msg(out, "entries", list); + } else if (!strcmp(op, "network_create")) { + const char *uuid = http_arg_get(&hc->hc_req_args, "uuid"); + const char *conf = http_arg_get(&hc->hc_req_args, "conf"); + if (!uuid || !conf) return 404; + mi = idnode_find(uuid, &mpegts_input_class); + if (!mi) return 404; + mn = mi->mi_network_create(mi, htsmsg_json_deserialize(conf)); + if (mn) + mn->mn_config_save(mn); + else { + // TODO: Check for error + } + } + + htsmsg_json_serialize(out, hq, 0); + http_output_content(hc, "text/x-json; charset=UTF-8"); + htsmsg_destroy(out); + + return 0; +} /** * DVB WEB user interface @@ -285,13 +353,14 @@ extjs_mpegts_networks void extjs_start_dvb(void) { - printf("extjs_start_dvb()\n"); http_path_add("/api/mpegts/network", - NULL, extjs_mpegts_networks, ACCESS_WEB_INTERFACE); + NULL, extjs_mpegts_network, ACCESS_WEB_INTERFACE); http_path_add("/api/mpegts/mux", - NULL, extjs_mpegts_muxes, ACCESS_WEB_INTERFACE); + NULL, extjs_mpegts_mux, ACCESS_WEB_INTERFACE); http_path_add("/api/mpegts/service", - NULL, extjs_mpegts_services, ACCESS_WEB_INTERFACE); + NULL, extjs_mpegts_service, ACCESS_WEB_INTERFACE); + http_path_add("/api/mpegts/input", + NULL, extjs_mpegts_input, ACCESS_WEB_INTERFACE); #if 0 http_path_add("/dvb/locations", NULL, extjs_dvblocations, ACCESS_WEB_INTERFACE); diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index cbec99cf..677e3d2b 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -1,3 +1,146 @@ +json_decode = function(d) +{ + if (d && d.responseText) { + d = Ext.util.JSON.decode(d.responseText) + d = d.entries; + } else { + d = [] + } + return d; +} + +/* + * IDnode creation dialog + */ +tvheadend.idnode_create = function(conf) +{ + var puuid = null; + var panel = null; + + /* Buttons */ + var saveBtn = new Ext.Button({ + tooltip : 'Create new entry', + text : 'Create', + hidden : true, + handler : function(){ + params = conf.create.params || {} + params['uuid'] = puuid; + params['conf'] = Ext.util.JSON.encode(panel.getForm().getValues()); + Ext.Ajax.request({ + url : conf.create.url || conf.url, + params : params, + success : function(d) { + win.close(); + } + }); + } + }); + var undoBtn = new Ext.Button({ + tooltip : 'Cancel operation', + text : 'Cancel', + handler : function(){ + win.close(); + } + }); + + /* Form */ + panel = new Ext.FormPanel({ + frame : true, + border : true, + bodyStyle : 'padding: 5px', + labelAlign : 'left', + labelWidth : 200, + autoWidth : true, + autoHeight : true, + defaultType : 'textfield', + buttonAlign : 'left', + items : [], + buttons : [ undoBtn, saveBtn ] + }); + + /* Create window */ + win = new Ext.Window({ + title : 'Add ' + conf.title, + layout : 'fit', + autoWidth : true, + autoHeight : true, + plain : true, + items : panel + }); + + /* + * Build the edit panel + */ + function build_form (d) + { + saveBtn.setVisible(true); + + /* Fields */ + for (i = 0; i < d.length; i++) { + if (d[i].rdonly) continue; + if (d[i].type == 'int' || d[i].type == 'u16' || d[i].type == 'u32') { + panel.add(new Ext.form.NumberField({ + fieldLabel : d[i].caption, + name : d[i].id, + width : 300 + })); + } else if (d[i].type == 'bool') { + panel.add(new Ext.form.Checkbox({ + fieldLabel : d[i].caption, + name : d[i].id + })); + } else if (d[i].type == 'str') { + panel.add(new Ext.form.TextField({ + fieldLabel : d[i].caption, + name : d[i].id, + width : 300 + })); + } + } + panel.doLayout(); + } + + /* Do we need to first select a class? */ + if (conf.select) { + + /* Parent selector */ + var combo = new Ext.form.ComboBox({ + fieldLabel : conf.select.label, + grow : true, + editable : false, + allowBlank : false, + displayField : conf.select.displayField, + valueField : conf.select.valueField, + mode : 'remote', + triggerAction : 'all', + store : new Ext.data.JsonStore({ + root : 'entries', + url : conf.select.url, + baseParams : conf.select.params, + fields : [ conf.select.valueField, conf.select.displayField ] + }), + listeners : { + select: function (s, n, o) { + params = conf.select.clazz.params || {}; + params['uuid'] = puuid = n.data.uuid; + Ext.Ajax.request({ + url : conf.select.clazz.url || conf.select.url || conf.url, + success : function(d) { + panel.remove(s); + build_form(json_decode(d)); + }, + params : params + }); + } + } + }); + + panel.add(combo); + win.show(); + } +} + + /* * IDnode grid */ @@ -5,27 +148,42 @@ tvheadend.idnode_grid = function(panel, conf) { function build (d) { - if (d && d.responseText) { - d = Ext.util.JSON.decode(d.responseText) - d = d.entries; - } else - d = [] - - /* Process object */ var columns = []; var filters = []; var fields = []; + var buttons = []; + var saveBtn = null; + var undoBtn = null; + var addBtn = null; + var delBtn = null; + + /* Load Class */ + d = json_decode(d); + + /* Process */ for (i = 0; i < d.length; i++) { var type = 'string'; - if (d[i].type == 'int' || d[i].type == 'u16' || d[i].type == 'u32') + var edit = null; + if (d[i].type == 'int' || d[i].type == 'u16' || d[i].type == 'u32') { type = 'numeric'; - else if (d[i].type == 'bool') + if (!d[i].rdonly) + edit = new Ext.form.NumberField({ + // TODO: min/max + }) + } else if (d[i].type == 'bool') { type = 'boolean'; + if (!d[i].rdonly) + edit = new Ext.form.Checkbox({}); + } else if (d[i].type == 'str') { + if (!d[i].rdonly) + edit = new Ext.form.TextField({}); + } fields.push(d[i].id) columns.push({ dataIndex: d[i].id, header : d[i].caption, - sortable : true + sortable : true, + editor : edit }); filters.push({ type : type, @@ -59,19 +217,87 @@ tvheadend.idnode_grid = function(panel, conf) columns : columns }); + /* Selection */ + var select = new Ext.grid.RowSelectionModel({ + singleSelect : false + }); + + /* Event handlers */ + store.on('update', function(s, r, o){ + d = (s.getModifiedRecords().length == 0); + undoBtn.setDisabled(d); + saveBtn.setDisabled(d); + }); + select.on('selectionchange', function(s){ + if (delBtn) + delBtn.setDisabled(s.getCount() == 0); + }); + + /* Top bar */ + saveBtn = new Ext.Toolbar.Button({ + tooltip : 'Save pending changes (marked with red border)', + iconCls : 'save', + text : 'Save', + disabled : true, + handler : function(){} + }); + buttons.push(saveBtn); + undoBtn = new Ext.Toolbar.Button({ + tooltip : 'Revert pending changes (marked with red border)', + iconCls : 'undo', + text : 'Undo', + disabled : true, + handler : function() { + store.rejectChanges(); + } + }); + buttons.push(undoBtn); + buttons.push('-'); + if (conf.add) { + addBtn = new Ext.Toolbar.Button({ + tooltip : 'Add a new entry', + iconCls : 'add', + text : 'Add', + disabled : false, + handler : function() { + tvheadend.idnode_create(conf.add); + } + }); + buttons.push(addBtn); + } + if (conf.del) { + delBtn = new Ext.Toolbar.Button({ + tooltip : 'Delete selected entries', + iconCls : 'remove', + text : 'Delete', + disabled : true, + handler : function(){} + }); + buttons.push(delBtn); + } + buttons.push('->'); + if (conf.help) { + buttons.push({ + text : 'Help', + handler : conf.help + }); + } + /* Grid Panel */ var grid = new Ext.grid.EditorGridPanel({ stripeRows : true, title : conf.title, store : store, cm : model, + selModel : select, plugins : [ filter ], viewConfig : { forceFit : true }, - bbar : new Ext.PagingToolbar({ + tbar : buttons, + bbar : new Ext.PagingToolbar({ store : store, pageSize : 50, displayInfo : true, diff --git a/src/webui/static/app/mpegts.js b/src/webui/static/app/mpegts.js index 1c4d0605..ce4389a7 100644 --- a/src/webui/static/app/mpegts.js +++ b/src/webui/static/app/mpegts.js @@ -9,23 +9,45 @@ tvheadend.networks = function(panel) { tvheadend.idnode_grid(panel, { - url : 'api/mpegts/network', - title: 'Networks' + title : 'Networks', + url : 'api/mpegts/network', + add : { + url : 'api/mpegts/input', + title : 'Network', + select : { + label : 'Input', + params : { op: 'list', limit: -1 }, + displayField : 'fe_path',//displayname', + valueField : 'uuid', + url : 'api/mpegts/input', + clazz : { + params : { op: 'network_class' } + } + }, + create : { + params : { op: 'network_create' } + } + }, + del : true }); } tvheadend.muxes = function(panel) { tvheadend.idnode_grid(panel, { - url : 'api/mpegts/mux', - title: 'Muxes' + url : 'api/mpegts/mux', + title : 'Muxes', + add : true, + del : true }); } tvheadend.services = function(panel) { tvheadend.idnode_grid(panel, { - url : 'api/mpegts/service', - title: 'Services' + url : 'api/mpegts/service', + title : 'Services', + add : false, + del : false }); }