Skip to content

Commit e3d1813

Browse files
committed
8.6 release
More small-screen friendly and responsive layout (no more label/bubble overlaps) Top menu buttons collapsed in 1 MENU button that slides menu from the side Show Metric values in Node labels via {MetricLabel} token Show Node icon in Node edit screen Node detail top section styled Hidden nodes show in light red when visible Graph x-axis scaled correctly when incoming data is plotted
1 parent 62f71ae commit e3d1813

3 files changed

Lines changed: 157 additions & 29 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "RaspberryPi-Gateway",
3-
"version": "0.2.0",
3+
"version": "8.6",
44
"description": "RaspberryPi Home Automation Gateway",
55
"main": "gateway.js",
66
"repository": "https://github.com/LowPowerLab/RaspberryPi-Gateway.git",

www/images/icon_multinode.png

14.6 KB
Loading

www/index.html

Lines changed: 156 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
.ui-content { padding-top:0; }
6868
.btn-text, #loadingsocket { font-size: 32px; }
6969
#loadingsocket, #loadinggraph {padding:15px;}
70-
.labelbold { font-weight:bold !important; }
70+
.labelbold { font-weight:bold !important; overflow:hidden; text-overflow:ellipsis;white-space:nowrap; }
7171
#wrap { padding: 12px; }
7272
.ui-listview>li.hiddenNode { display:none; }
7373
.ui-listview>li.hiddenNodeShow { display:block; }
@@ -142,18 +142,104 @@
142142
#rawActionIDspan div { border-radius: 0.3em 0 0 0.3em; }
143143
#rawActionTextspan div { border-radius: 0; }
144144
#rawActionSend { border-radius: 0 0.3em 0.3em 0; }
145+
146+
.hiddenNodeShow a { background-color: #ffe5e5 !important; }
147+
148+
#nodeList > li.ui-li-has-count > a { padding-right:2.5em; }
149+
@media (max-width: 599px) {
150+
#nodeList > li.ui-li-has-count > a { padding-left: 5.25em; }
151+
#nodeList > li.ui-li-has-count > a > span.ui-li-count {
152+
top:70%;
153+
}}
154+
@media (max-width: 767px) {
155+
.sideButton { display:none !important; }
156+
}
157+
158+
#nav-panel-popup {
159+
right: 0 !important;
160+
left: auto !important;
161+
}
162+
#nav-panel {
163+
width: 200px;
164+
background: rgba(0,0,0,.7);
165+
border: 1px solid #000;
166+
border-right: none;
167+
/*margin: -1px 0;*/
168+
/*padding-top:20px;*/
169+
170+
}
171+
#nav-panel > ul { margin-top: 0 !important; }
172+
#nav-panel .ui-btn {
173+
/*margin: 2em 15px;*/
174+
}
175+
176+
.content-panel {
177+
margin: 1em 0;
178+
border: 1px solid #ddd;
179+
display: block;
180+
box-shadow: 0 1px 3px rgba(0,0,0,.15);
181+
border-radius: .3125em;
182+
text-shadow: 0 1px 0 #f3f3f3;
183+
padding-right: .5em;
184+
}
185+
186+
@media (max-width: 539px) {
187+
.content-panel { padding: 0 .5em; }
188+
.nodeDetailImageWrapper { text-align:center; }
189+
#nodeDetailInputList { padding-left: 0; padding-top: 0; margin-top:0;}
190+
}
191+
192+
@media (max-width: 448px) {
193+
label.labelbold { margin: .4em .4em .1em .4em; }
194+
}
195+
196+
@media (min-width: 540px) {
197+
.nodeDetailImageWrapper {
198+
position:absolute;
199+
float:left;
200+
padding-top:10px;
201+
}
202+
#nodeDetailInputList { padding-left: 130px; }
203+
}
204+
205+
@media (min-width: 1000px) {
206+
.nodeDetailImageWrapper { padding-left: 10%; }
207+
#nodeDetailInputList { padding-left: 25%; padding-right: 10%; }
208+
}
209+
@media (min-width: 1600px) {
210+
.nodeDetailImageWrapper { padding-left: 20%; }
211+
#nodeDetailInputList { padding-left: 30%; padding-right: 25%; }
212+
}
145213
</style>
146214
</head>
147215
<body>
148216
<div data-role="page" id="homepage">
217+
<div id="nav-panel" data-role="popup" data-corners="false" data-overlay-theme="b" data-theme="b" data-shadow="true" data-tolerance="0,0">
218+
<ul data-role="listview" data-theme="a" data-divider-theme="a" style="margin-top:-16px;" class="nav-search">
219+
<li data-icon="delete">
220+
<a id="nav-panel-close" href="#" data-rel="close">Close (Esc)</a>
221+
</li>
222+
223+
<li data-icon="eye" data-iconpos="left">
224+
<a id="btnHiddenNodesToggle" href="#">Show Hidden</a>
225+
</li>
226+
<li data-icon="search">
227+
<a id="btnSearch" href="#">Search</a>
228+
</li>
229+
<li data-icon="fa-terminal">
230+
<a href="#logpage">Log / Terminal</a>
231+
</li>
232+
<li data-icon="fa-gear">
233+
<a href="#settingspage">Settings</a>
234+
</li>
235+
</ul>
236+
</div><!-- /panel -->
237+
149238
<div data-role="header">
150239
<a href="http://lowpowerlab.com/gateway" target="new" data-role="none"><img src="images/logo.png" alt="LowPowerLab" title="LowPowerLab.com" style="float:left;display:inline;max-width:30px;padding:4px"/></a>
151240
<h1>Moteino Gateway Dashboard</h1>
152241
<div class="ui-btn-right" data-role="controlgroup" data-type="horizontal" data-mini="true">
153-
<a id="btnHiddenNodesToggle" href="#" data-role="button" data-icon="eye" data-iconpos="notext" title="Show hidden nodes">Show hidden</a>
154-
<a id="btnSearch" href="#" data-role="button" data-icon="search" data-iconpos="notext" title="search nodes">Search</a>
155-
<a href="#logpage" data-role="button" data-icon="fa-terminal" data-iconpos="notext" title="terminal/log">Log</a>
156-
<a href="#settingspage" data-role="button" data-icon="fa-gear" data-iconpos="notext" title="settings">Settings</a>
242+
<a class="sideMenuButton" href="#nav-panel" data-icon="bars" data-role="button" data-rel="popup" data-transition="slide" data-position-to="window" >Menu</a>
157243
</div>
158244
</div>
159245

