custom subscriptions configurator/widget preview
custom subscriptions configurator/widget preview

--- a/assets/js/iotcc.js
+++ b/assets/js/iotcc.js
@@ -91,9 +91,7 @@
             widgetId = iotCC.formatTopic(topic),
             widget, html;
             $('.dashboard-notification-html').remove();
-            if (iotCC.mqttConfig.debug) {
-                logger.log('Received Topic:= ' + topic + '\n\tMessage:= ' + message.toString());
-            }
+            logger.log('Received Topic:= ' + topic + '\n\tMessage:= ' + message.toString());
 
             if (topicPath[4] == 'config') {
                 var page = {'pageId': json.pageId, 'pageName': json.pageName, 'icon': json.icon};
@@ -101,10 +99,10 @@
                 var publishTopic = json.publishTopic ? json.publishTopic : json.topic + '/data';
                 if (json.widget == 'toggle') {
                     if ($('input[name="' + widgetId + '"]').exists() == false) {
-                        html = '<label class="switch switch--material {class4}">';
-                        html += '<input type="checkbox" name="' + widgetId + '" data-widget="toggle" data-status="' + (json.checked==true?'1':'0') + '" class="switch__input switch--material__input {class5}" ' + (json.checked==true?'checked="checked"':'') + '>';
-                        html += '<div class="switch__toggle switch--material__toggle {class6}">';
-                        html += '<div class="switch__handle switch--material__handle {class7}">';
+                        html = '<label class="switch switch--material {class10}">';
+                        html += '<input type="checkbox" name="' + widgetId + '" data-widget="toggle" data-status="' + (json.checked==true?'1':'0') + '" class="switch__input switch--material__input {class11}" ' + (json.checked==true?'checked="checked"':'') + '>';
+                        html += '<div class="switch__toggle switch--material__toggle {class12}">';
+                        html += '<div class="switch__handle switch--material__handle {class13}">';
                         html += '</div>';
                         html += '</div>';
                         html += '</label>';
@@ -114,10 +112,18 @@
                         json.callback = function() {
                             if ($(this).prop('checked') == true) {
                                 $(this).data('status', 1);
-                                iotCC.mqttClient.publish(publishTopic, '{"status":1}', {qos: 1, retained: false});
+                                var message = '{"status":1}';
+                                iotCC.mqttClient.publish(publishTopic, message, {qos: 1, retained: false});
+                                if (iotCC.mqttConfig.debug) {
+                                    logger.log('Publish Topic:= ' + publishTopic + '\n\tMessage:= ' + message.toString());
+                                }
                             } else {
                                 $(this).data('status', 0);
-                                iotCC.mqttClient.publish(publishTopic, '{"status":0}', {qos: 1, retained: false});
+                                var message = '{"status":0}';
+                                iotCC.mqttClient.publish(publishTopic, message, {qos: 1, retained: false});
+                                if (iotCC.mqttConfig.debug) {
+                                    logger.log('Publish Topic:= ' + publishTopic + '\n\tMessage:= ' + message);
+                                }
                             }
                         };
                         iotCC.addWidget(json);
@@ -129,17 +135,20 @@
                     if ($('input[name="' + widgetId + '"]').exists() == false) {
                         html = '';
                         $(json.options).each(function(k, v) {
-                            html += '<label class="radio-button radio-button--material {class3}">';
-                            html += '<input type="radio" name="' + widgetId + '" data-widget="radios" data-status="' + v.status + '" class="radio-button__input radio-button--material__input {class4}" name="r" ' + (v.checked==true?'checked="checked"':'') + '>';
-                            html += '<div class="radio-button__checkmark radio-button--material__checkmark {class5}">';
-                            html += '</div>';
+                            html += '<label class="radio-button radio-button--material {class10}">';
+                            html += '<input type="radio" name="' + widgetId + '" data-widget="radios" data-status="' + v.status + '" class="radio-button__input radio-button--material__input {class11}" name="r" ' + (v.checked==true?'checked="checked"':'') + ' />';
+                            html += '<div class="radio-button__checkmark radio-button--material__checkmark {class12}"></div>';
                             html += v.label +'</label>';
                         });
                         json.content = html;
                         json.widgetId = widgetId;
                         json.selector = 'input';
                         json.callback = function() {
-                            iotCC.mqttClient.publish(publishTopic, '{"status":"' + $(this).data('status') + '"}', {qos: 1, retained: false});
+                            var message = '{"status":"' + $(this).data('status') + '"}';
+                            iotCC.mqttClient.publish(publishTopic, message, {qos: 1, retained: false});
+                            if (iotCC.mqttConfig.debug) {
+                                logger.log('Publish Topic:= ' + publishTopic + '\n\tMessage:= ' + message);
+                            }
                         };
                         iotCC.addWidget(json);
                     } else {
@@ -151,9 +160,9 @@
                 } else if (json.widget == 'data' || json.widget == 'data-control') {
                     if ($('span[name="' + widgetId + '"]').exists() == false) {
                         html = '';
-                        if (json.widget == 'data-control') html += '<button name="' + widgetId + '" data-widget="' + json.widget + '" data-action="-" class="button button--material btn-xs">-</button> ';
-                        html += '<span name="' + widgetId + '" data-widget="' + json.widget + '" data-value="' + json.value + '" class="text">' + json.value + '</span> ' + (json.valuedescription?'<span class="text">' + json.valuedescription + '</span>':'') + '';
-                        if (json.widget == 'data-control') html += ' <button name="' + widgetId + '" data-widget="' + json.widget + '" data-action="+" class="button button--material btn-xs">+</button>';
+                        if (json.widget == 'data-control') html += '<button name="' + widgetId + '" data-widget="' + json.widget + '" data-action="-" class="button button--material btn-xs {class10}">-</button> ';
+                        html += '<span name="' + widgetId + '" data-widget="' + json.widget + '" data-value="' + json.value + '" class="text {class11}">' + json.value + '</span> ' + (json.valuedescription?'<span class="text {class12}">' + json.valuedescription + '</span>':'') + '';
+                        if (json.widget == 'data-control') html += ' <button name="' + widgetId + '" data-widget="' + json.widget + '" data-action="+" class="button button--material btn-xs {class13}">+</button>';
                         json.content = html;
                         json.widgetId = widgetId;
                         json.selector = 'button';
@@ -161,7 +170,11 @@
                             var action = $(this).data('action');
                             var value = $('span[name="' + $(this).attr('name') + '"]').data('value');
                             value = action == '+' ? iotCC.formatData(value, json.format) + 1 : iotCC.formatData(value, json.format) - 1;
-                            iotCC.mqttClient.publish(publishTopic, '{"value":"' + value + '"}', {qos: 1, retained: false});
+                            var message = '{"value":"' + value + '"}';
+                            iotCC.mqttClient.publish(publishTopic, message, {qos: 1, retained: false});
+                            if (iotCC.mqttConfig.debug) {
+                                logger.log('Publish Topic:= ' + publishTopic + '\n\tMessage:= ' + message);
+                            }
                         };
                         iotCC.addWidget(json);
                     } else {
@@ -305,6 +318,8 @@
         config.mqttConfig.debug = $('#debug').prop('checked');
         config.mqttConfig.simulateDevices = $('#simulateDevices').prop('checked');
 
+        this.mqttConfig = Object.assign(this.mqttConfig, config.mqttConfig);
+
         try {
             localStorage.setItem('iotCCConfig', JSON.stringify(config));
             iotCC.showNotification('MQTT connection data', 'Data saved succesfully', 'settings-notification1', 'info', 1.5);
@@ -378,11 +393,11 @@
             return parseFloat(v).toFixed(d);
         }
     },
-    parseTemplate: function(json, html) {
+    parseTemplate: function(json, html, debug) {
         for (var key in json) {
             html = html.replace('{' + key + '}', json[key]);
         }
-        if (this.appConfig.templateDebug == false) {
+        if (this.appConfig.templateDebug == false && debug == undefined) {
             html = html.replace(/{(\w*)}/g, '');
         }
         return html;
@@ -398,7 +413,7 @@
             html += '<td>' + subscription.topic + '</td>';
             html += '<td>' + subscription.widget + '</td>';
             html += '<td>' + subscription.widgetJson + '</td>';
-            html += '<td>' + subscription.actionTopic + '</td>';
+            html += '<td>' + subscription.publishTopic + '</td>';
             html += '<td>' + subscription.active + '</td>';
             html += '</tr>';
             $('.subscriptions-table').append(html);
@@ -410,11 +425,12 @@
             $('input[name="index"]').val( $(tr).find('td:eq(1)').html() );
             $('input[name="topic"]').val( $(tr).find('td:eq(2)').html() );
             $('select[name="widget"]').val( $(tr).find('td:eq(3)').html() ).trigger('change');
-            $('input[name="widgetJson"]').val( $(tr).find('td:eq(4)').html() );
-            $('input[name="actionTopic"]').val( $(tr).find('td:eq(5)').html() );
+            $('textarea[name="widgetJson"]').val( $(tr).find('td:eq(4)').html() );
+            $('input[name="publishTopic"]').val( $(tr).find('td:eq(5)').html() );
             $('input[name="active"]').prop('checked', $(tr).find('td:eq(6)').html()=='true'?true:false);
-        });
-        $('.subscriptions-table').find('a.fa-remove').click(function(e){
+            $('#widgetJson').trigger('keyup');
+        });
+        $('.subscriptions-table').find('a.fa-remove').click(function(e) {
             e.preventDefault();
             var tr = $(this).parent().parent();
             var index = $(tr).find('td:eq(1)').html();
@@ -440,6 +456,7 @@
             var subscription = config.customSubscriptions[i];
             if (topic == subscription.topic) {
                 widgetJson = JSON.parse(subscription.widgetJson);
+                widgetJson.publishTopic = subscription.publishTopic;
                 if (subscription.widget == 'toggle') {
                     if (parseInt(message) > 0) {
                         widgetJson.checked = true;
@@ -509,16 +526,68 @@
         iotCC.refreshDevices();
     });
 
+    $('a').filter('[data-toggle="control-console"]').click(function(e) {
+        e.preventDefault();
+        if ($('#mqttConsole').hasClass('hide')) {
+            $('#mqttConsole').removeClass('hide')
+        } else {
+            $('#mqttConsole').addClass('hide');
+        }
+    });
 
     $('#widget').change(function(e) {
         e.preventDefault();
-        $('.help-block.widgetJson').html('Example JSON Options: ' + iotCC.customSubscriptionWidgetJson[$(this).val()]);
+        var widgetJson = iotCC.customSubscriptionWidgetJson[$(this).val()];
+        $('.help-block.widgetJson').html('Example JSON Options: ' + widgetJson).click(function(e){
+            e.preventDefault();
+            $('.subscriptionWidgetJson-html').remove();
+            $('#widgetJson').val(JSON.stringify(JSON.parse(widgetJson), null, 4)).trigger('keyup');
+        });
+
         if ($(this).find('option:selected').data('action') == 'on') {
             $('.subscriptionAction').removeClass('hide');
         } else {
             $('.subscriptionAction').addClass('hide');
         }
     });
+
+    $('#widgetJson').keyup(function() {
+        $('.subscriptionWidgetJson-html').remove();
+        var val = $('#widgetJson').val();
+        if (val) {
+            try { json = JSON.parse(val); }
+            catch (e) {
+                iotCC.showNotification('JSON Error', 'Error in parsing json. ' + e, 'subscriptionWidgetJson', 'danger');
+                return;
+            }
+        } else {
+            return;
+        }
+        if (json.template) {
+            $.ajaxSetup({ cache: false });
+            $.get('assets/template/' + json.template + '.html', function(html) {
+                html = iotCC.parseTemplate(json, html, true);
+                $('.box-body.widget-preview').html(html);
+                var template = $('.box-body.widget-preview').find('.widgetcontainer').html();
+                $('.box-body.widget-preview').find('.widgetcontainer').replaceWith(template);
+                $('#widgetCodePreview').val(template.trim());
+            });
+        }
+    });
+
+    $('button.beautify').click(function() {
+        var val = $('#widgetJson').val();
+        if (val) {
+            try { json = JSON.parse(val); }
+            catch (e) {
+                return;
+            }
+        } else {
+            return;
+        }
+        $('#widgetJson').val(JSON.stringify(json, null, 4));
+    });
+
 
     $('#saveSubscription').click(function(e) {
         e.preventDefault();
@@ -538,6 +607,7 @@
             iotCC.showNotification('Custom subscriptions', 'Data saved succesfully', 'subscriptions-notification', 'info', 1.5);
             iotCC.customSubscriptions();
             $('#customSubscriptions')[0].reset();
+            $('.help-block.widgetJson').html('');
         } catch(ex) {
             console.log (ex);
             iotCC.showNotification('Custom subscriptions', 'Cannot save data to local storage', 'subscriptions-notification', 'danger', 3);
@@ -557,6 +627,19 @@
         }
     });
 
+    $('#mqttConsoleSend').click(function(e) {
+        e.preventDefault();
+        iotCC.mqttClient.publish($('#mqttConsoleTopic').val(), $('#mqttConsoleMessage').val(), {qos: 1, retained: false});
+        if (iotCC.mqttConfig.debug) {
+            logger.log('Console Publish Topic:= ' + $('#mqttConsoleTopic').val() + '\n\tMessage:= ' + $('#mqttConsoleMessage').val());
+        }
+    });
+
+    if (window.location.hash) {
+        var section = window.location.hash.replace("#", "");
+        $('a.navigation').filter('[data-section="' + section + '"]').trigger('click');
+    }
+
     iotCC.init();
 });
 

--- a/assets/js/simulateDevices.js
+++ b/assets/js/simulateDevices.js
@@ -11,37 +11,37 @@
     }
     if (topicPath[2] == 'device') {
         iotCC.mqttClient.publish('/iotcc/heater1/device', '{"name":"House heating 1","desc":"", "pages" : [{"pageId": 10, "pageName": "House heating", "icon": "ion-ios-home", "class1":"bg-blue", "order": "10"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/heater1/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Hollway Heater", "topic":"/iotcc/heater1/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-3", "icon": "ion-ios-home", "class": "bg-blue", "order": 40}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/heater1/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Hollway Heater", "topic":"/iotcc/heater1/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-1", "icon": "ion-ios-home", "class4": "bg-blue", "order": 40}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/heater2/device', '{"name":"House heating 2","desc":"", "pages" : [{"pageId": 10, "pageName": "House heating", "icon": "ion-ios-home", "class1":"bg-blue", "order": "10"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/heater2/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Kitchen Heater", "topic":"/iotcc/heater2/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-3", "icon": "ion-ios-home", "class": "bg-blue", "order": 30}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/heater2/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Kitchen Heater", "topic":"/iotcc/heater2/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-1", "icon": "ion-ios-home", "class4": "bg-blue", "order": 30}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/heater3/device', '{"name":"House heating 3","desc":"", "pages" : [{"pageId" : 10, "pageName" : "House heating", "icon": "ion-ios-home", "class1":"bg-blue", "order": "10"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/heater3/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Bedroom Heater", "topic":"/iotcc/heater3/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-3", "icon": "ion-ios-home", "class": "bg-blue", "order": 10}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/heater3/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Bedroom Heater", "topic":"/iotcc/heater3/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-1", "icon": "ion-ios-home", "class4": "bg-blue", "order": 10}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/heater4/device', '{"name":"House heating 4","desc":"", "pages" : [{"pageId" : 10, "pageName" : "House heating", "icon": "ion-ios-home", "class1":"bg-blue", "order": "10"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/heater4/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Living Heater", "topic":"/iotcc/heater4/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-3", "icon": "ion-ios-home", "class": "bg-blue", "order": 20}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/heater4/heater/config', '{"pageName": "House heating", "pageId": 10, "widget":"radios", "title":"Living Heater", "topic":"/iotcc/heater4/heater", "options":[{"checked":true, "label": "Off", "status":"1"}, {"label": "Confort", "status":"2"}, {"label": "Anti freeze", "status":"3"}, {"label": "Confort -2", "status":"4"}], "template": "template-1", "icon": "ion-ios-home", "class4": "bg-blue", "order": 20}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/dogsheating/device', '{"name":"Dogs heating","desc":"", "pages" : [{"pageId" : 20, "pageName" : "Dogs heating", "icon": "ion-ios-paw", "class1":"bg-green", "order": "40"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/dogsheating/toggle1/config', '{"pageName": "Dogs heating", "pageId": 20, "widget":"toggle", "title":"Mara", "topic":"/iotcc/dogsheating/toggle1", "checked":true, "template": "template-1", "icon": "ion-ios-paw", "class": "bg-green", "order" : 10}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/dogsheating/toggle2/config', '{"pageName": "Dogs Heating", "pageId": 20, "widget":"toggle", "title":"Linda", "topic":"/iotcc/dogsheating/toggle2", "template": "template-1", "icon": "ion-ios-paw", "class": "bg-green", "order" : 20}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/dogsheating/toggle1/config', '{"pageName": "Dogs heating", "pageId": 20, "widget":"toggle", "title":"Mara", "topic":"/iotcc/dogsheating/toggle1", "checked":true, "template": "template-1", "icon": "ion-ios-paw", "class4": "bg-green", "order" : 10}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/dogsheating/toggle2/config', '{"pageName": "Dogs Heating", "pageId": 20, "widget":"toggle", "title":"Linda", "topic":"/iotcc/dogsheating/toggle2", "template": "template-1", "icon": "ion-ios-paw", "class4": "bg-green", "order" : 20}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/outdoorlights1/device', '{"name":"Outdoor lighting 1","desc":"", "pages" : [{"pageId" : 30, "pageName" : "Outdoor Lights", "icon": "ion-ios-home", "class1":"bg-orange", "order": "20"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/outdoorlights1/garage/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"Garage", "topic":"/iotcc/outdoorlights1/garage", "checked":true, "template": "template-1", "icon": "ion-model-s", "class": "bg-orange", "order": 40}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/outdoorlights1/garage/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"Garage", "topic":"/iotcc/outdoorlights1/garage", "checked":true, "template": "template-1", "icon": "ion-model-s", "class4": "bg-orange", "order": 40}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/outdoorlights2/device', '{"name":"Outdoor lighting 2","desc":"", "pages" : [{"pageId" : 30, "pageName" : "Outdoor Lights", "icon": "ion-ios-home", "class1":"bg-green", "order": "20"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/outdoorlights2/house1/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"House front", "topic":"/iotcc/outdoorlights2/house1", "template": "template-1", "icon": "ion-ios-home", "class": "bg-orange", "order": 10}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/outdoorlights2/house1/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"House front", "topic":"/iotcc/outdoorlights2/house1", "template": "template-1", "icon": "ion-ios-home", "class4": "bg-orange", "order": 10}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/outdoorlights3/device', '{"name":"Outdoor lighting 3","desc":"", "pages" : [{"pageId" : 30, "pageName" : "Outdoor Lights", "icon": "ion-ios-home", "class1":"bg-green", "order": "20"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/outdoorlights3/house2/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"House back", "topic":"/iotcc/outdoorlights3/house2", "template": "template-1", "icon": "ion-ios-home", "class": "bg-orange", "order": 20}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/outdoorlights3/house2/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"House back", "topic":"/iotcc/outdoorlights3/house2", "template": "template-1", "icon": "ion-ios-home", "class4": "bg-orange", "order": 20}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/outdoorlights4/device', '{"name":"Outdoor lighting 4","desc":"", "pages" : [{"pageId" : 30, "pageName" : "Outdoor Lights", "icon": "ion-ios-home", "class1":"bg-green", "order": "20"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/outdoorlights4/house3/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"House sides", "topic":"/iotcc/outdoorlights4/house3", "template": "template-1", "icon": "ion-ios-home", "class": "bg-orange", "order": 30}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/outdoorlights4/house3/config', '{"pageName": "Outdoor lightling", "pageId": 30, "widget":"toggle", "title":"House sides", "topic":"/iotcc/outdoorlights4/house3", "template": "template-1", "icon": "ion-ios-home", "class4": "bg-orange", "order": 30}', {qos: 1, retained: false});
 
         iotCC.mqttClient.publish('/iotcc/greenhouse/device', '{"name":"Greenhouse","desc":"", "pages" : [{"pageId" : 40, "pageName" : "Greenhouse", "icon": "ion-ios-home", "class1":"bg-green", "order": "30"}]}', {qos: 1, retained: false});
-        iotCC.mqttClient.publish('/iotcc/greenhouse/tempsensor1/config', '{"pageName": "Greenhouse", "pageId": 40, "widget":"data", "format":"int", "title":"Temp sensor 1", "topic":"/iotcc/greenhouse/tempsensor1", "value": "22", "valuedescription": "degrees C", "template": "template-1", "icon": "ion-ios-home", "class": "bg-green", "order": 30}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/greenhouse/tempsensor1/config', '{"pageName": "Greenhouse", "pageId": 40, "widget":"data", "format":"int", "title":"Temp sensor 1", "topic":"/iotcc/greenhouse/tempsensor1", "value": "22", "valuedescription": "degrees C", "template": "template-3", "icon": "ion-ios-home", "class": "bg-green", "order": 30}', {qos: 1, retained: false});
         iotCC.mqttClient.publish('/iotcc/greenhouse/tempsensor1/data', '{"value":"' + (Math.floor(Math.random() * (10)) + 20) + '"}');
-        iotCC.mqttClient.publish('/iotcc/greenhouse/heater/config', '{"pageName": "Greenhouse", "pageId": 40, "widget":"data-control", "format":"int", "title":"Heater", "topic":"/iotcc/greenhouse/heater", "value": "22", "valuedescription": "degrees C", "template": "template-3", "icon": "ion-ios-home", "class": "bg-green", "class2": "text-center", "order": 30}', {qos: 1, retained: false});
+        iotCC.mqttClient.publish('/iotcc/greenhouse/heater/config', '{"pageName": "Greenhouse", "pageId": 40, "widget":"data-control", "format":"int", "title":"Heater", "topic":"/iotcc/greenhouse/heater", "value": "22", "valuedescription": "degrees C", "template": "template-3", "icon": "ion-ios-home", "class": "bg-green", "class2": "text-center", "class10": "xs", "order": 30}', {qos: 1, retained: false});
         iotCC.mqttClient.publish('/iotcc/greenhouse/heater/data', '{"value":"' + (Math.floor(Math.random() * (10)) + 20) + '"}');
     }
 });

--- a/assets/template/template-1.html
+++ b/assets/template/template-1.html
@@ -1,12 +1,11 @@
-<div class="col-md-3 col-sm-6 col-xs-12 widgetcontainer" data-order="{order}"  data-template="{template}">
-    <div class="info-box widget">
-        <span class="info-box-icon {class}"><i class="ion {icon}"></i></span>
-        <div class="info-box-content">
-            <span class="info-box-text {class1}"><b>{title}</b></span>
-            <span class="{class2}">
+<div class="col-md-3 col-sm-6 col-xs-12 widgetcontainer" data-order="{order}" data-template="{template}">
+    <div class="info-box {class}">
+        <span class="info-box-icon {class4}"><i class="{icon}"></i></span>
+        <div class="info-box-content {class2}">
+            <span class="info-box-text {class1}">{title}</span>
+            <span class="{class3}">
                 {content}
             </span>
-            <span class="info-box-text {class3}">{description}</span>
         </div>
     </div>
 </div>

--- a/assets/template/template-2.html
+++ b/assets/template/template-2.html
@@ -1,11 +1,10 @@
-<div class="col-lg-3 col-xs-6 widgetcontainer" data-order="{order}"  data-template="{template}">
-    <div class="small-box {class}">
-        <div class="inner {class1}">
-            <h3>{title}</h3>
-            <p class="{class3}">{content}</p>
+<div class="col-md-3 col-sm-6 col-xs-12 widgetcontainer" data-order="{order}" data-template="{template}">
+    <div class="box box-solid {class}">
+        <div class="box-header {class3}">
+            <h3 class="box-title {class1}">{title}</h3>
         </div>
-        <div class="icon">
-            <i class="ion {icon}"></i>
+        <div class="box-body {class2}">
+            {content}
         </div>
     </div>
 </div>

--- a/assets/template/template-3.html
+++ b/assets/template/template-3.html
@@ -1,15 +1,11 @@
 <div class="col-md-3 col-sm-6 col-xs-12 widgetcontainer" data-order="{order}" data-template="{template}">
-    <div class="info-box box">
-        <div class="box-header with-border {class}">
-            <span class="info-box-text {class1}"><b>{title}</b></span>
-            <div class="box-tools pull-right">
-                <div class="icon">
-                    <i class="ion {icon}"></i>
-                </div>
-            </div>
+    <div class="small-box {class}">
+        <div class="inner {class3}">
+            <h3 class="{class1}">{title}</h3>
+            <p class="{class2}">{content}</p>
         </div>
-        <div class="box-body {class2}">
-            {content}
+        <div class="icon">
+            <i class="{icon}"></i>
         </div>
     </div>
 </div>

--- /dev/null
+++ b/assets/template/template-4.html
@@ -1,1 +1,11 @@
-
+<div class="col-md-3 col-sm-6 col-xs-12 widgetcontainer" data-order="{order}" data-template="{template}">
+    <div class="box {class}">
+        <div class="box-header with-border {class3}">
+            <h3 class="box-title {class1}">{title}</h3>
+            <div class="box-tools pull-right {icon}"></div>
+        </div>
+        <div class="box-body {class2}">
+            {content}
+        </div>
+    </div>
+</div>

file:a/bower.json -> file:b/bower.json
--- a/bower.json
+++ b/bower.json
@@ -19,7 +19,9 @@
     "jquery": "^3.1.1",
     "AdminLTE": "adminlte#^2.3.11",
     "bootstrap": "^3.3.7",
-    "jquery-serialize-object": "^2.5.0"
+    "jquery-serialize-object": "^2.5.0",
+    "font-awesome": "fontawesome#^4.7.0",
+    "Ionicons": "ionicons#^2.0.1"
   }
 }
 

