forked from community/product-iots
Merge pull request #1 from wso2-incubator/es-integration
Add analytics components to storeapplication-manager-new
commit
3f678d9096
@ -0,0 +1,281 @@
|
||||
<%
|
||||
/*
|
||||
* Copyright (c) 2015, 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 uri = request.getRequestURI();
|
||||
var uriMatcher = new URIMatcher(String(uri));
|
||||
|
||||
var log = new Log("api/stats-api.jag");
|
||||
|
||||
var from = request.getParameter("from");
|
||||
var to = request.getParameter("to");
|
||||
|
||||
var carbon = require('carbon');
|
||||
var carbonHttpsServletTransport = carbon.server.address('https');
|
||||
var deviceCloudGroupService = carbonHttpsServletTransport + "/common/group_manager";
|
||||
var deviceCloudDeviceService = carbonHttpsServletTransport + "/common/device_manager";
|
||||
|
||||
var statsClient = new Packages.org.wso2.carbon.device.mgt.iot.common.analytics.statistics.IoTUsageStatisticsClient;
|
||||
var stats = {};
|
||||
var deviceId;
|
||||
var deviceType;
|
||||
|
||||
var server = require('store').server;
|
||||
var user = server.current(session).um;
|
||||
|
||||
log.info(server);
|
||||
log.info(session);
|
||||
log.info(user);
|
||||
|
||||
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")){
|
||||
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");
|
||||
|
||||
for (var device in devices.data){
|
||||
deviceId = devices.data[device].deviceIdentifier;
|
||||
deviceType = devices.data[device].type;
|
||||
getDeviceData(deviceType, deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
// returning the result.
|
||||
if (stats) {
|
||||
print(stats);
|
||||
}
|
||||
|
||||
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){
|
||||
return;
|
||||
}
|
||||
var uname = device.enrolmentInfo.owner;
|
||||
switch (deviceType) {
|
||||
case "firealarm":
|
||||
getFireAlarmData(uname, device, from, to);
|
||||
break;
|
||||
case "sensebot":
|
||||
getSensebotData(uname, device, from, to);
|
||||
break;
|
||||
case "arduino":
|
||||
getArduinoData(uname, device, from, to);
|
||||
break;
|
||||
case "digital_display":
|
||||
getDigitalDisplayData(uname, device, from, to);
|
||||
break;
|
||||
case "android_sense":
|
||||
getAndroidSenseData(uname, device, from, to);
|
||||
break;
|
||||
case "raspberrypi":
|
||||
getDigitalDisplayData(uname, device, from, to);
|
||||
break;
|
||||
default:
|
||||
getAllDeviceData(uname, device, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
function getFireAlarmData(user, device, from, to) {
|
||||
if (stats['temperatureData'] == null){
|
||||
stats['temperatureData'] = [];
|
||||
}
|
||||
if (stats['sonarData'] == null){
|
||||
stats['sonarData'] = [];
|
||||
}
|
||||
if (stats['motionData'] == null){
|
||||
stats['motionData'] = [];
|
||||
}
|
||||
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)});
|
||||
}
|
||||
|
||||
function getSensebotData(user, device, from, to) {
|
||||
if (stats['sonarData'] == null){
|
||||
stats['sonarData'] = [];
|
||||
}
|
||||
if (stats['motionData'] == null){
|
||||
stats['motionData'] = [];
|
||||
}
|
||||
if (stats['lightData'] == null){
|
||||
stats['lightData'] = [];
|
||||
}
|
||||
if (stats['temperatureData'] == null){
|
||||
stats['temperatureData'] = [];
|
||||
}
|
||||
|
||||
//Uncomment below section to emulate data
|
||||
var timeInterval = 30;
|
||||
var i, rnd, chartData;
|
||||
chartData = [];
|
||||
var i = parseInt(from);
|
||||
while (i < parseInt(to)) {
|
||||
rnd = rnd = Math.random() * 50;
|
||||
chartData.push({time: i, value: rnd});
|
||||
i += timeInterval;
|
||||
}
|
||||
stats['sonarData'].push({"device": device.name, "stats" : chartData});
|
||||
chartData = [];
|
||||
var i = parseInt(from);
|
||||
while (i < parseInt(to)) {
|
||||
rnd = Math.round(Math.random());
|
||||
chartData.push({time: i, value: rnd});
|
||||
i += timeInterval;
|
||||
}
|
||||
stats['motionData'].push({"device": device.name, "stats" : chartData});
|
||||
chartData = [];
|
||||
var i = parseInt(from);
|
||||
while (i < parseInt(to)) {
|
||||
rnd = Math.round(Math.random());
|
||||
chartData.push({time: i, value: rnd});
|
||||
i += timeInterval;
|
||||
}
|
||||
stats['lightData'].push({"device": device.name, "stats" : chartData});
|
||||
chartData = [];
|
||||
var i = parseInt(from);
|
||||
while (i < parseInt(to)) {
|
||||
rnd = Math.random() * (27 - 24) + 24;
|
||||
chartData.push({time: i, value: rnd});
|
||||
i += timeInterval;
|
||||
}
|
||||
stats['temperatureData'].push({"device": device.name, "stats" : chartData});
|
||||
|
||||
/*
|
||||
//Comment below section to emulate data
|
||||
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)});
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
function getArduinoData(user, device, from, to) {
|
||||
if (stats['temperatureData'] == null){
|
||||
stats['temperatureData'] = [];
|
||||
}
|
||||
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){
|
||||
stats['ramData'] = [];
|
||||
}
|
||||
if (stats['cpuData'] == null){
|
||||
stats['cpuData'] = [];
|
||||
}
|
||||
if (stats['temperatureData'] == null){
|
||||
stats['temperatureData'] = [];
|
||||
}
|
||||
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)});
|
||||
}
|
||||
|
||||
function getDigitalDisplayData(user, device, from, to) {
|
||||
if (stats['ramData'] == null){
|
||||
stats['ramData'] = [];
|
||||
}
|
||||
if (stats['cpuData'] == null){
|
||||
stats['cpuData'] = [];
|
||||
}
|
||||
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)});
|
||||
}
|
||||
|
||||
function getAllDeviceData(user, device, from, to) {
|
||||
if (stats['ramData'] == null){
|
||||
stats['ramData'] = [];
|
||||
}
|
||||
if (stats['cpuData'] == null){
|
||||
stats['cpuData'] = [];
|
||||
}
|
||||
if (stats['temperatureData'] == null){
|
||||
stats['temperatureData'] = [];
|
||||
}
|
||||
if (stats['sonarData'] == null){
|
||||
stats['sonarData'] = [];
|
||||
}
|
||||
if (stats['motionData'] == null){
|
||||
stats['motionData'] = [];
|
||||
}
|
||||
if (stats['lightData'] == null){
|
||||
stats['lightData'] = [];
|
||||
}
|
||||
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)});
|
||||
}
|
||||
|
||||
|
||||
function getSensorData(table, column, user, deviceIdentifier, from, to) {
|
||||
|
||||
var fetchedData = null;
|
||||
|
||||
try {
|
||||
fetchedData = statsClient.getDeviceStats(table, column, user, deviceIdentifier, from, to);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
var sensorData = [];
|
||||
|
||||
if (fetchedData == null) return [];
|
||||
|
||||
for (var i = 0; i < fetchedData.size(); i++) {
|
||||
sensorData.push({
|
||||
time: fetchedData.get(i).getTime(),
|
||||
value: fetchedData.get(i).getValue()
|
||||
});
|
||||
}
|
||||
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
%>
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
app.dependencies = ['store-common'];
|
||||
app.server = function(ctx) {
|
||||
return {
|
||||
endpoints: {
|
||||
pages: [{
|
||||
title: 'Store | Device Page',
|
||||
url: 'device_global',
|
||||
path: 'device_global.jag'
|
||||
}, {
|
||||
title: 'Store | Device Splash page',
|
||||
url: 'devices',
|
||||
path: 'device_top_assets.jag'
|
||||
}, {
|
||||
title: 'Store | Device Analytics page',
|
||||
url: 'analytics',
|
||||
path: 'device-analytics.jag'
|
||||
}],
|
||||
apis: [{
|
||||
url: 'stats',
|
||||
path: 'stats-api.jag',
|
||||
secured:false
|
||||
}]
|
||||
},
|
||||
configs: {
|
||||
disabledAssets: ['ebook', 'api', 'wsdl', 'servicex','policy','proxy','schema','sequence','uri','wadl','endpoint', 'swagger','restservice','comments','soapservice', 'service', 'license', 'gadget', 'site']
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
<%
|
||||
/*
|
||||
* Copyright (c) 2015, 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 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";
|
||||
appManager = app.createUserAppManager(session);
|
||||
var output = appManager.render([], page);
|
||||
caramel.render(output);
|
||||
|
||||
|
||||
}, request, response, session);
|
||||
%>
|
@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<h1 id="assetNewGlobalPage">New Global Page</h1>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<h1 id="assetOverridenGlobalPage">Overriden Top Assets Page</h1>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,331 @@
|
||||
.date-picker {
|
||||
width: 170px;
|
||||
height: 25px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
line-height: 25px;
|
||||
padding-left: 10px;
|
||||
font-size: 12px;
|
||||
font-family: Arial;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: #303030;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.date-picker-wrapper {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
border: 1px solid #bfbfbf;
|
||||
background-color: #efefef;
|
||||
width: 448px;
|
||||
padding: 5px 12px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #aaa;
|
||||
font-family: Arial;
|
||||
box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.date-picker-wrapper.single-date {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.date-picker-wrapper.no-shortcuts {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .footer {
|
||||
display: none;
|
||||
font-size: 11px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.date-picker-wrapper b {
|
||||
color: #666;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.date-picker-wrapper a {
|
||||
color: rgb(107, 180, 214);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper {
|
||||
border: 1px solid #bfbfbf;
|
||||
border-radius: 3px;
|
||||
background-color: #fff;
|
||||
padding: 5px;
|
||||
cursor: default;
|
||||
position: relative;
|
||||
_overflow: hidden;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table {
|
||||
width: 190px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table.month2 {
|
||||
width: 190px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table th,
|
||||
.date-picker-wrapper .month-wrapper table td {
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table .day {
|
||||
height: 19px;
|
||||
line-height: 19px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 1px;
|
||||
color: #999;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table div.day.lastMonth,
|
||||
.date-picker-wrapper .month-wrapper table div.day.nextMonth {
|
||||
color: #999;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table .day.checked {
|
||||
background-color: rgb(156, 219, 247);
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table .week-name {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table .day.has-tooltip {
|
||||
cursor: help !important;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table .day.toMonth.valid {
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table .day.real-today {
|
||||
background-color: rgb(255, 230, 132);
|
||||
}
|
||||
|
||||
.date-picker-wrapper .month-wrapper table .day.real-today.checked {
|
||||
background-color: rgb(112, 204, 213);
|
||||
}
|
||||
|
||||
.date-picker-wrapper table .caption {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.date-picker-wrapper table .caption .next,
|
||||
.date-picker-wrapper table .caption .prev {
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.date-picker-wrapper table .caption .next:hover,
|
||||
.date-picker-wrapper table .caption .prev:hover {
|
||||
background-color: #ccc;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap {
|
||||
position: absolute;
|
||||
display: none;
|
||||
top: 0px;
|
||||
left: 204px;
|
||||
z-index: 1;
|
||||
width: 15px;
|
||||
height: 100%;
|
||||
background-color: red;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap .gap-lines {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap .gap-line {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap .gap-line .gap-1 {
|
||||
z-index: 1;
|
||||
height: 0;
|
||||
border-left: 8px solid white;
|
||||
border-top: 8px solid #eee;
|
||||
border-bottom: 8px solid #eee;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap .gap-line .gap-2 {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0px;
|
||||
z-index: 2;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-top: 8px solid white;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap .gap-line .gap-3 {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 8px;
|
||||
z-index: 2;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-bottom: 8px solid white;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap .gap-top-mask {
|
||||
width: 6px;
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 1px;
|
||||
background-color: #eee;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .gap .gap-bottom-mask {
|
||||
width: 6px;
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 7px;
|
||||
background-color: #eee;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .selected-days {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar {
|
||||
line-height: 40px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar .error-top {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar .normal-top {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar .default-top {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar.error .default-top {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar.error .error-top {
|
||||
display: block;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar.normal .default-top {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar.normal .normal-top {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar .apply-btn {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 6px;
|
||||
padding: 3px 5px;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
color: #d9eef7;
|
||||
border: solid 1px #0076a3;
|
||||
background: #0095cd;
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#00adee), to(#0078a5));
|
||||
background: -moz-linear-gradient(top, #00adee, #0078a5);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00adee', endColorstr='#0078a5');
|
||||
color: white;
|
||||
}
|
||||
|
||||
.date-picker-wrapper .drp_top-bar .apply-btn.disabled {
|
||||
pointer-events: none;
|
||||
color: #606060;
|
||||
border: solid 1px #b7b7b7;
|
||||
background: #fff;
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#ededed));
|
||||
background: -moz-linear-gradient(top, #fff, #ededed);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed');
|
||||
}
|
||||
|
||||
/*time styling*/
|
||||
.time {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.time input[type=range] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.time1, .time2 {
|
||||
width: 180px;
|
||||
padding: 0 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.time1 {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.time2 {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.hour, .minute {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input.hour-range, input.minute-range {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#dateRangePickerContainer .date-range, #dateRangePickerContainer .input-append {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
#date-range{
|
||||
padding-right:30px;
|
||||
width:300px;
|
||||
height:100%;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
#dateRangePickerContainer{
|
||||
float:right;
|
||||
margin-top:-6px;
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
.rickshaw_graph .detail {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
transition: opacity 0.25s linear;
|
||||
-moz-transition: opacity 0.25s linear;
|
||||
-o-transition: opacity 0.25s linear;
|
||||
-webkit-transition: opacity 0.25s linear;
|
||||
}
|
||||
.rickshaw_graph .detail.inactive {
|
||||
opacity: 0;
|
||||
}
|
||||
.rickshaw_graph .detail .item.active {
|
||||
opacity: 1;
|
||||
}
|
||||
.rickshaw_graph .detail .x_label {
|
||||
font-family: Arial, sans-serif;
|
||||
border-radius: 3px;
|
||||
padding: 6px;
|
||||
opacity: 0.5;
|
||||
border: 1px solid #e0e0e0;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
background: white;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.rickshaw_graph .detail .x_label.left {
|
||||
left: 0;
|
||||
}
|
||||
.rickshaw_graph .detail .x_label.right {
|
||||
right: 0;
|
||||
}
|
||||
.rickshaw_graph .detail .item {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
border-radius: 3px;
|
||||
padding: 0.25em;
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
opacity: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.4);
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
margin-top: -1em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.rickshaw_graph .detail .item.left {
|
||||
left: 0;
|
||||
}
|
||||
.rickshaw_graph .detail .item.right {
|
||||
right: 0;
|
||||
}
|
||||
.rickshaw_graph .detail .item.active {
|
||||
opacity: 1;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.rickshaw_graph .detail .item:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
content: "";
|
||||
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
.rickshaw_graph .detail .item.left:after {
|
||||
top: 1em;
|
||||
left: -5px;
|
||||
margin-top: -5px;
|
||||
border-right-color: rgba(0, 0, 0, 0.8);
|
||||
border-left-width: 0;
|
||||
}
|
||||
.rickshaw_graph .detail .item.right:after {
|
||||
top: 1em;
|
||||
right: -5px;
|
||||
margin-top: -5px;
|
||||
border-left-color: rgba(0, 0, 0, 0.8);
|
||||
border-right-width: 0;
|
||||
}
|
||||
.rickshaw_graph .detail .dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
margin-left: -3px;
|
||||
margin-top: -3.5px;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
|
||||
box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
background: white;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
display: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.rickshaw_graph .detail .dot.active {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/* graph */
|
||||
|
||||
.rickshaw_graph {
|
||||
position: relative;
|
||||
}
|
||||
.rickshaw_graph svg {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ticks */
|
||||
|
||||
.rickshaw_graph .x_tick {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 0px;
|
||||
border-left: 1px dotted rgba(0, 0, 0, 0.2);
|
||||
pointer-events: none;
|
||||
}
|
||||
.rickshaw_graph .x_tick .title {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
opacity: 0.5;
|
||||
white-space: nowrap;
|
||||
margin-left: 3px;
|
||||
bottom: -20px;
|
||||
}
|
||||
|
||||
/* annotations */
|
||||
|
||||
.rickshaw_annotation_timeline {
|
||||
height: 1px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.rickshaw_annotation_timeline .annotation {
|
||||
position: absolute;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
margin-left: -2px;
|
||||
top: -3px;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.rickshaw_graph .annotation_line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: -6px;
|
||||
width: 0px;
|
||||
border-left: 2px solid rgba(0, 0, 0, 0.3);
|
||||
display: none;
|
||||
}
|
||||
.rickshaw_graph .annotation_line.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rickshaw_graph .annotation_range {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: -6px;
|
||||
}
|
||||
.rickshaw_graph .annotation_range.active {
|
||||
display: block;
|
||||
}
|
||||
.rickshaw_graph .annotation_range.active.offscreen {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rickshaw_annotation_timeline .annotation .content {
|
||||
background: white;
|
||||
color: black;
|
||||
opacity: 0.9;
|
||||
padding: 5px 5px;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
font-size: 12px;
|
||||
padding: 6px 8px 8px;
|
||||
top: 18px;
|
||||
left: -11px;
|
||||
width: 160px;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rickshaw_annotation_timeline .annotation .content:before {
|
||||
content: "\25b2";
|
||||
position: absolute;
|
||||
top: -11px;
|
||||
color: white;
|
||||
text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.rickshaw_annotation_timeline .annotation.active,
|
||||
.rickshaw_annotation_timeline .annotation:hover {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
cursor: none;
|
||||
}
|
||||
.rickshaw_annotation_timeline .annotation .content:hover {
|
||||
z-index: 50;
|
||||
}
|
||||
.rickshaw_annotation_timeline .annotation.active .content {
|
||||
display: block;
|
||||
}
|
||||
.rickshaw_annotation_timeline .annotation:hover .content {
|
||||
display: block;
|
||||
z-index: 50;
|
||||
}
|
||||
.rickshaw_graph .y_axis,
|
||||
.rickshaw_graph .x_axis_d3 {
|
||||
fill: none;
|
||||
}
|
||||
.rickshaw_graph .y_ticks .tick line,
|
||||
.rickshaw_graph .x_ticks_d3 .tick {
|
||||
stroke: rgba(0, 0, 0, 0.16);
|
||||
stroke-width: 2px;
|
||||
shape-rendering: crisp-edges;
|
||||
pointer-events: none;
|
||||
}
|
||||
.rickshaw_graph .y_grid .tick,
|
||||
.rickshaw_graph .x_grid_d3 .tick {
|
||||
z-index: -1;
|
||||
stroke: rgba(0, 0, 0, 0.20);
|
||||
stroke-width: 1px;
|
||||
stroke-dasharray: 1 1;
|
||||
}
|
||||
.rickshaw_graph .y_grid .tick[data-y-value="0"] {
|
||||
stroke-dasharray: 1 0;
|
||||
}
|
||||
.rickshaw_graph .y_grid path,
|
||||
.rickshaw_graph .x_grid_d3 path {
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
.rickshaw_graph .y_ticks path,
|
||||
.rickshaw_graph .x_ticks_d3 path {
|
||||
fill: none;
|
||||
stroke: #808080;
|
||||
}
|
||||
.rickshaw_graph .y_ticks text,
|
||||
.rickshaw_graph .x_ticks_d3 text {
|
||||
opacity: 0.5;
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.rickshaw_graph .x_tick.glow .title,
|
||||
.rickshaw_graph .y_ticks.glow text {
|
||||
fill: black;
|
||||
color: black;
|
||||
text-shadow:
|
||||
-1px 1px 0 rgba(255, 255, 255, 0.1),
|
||||
1px -1px 0 rgba(255, 255, 255, 0.1),
|
||||
1px 1px 0 rgba(255, 255, 255, 0.1),
|
||||
0px 1px 0 rgba(255, 255, 255, 0.1),
|
||||
0px -1px 0 rgba(255, 255, 255, 0.1),
|
||||
1px 0px 0 rgba(255, 255, 255, 0.1),
|
||||
-1px 0px 0 rgba(255, 255, 255, 0.1),
|
||||
-1px -1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.rickshaw_graph .x_tick.inverse .title,
|
||||
.rickshaw_graph .y_ticks.inverse text {
|
||||
fill: white;
|
||||
color: white;
|
||||
text-shadow:
|
||||
-1px 1px 0 rgba(0, 0, 0, 0.8),
|
||||
1px -1px 0 rgba(0, 0, 0, 0.8),
|
||||
1px 1px 0 rgba(0, 0, 0, 0.8),
|
||||
0px 1px 0 rgba(0, 0, 0, 0.8),
|
||||
0px -1px 0 rgba(0, 0, 0, 0.8),
|
||||
1px 0px 0 rgba(0, 0, 0, 0.8),
|
||||
-1px 0px 0 rgba(0, 0, 0, 0.8),
|
||||
-1px -1px 0 rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.custom_rickshaw_graph {
|
||||
position: relative;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.custom_y_axis {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.custom_slider {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.custom_x_axis {
|
||||
position: relative;
|
||||
left: 40px;
|
||||
height: 30px;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
.rickshaw_legend {
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
background: #404040;
|
||||
display: inline-block;
|
||||
padding: 12px 5px;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
float: right;
|
||||
}
|
||||
.rickshaw_legend:hover {
|
||||
z-index: 10;
|
||||
}
|
||||
.rickshaw_legend .swatch {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.rickshaw_legend .line {
|
||||
clear: both;
|
||||
line-height: 140%;
|
||||
padding-right: 15px;
|
||||
}
|
||||
.rickshaw_legend .line .swatch {
|
||||
display: inline-block;
|
||||
margin-right: 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.rickshaw_legend .label {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
font-weight: normal;
|
||||
line-height: normal;
|
||||
padding: 0px;
|
||||
text-shadow: none;
|
||||
}
|
||||
.rickshaw_legend .action:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.rickshaw_legend .action {
|
||||
margin-right: 0.2em;
|
||||
font-size: 10px;
|
||||
opacity: 0.2;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.rickshaw_legend .line.disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.rickshaw_legend ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rickshaw_legend li {
|
||||
padding: 0 0 0 2px;
|
||||
min-width: 80px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.rickshaw_legend li:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.rickshaw_legend li:active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.legend {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
left: 8px;
|
||||
}
|
||||
.legend_container {
|
||||
float: right;
|
||||
padding-right: 10px;
|
||||
width: 0;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
opacity: 0.7;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
div, span, p, td {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
#chart {
|
||||
display: inline-block;
|
||||
}
|
||||
#legend {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
left: 8px;
|
||||
}
|
||||
#legend_container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 26px;
|
||||
width: 0;
|
||||
}
|
||||
#chart_container {
|
||||
float: left;
|
||||
position: relative;
|
||||
}
|
@ -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: ['libs/jquery-ui.js', 'libs/d3.min.js', 'libs/moment.min.js', 'libs/jquery.daterangepicker.js', 'libs/rickshaw.min.js', 'graph_util.js'],
|
||||
css: ['daterangepicker.css', 'detail.css', 'graph.css', 'legend.css', 'lines.css']
|
||||
};
|
||||
};
|
@ -0,0 +1,597 @@
|
||||
var fromDate;
|
||||
var toDate;
|
||||
|
||||
var currentDay = new Date();
|
||||
var startDate = new Date(currentDay.getTime() - (60 * 60 * 24 * 100));
|
||||
var endDate = new Date(currentDay.getTime());
|
||||
|
||||
var groupId;
|
||||
|
||||
var color = ['#c05020', '#30c020', '#6060c0', '#170B3B', '#5E610B', '#2F0B3A', '#FF4000', '#2F0B3A', 'steelblue'];
|
||||
|
||||
// create a custom bar renderer that shift bars
|
||||
Rickshaw.Graph.Renderer.BinaryBar = Rickshaw.Class.create(Rickshaw.Graph.Renderer.Bar, {
|
||||
name: 'binary_bar',
|
||||
render: function (args) {
|
||||
|
||||
args = args || {};
|
||||
|
||||
var graph = this.graph;
|
||||
var series = args.series || graph.series;
|
||||
|
||||
var vis = args.vis || graph.vis;
|
||||
vis.selectAll('*').remove();
|
||||
|
||||
var barWidth = this.barWidth(series.active()[0]);
|
||||
var barXOffset = 0;
|
||||
|
||||
var activeSeriesCount = series.filter(function (s) {
|
||||
return !s.disabled;
|
||||
}).length;
|
||||
var seriesBarWidth = this.unstack ? barWidth / activeSeriesCount : barWidth;
|
||||
|
||||
var transform = function (d) {
|
||||
// add a matrix transform for negative values
|
||||
var matrix = [1, 0, 0, (d.y < 0 ? -1 : 1), 0, (d.y < 0 ? graph.y.magnitude(Math.abs(d.y)) * 2 : 0)];
|
||||
return "matrix(" + matrix.join(',') + ")";
|
||||
};
|
||||
|
||||
var index = 0;
|
||||
series.forEach(function (series) {
|
||||
if (series.disabled) return;
|
||||
|
||||
var nodes = vis.selectAll("path")
|
||||
.data(series.stack.filter(function (d) {
|
||||
return d.y !== null
|
||||
}))
|
||||
.enter().append("svg:rect")
|
||||
.attr("x", function (d) {
|
||||
return graph.x(d.x) + barXOffset
|
||||
})
|
||||
.attr("y", function (d) {
|
||||
return ((graph.y(index + Math.abs(d.y))) * (d.y < 0 ? -1 : 1 ))
|
||||
})
|
||||
.attr("width", seriesBarWidth)
|
||||
.attr("height", function (d) {
|
||||
return graph.y.magnitude(Math.abs(d.y))
|
||||
})
|
||||
.attr("transform", transform);
|
||||
|
||||
index++;
|
||||
Array.prototype.forEach.call(nodes[0], function (n) {
|
||||
n.setAttribute('fill', series.color);
|
||||
});
|
||||
|
||||
if (this.unstack) barXOffset += seriesBarWidth;
|
||||
|
||||
}, this);
|
||||
}
|
||||
});
|
||||
|
||||
function initDate() {
|
||||
currentDay = new Date();
|
||||
}
|
||||
|
||||
var configObject = {
|
||||
startOfWeek: 'monday',
|
||||
separator: ' to ',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
autoClose: false,
|
||||
time: {
|
||||
enabled: true
|
||||
},
|
||||
shortcuts: 'hide',
|
||||
endDate: currentDay,
|
||||
maxDays: 2,
|
||||
getValue: function () {
|
||||
return this.value;
|
||||
},
|
||||
setValue: function (s) {
|
||||
this.value = s;
|
||||
}
|
||||
};
|
||||
|
||||
var DateRange = convertDate(startDate) + " " + configObject.separator + " " + convertDate(endDate);
|
||||
|
||||
$(document).ready(function () {
|
||||
initDate();
|
||||
groupId = $("#request-group-id").data("groupid");
|
||||
|
||||
$('#date-range').html(DateRange);
|
||||
$('#date-range').dateRangePicker(configObject)
|
||||
.bind('datepicker-apply', function (event, dateRange) {
|
||||
$(this).addClass('active');
|
||||
$(this).siblings().removeClass('active');
|
||||
fromDate = dateRange.date1 != "Invalid Date" ? dateRange.date1.getTime() / 1000 : null;
|
||||
toDate = dateRange.date2 != "Invalid Date" ? dateRange.date2.getTime() / 1000 : null;
|
||||
getStats(fromDate, toDate);
|
||||
}
|
||||
);
|
||||
getDateTime(currentDay.getTime() - 3600000, currentDay.getTime());
|
||||
$('#hour-btn').addClass('active');
|
||||
});
|
||||
|
||||
//hour
|
||||
$('#hour-btn').on('click', function () {
|
||||
initDate();
|
||||
getDateTime(currentDay.getTime() - 3600000, currentDay.getTime());
|
||||
});
|
||||
|
||||
//12 hours
|
||||
$('#h12-btn').on('click', function () {
|
||||
initDate();
|
||||
getDateTime(currentDay.getTime() - (3600000 * 12), currentDay.getTime());
|
||||
});
|
||||
|
||||
//24 hours
|
||||
$('#h24-btn').on('click', function () {
|
||||
initDate();
|
||||
getDateTime(currentDay.getTime() - (3600000 * 24), currentDay.getTime());
|
||||
});
|
||||
|
||||
//48 hours
|
||||
$('#h48-btn').on('click', function () {
|
||||
initDate();
|
||||
getDateTime(currentDay.getTime() - (3600000 * 48), currentDay.getTime());
|
||||
});
|
||||
|
||||
$('body').on('click', '.btn-group button', function (e) {
|
||||
$(this).addClass('active');
|
||||
$(this).siblings().removeClass('active');
|
||||
});
|
||||
|
||||
function getDateTime(from, to) {
|
||||
fromDate = from;
|
||||
toDate = to;
|
||||
startDate = new Date(from);
|
||||
endDate = new Date(to);
|
||||
DateRange = convertDate(startDate) + " " + configObject.separator + " " + convertDate(endDate);
|
||||
console.log(DateRange);
|
||||
$('#date-range').html(DateRange);
|
||||
getStats(from / 1000, to / 1000);
|
||||
}
|
||||
|
||||
function getStats(from, to) {
|
||||
var requestData = new Object();
|
||||
var getStatsRequest;
|
||||
if (from) {
|
||||
requestData['from'] = from;
|
||||
}
|
||||
if (to) {
|
||||
requestData['to'] = to;
|
||||
}
|
||||
if (groupId && groupId != "0") {
|
||||
requestData['groupId'] = groupId;
|
||||
getStatsRequest = $.ajax({
|
||||
url: "../apis/stats/group",
|
||||
method: "GET",
|
||||
data: requestData
|
||||
});
|
||||
} else {
|
||||
var deviceId = getUrlParameter('deviceId');
|
||||
var deviceType = getUrlParameter('deviceType');
|
||||
|
||||
requestData['deviceId'] = deviceId;
|
||||
requestData['deviceType'] = deviceType;
|
||||
|
||||
getStatsRequest = $.ajax({
|
||||
url: "../apis/stats",
|
||||
method: "GET",
|
||||
data: requestData
|
||||
});
|
||||
}
|
||||
getStatsRequest.done(function (stats) {
|
||||
updateGraphs(JSON.parse(stats));
|
||||
});
|
||||
|
||||
getStatsRequest.fail(function (jqXHR, textStatus) {
|
||||
alert("Request failed: " + textStatus);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var temperatureData = stats['temperatureData'];
|
||||
if (typeof temperatureData != 'undefined') {
|
||||
$('#div-temperatureData').html("").html("<div class='row margin-double'><div><h2 class='grey'>Temperature</h2><hr><div id='canvas-wrapper1'></div></div><hr></div>");
|
||||
drawLineGraph(1, temperatureData);
|
||||
} else {
|
||||
$('#div-temperatureData').html("");
|
||||
}
|
||||
|
||||
var lightData = stats['lightData'];
|
||||
if (typeof lightData != 'undefined') {
|
||||
$('#div-lightData').html("").html("<div class='row margin-double'><div><h2 class='grey'>Light</h2><hr><div id='canvas-wrapper2'></div></div><hr></div>");
|
||||
drawBarGraph(2, lightData);
|
||||
} else {
|
||||
$('#div-lightData').html("");
|
||||
}
|
||||
|
||||
var motionData = stats['motionData'];
|
||||
if (typeof motionData != 'undefined') {
|
||||
$('#div-motionData').html("").html("<div class='row margin-double'><div><h2 class='grey'>Motion</h2><hr><div id='canvas-wrapper3'></div></div><hr></div>");
|
||||
drawBarGraph(3, motionData);
|
||||
} else {
|
||||
$('#div-motionData').html("");
|
||||
}
|
||||
|
||||
var sonarData = stats['sonarData'];
|
||||
if (typeof sonarData != 'undefined') {
|
||||
$('#div-sonarData').html("").html("<div class='row margin-double'><div><h2 class='grey'>Sonar</h2><hr><div id='canvas-wrapper4'></div></div><hr></div>");
|
||||
drawLineGraph(4, sonarData);
|
||||
} else {
|
||||
$('#div-sonarData').html("");
|
||||
}
|
||||
|
||||
var fanData = stats['fanData'];
|
||||
if (typeof fanData != 'undefined') {
|
||||
$('#div-fanData').html("").html("<div class='row margin-double'><div><h2 class='grey'>Fan Status</h2><hr><div id='canvas-wrapper5'></div></div><hr></div>");
|
||||
drawBarGraph(5, fanData);
|
||||
} else {
|
||||
$('#div-fanData').html("");
|
||||
}
|
||||
|
||||
var bulbData = stats['bulbData'];
|
||||
if (typeof bulbData != 'undefined') {
|
||||
$('#div-bulbData').html("").html("<div class='row margin-double'><div><h2 class='grey'>Bulb Status</h2><hr><div id='canvas-wrapper6'></div></div><hr></div>");
|
||||
drawBarGraph(6, bulbData);
|
||||
} else {
|
||||
$('#div-bulbData').html("");
|
||||
}
|
||||
|
||||
var cpuData = stats['cpuData'];
|
||||
if (typeof cpuData != 'undefined') {
|
||||
$('#div-CPUData').html("").html("<div class='row margin-double'><div><h2 class='grey'>CPU Load</h2><hr><div id='canvas-wrapper7'></div></div><hr></div>");
|
||||
drawLineGraph(7, cpuData);
|
||||
} else {
|
||||
$('#div-CPUData').html("");
|
||||
}
|
||||
|
||||
var ramData = stats['ramData'];
|
||||
if (typeof ramData != 'undefined') {
|
||||
$('#div-RAMData').html("").html("<div class='row margin-double'><div><h2 class='grey'>RAM Usage</h2><hr><div id='canvas-wrapper8'></div></div><hr></div>");
|
||||
drawLineGraph(8, ramData);
|
||||
} else {
|
||||
$('#div-RAMData').html("");
|
||||
}
|
||||
|
||||
var cpuTemperatureData = stats['cpuTemperatureData'];
|
||||
if (typeof cpuTemperatureData != 'undefined') {
|
||||
$('#div-cpuTemperatureData').html("").html("<div class='row margin-double'><div><h2 class='grey'>CPU Temperature</h2><hr><div id='canvas-wrapper9'></div></div><hr></div>");
|
||||
drawLineGraph(9, cpuTemperatureData);
|
||||
} else {
|
||||
$('#div-cpuTemperatureData').html("");
|
||||
}
|
||||
|
||||
scaleGraphs();
|
||||
}
|
||||
|
||||
function drawLineGraph(graphId, chartDataRaw) {
|
||||
var chartWrapperElmId = "#canvas-wrapper" + graphId;
|
||||
var graphWidth = $(chartWrapperElmId).width() - 50;
|
||||
if (chartDataRaw.length == 0) {
|
||||
$(chartWrapperElmId).html("No data available...");
|
||||
return;
|
||||
}
|
||||
|
||||
var chartDiv = "chart" + graphId;
|
||||
var sliderDiv = "slider" + graphId;
|
||||
var y_axis = "y_axis" + graphId;
|
||||
$(chartWrapperElmId).html("").html('<div id = "' + y_axis
|
||||
+ '" class="custom_y_axis"></div><div class="legend_container" id="legend_container'
|
||||
+ graphId + '"><div id="smoother' + graphId + '" title="Smoothing"></div><div class="legend" id="legend'
|
||||
+ graphId + '"></div></div><div id="' + chartDiv
|
||||
+ '" class="custom_rickshaw_graph"></div><div class="custom_x_axis"></div><div id="' + sliderDiv
|
||||
+ '" class="custom_slider"></div>');
|
||||
|
||||
var graphConfig = {
|
||||
element: document.getElementById(chartDiv),
|
||||
width: graphWidth,
|
||||
height: 400,
|
||||
strokeWidth: 2,
|
||||
renderer: 'line',
|
||||
unstack: true,
|
||||
stack: false,
|
||||
xScale: d3.time.scale(),
|
||||
padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0},
|
||||
series: []
|
||||
};
|
||||
|
||||
var k = 0;
|
||||
var min = Number.MAX_VALUE;
|
||||
var max = Number.MIN_VALUE;
|
||||
var range_min = 99999, range_max = 0;
|
||||
for (var i = 0; i < chartDataRaw.length; i++) {
|
||||
var chartData = [];
|
||||
if (chartDataRaw[i].stats.length > 0) {
|
||||
var max_val = parseInt(chartDataRaw[i].stats[0].value);
|
||||
var min_val = max_val;
|
||||
for (var j = 0; j < chartDataRaw[i].stats.length; j++) {
|
||||
var y_val = parseInt(chartDataRaw[i].stats[j].value);
|
||||
if (y_val > max_val) {
|
||||
max_val = y_val;
|
||||
} else if (y_val < min_val) {
|
||||
min_val = y_val;
|
||||
}
|
||||
chartData.push({
|
||||
x: parseInt(chartDataRaw[i].stats[j].time),
|
||||
y: y_val
|
||||
});
|
||||
}
|
||||
if (range_max < max_val) {
|
||||
range_max = max_val;
|
||||
}
|
||||
if (range_min > min_val) {
|
||||
range_min = min_val;
|
||||
}
|
||||
graphConfig['series'].push({
|
||||
'color': color[k],
|
||||
'data': summerizeLine(chartData),
|
||||
'name': chartDataRaw[i].device,
|
||||
'scale': d3.scale.linear().domain([Math.min(min, min_val), Math.max(max, max_val)]).nice()
|
||||
});
|
||||
}
|
||||
if (++k == color.length) {
|
||||
k = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (graphConfig['series'].length == 0) {
|
||||
$(chartWrapperElmId).html("No data available...");
|
||||
return;
|
||||
}
|
||||
|
||||
var graph = new Rickshaw.Graph(graphConfig);
|
||||
|
||||
graph.render();
|
||||
|
||||
var xAxis = new Rickshaw.Graph.Axis.Time({
|
||||
graph: graph
|
||||
});
|
||||
|
||||
xAxis.render();
|
||||
|
||||
var yAxis = new Rickshaw.Graph.Axis.Y.Scaled({
|
||||
graph: graph,
|
||||
orientation: 'left',
|
||||
element: document.getElementById(y_axis),
|
||||
width: 40,
|
||||
height: 410,
|
||||
'scale': d3.scale.linear().domain([Math.min(min, range_min), Math.max(max, range_max)]).nice()
|
||||
});
|
||||
|
||||
yAxis.render();
|
||||
|
||||
var slider = new Rickshaw.Graph.RangeSlider.Preview({
|
||||
graph: graph,
|
||||
element: document.getElementById(sliderDiv)
|
||||
});
|
||||
|
||||
var legend = new Rickshaw.Graph.Legend({
|
||||
graph: graph,
|
||||
element: document.getElementById('legend' + graphId)
|
||||
|
||||
});
|
||||
|
||||
var hoverDetail = new Rickshaw.Graph.HoverDetail({
|
||||
graph: graph
|
||||
});
|
||||
|
||||
var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
|
||||
graph: graph,
|
||||
legend: legend
|
||||
});
|
||||
|
||||
var order = new Rickshaw.Graph.Behavior.Series.Order({
|
||||
graph: graph,
|
||||
legend: legend
|
||||
});
|
||||
|
||||
var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight({
|
||||
graph: graph,
|
||||
legend: legend
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function drawBarGraph(graphId, chartDataRaw) {
|
||||
var chartWrapperElmId = "#canvas-wrapper" + graphId;
|
||||
var graphWidth = $(chartWrapperElmId).width() - 50;
|
||||
if (chartDataRaw.length == 0) {
|
||||
$(chartWrapperElmId).html("No data available...");
|
||||
return;
|
||||
}
|
||||
|
||||
var chartDiv = "chart" + graphId;
|
||||
var sliderDiv = "slider" + graphId;
|
||||
var y_axis = "y_axis" + graphId;
|
||||
$(chartWrapperElmId).html("").html('<div id = "' + y_axis
|
||||
+ '" class="custom_y_axis"></div><div class="legend_container" id="legend_container'
|
||||
+ graphId + '"><div id="smoother' + graphId + '" title="Smoothing"></div><div class="legend" id="legend'
|
||||
+ graphId + '"></div></div><div id="' + chartDiv
|
||||
+ '" class="custom_rickshaw_graph"></div><div class="custom_x_axis"></div><div id="' + sliderDiv
|
||||
+ '" class="custom_slider"></div>');
|
||||
|
||||
var graphConfig = {
|
||||
element: document.getElementById(chartDiv),
|
||||
width: graphWidth,
|
||||
height: 50 * chartDataRaw.length,
|
||||
strokeWidth: 0.5,
|
||||
renderer: 'binary_bar',
|
||||
offset: 'zero',
|
||||
xScale: d3.time.scale(),
|
||||
padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0},
|
||||
series: []
|
||||
};
|
||||
|
||||
var k = 0;
|
||||
for (var i = 0; i < chartDataRaw.length; i++) {
|
||||
var chartData = [];
|
||||
if (chartDataRaw[i].stats.length > 0) {
|
||||
for (var j = 0; j < chartDataRaw[i].stats.length; j++) {
|
||||
chartData.push({
|
||||
x: parseInt(chartDataRaw[i].stats[j].time),
|
||||
y: parseInt(chartDataRaw[i].stats[j].value)
|
||||
});
|
||||
}
|
||||
graphConfig['series'].push({
|
||||
'color': color[k],
|
||||
'data': summerizeBar(chartData),
|
||||
'name': chartDataRaw[i].device
|
||||
});
|
||||
}
|
||||
if (++k == color.length) {
|
||||
k = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (graphConfig['series'].length == 0) {
|
||||
$(chartWrapperElmId).html("No data available...");
|
||||
return;
|
||||
}
|
||||
|
||||
var graph = new Rickshaw.Graph(graphConfig);
|
||||
|
||||
graph.registerRenderer(new Rickshaw.Graph.Renderer.BinaryBar({graph: graph}));
|
||||
|
||||
graph.render();
|
||||
|
||||
var xAxis = new Rickshaw.Graph.Axis.Time({
|
||||
graph: graph
|
||||
});
|
||||
|
||||
xAxis.render();
|
||||
|
||||
var yAxis = new Rickshaw.Graph.Axis.Y({
|
||||
graph: graph,
|
||||
orientation: 'left',
|
||||
element: document.getElementById(y_axis),
|
||||
width: 40,
|
||||
height: 55 * chartDataRaw.length,
|
||||
tickFormat: function (y) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
yAxis.render();
|
||||
|
||||
var slider = new Rickshaw.Graph.RangeSlider.Preview({
|
||||
graph: graph,
|
||||
element: document.getElementById(sliderDiv)
|
||||
});
|
||||
|
||||
var legend = new Rickshaw.Graph.Legend({
|
||||
graph: graph,
|
||||
element: document.getElementById('legend' + graphId)
|
||||
|
||||
});
|
||||
|
||||
var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
|
||||
graph: graph,
|
||||
legend: legend
|
||||
});
|
||||
|
||||
var order = new Rickshaw.Graph.Behavior.Series.Order({
|
||||
graph: graph,
|
||||
legend: legend
|
||||
});
|
||||
|
||||
var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight({
|
||||
graph: graph,
|
||||
legend: legend
|
||||
});
|
||||
}
|
||||
|
||||
function scaleGraphs() {
|
||||
var sliders = $('.right_handle');
|
||||
if (sliders.length == 0) {
|
||||
return;
|
||||
}
|
||||
var graphWidth = 0;
|
||||
for (var i = 1; i < 10; i++) {
|
||||
if ($('#canvas-wrapper' + i).length) {
|
||||
graphWidth = $('#canvas-wrapper' + i).width() - 50;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (graphWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Scale graphs
|
||||
var sliderX = graphWidth * 60 * 60000 / (toDate - fromDate);
|
||||
if (sliderX < graphWidth) {
|
||||
// fake handle move
|
||||
if (sliderX < 50) {
|
||||
sliderX = 50;
|
||||
}
|
||||
var edown = document.createEvent("HTMLEvents");
|
||||
edown.initEvent("mousedown", true, true);
|
||||
edown.clientX = graphWidth;
|
||||
var emove = document.createEvent("HTMLEvents");
|
||||
emove.initEvent("mousemove", true, true);
|
||||
emove.clientX = sliderX;
|
||||
var eup = document.createEvent("HTMLEvents");
|
||||
eup.initEvent("mouseup", true, true);
|
||||
for (var slider in sliders) {
|
||||
sliders[slider].dispatchEvent(edown);
|
||||
document.dispatchEvent(emove);
|
||||
document.dispatchEvent(eup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertDate(date) {
|
||||
var month = date.getMonth() + 1;
|
||||
var day = date.getDate();
|
||||
var hour = date.getHours();
|
||||
var minute = date.getMinutes();
|
||||
return date.getFullYear() + '-' + (('' + month).length < 2 ? '0' : '')
|
||||
+ month + '-' + (('' + day).length < 2 ? '0' : '') + day + " " + (('' + hour).length < 2 ? '0' : '')
|
||||
+ hour + ":" + (('' + minute).length < 2 ? '0' : '') + minute;
|
||||
}
|
||||
|
||||
function summerizeLine(data) {
|
||||
if (data.length > 1500) {
|
||||
var nData = [];
|
||||
var i = 1;
|
||||
while (i < data.length) {
|
||||
var t_avg = (data[i - 1].x + data[i].x) / 2;
|
||||
var v_avg = (data[i - 1].y + data[i].y) / 2;
|
||||
nData.push({x: t_avg, y: v_avg});
|
||||
i += 2;
|
||||
}
|
||||
return summerizeLine(nData);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
function summerizeBar(data) {
|
||||
if (data.length > 1500) {
|
||||
var nData = [];
|
||||
var i = 1;
|
||||
while (i < data.length - 1) {
|
||||
var t_avg = (data[i - 1].x + data[i].x) / 2;
|
||||
var v_avg = (data[i - 1].y + data[i].y + data[i + 1].y) / 3;
|
||||
nData.push({x: t_avg, y: Math.round(v_avg)});
|
||||
i += 2;
|
||||
}
|
||||
return summerizeBar(nData);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,60 @@
|
||||
<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 ">
|
||||
<div class="col-lg-12 margin-top-double">
|
||||
<h1 class="grey ">{{title}}</h1>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div id="rangeSliderWrapper" class="pull-right" style="width: 620px;">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div id="dateRangePickerContainer">
|
||||
<div class="btn-group" role="group">
|
||||
<button id="hour-btn" type="button"
|
||||
class="btn btn-default date-range">Hour
|
||||
</button>
|
||||
<button id="h12-btn" type="button"
|
||||
class="btn btn-default date-range">12 Hours
|
||||
</button>
|
||||
<button id="h24-btn" type="button"
|
||||
class="btn btn-default date-range">24 Hours
|
||||
</button>
|
||||
<button id="h48-btn" type="button"
|
||||
class="btn btn-default date-range">48 Hours
|
||||
</button>
|
||||
<button id="date-range" type="button"
|
||||
class="btn btn-default date-range last-child"
|
||||
data-toggle="popup"
|
||||
title="Click to set custom date range"></button>
|
||||
</div>
|
||||
<span class="add-on-cal"><i style="margin-right:0px;"
|
||||
class="icon-calendar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="clear"></div>
|
||||
<div id="div-temperatureData">
|
||||
</div>
|
||||
<div id="div-lightData">
|
||||
</div>
|
||||
<div id="div-motionData">
|
||||
</div>
|
||||
<div id="div-sonarData">
|
||||
</div>
|
||||
<div id="div-fanData">
|
||||
</div>
|
||||
<div id="div-bulbData">
|
||||
</div>
|
||||
<div id="div-cpuTemperatureData">
|
||||
</div>
|
||||
<div id="div-CPUData">
|
||||
</div>
|
||||
<div id="div-RAMData">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 render = function(theme, data, meta, require) {
|
||||
theme('2-column-right', {
|
||||
title: 'Store | Device Analytics',
|
||||
header: [{
|
||||
partial: 'header',
|
||||
context: data
|
||||
}],
|
||||
body: [{
|
||||
partial: 'analytics',
|
||||
context: data
|
||||
}]
|
||||
});
|
||||
};
|
Loading…
Reference in new issue