@@ -194,13 +280,18 @@ <h3><span id="nodeDetailTitle">Node details</span> <span class="nodeUpdated">x</
194280
</div>
195281
</div>
196282
<div data-role="main" class="ui-content">
197-
<div class="ui-field-contain">
198-
<label for="nodeMoteType" class="labelbold">Type:</label>
199-
<select id="nodeMoteType" data-mini="true"></select>
200-
<label for="nodeLabel" class="labelbold">Label:</label>
201-
<input type="text" name="nodeLabel" id="nodeLabel" placeholder="node label..." />
202-
<label for="nodeDescr" class="labelbold">Description:</label>
203-
<input type="text" name="nodeDescr" id="nodeDescr" placeholder="description/location..." />
283+
<div class="content-panel">
284+
<div class="nodeDetailImageWrapper">
285+
<img id="nodeDetailImage" class="productimg">
286+
</div>
287+
<div id="nodeDetailInputList" class="ui-field-contain">
288+
<label for="nodeMoteType" class="labelbold">Type:</label>
289+
<select id="nodeMoteType" data-mini="true"></select>
290+
<label for="nodeLabel" class="labelbold">Label:</label>
291+
<input type="text" name="nodeLabel" id="nodeLabel" placeholder="node label..." />
292+
<label for="nodeDescr" class="labelbold">Description:</label>
293+
<input type="text" name="nodeDescr" id="nodeDescr" placeholder="description/location..." />
294+
</div>
204295
</div>
205296
<ul id="metricList" data-role="listview" data-inset="true" data-theme="a" data-dividertheme="b"></ul>
206297

@@ -671,11 +762,20 @@ <h1>Settings</h1>
671762