file:a/index.html -> file:b/index.html
--- a/index.html
+++ b/index.html
@@ -4,11 +4,10 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <title>IoT Control Center</title>
-    <!-- Tell the browser to be responsive to screen width -->
     <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
     <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
+    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
+    <link rel="stylesheet" href="bower_components/Ionicons/css/ionicons.min.css">
 
     <link rel="stylesheet" href="bower_components/AdminLTE/dist/css/AdminLTE.min.css">
     <link rel="stylesheet" href="bower_components/AdminLTE/dist/css/skins/_all-skins.min.css">
@@ -18,26 +17,22 @@
 <body class="hold-transition skin-blue sidebar-mini sidebar-collapse">
 <div class="wrapper">
     <header class="main-header">
-        <!-- Logo -->
         <a href="#dashboard" class="logo">
-            <!-- mini logo for sidebar mini 50x50 pixels -->
             <span class="logo-mini"><b>IoT</b></span>
-            <!-- logo for regular state and mobile devices -->
             <span class="logo-lg"><b>IoT</b> Control Center</span>
         </a>
 
-        <!-- Header Navbar: style can be found in header.less -->
         <nav class="navbar navbar-static-top">
-            <!-- Sidebar toggle button-->
             <a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
                 <span class="sr-only">Toggle navigation</span>
             </a>
