Secure stats-api and analytics page

application-manager-new
charithag 9 years ago
parent 2fe3f14b55
commit 6ff453e213

@ -35,46 +35,50 @@ var stats = {};
var deviceId;
var deviceType;
var server = require('store').server;
var user = server.current(session).um;
var responseProcessor = require('utils').response;
response.contentType = 'application/json';
log.info(server);
log.info(session);
log.info(user);
var server = require('store').server;
var user = server.current(session);
if (uriMatcher.match("/{context}/apis/stats")) {
if (!user) {
response = responseProcessor.buildErrorResponse(response, 401, "Unauthorized");
} else {
user = user.um;
if (uriMatcher.match("/{context}/apis/stats")) {
deviceId = request.getParameter("deviceId");
deviceType = request.getParameter("deviceType");
getDeviceData(deviceType, deviceId);
}else if (uriMatcher.match("/{context}/apis/stats/group")){
} else if (uriMatcher.match("/{context}/apis/stats/group")) {
var groupId = request.getParameter("groupId");
//URL: GET https://localhost:9443/devicecloud/group_manager/group/id/{groupId}/device/all
var endPoint = deviceCloudGroupService + "/group/id/" + groupId + "/device/all";
var data = {"username": user.username};
var devices = get(endPoint, data, "json");
var devices = get(endPoint, data, "json").data;
for (var device in devices.data){
deviceId = devices.data[device].deviceIdentifier;
deviceType = devices.data[device].type;
for (var device in devices) {
deviceId = devices[device].deviceIdentifier;
deviceType = devices[device].type;
getDeviceData(deviceType, deviceId);
}
}
}
// returning the result.
if (stats) {
// returning the result.
if (stats) {
print(stats);
}
}
function getDeviceData(deviceType, deviceId){
function getDeviceData(deviceType, deviceId) {
//URL: GET https://localhost:9443/devicecloud/group_manager/group/id/{groupId}/device/all
var endPoint = deviceCloudDeviceService + "/devices/" + deviceType + "/" + deviceId;
var data = {"username": user.username};
var device = get(endPoint, data, "json").data;
if (!device){
if (!device) {
return;
}
var uname = device.enrolmentInfo.owner;
@ -103,35 +107,47 @@ function getDeviceData(deviceType, deviceId){
}
function getFireAlarmData(user, device, from, to) {
if (stats['temperatureData'] == null){
if (stats['temperatureData'] == null) {
stats['temperatureData'] = [];
}
if (stats['sonarData'] == null){
if (stats['sonarData'] == null) {
stats['sonarData'] = [];
}
if (stats['motionData'] == null){
if (stats['motionData'] == null) {
stats['motionData'] = [];
}
if (stats['lightData'] == null){
if (stats['lightData'] == null) {
stats['lightData'] = [];
}
stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)});
stats['sonarData'].push({"device": device.name, "stats" : getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, device.deviceIdentifier, from, to)});
stats['motionData'].push({"device": device.name, "stats" : getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)});
stats['lightData'].push({"device": device.name, "stats" : getSensorData("LDR_LIGHT_SENSOR_SUMMARY", "light", user, device.deviceIdentifier, from, to)});
stats['temperatureData'].push({
"device": device.name,
"stats": getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)
});
stats['sonarData'].push({
"device": device.name,
"stats": getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, device.deviceIdentifier, from, to)
});
stats['motionData'].push({
"device": device.name,
"stats": getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)
});
stats['lightData'].push({
"device": device.name,
"stats": getSensorData("LDR_LIGHT_SENSOR_SUMMARY", "light", user, device.deviceIdentifier, from, to)
});
}
function getSensebotData(user, device, from, to) {
if (stats['sonarData'] == null){
if (stats['sonarData'] == null) {
stats['sonarData'] = [];
}
if (stats['motionData'] == null){
if (stats['motionData'] == null) {
stats['motionData'] = [];
}
if (stats['lightData'] == null){
if (stats['lightData'] == null) {
stats['lightData'] = [];
}
if (stats['temperatureData'] == null){
if (stats['temperatureData'] == null) {
stats['temperatureData'] = [];
}
@ -145,7 +161,7 @@ function getSensebotData(user, device, from, to) {
chartData.push({time: i, value: rnd});
i += timeInterval;
}
stats['sonarData'].push({"device": device.name, "stats" : chartData});
stats['sonarData'].push({"device": device.name, "stats": chartData});
chartData = [];
var i = parseInt(from);
while (i < parseInt(to)) {
@ -153,7 +169,7 @@ function getSensebotData(user, device, from, to) {
chartData.push({time: i, value: rnd});
i += timeInterval;
}
stats['motionData'].push({"device": device.name, "stats" : chartData});
stats['motionData'].push({"device": device.name, "stats": chartData});
chartData = [];
var i = parseInt(from);
while (i < parseInt(to)) {
@ -161,7 +177,7 @@ function getSensebotData(user, device, from, to) {
chartData.push({time: i, value: rnd});
i += timeInterval;
}
stats['lightData'].push({"device": device.name, "stats" : chartData});
stats['lightData'].push({"device": device.name, "stats": chartData});
chartData = [];
var i = parseInt(from);
while (i < parseInt(to)) {
@ -169,7 +185,7 @@ function getSensebotData(user, device, from, to) {
chartData.push({time: i, value: rnd});
i += timeInterval;
}
stats['temperatureData'].push({"device": device.name, "stats" : chartData});
stats['temperatureData'].push({"device": device.name, "stats": chartData});
/*
//Comment below section to emulate data
@ -182,75 +198,120 @@ function getSensebotData(user, device, from, to) {
}
function getArduinoData(user, device, from, to) {
if (stats['temperatureData'] == null){
if (stats['temperatureData'] == null) {
stats['temperatureData'] = [];
}
stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)});
stats['temperatureData'].push({
"device": device.name,
"stats": getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)
});
}
function getAndroidSenseData(user, device, from, to) {
if (stats['ramData'] == null){
if (stats['ramData'] == null) {
stats['ramData'] = [];
}
if (stats['cpuData'] == null){
if (stats['cpuData'] == null) {
stats['cpuData'] = [];
}
if (stats['temperatureData'] == null){
if (stats['temperatureData'] == null) {
stats['temperatureData'] = [];
}
if (stats['motionData'] == null){
if (stats['motionData'] == null) {
stats['motionData'] = [];
}
stats['ramData'].push({"device": device.name, "stats" : getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)});
stats['cpuData'].push({"device": device.name, "stats" : getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)});
stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)});
stats['motionData'].push({"device": device.name, "stats" : getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)});
stats['ramData'].push({
"device": device.name,
"stats": getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)
});
stats['cpuData'].push({
"device": device.name,
"stats": getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)
});
stats['temperatureData'].push({
"device": device.name,
"stats": getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)
});
stats['motionData'].push({
"device": device.name,
"stats": getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)
});
}
function getDigitalDisplayData(user, device, from, to) {
if (stats['ramData'] == null){
if (stats['ramData'] == null) {
stats['ramData'] = [];
}
if (stats['cpuData'] == null){
if (stats['cpuData'] == null) {
stats['cpuData'] = [];
}
if (stats['cpuTemperatureData'] == null){
if (stats['cpuTemperatureData'] == null) {
stats['cpuTemperatureData'] = [];
}
stats['ramData'].push({"device": device.name, "stats" : getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)});
stats['cpuData'].push({"device": device.name, "stats" : getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)});
stats['cpuTemperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)});
stats['ramData'].push({
"device": device.name,
"stats": getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)
});
stats['cpuData'].push({
"device": device.name,
"stats": getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)
});
stats['cpuTemperatureData'].push({
"device": device.name,
"stats": getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)
});
}
function getAllDeviceData(user, device, from, to) {
if (stats['ramData'] == null){
if (stats['ramData'] == null) {
stats['ramData'] = [];
}
if (stats['cpuData'] == null){
if (stats['cpuData'] == null) {
stats['cpuData'] = [];
}
if (stats['temperatureData'] == null){
if (stats['temperatureData'] == null) {
stats['temperatureData'] = [];
}
if (stats['sonarData'] == null){
if (stats['sonarData'] == null) {
stats['sonarData'] = [];
}
if (stats['motionData'] == null){
if (stats['motionData'] == null) {
stats['motionData'] = [];
}
if (stats['lightData'] == null){
if (stats['lightData'] == null) {
stats['lightData'] = [];
}
if (stats['cpuTemperatureData'] == null){
if (stats['cpuTemperatureData'] == null) {
stats['cpuTemperatureData'] = [];
}
stats['ramData'].push({"device": device.name, "stats" : getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)});
stats['cpuData'].push({"device": device.name, "stats" : getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)});
stats['sonarData'].push({"device": device.name, "stats" : getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, device.deviceIdentifier, from, to)});
stats['motionData'].push({"device": device.name, "stats" : getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)});
stats['lightData'].push({"device": device.name, "stats" : getSensorData("LDR_LIGHT_SENSOR_SUMMARY", "light", user, device.deviceIdentifier, from, to)});
stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)});
stats['cpuTemperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)});
stats['ramData'].push({
"device": device.name,
"stats": getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)
});
stats['cpuData'].push({
"device": device.name,
"stats": getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)
});
stats['sonarData'].push({
"device": device.name,
"stats": getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, device.deviceIdentifier, from, to)
});
stats['motionData'].push({
"device": device.name,
"stats": getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)
});
stats['lightData'].push({
"device": device.name,
"stats": getSensorData("LDR_LIGHT_SENSOR_SUMMARY", "light", user, device.deviceIdentifier, from, to)
});
stats['temperatureData'].push({
"device": device.name,
"stats": getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)
});
stats['cpuTemperatureData'].push({
"device": device.name,
"stats": getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)
});
}

@ -29,14 +29,15 @@ app.server = function(ctx) {
url: 'devices',
path: 'device_top_assets.jag'
}, {
title: 'Store | Device Analytics page',
title: 'Store | Analytics',
url: 'analytics',
path: 'device-analytics.jag'
path: 'device-analytics.jag',
secured:true
}],
apis: [{
url: 'stats',
path: 'stats-api.jag',
secured:false
secured:true
}]
},
configs: {
@ -44,3 +45,51 @@ app.server = function(ctx) {
}
}
};
app.pageHandlers = function (ctx) {
return {
onPageLoad: function () {
if ((ctx.isAnonContext) && (ctx.endpoint.secured)) {
ctx.res.sendRedirect(ctx.appContext + '/login');
return false;
}
return true;
}
}
};
app.apiHandlers = function (ctx) {
return {
onApiLoad: function () {
if ((ctx.isAnonContext) && (ctx.endpoint.secured)) {
ctx.res.status = '401';
ctx.res.sendRedirect(ctx.appContext + '/login');
return false;
}
return true;
}
}
};
app.renderer = function(ctx) {
var decoratorApi = require('/modules/page-decorators.js').pageDecorators;
return {
pageDecorators: {
navigationBar: function(page) {
return decoratorApi.navigationBar(ctx, page, this);
},
searchBar: function(page) {
return decoratorApi.searchBar(ctx, page, this);
},
authenticationDetails: function(page) {
return decoratorApi.authenticationDetails(ctx, page, this);
},
recentAssetsOfActivatedTypes: function(page) {
return decoratorApi.recentAssetsOfActivatedTypes(ctx, page, this);
},
popularAssets:function(page){
return decoratorApi.popularAssets(ctx,page,this);
}
}
}
};

@ -21,13 +21,29 @@ var caramel;
require('/modules/store.js').exec(function(ctx) {
caramel = require('caramel');
var app = require('rxt').app;
var constants = require('rxt').constants;
var server = require('store').server;
var user = server.current(ctx.session);
var ui = require('rxt').ui;
var page = ui.buildPage(ctx.session, ctx.request);
var appManager;
page.title ="test";
var groupId = request.getParameter("groupId");
var title;
if (groupId){
title = request.getParameter("name");
}else{
var carbon = require('carbon');
var carbonHttpsServletTransport = carbon.server.address('https');
var deviceCloudDeviceService = carbonHttpsServletTransport + "/common/device_manager";
var deviceId = request.getParameter("deviceId");
var deviceType = request.getParameter("deviceType");
var endPoint = deviceCloudDeviceService + "/devices/" + deviceType + "/" + deviceId;
var data = {"username": user.um.username};
var device = get(endPoint, data, "json").data;
title = device.name;
}
page.title = title + " Analytics";
page.page_title = "Analytics";
appManager = app.createUserAppManager(session);
var output = appManager.render([], page);
caramel.render(output);

@ -26,6 +26,8 @@
white-space: nowrap;
margin-left: 3px;
bottom: -20px;
height: auto;
border-bottom: none;
}
/* annotations */

@ -0,0 +1,25 @@
/*
* Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var resources = function (page, meta) {
return {
js: ['asset-helpers.js','navigation.js','popover.js'],
css: ['navigation.css','custom-desktop.css']
};
};

@ -95,7 +95,7 @@ var DateRange = convertDate(startDate) + " " + configObject.separator + " " + co
$(document).ready(function () {
initDate();
groupId = $("#request-group-id").data("groupid");
groupId = getQueryParams().groupId;
$('#date-range').html(DateRange);
$('#date-range').dateRangePicker(configObject)
@ -152,7 +152,7 @@ function getDateTime(from, to) {
}
function getStats(from, to) {
var requestData = new Object();
var requestData = {};
var getStatsRequest;
if (from) {
requestData['from'] = from;
@ -168,8 +168,8 @@ function getStats(from, to) {
data: requestData
});
} else {
var deviceId = getUrlParameter('deviceId');
var deviceType = getUrlParameter('deviceType');
var deviceId = getQueryParams().deviceId;
var deviceType = getQueryParams().deviceType;
requestData['deviceId'] = deviceId;
requestData['deviceType'] = deviceType;
@ -181,25 +181,14 @@ function getStats(from, to) {
});
}
getStatsRequest.done(function (stats) {
updateGraphs(JSON.parse(stats));
updateGraphs(stats);
});
getStatsRequest.fail(function (jqXHR, textStatus) {
alert("Request failed: " + textStatus);
alert("Request failed: " + jqXHR.statusText);
});
}
function getUrlParameter(paramName) {
var pageURL = window.location.search.substring(1);
var urlVariables = pageURL.split('&');
for (var i = 0; i < urlVariables.length; i++) {
var parameterName = urlVariables[i].split('=');
if (parameterName[0] == paramName) {
return parameterName[1];
}
}
}
function updateGraphs(stats) {
console.log(stats);
@ -595,3 +584,17 @@ function summerizeBar(data) {
return data;
}
}
function getQueryParams() {
var qs = document.location.search.split('+').join(' ');
var params = {},
tokens,
re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params;
}

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{include title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="{{url "/themes/store/css/bootstrap.min.css"}}" rel="stylesheet">
<link href="{{url "/themes/store/css/custom.css"}}" rel="stylesheet">
<link href="{{url "/themes/store/css/font-awesome.min.css"}}" rel="stylesheet">
<link href="{{url "/themes/store/css/fontwso2.css"}}" rel="stylesheet">
<link href="{{url "/themes/store/css/fontwso2-extend.css"}}" rel="stylesheet">
{{css}}
<!-- Fav and touch icons -->
<link rel="shortcut icon" href="http://wso2.com/files/favicon.ico" type="image/x-icon" />
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="{{url "/themes/store/js/html5shiv.min.js"}}"></script>
<script src="{{url "/themes/store/js/respond.min.js"}}"></script>
<link rel="stylesheet" href="{{url "/themes/store/css/ie.css"}}" type="text/css">
<![endif]-->
</head>
<body>
<div class="wrapper">
<div class="container-fluid header-container">
{{ include header}}
</div>
<div class="container-fluid navigation-container">
{{include navigation}}
</div>
<div class="container-fluid body-container">
<div id="assets-container">
{{include body}}
</div>
</div>
</div>
{{>footer}}
<!-- /container -->
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="{{url "/themes/store/js/jquery-1.11.1.min.js"}}"></script>
<script src="{{url "/themes/store/js/bootstrap.min.js"}}"></script>
<script src="{{url "/themes/store/js/async.min.js"}}"></script>
<script src="{{url "/themes/store/js/handlebars.js"}}"></script>
<script src="{{url "/themes/store/js/theme.js"}}"></script>
{{js}}
{{code}}
<script src="{{url "/themes/store/js/caramel.handlebars.client.js"}}"></script>
<script src="{{url "/themes/store/js/caramel-client.js"}}"></script>
<script type="text/javascript">
$(document).ready(function () {
$('.navigation-container').affix({
offset: {
top: $('header').height()
}
});
});
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
$("[data-toggle=popover]").popover();
$(".ctrl-filter-type-switcher").popover({
html: true,
content: function () {
return $('#content-filter-types').html();
}
});
$('#nav').affix({
offset: {
top: $('header').height()
}
});
});
function openCollapsedNav() {
$('.wr-hidden-nav-toggle-btn').addClass('active');
$('#hiddenNav').slideToggle('slideDown', function () {
if ($(this).css('display') == 'none') {
$('.wr-hidden-nav-toggle-btn').removeClass('active');
}
});
}
</script>
</body>
</html>

@ -1,5 +1,4 @@
<br />
<span id="request-group-id" data-groupid="{{groupId}}"></span>
<div class="container container-bg white-bg">
<div class=" margin-top-double">
<div class="row row padding-top-double padding-bottom-double margin-bottom-double ">

@ -0,0 +1,47 @@
<div id="nav" class="row wr-app-bar">
<div class="wr-action-container">
<div class="wr-action-btn-bar">
<a href="javascript:openCollapsedNav()" class="cu-btn wr-hidden-nav-toggle-btn">
<i class="fw fw-tiles fw-2x"></i>
</a><a class="cu-btn page-title" href='javascript:location.reload();'>
<span class="fw-stack"></span>
{{page_title}}
</a>{{#each currentActions}}<a href="{{url}}" class="cu-btn {{class}}">
<span class="fw-stack">
<i class="fw fw-ring fw-stack-2x"></i>
<i class="fw {{icon}} fw-stack-1x"></i>
</span>
{{action_title}}
</a>
{{/each}}{{#if enableBack}}<a href="javascript:history.go(-1)" class="cu-btn">
<span class="fw-stack">
<i class="fw fw-ring fw-stack-2x"></i>
<i class="fw fw-left-arrow fw-stack-1x"></i>
</span>
Go Back
</a>
{{/if}}
<a href="javascript:toggleNotificationbar()" class="cu-btn wr-notification-toggle-btn">
<span class="fw-stack-md">
<i class="fw fw-bell fw-stack-1-5x"></i>
</span>
<span class="wr-notification-bubble">0</span>
</a>
</div>
</div>
</div>
<!-- secondary header - app bar -->
<!-- common navigation -->
<div id="hiddenNav" class="wr-hidden-nav">
<ul>
<li><a href="/iotserver/dashboard"><i class="fw fw-dashboard"></i>Dashboard</a></li>
<li><a href="/iotserver/devices"><i class="fw fw-devices"></i>My Devices</a></li>
<li><a href="/iotserver/groups"><i class="fw fw-grouping"></i>My Groups</a></li>
{{#if permissions.ADD_USER}}
<li><a href="/iotserver/users"><i class="fw fw-user"></i>User Management</a></li>
{{/if}}
<li><a href="/iotserver/policies"><i class="fw fw-policy"></i>Policy Management</a></li>
</ul>
</div>
<!-- /common navigation -->

@ -18,11 +18,15 @@
*/
var render = function(theme, data, meta, require) {
theme('2-column-right', {
title: 'Store | Device Analytics',
title: data.meta.title,
header: [{
partial: 'header',
context: data
}],
navigation: [{
partial: 'navigation',
context: data
}],
body: [{
partial: 'analytics',
context: data

Loading…
Cancel
Save