672763
function addGraphDataPoint(point) {
673764
if (graphFrozen) return;
674-
graphData.push(point);
675-
if (graphData.length > 10)
676-
graphData.shift(); //remove first point in graph
677-
graphOptions.xaxis.min = graphView.start = graphData[0][0];
678-
graphOptions.xaxis.max = graphView.end = graphData[graphData.length-1][0];
765+
766+
//shift visible timeline
767+
graphOptions.xaxis.min = graphView.start = point[0] - (graphView.end - graphView.start); //calculate new min
768+
graphOptions.xaxis.max = graphView.end = point[0];
769+
770+
graphData.push(point); //add new data point
771+
772+
//remove points that are invisible after shifting the visible timeline
773+
var i = 0;
774+
while (graphData[i]) {
775+
if (graphData[i++][0] < graphView.start)
776+
graphData.shift();
777+
}
778+
679779
//plot = $.plot(metricGraph, [graphData], graphOptions);
680780
$.plot(metricGraph, [{label:graphOptions.legendLbl, data:graphData}], graphOptions);
681781
//redraw alternative:
@@ -684,14 +784,26 @@ <h1>Settings</h1>
684784
//plot.draw();
685785
$(graphStat).clone().appendTo('#metricGraph');
686786
}
787+
788+
/// resolves any metrics in the label or description of a node
789+
function nodeResolveString(theString, metrics) {
790+
for(var key in metrics)
791+
{
792+
if (/\{.+\}/ig.test(theString) == false) return theString; //if string has no more "{...}" just return it
793+
metric = metrics[key];
794+
theString = theString.replace('{'+metric.label+'}', metricsValues({0:metric}, true));
795+
theString = theString.replace('{'+metric.label+':updated}', ago(metric.updated).tag);
796+
}
797+
return theString;
798+
}
687799