-            <!-- Navbar Right Menu -->
             <div class="navbar-custom-menu">
                 <ul class="nav navbar-nav">
-                    <!-- Control Sidebar Toggle Button -->
                     <li>
                         <a href="#" data-toggle="control-refresh" title="Refresh devices"><i class="fa fa-refresh"></i></a>
+                    </li>
+                    <li>
+                        <a href="#" data-toggle="control-console" title="Show/hide MQTT console"><i class="fa fa-paper-plane"></i></a>
                     </li>
                     <li>
                         <a href="#" data-toggle="control-sidebar"><i class="fa fa-gear"></i></a>
@@ -46,14 +41,9 @@
             </div>
         </nav>
     </header>
-    <!-- Left side column. contains the logo and sidebar -->
     <aside class="main-sidebar">
-        <!-- sidebar: style can be found in sidebar.less -->
         <section class="sidebar">
-            <!-- /.search form -->
-            <!-- sidebar menu: : style can be found in sidebar.less -->
             <ul class="sidebar-menu">
-                <!--li class="header">Options</li-->
                 <li class="active">
                     <a href="#" class="navigation" data-section="dashboard">
                         <i class="fa fa-dashboard"></i> <span>Dashboard</span>
@@ -76,7 +66,6 @@
                 </li>
             </ul>
         </section>
-        <!-- /.sidebar -->
     </aside>
 
     <!-- Content Wrapper. Contains page content -->
@@ -111,6 +100,27 @@
 
         <!-- Dashboard content -->
         <section class="content" data-section="dashboard">
+
+            <div class="box box-primary hide" id="mqttConsole">
+                <div class="box-header with-border">
+                    <h3 class="box-title">MQTT Console</h3>
+                </div>
+                <form role="form">
+                    <div class="box-body">
+                        <div class="form-group">
+                            <label for="mqttConsoleTopic">Topic</label>
+                            <input type="text" class="form-control" id="mqttConsoleTopic" placeholder="Enter topic">
+                        </div>
+                        <div class="form-group">
+                            <label for="mqttConsoleMessage">Message</label>
+                            <input type="text" class="form-control" id="mqttConsoleMessage" placeholder="Enter message">
+                        </div>
+                    </div>
+                    <div class="box-footer">
+                        <button type="submit" class="btn btn-primary" id="mqttConsoleSend">Send</button>
+                    </div>
+                </form>
+            </div>
 
             <div class="row dashboard-notification">
                 <div class="col-md-12">
@@ -132,13 +142,11 @@
         <section class="content hide" data-section="custom-subscriptions">
 
             <div class="row subscriptions-notification">