688-
function metricsValues(metrics) {
800+
function metricsValues(metrics, ignorePin=false) {
689801
var label = '';
690802
var metric;
691803
for(var key in metrics)
692804
{
693805
metric = metrics[key];
694-
if(metric.pin=='1' || metrics.length==1)
806+
if(metric.pin=='1' || metrics.length==1 || ignorePin)
695807
{
696808
var agoText = ago(metric.updated).text;
697809
//metric.value + (metric.unit || '')
@@ -703,9 +815,9 @@ <h1>Settings</h1>
703815
return label;
704816
}
705817

706-
function getNodeIcon(nodeType) {
707-
if (motesDef != undefined && nodeType != undefined && motesDef[nodeType] != undefined)
708-
return motesDef[nodeType].icon || 'icon_default.png';
818+
function getNodeIcon(node) {
819+
if (motesDef != undefined && node.type != undefined && motesDef[node.type] != undefined)
820+
return motesDef[node.type].icon || 'icon_default.png';
709821
return 'icon_default.png';
710822
};
711823

@@ -730,7 +842,7 @@ <h1>Settings</h1>
730842
nodes[node._id] = node;
731843
var nodeValue = metricsValues(node.metrics);
732844
var lowBat = node.metrics.V != null && node.metrics.V.value < 3.55;
733-
var newLI = $('<li id="' + node._id + '"><a node-id="' + node._id + '" href="#nodedetails" class="nodedetails"><img class="productimg" src="images/'+getNodeIcon(node.type)+'"><h2>' + (node.label || node._id) + ' ' + resolveRSSIImage(node.rssi) + ' ' + (lowBat ? '<img src="images/lowbattery.png" style="max-width:32px"/> ' : '') + ago(node.updated, 0).tag + (node.hidden ? ' <img class="listIcon20px" src="images/icon_hidden.png" />' : '') + '</h2><p>' + (node.descr || '&nbsp;') + '</p>' + (nodeValue ? '<span class="ui-li-count ui-li-count16">' + nodeValue + '</span>' : '') + '</a></li>');
845+
var newLI = $('<li id="' + node._id + '"><a node-id="' + node._id + '" href="#nodedetails" class="nodedetails"><img class="productimg" src="images/'+getNodeIcon(node)+'"><h2>' + (nodeResolveString(node.label, node.metrics) || node._id) + ' ' + resolveRSSIImage(node.rssi) + ' ' + (lowBat ? '<img src="images/lowbattery.png" style="max-width:32px"/> ' : '') + ago(node.updated, 0).tag + (node.hidden ? ' <img class="listIcon20px" src="images/icon_hidden.png" />' : '') + '</h2><p>' + (nodeResolveString(node.descr, node.metrics) || '&nbsp;') + '</p>' + (nodeValue ? '<span class="ui-li-count ui-li-count16">' + nodeValue + '</span>' : '') + '</a></li>');
734846
var existingNode = $('#nodeList li#' + node._id);
735847
if (node.hidden)
736848
if (showHiddenNodes)
@@ -774,6 +886,7 @@ <h1>Settings</h1>
774886
$('#nodeList').listview('refresh'); //re-render the listview
775887
}
776888

889+
/// calculated a style for an "ago" label based on a given timestamp in the past
777890
function ago(time, agoPrefix)
778891
{
779892
agoPrefix = (typeof agoPrefix !== 'undefined') ? agoPrefix : true;
@@ -799,6 +912,7 @@ <h1>Settings</h1>
799912
return {text:updated,color:theColor,tag:'<span data-time="'+time+'" class="nodeAgo" style="color:'+theColor+';">'+updated+'</span>'};
800913
}
801914

915+
/// updates the "ago" labels text and color according the to elapsed time since the last update timestamp associated with those labels
802916
function updateAgos()
803917
{
804918
$("span.nodeAgo").each(function(){
@@ -823,13 +937,14 @@ <h1>Settings</h1>
823937
if ($("#searchBox").is(":visible"))
824938
{
825939
$("#searchBox").slideUp('fast');
826-
$("#btnSearch").removeClass('ui-btn-b');
940+
$("#btnSearch").css('background-color', '');
827941
}
828942
else
829943
{
830944
$("#searchBox").slideDown('fast');
831-
$("#btnSearch").addClass('ui-btn-b');
945+
$("#btnSearch").css('background-color', '#9bffbe');
832946
}
947+
$( "#nav-panel" ).popup( "close" );
833948
});
834949

835950
$("#btnHiddenNodesToggle").click("tap", function(event) {
@@ -842,9 +957,10 @@ <h1>Settings</h1>
842957
else
843958
{
844959
$(".hiddenNode").removeClass('hiddenNode').addClass('hiddenNodeShow');
845-
$('#btnHiddenNodesToggle').css('background-color', '#D00');
960+
$('#btnHiddenNodesToggle').css('background-color', '#ffcaca');
846961
showHiddenNodes = true;
847962
}
963+
$( "#nav-panel" ).popup( "close" );
848964
});
849965

850966
$("#btnHideNodeToggle").click("tap", function(event) {
@@ -872,10 +988,11 @@ <h1>Settings</h1>
872988

873989
function refreshNodeDetails(node) {
874990
$('#nodeLabel').val(node.label || '');
875-
$('#nodeDetailTitle').html(node.label || 'Node details');
991+
$('#nodeDetailTitle').html(nodeResolveString(node.label, node.metrics) || 'Node details');
876992
$('#nodeMoteType').val(node.type || '');
877993
$("#nodeMoteType").selectmenu('refresh',true);
878994
$('#nodeDescr').val(node.descr || '');
995+
$('#nodeDetailImage').attr('src', 'images/'+getNodeIcon(node));
879996

880997
if (node.hidden) $("#btnHideNodeToggle").addClass('hidden').css('background-color','#D00');
881998
else $("#btnHideNodeToggle").removeClass('hidden').css('background-color','');;
@@ -1023,7 +1140,7 @@ <h1>Settings</h1>
10231140
socket.emit('EDITNODEEVENT', selectedNodeId, eventKey, null, true);
10241141
});
10251142

1026-
$('#nodeLabel').keyup(function() {$('#nodeDetailTitle').html($('#nodeLabel').val() || 'no label');});
1143+
$('#nodeLabel').keyup(function() {$('#nodeDetailTitle').html( nodeResolveString($('#nodeLabel').val(), nodes[selectedNodeId].metrics) || 'Node details');});
10271144
$('#metricLabel').keyup(function() {$('#metricDetailTitle').html($('#metricLabel').val() || 'no label');});
10281145

10291146
$("#btnPinMetric").click("tap", function(event) {
@@ -1174,6 +1291,17 @@ <h1>Settings</h1>
11741291
'font-weight':'bold',
11751292
'text-shadow':'0 1px 0 black',
11761293
});
1294+
1295+
$( "#nav-panel" ).on({
1296+
popupbeforeposition: function() {
1297+
var h = $( window ).height();
1298+
$( "#nav-panel" ).css( "height", h );
1299+
}
1300+
});
1301+
1302+
$("#nav-panel-close").click("tap", function(event) {
1303+
$( "#nav-panel" ).popup( "close" );
1304+
});
11771305
});
11781306
}
11791307
</script>

0 commit comments

Comments
 (0)