-                <div class="col-md-12">
+                <div class="col-md-9">
                     <div class="box box-primary">
                         <div class="box-header with-border">
                             <h3 class="box-title">Add new subscription</h3>
                         </div>
-                        <!-- /.box-header -->
-                        <!-- form start -->
                         <form id="customSubscriptions">
                             <div class="box-body">
                                 <div class="form-group">
@@ -156,13 +164,14 @@
                                     </select>
                                 </div>
                                 <div class="form-group subscriptionWidgetJson">
-                                    <label for="widgetJson">Widget JSON options</label>
-                                    <input type="text" class="form-control" id="widgetJson" name="widgetJson" placeholder="Widget JSON options" />
+                                    <label for="widgetJson">IoTCC Config</label>
+                                    <textarea class="form-control" id="widgetJson" name="widgetJson" placeholder="Widget JSON" rows="10"></textarea>
+                                    <button type="button" class="btn btn-primary beautify">Beautify JSON</button>
                                     <span class="help-block widgetJson"></span>
                                 </div>
                                 <div class="form-group subscriptionAction hide">
-                                    <label for="actionTopic">Widget action topic</label>
-                                    <input type="text" class="form-control" id="actionTopic" name="actionTopic" placeholder="Widget action topic" />
+                                    <label for="publishTopic">Widget publish topic</label>
+                                    <input type="text" class="form-control" id="publishTopic" name="publishTopic" placeholder="Widget publish topic" />
                                 </div>
                                 <div class="checkbox">
                                     <label for="active">
@@ -170,13 +179,41 @@
                                 </label>
                                 </div>
                             </div>
-                            <!-- /.box-body -->
-
                             <div class="box-footer">
                                 <button type="submit" class="btn btn-primary" id="saveSubscription">Save</button>
                             </div>
                             <input type="hidden" name="index" />
                         </form>
+                    </div>
+                </div>
+
+                <div class="col-md-3">
+                    <div class="box box-primary">
+                        <div class="box-header with-border">
+                            <h3 class="box-title">Widget Preview</h3>
+                        </div>
+                        <div class="box-body widget-preview">
+
+                        </div>
+                        <div class="box-body">
+                            <textarea class="form-control" id="widgetCodePreview" rows="10"></textarea>
+                            Available icons
+                            <ul>
+                                <li>
+                                    <a href="http://ionicons.com/" target="_blank">Ionicons</a>
+                                </li>
+                                <li>
+                                    <a href="http://fontawesome.io/icons/" target="_blank">Font Awesome</a>
+                                </li>
+                            </ul>
+                            Available templates
+                            <ul>
+                                <li>template-1</li>
+                                <li>template-2</li>
+                                <li>template-3</li>
+                                <li>template-4</li>
+                            </ul>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -196,7 +233,7 @@
                                         <th>Topic</th>
                                         <th>Widget</th>
                                         <th>Widget JSON</th>
-                                        <th>Action topic</th>
+                                        <th>Publish topic</th>
                                         <th>Active</th>
                                     </tr>
                                 </thead>
@@ -219,8 +256,6 @@
                         <div class="box-header with-border">
                             <h3 class="box-title">MQTT connection data</h3>
                         </div>
-                        <!-- /.box-header -->
-                        <!-- form start -->
                         <form id="mqttConfig">
                             <div class="box-body">
                                 <div class="form-group">
@@ -259,8 +294,6 @@
                                 </label>
                                 </div>
                             </div>
-                            <!-- /.box-body -->
-
                             <div class="box-footer">
                                 <button type="submit" class="btn btn-primary" id="saveMqttConfig">Save</button>
                             </div>
@@ -275,8 +308,6 @@
                         <div class="box-header with-border">
                             <h3 class="box-title">Options</h3>
                         </div>
-                        <!-- /.box-header -->
-                        <!-- form start -->
                         <form id="appConfig">
                             <div class="box-body">
                                 <div class="checkbox">
@@ -290,8 +321,6 @@
                                     </label>
                                 </div>
                             </div>
-                            <!-- /.box-body -->
-
                             <div class="box-footer">
                                 <button type="submit" class="btn btn-primary" id="saveAppConfig">Save</button>
                             </div>
@@ -306,8 +335,6 @@
                         <div class="box-header with-border">
                             <h3 class="box-title">Backup/Restore</h3>
                         </div>
-                        <!-- /.box-header -->
-                        <!-- form start -->
                         <form id="iotCCConfig">
                             <div class="box-body">
                                 <div class="form-group">
@@ -315,8 +342,6 @@
                                     <textarea class="form-control" id="iotccconfig" name="iotccconfig" placeholder="IoTCC Config" rows="10"></textarea>
                                 </div>
                             </div>
-                            <!-- /.box-body -->
-
                             <div class="box-footer">
                                 <button type="submit" class="btn btn-primary" id="saveIoTCCConfig">Save</button>
                             </div>
@@ -326,15 +351,21 @@
             </div>
 
         </section>
-        <!-- /.content -->
 
         <!-- Log content -->
         <section class="content hide" data-section="log">
-            <pre class="console"></pre>
-        </section>
-        <!-- /.content -->
+
+            <div class="box box-primary">
+                <div class="box-header with-border">
+                    <h3 class="box-title">MQTT Log</h3>
+                </div>
+                <div class="box-body">
+                    <pre class="console"></pre>
+                </div>
+            </div>
+
+        </section>
     </div>
-    <!-- /.content-wrapper -->
 
     <footer class="main-footer">
         <div class="pull-right hidden-xs">
@@ -343,21 +374,14 @@
         <strong>Copyright &copy; 2017 <a href="http://www.razvi.ro">Razvan Stanga</a>.</strong> All rights reserved.
     </footer>
 
-    <!-- Control Sidebar -->
     <aside class="control-sidebar control-sidebar-dark">
-        <!-- Create the tabs -->
         <ul class="nav nav-tabs nav-justified control-sidebar-tabs">
             <li><a href="#control-sidebar-home-tab" data-toggle="tab"><i class="fa fa-gear"></i></a></li>
         </ul>
-        <!-- Tab panes -->
         <div class="tab-content">
-            <!-- Home tab content -->
             <div class="tab-pane" id="control-sidebar-home-tab">
-
-            </div>
-            <!-- /.tab-pane -->
-
-            <!-- Settings tab content -->
+            </div>
+
             <div class="tab-pane" id="control-sidebar-settings-tab">
                 <form method="post">
                     <h3 class="control-sidebar-heading">General Settings</h3>
@@ -372,18 +396,12 @@
                             Allow the user to show his name in blog posts
                         </p>
                     </div>
-                    <!-- /.form-group -->
                 </form>
             </div>
-            <!-- /.tab-pane -->
         </div>
     </aside>
-    <!-- /.control-sidebar -->
-    <!-- Add the sidebar's background. This div must be placed
-       immediately after the control sidebar -->
     <div class="control-sidebar-bg"></div>
 </div>
-<!-- ./wrapper -->
 
 <script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
 <script type="text/javascript" src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>

file:a/readme.md -> file:b/readme.md
--- a/readme.md
+++ b/readme.md
@@ -18,7 +18,9 @@
 - TODO: create `scenarios` based on IoT data. if sensor1Temp < 10 then turn on heat
 - TODO: use NodeRed to create scenarios/notifications
 - BETA: subscribe to custom topics, assign template for display. To do configurator
+- TODO: custom subscriptions callbacks to parse the message data
 - TODO: OTA updates with widget configuration
+- TODO: optimizations
 
 How it works (WIP) :
 IoT CC subscribes to

 Binary files a/screenshots/custom-subscriptions-page.png and b/screenshots/custom-subscriptions-page.png differ
comments