--- /dev/null
+.DS_Store/
+.DS_Store
+err.log
+out.log
+node_modules/
+npm-debug.log
+fixtures/
+.build
--- /dev/null
+#
+# Copyright 2016 RIFT.IO Inc
+#
+# Licensed 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.
+#
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+#
+
+##
+# DEPENDENCY ALERT
+# The submodule dependencies must be specified in the
+# .gitmodules.dep file at the top level (supermodule) directory
+# If this submodule depends other submodules remember to update
+# the .gitmodules.dep
+##
+
+cmake_minimum_required(VERSION 2.8)
+
+##
+# DO NOT add any code before this and DO NOT
+# include this file anywhere else
+##
+include(rift_submodule)
+
+##
+# Submodule specific includes will go here,
+# These are specified here, since these variables are accessed
+# from multiple sub directories. If the variable is subdirectory
+# specific it must be declared in the subdirectory.
+##
+
+
+##
+# Include the subdirs
+##
+set(
+ subdirs
+ skyquake
+ )
+rift_add_subdirs(
+ SUBDIR_LIST
+ ${subdirs}
+ )
+
+##
+# This macro adds targets for documentaion, unittests, code coverage and packaging
+##
+rift_add_submodule_targets(SUBMODULE_PACKAGE_NAME "rw.ui")
--- /dev/null
+#
+# Copyright 2016 RIFT.IO Inc
+#
+# Licensed 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.
+#
+# Author(s): Austin Cormier
+# Creation Date: 07/27/2016
+#
+#
+
+.PHONY : clean
+
+makefile.top := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+TOP_SRC_PATH := $(makefile.top)
+TOP_ROOT_PATH := /usr/rift
+CMAKE_MODULE_PATH := $(TOP_ROOT_PATH)/cmake/modules
+
+RIFT_BUILD = $(TOP_SRC_PATH)/.build
+RIFT_ARTIFACTS = $(TOP_ROOT_PATH)/artifacts
+RIFT_INSTALL = $(TOP_ROOT_PATH)
+RIFT_SHELL_EXE = $(TOP_ROOT_PATH)/rift-shell -b $(RIFT_BUILD) -i $(RIFT_INSTALL) -a $(RIFT_ARTIFACTS) --
+
+CONFD = XML_ONLY
+
+BUILD_TYPE = Debug
+NOT_DEVELOPER_TYPE = FALSE
+COVERAGE_BUILD = FALSE
+RIFT_AGENT_BUILD = $(CONFD)
+PROJECT_TOP_DIR = $(TOP_ROOT_PATH)
+
+all: rw
+
+cmake::
+ mkdir -p $(RIFT_BUILD)
+ mkdir -p $(RIFT_ARTIFACTS)
+ mkdir -p $(RIFT_INSTALL)
+ cd $(RIFT_BUILD) && $(RIFT_SHELL_EXE) cmake $(TOP_SRC_PATH) -DCMAKE_INSTALL_PREFIX=$(TOP_ROOT_PATH) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DNOT_DEVELOPER_BUILD=$(NOT_DEVELOPER_TYPE) -DCOVERAGE_BUILD=$(COVERAGE_TYPE) -DRIFT_AGENT_BUILD=$(RIFT_AGENT_BUILD) -DPROJECT_TOP_DIR=$(PROJECT_TOP_DIR) -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DRIFT_SUBMODULE_NAME=$(PROJECT_TOP_DIR)
+
+rw: cmake
+ $(RIFT_SHELL_EXE) $(MAKE) -C $(RIFT_BUILD)
+
+install:
+ $(RIFT_SHELL_EXE) $(MAKE) -C $(RIFT_BUILD) install
+
+unittest:
+ $(RIFT_SHELL_EXE) $(MAKE) -C $(RIFT_BUILD) rw.unittest
+
+clean:
+ @echo "Cleaning up.."
+ -rm -rf .build
--- /dev/null
+#
+# webapp/node_modules
+#
+RIFT.ui, webapp/node_modules/angular, angularjs, MIT, https://github.com/angular/angular.js
+RIFT.ui, webapp/node_modules/angular-ui-router, angular-ui-router, MIT, https://github.com/angular-ui/ui-router
+RIFT.ui, webapp/node_modules/alt, alt, MIT, https://github.com/goatslacker/alt
+RIFT.ui, webapp/node_modules/babel-core, babel-core, MIT, https://www.npmjs.com/package/babel-core
+RIFT.ui, webapp/node_modules/babel-loader, babel-loader, MIT, https://github.com/babel/babel-loader
+RIFT.ui, webapp/node_modules/babel-preset-es2015, babel-preset-es2015, MIT, https://github.com/babel/babel/tree/master/packages/babel-preset-es2015
+RIFT.ui, webapp/node_modules/babel-runtime, babel-runtime, MIT, https://www.npmjs.com/package/babel-runtime
+RIFT.ui, webapp/node_modules/baggage-loader, baggage-loader, MIT, https://github.com/deepsweet/baggage-loader
+#RIFT.ui, canv-gauge, canv-gauge, MIT, https://github.com/Mikhus/canv-gauge
+RIFT.ui, webapp/node_modules/css-loader, css-loader, MIT, https://github.com/webpack/css-loader
+RIFT.ui, webapp/node_modules/express, express, MIT, https://github.com/strongloop/express
+RIFT.ui, webapp/node_modules/file-loader, file-loader, MIT, https://github.com/webpack/file-loader
+RIFT.ui, webapp/node_modules/foundation-apps, foundation-apps, MIT, https://github.com/zurb/foundation-apps
+RIFT.ui, webapp/node_modules/html-loader, html-loader, MIT, https://github.com/webpack/html-loader
+RIFT.ui, webapp/node_modules/http-proxy, http-proxy, MIT, https://github.com/nodejitsu/node-http-proxy
+RIFT.ui, webapp/node_modules/jquery, jquery, MIT-like, https://github.com/jquery/jquery
+RIFT.ui, webapp/node_modules/loader-utils, loader-utils, MIT, https://github.com/webpack/loader-utils
+RIFT.ui, webapp/node_modules/loaders.css, loaders.css, MIT, https://github.com/ConnorAtherton/loaders.css/tree/master
+RIFT.ui, webapp/node_modules/minimist, minimist, MIT, https://github.com/substack/minimist
+RIFT.ui, webapp/node_modules/ngtemplate-loader, ngtemplate-loader, MIT, https://github.com/WearyMonkey/ngtemplate-loader
+RIFT.ui, webapp/node_modules/node-sass, node-sass, MIT-like, https://github.com/sass/node-sass
+RIFT.ui, webapp/node_modules/open-iconic, open-iconic, MIT and SIL, https://github.com/iconic/open-iconic
+RIFT.ui, webapp/node_modules/prismjs, prismjs, MIT, https://github.com/PrismJS/prism
+RIFT.ui, webapp/node_modules/react, react, BSD, https://github.com/facebook/react
+RIFT.ui, webapp/node_modules/react-d3-histogram, react-d3-histogram, ISC, https://github.com/english/react-d3-histogram
+RIFT.ui, webapp/node_modules/react-slick, react-slick, MIT, https://github.com/akiran/react-slick
+RIFT.ui, webapp/node_modules/react-tabs, react-tabs, MIT, https://github.com/rackt/react-tabs/blob/master/LICENSE
+RIFT.ui, webapp/node_modules/react-treeview, react-treeview, MIT, https://www.npmjs.com/package/react-treeview
+RIFT.ui, webapp/node_modules/sass-loader, sass-loader, MIT, https://github.com/jtangelder/sass-loader
+RIFT.ui, webapp/node_modules/source-map, source-map, BSD, https://github.com/mozilla/source-map
+RIFT.ui, webapp/node_modules/style-loader, style-loader, MIT, https://github.com/webpack/style-loader
+RIFT.ui, webapp/node_modules/underscore, underscore, MIT-like, https://github.com/webpack/style-loader
+RIFT.ui, webapp/node_modules/webpack, webpack, MIT, https://github.com/webpack/webpack
+RIFT.ui, webapp/node_modules/webpack-dev-server, webpack-dev-server, MIT, https://github.com/webpack/webpack-dev-server
+#
+# api/node_modules
+#
+RIFT.ui, api/node_modules/bluebird, bluebird, MIT, https://github.com/petkaantonov/bluebird
+RIFT.ui, api/node_modules/body-parser, body-parser, MIT, https://github.com/expressjs/body-parser
+RIFT.ui, api/node_modules/cors, cors, MIT, https://github.com/expressjs/cors
+RIFT.ui, api/node_modules/express, express, MIT, https://github.com/strongloop/express
+RIFT.ui, api/node_modules/hawk, hawk, BSD-3-Clause, https://github.com/hueniverse/hawk
+RIFT.ui, api/node_modules/minimist, minimist, MIT, https://github.com/substack/minimist
+RIFT.ui, api/node_modules/promise, promise, MIT, https://github.com/then/promise
+RIFT.ui, api/node_modules/qs, qs, BSD-3-Clause, https://github.com/hapijs/qs
+RIFT.ui, api/node_modules/request, request, Apache-2.0, https://github.com/request/request
+RIFT.ui, api/node_modules/request-promise, request-promise, MIT, https://github.com/request/request-promise
+RIFT.ui, api/node_modules/underscore, underscore, MIT, https://github.com/jashkenas/underscore
+RIFT.ui, api/node_modules/ws, ws, MIT, https://github.com/websockets/ws
+RIFT.ui, api/node_modules/jasmine-node, jasmine-node, MIT, https://github.com/mhevery/jasmine-node
+RIFT.ui, api/node_modules/nock, Nock is an HTTP mocking and expectations library for Node.js, MIT, https://github.com/pgte/nock
+RIFT.ui, api/node_modules/replay, Node Replay, MIT, https://github.com/assaf/node-replay
+RIFT.ui, api/node_modules/express-session, express-session, MIT, https://github.com/expressjs/session
+#
+# webapp/public
+#
+#RIFT.ui, webapp/public/vendor/css-reset-2.0, CSS Reset, NONE, http://meyerweb.com/eric/tools/css/reset/
+
+RIFT.ui, skyquake/plugins/composer/node_modules/json2yaml, json2yaml, Apache-2.0, https://github.com/coolaj86/json2yaml
+RIFT.ui, skyquake/plugins/composer/node_modules/react-inlinesvg, react-inlinesvg, MIT, https://github.com/matthewwithanm/react-inlinesvg
--- /dev/null
+{
+ "presets": [
+ "es2015", "stage-0", "react"
+ ]
+}
--- /dev/null
+npm-debug.log
+*.swp
+node_modules
+public
+dist
--- /dev/null
+import { configure } from '@kadira/storybook';
+
+function loadStories() {
+ // require('../tests/stories/button');
+ // require('../tests/stories/sq-input-slider');
+ // require('../tests/stories/sshKeyCard');
+ // require('../tests/stories/button');
+ // require('../tests/stories/sq-input-slider');
+ require('../tests/stories/catalogCard');
+ // require as many stories as you need.
+}
+
+configure(loadStories, module);
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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 Webpack = require('webpack');
+var path = require('path');
+var nodeModulesPath = path.resolve(__dirname, 'node_modules');
+var buildPath = path.resolve(__dirname, 'public', 'build');
+var mainPath = path.resolve(__dirname, 'src', 'main.js');
+var frameworkPath = '../skyquake/framework/';
+var CommonsPlugin = new require("webpack/lib/optimize/CommonsChunkPlugin")
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var config = {
+ resolve: {
+ extensions: ['', '.js', '.jsx', '.css', '.scss'],
+ root: path.resolve(frameworkPath),
+ alias: {
+ 'widgets': path.resolve(frameworkPath) + '/widgets',
+ 'style': path.resolve(frameworkPath) + '/style',
+ 'utils': path.resolve(frameworkPath) + '/utils'
+ }
+ },
+ module: {
+ loaders: [{
+ test: /\.(jpe?g|png|gif|svg|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/i,
+ loader: "file-loader"
+ },
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader',
+ query: {
+ presets: ["es2015", "stage-0", "react"]
+ }
+ }, {
+ test: /\.css$/,
+ loader: 'style!css'
+ }, {
+ test: /\.scss/,
+ loader: 'style!css!sass?includePaths[]='+ path.resolve(frameworkPath)
+ }
+ ]
+ }
+};
+module.exports = config;
--- /dev/null
+#
+# Copyright 2016 RIFT.IO Inc
+#
+# Licensed 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.
+#
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+#
+
+##
+# DEPENDENCY ALERT
+# The submodule dependencies must be specified in the
+# .gitmodules.dep file at the top level (supermodule) directory
+# If this submodule depends other submodules remember to update
+# the .gitmodules.dep
+##
+
+cmake_minimum_required(VERSION 2.8)
+
+##
+# Submodule specific includes will go here,
+# These are specified here, since these variables are accessed
+# from multiple sub directories. If the variable is subdirectory
+# specific it must be declared in the subdirectory.
+##
+
+rift_externalproject_add(
+ skyquake
+ SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_COMMAND echo
+ BUILD_COMMAND
+ npm install
+ INSTALL_COMMAND
+ ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install_skyquake.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/skyquake/skyquake-build
+ ${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+ ${RIFT_SUBMODULE_INSTALL_PREFIX}/skyquake/${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+
+ BCACHE_COMMAND echo
+)
+
+
+##
+# Include the subdirs
+##
+set(
+ subdirs
+ plugins
+ )
+rift_add_subdirs(
+ SUBDIR_LIST
+ ${subdirs}
+ )
--- /dev/null
+jest.dontMock('../framework/widgets/skyquake_container/skyquakeNav.jsx');
+// /skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx
+import React from 'react';
+import ReactDOM from 'react-dom';
+import TestUtils from 'react-addons-test-utils';
+
+// import {returnLinkItem} from '../framework/widgets/skyquake_container/skyquakeNav.jsx';
+
+const SkyquakeNav = require('../framework/widgets/skyquake_container/skyquakeNav.jsx')
+
+describe('SkyquakeNav', () => {
+
+ let exampleRoutes;
+ let node;
+ beforeEach(function() {
+ node = document.createElement('div');
+ exampleRoutes = [{
+ "label": "Hello World Component 1",
+ "route": "/helloworld/#hello",
+ "component": "./helloWorldOne.jsx",
+ "path": "",
+ "type": "internal",
+ "isExternal": true
+ },{
+
+ "label": "Hello World Component 1",
+ "route": "hello",
+ "component": "./helloWorldOne.jsx",
+ "path": "",
+ "type": "internal",
+ "isExternal": false
+ }];
+ });
+ describe('returnLinkItem', () => {
+ it('Returns an <a> tag when external', () => {
+ let element = SkyquakeNav.returnLinkItem(exampleRoutes[0]);
+ let Tag = TestUtils.renderIntoDocument(element);
+ let TagNode = ReactDOM.findDOMNode(Tag);
+ expect(Tag.constructor.displayName).toEqual("A");
+ expect(TagNode.attributes.href).not.toEqual(undefined);
+ });
+ it('Returns a <Link> tag when internal', () => {
+ let element = SkyquakeNav.returnLinkItem(exampleRoutes[1]);
+ let Tag = TestUtils.renderIntoDocument(element);
+ let TagNode = ReactDOM.findDOMNode(Tag);
+ expect(TagNode.constructor.displayName).toEqual("Link");
+ expect(TagNode.attributes.href).not.toEqual(undefined);
+ })
+ })
+})
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+ /**
+ * constants module. Provides constants for use within the skyquake instance
+ * @module api_utils/constants
+ */
+
+var constants = {};
+
+constants.FOREVER_ON = true;
+constants.HTTP_HEADERS = {
+ accept: {
+ data: {
+ 'Accept': 'application/vnd.yang.data+json'
+ },
+ collection: {
+ 'Accept': 'application/vnd.yang.collection+json'
+ }
+ },
+ content_type: {
+ data: {
+ 'Content-Type': 'application/vnd.yang.data+json'
+ },
+ collection: {
+ 'Content-Type': 'application/vnd.yang.collection+json'
+ }
+ }
+};
+
+// (Incomplete) set of expected HTTP response codes
+constants.HTTP_RESPONSE_CODES = {
+ SUCCESS: {
+ OK: 200,
+ CREATED: 201,
+ ACCEPTED: 202,
+ NO_CONTENT: 204,
+ MOVED_PERMANENTLY: 301,
+ NOT_MODIFIED: 304
+ },
+ ERROR: {
+ BAD_REQUEST: 400,
+ UNAUTHORIZED: 401,
+ FORBIDDEN: 403,
+ NOT_FOUND: 404,
+ METHOD_NOT_ALLOWED: 405,
+ NOT_ACCEPTABLE: 406,
+ CONFLICT: 409,
+ INTERNAL_SERVER_ERROR: 500,
+ NOT_IMPLEMENTED: 501,
+ BAD_GATEWAY: 502,
+ SERVICE_UNAVAILABLE: 504,
+ HTTP_VERSION_UNSUPPORTED: 505
+
+ }
+}
+constants.SOCKET_BASE_PORT = 3500;
+constants.SOCKET_POOL_LENGTH = 20;
+constants.SERVER_PORT = process.env.SERVER_PORT || 8000;
+constants.SECURE_SERVER_PORT = process.env.SECURE_SERVER_PORT || 8443;
+
+module.exports = constants;
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+//SOCKET MANAGER
+// test
+//Supports localhost node polling subscriptions and pass through subscriptions to other websockets
+//
+//TODO REFACTOR: this needs to happen. there's too much boilerplate code in here.
+//TODO Document after refactoring
+//TODO Improved logging for debugging
+//TODO List of URLS
+
+var WebSocket = require('ws');
+var Request = require('request');
+var _ = require('lodash');
+var constants = require('./constants.js');
+var Promise = require('promise');
+var url = require('url');
+var sockjs = require('sockjs');
+var websocket_multiplex = require('websocket-multiplex');
+
+
+function getPortForProtocol (protocol) {
+ switch (protocol) {
+ case 'http':
+ return 8000;
+ case 'https':
+ return 8443;
+ }
+}
+
+var Subscriptions = function() {
+ this.ID = 0;
+ this.socketServers = {};
+};
+
+Subscriptions.prototype.configure = function(config) {
+ this.config = config;
+ this.ready = true;
+ // 1. Setup SockJS server
+ var sockjs_opts = {};
+ this.service = sockjs.createServer(sockjs_opts);
+ // 2. Setup multiplexing
+ this.multiplexer = new websocket_multiplex.MultiplexServer(this.service);
+
+ this.service.installHandlers(this.config.httpServer, {prefix:'/multiplex'});
+}
+
+/**
+ * [subscribe description]
+ * @param {Object} req
+ * @param {String} req.body.url May be http, https, or ws
+ * @param {Function} req.body.transform A function that will transform
+ * the data before sending it out
+ * through the socket. Receives one
+ * argument, which is the data
+ * returned from the subscription.
+ * @param {Function} callback Function that will receive the SubscriptionData reference object
+ * @return {Object} SubscriptionReference An object containing the subscription information.
+ * @return {Number} SubscriptionReference.id The subscription ID
+ */
+Subscriptions.prototype.subscribe = function(req, callback) {
+ var self = this;
+ var URL = req.body.url;
+ var SubscriptionReference;
+ var sessionId = req.session.id;
+ var protocolTest = /^(.{2,5}):\/\//;
+ var protocol = URL.match(protocolTest);
+
+ if (!protocol) {
+ var origin = '';
+ if (req.query['api_server']) {
+ var api_server_protocol = req.query['api_server'].match(protocolTest)[1];
+ var api_server_origin = req.query['api_server'] + ':' + getPortForProtocol(api_server_protocol);
+ origin = api_server_origin;
+ protocol = api_server_protocol;
+ } else {
+ // TODO: NEED A WAY (URL PARAM) TO TRIGGER THIS PART OF THE CODE
+ // WHICH IS NECESSARY FOR DEVELOPMENT ON MAC
+ // No protocol was passed with the url in the body. Assume req.protocol is protocol and construct URL
+ protocol = req.protocol || 'https';
+ // Converting relative URL to full path.
+ origin = protocol + '://' + req.headers.host
+ }
+ var a = url.resolve(origin, req.baseUrl);
+ var b = url.resolve(a, URL);
+ URL = b;
+ console.log('DEBUG URL IS', URL);
+ } else {
+ protocol = protocol[1]
+ }
+
+ return new Promise(function(resolve, reject) {
+
+ if (!self.ready) {
+ return reject({
+ statusCode: 500,
+ errorMessage: 'SocketManager not configured yet. Cannot proceed'
+ })
+ }
+
+ self.createWebSocketServer().then(function(successData) {
+
+ self.socketServers[sessionId + successData.id] = successData;
+ self.setUpSocketInstance(protocol, URL, req, self.socketServers[sessionId + successData.id].wss, successData.id);
+ return resolve({
+ statusCode: 200,
+ data: {
+ id: self.socketServers[sessionId + successData.id].id
+ }
+ });
+ },
+ function(errorData) {
+ return reject({
+ statusCode: 503,
+ errorMessage: errorData.error
+ });
+ });
+ });
+};
+
+Subscriptions.prototype.setUpSocketInstance = function(protocol, URL, req, wss, channelId) {
+ var self = this;
+ //Need to refactor this to make it more scalable/dynamic
+ switch (protocol) {
+ case 'http':
+ self.socketInstance(URL, req, wss, PollingSocket, channelId);
+ break;
+ case 'https':
+ self.socketInstance(URL, req, wss, PollingSocket, channelId);
+ break;
+ case 'ws':
+ self.socketInstance(URL, req, wss, WebSocket, channelId);
+ break;
+ case 'wss':
+ self.socketInstance(URL, req, wss, WebSocket, channelId);
+ break;
+ }
+}
+
+Subscriptions.prototype.createWebSocketServer = function() {
+ var self = this;
+
+ return new Promise(function(resolve, reject) {
+ var wss = null;
+
+ self.ID++;
+
+ wss = self.multiplexer.registerChannel(self.ID);
+
+ return resolve({
+ id: self.ID,
+ wss: wss
+ });
+ });
+};
+
+Subscriptions.prototype.socketInstance = function(url, req, wss, Type, channelId) {
+ console.log('Creating a new socketInstance for:', url, 'sessionId:', req.session.id);
+ var self = this;
+ var Socket = null;
+ var Connections = [];
+ var Index = 0;
+ var sessionId = req.session.id;
+ var wssRef = wss;
+ var channelIdRef = channelId;
+ wss.on('connection', function(conn) {
+ console.log('New connection to multiplex-server for channelId', channelIdRef);
+
+ conn.on('data', function(msg) {
+ console.log('Test purposes only. Received message from client:', msg);
+ conn.write('Test purposes only. Echo: ' + msg);
+ });
+
+ if (!Socket) {
+ if (Type == PollingSocket) {
+ Socket = new Type(url, req, 1000, req.body);
+ } else {
+ Socket = new Type(url);
+ }
+ console.log('Socket assigned for url', url);
+ }
+ conn.index = Index++;
+ // Add this client-connection into list of connections for this channelId/wss
+ Connections.push(conn);
+
+ conn.on('close', function() {
+ // Remove the browser connection from list of Connections for this channelId/wss
+ Connections.splice(conn.index, 1);
+ console.log('splicing conn.index', conn.index,' for channel', channelIdRef);
+
+ // Check if no other connections exist
+ if (Connections.length == 0) {
+ console.log('No more connections for', channelId, '. Will close socket server and downstream socket/poller.');
+ try {
+ // Close downstream socket/poller
+ Socket.close();
+
+ // Close socket server
+ conn.end();
+
+ // Remove from list of socketServers
+ delete self.socketServers[sessionId + wss.id];
+
+ // There is no unregisterChannel. Assuming
+ // sockjs/websocket-multiplex do the right
+ // things and cleanup after themselves.
+ } catch (e) {
+ console.log('Error closing socket server: ', e);
+ }
+ Index = 0;
+ delete Socket;
+ }
+ });
+
+ Socket.onopen = function() {
+ console.log('Opened a websocket to southbound server');
+ };
+
+ Socket.onerror = function(error) {
+ console.log('Error on southbound connection. Error:', error);
+ }
+
+ Socket.onmessage = function(data) {
+ var i;
+ var self = this;
+ if (req.body.transform && req.body.transform.constructor.name == "String") {
+ //someTransformObject[req.body.transform](data, send)
+ //req.body.transform(data, send);
+ } else {
+ if (Type == PollingSocket) {
+ send(data);
+ } else {
+ send(data.data);
+ }
+ }
+
+ function send(payload) {
+ var is401 = false;
+ try {
+ if (typeof payload == 'string') {
+ var jsonPayload = JSON.parse(payload);
+ is401 = jsonPayload.statusCode == 401;
+ }
+ else {
+ is401 = payload.statusCode == 401;
+ }
+ } catch(e) {
+ payload = {}
+ }
+
+ for (i = Connections.length - 1; i >= 0; i -= 1) {
+ // console.log('Sending payload to channelId:', channelId, ' on connection', i);
+ Connections[i].write(payload);
+ };
+ if (is401) {
+ try {
+ Socket.close();
+ } catch (e) {
+ console.log('Error closing Socket')
+ }
+ }
+ }
+
+ };
+ });
+};
+
+function PollingSocket(url, req, interval, config) {
+ console.log('Creating a new PollingSocket for url', url, 'sessionId:', req.session.id);
+ var self = this;
+ self.isClosed = false;
+ var requestHeaders = {};
+ _.extend(requestHeaders, {
+ 'Authorization': req.get('Authorization')
+ });
+
+ var pollServer = function() {
+ Request({
+ url: url,
+ method: config.method || 'GET',
+ headers: requestHeaders,
+ json: config.payload,
+ rejectUnauthorized: false,
+ forever: constants.FOREVER_ON
+ }, function(error, response, body) {
+ if (error) {
+ console.log('Error polling: ' + url);
+ } else {
+ if (!self.isClosed) {
+ self.poll = setTimeout(pollServer, 1000 || interval);
+ var data = response.body;
+ if (self.onmessage) {
+ self.onmessage(data);
+ }
+ }
+ }
+ });
+ };
+ pollServer();
+};
+
+PollingSocket.prototype.close = function() {
+ console.log('Closing PollingSocket');
+ var self = this;
+ this.isClosed = true;
+ clearTimeout(self.poll);
+};
+
+
+module.exports = Subscriptions;
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+// Helper Functions
+
+
+/**
+ * Utils for use across the api_server.
+ * @module framework/core/utils
+ */
+
+var fs = require('fs');
+var request = require('request');
+var Promise = require('promise');
+var CONSTANTS = require('./constants.js');
+var CONFD_PORT = '8008';
+var APIVersion = '/v1';
+var _ = require('lodash');
+
+var requestWrapper = function(request) {
+ if (process.env.HTTP_PROXY && process.env.HTTP_PROXY != '') {
+ request = request.defaults({
+ 'proxy': process.env.HTTP_PROXY
+ });
+ }
+ return request;
+}
+
+var confdPort = function(api_server) {
+ try {
+ api_server = api_server.replace(api_server.match(/[0-9](:[0-9]+)/)[1], '')
+ } catch (e) {
+
+ }
+ return api_server + ':' + CONFD_PORT;
+};
+
+
+var validateResponse = function(callerName, error, response, body, resolve, reject) {
+ var res = {};
+
+ if (error) {
+ console.log('Problem with "', callerName, '": ', error);
+ res.statusCode = 500;
+ res.errorMessage = {
+ error: 'Problem with ' + callerName + ': ' + error
+ };
+ reject(res);
+ return false;
+ } else if (response.statusCode >= 400) {
+ console.log('Problem with "', callerName, '": ', response.statusCode, ':', body);
+ res.statusCode = response.statusCode;
+
+ // auth specific
+ if (response.statusCode == 401) {
+ res.errorMessage = {
+ error: 'Authentication needed' + body
+ };
+ reject(res);
+ return false;
+ }
+
+ res.errorMessage = {
+ error: 'Problem with ' + callerName + ': ' + response.statusCode + ': ' + typeof(body) == 'string' ? body : JSON.stringify(body),
+ body: body
+ };
+
+ reject(res);
+ return false;
+ } else if (response.statusCode == 204) {
+ resolve({
+ statusCode: response.statusCode,
+ data: {}
+ });
+ return false;
+ } else {
+ return true;
+ }
+};
+
+
+var checkAuthorizationHeader = function(req) {
+ return new Promise(function(resolve, reject) {
+ if (req.get('Authorization') == null) {
+ reject();
+ } else {
+ resolve();
+ }
+ });
+};
+
+if (process.env.LOG_REQUESTS) {
+ var logFile = process.env.REQUESTS_LOG_FILE;
+
+ if (logFile && logFile != '') {
+ validateResponse = function(callerName, error, response, body, resolve, reject) {
+ var res = {};
+
+ if (error) {
+ console.log('Problem with "', callerName, '": ', error);
+ res.statusCode = 500;
+ res.errorMessage = {
+ error: 'Problem with ' + callerName + ': ' + error
+ };
+ reject(res);
+ fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error: ' + error);
+ return false;
+ } else if (response.statusCode >= 400) {
+ console.log('Problem with "', callerName, '": ', response.statusCode, ':', body);
+ res.statusCode = response.statusCode;
+
+ // auth specific
+ if (response.statusCode == 401) {
+ res.errorMessage = {
+ error: 'Authentication needed' + body
+ };
+ reject(res);
+ fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error Body: ' + body);
+ return false;
+ }
+
+ res.errorMessage = {
+ error: 'Problem with ' + callerName + ': ' + response.statusCode + ': ' + body
+ };
+
+ reject(res);
+ fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error Body: ' + body);
+ return false;
+ } else if (response.statusCode == 204) {
+ resolve();
+ fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Response Body: ' + body);
+ return false;
+ } else {
+ fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Response Body: ' + body);
+ return true;
+ }
+ };
+ }
+}
+
+/**
+ * Serve the error response back back to HTTP requester
+ * @param {Object} error - object of the format
+ * {
+ * statusCode - HTTP code to respond back with
+ * error - actual error JSON object to serve
+ * }
+ * @param {Function} res - a handle to the express response function
+ */
+var sendErrorResponse = function(error, res) {
+ res.status(error.statusCode);
+ res.send(error);
+}
+
+/**
+ * Serve the success response back to HTTP requester
+ * @param {Object} response - object of the format
+ * {
+ * statusCode - HTTP code to respond back with
+ * data - actual data JSON object to serve
+ * }
+ * @param {Function} res - a handle to the express response function
+ */
+var sendSuccessResponse = function(response, res) {
+ res.status(response.statusCode);
+ res.send(response.data);
+}
+
+var passThroughConstructor = function(app) {
+ app.get('/passthrough/:type/*', function(req, res) {
+ var url = req.params[0];
+ var type = req.params.type;
+ var api_server = req.query["api_server"];
+ var uri = confdPort(api_server) + APIVersion + '/' + url + '?deep';
+ // Check that type is valid
+ switch (type) {
+ case 'data':
+ ;
+ case 'collection':
+ break;
+ default:
+ res.send({});
+ }
+ new Promise(function(resolve, reject) {
+ request({
+ uri: uri,
+ method: 'GET',
+ headers: _.extend({}, CONSTANTS.HTTP_HEADERS.accept[type], {
+ 'Authorization': req.get('Authorization'),
+ forever: CONSTANTS.FOREVER_ON,
+ rejectUnauthorized: false,
+ })
+ }, function(error, response, body) {
+ if (validateResponse('Passthrough: ' + url, error, response, body, resolve, reject)) {
+ resolve(JSON.parse(response.body))
+ };
+ });
+ }).then(function(data) {
+ res.send(data);
+ }, function(error) {
+ res.send({'error': error, uri: uri})
+ });;
+ });
+}
+
+module.exports = {
+ /**
+ * Ensure confd port is on api_server variable.
+ **/
+ confdPort: confdPort,
+
+ validateResponse: validateResponse,
+
+ checkAuthorizationHeader: checkAuthorizationHeader,
+
+ request: requestWrapper.call(null, request),
+
+ sendErrorResponse: sendErrorResponse,
+
+ sendSuccessResponse: sendSuccessResponse,
+
+ passThroughConstructor: passThroughConstructor
+};
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * Configuration api module. Provides API functions to configure node
+ * @module framework/core/modules/api/configuration
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var Promise = require('bluebird');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var request = utils.request;
+var configurationAPI = {};
+var _ = require('lodash');
+var GLOBAL_CONFIGURATION = {
+ api_server: 'localhost',
+ ssl_enabled: true
+};
+
+/**
+ * Get the server configuration for the Express API
+ * @param {Object} req - the Express request object
+ * @return {Function} Promise that resolves with success object or rejects
+ * with error
+ */
+configurationAPI.get = function(req) {
+ return new Promise(function(resolve, reject) {
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: GLOBAL_CONFIGURATION
+ });
+ });
+};
+
+/**
+ * Update the server configuration for the Express API
+ * @param {Object} req - the Express request object
+ * @return {Function} Promise that resolves with success object or rejects
+ * with error
+ */
+configurationAPI.update = function(req) {
+ var newConfiguration = req.body;
+ return new Promise(function(resolve, reject) {
+ try {
+ _.merge(GLOBAL_CONFIGURATION, newConfiguration);
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT,
+ data: {}
+ })
+ } catch (e) {
+ console.log('Merging on new configuration failed. Error:', e);
+ reject({
+ statusCode: constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR,
+ errorMessage: {
+ error: 'Problem with merging new configuration. Error: ' + JSON.stringify(e)
+ }
+ });
+ }
+ });
+};
+
+configurationAPI.globalConfiguration = {};
+/**
+ * Internally used (by Node.js components) to update server configuration
+ * @param {Object} data - the configuration to merge
+ */
+configurationAPI.globalConfiguration.update = function(data) {
+ _.merge(GLOBAL_CONFIGURATION, data);
+};
+
+/**
+ * Internally used (by Node.js components) to get server configuration
+ * @param {Object} data - the configuration to merge
+ */
+configurationAPI.globalConfiguration.get = function() {
+ return GLOBAL_CONFIGURATION;
+};
+
+module.exports = configurationAPI;
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+// DescriptorModelMeta API (NSD + VNFD)
+
+
+var ModelMeta = {};
+var Promise = require('bluebird');
+var rp = require('request-promise');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var _ = require('lodash');
+
+ModelMeta.get = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ uri: utils.confdPort(api_server) + '/api/schema/nsd-catalog/nsd',
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+ 'Authorization': req.get('Authorization')
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ }),
+ rp({
+ uri: utils.confdPort(api_server) + '/api/schema/vnfd-catalog/vnfd',
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+ 'Authorization': req.get('Authorization')
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body && result[1].body) {
+ response['data']['nsd'] = JSON.parse(result[0].body)['nsd'];
+ response['data']['vnfd'] = JSON.parse(result[1].body)['vnfd'];
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with ModelMeta.get', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to get descriptorModelMeta' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+module.exports = ModelMeta;
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * navigation api module. Provides API functions to interact with
+ * the navigation_manager
+ * @module framework/core/modules/api/navigation
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var navigation_manager = require('../navigation_manager');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var navigationAPI = {};
+
+/**
+ * Get the navigation object stored in this instance
+ * @param {Object} req - the Express request object with or without a plugin_id
+ * @return {Function} Promise that resolves with success object or rejects
+ * with error
+ */
+navigationAPI.get = function(req) {
+ return new Promise(function(resolve, reject) {
+ if (req.params.plugin_id) {
+ var navigation = navigation_manager.getNavigation();
+ if (navigation[req.params.plugin_id]) {
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: navigation[req.params.plugin_id]
+ });
+ } else {
+ reject({
+ statusCode: constants.HTTP_RESPONSE_CODES.ERROR.NOT_FOUND,
+ errorMessage: 'No navigation found for plugin_id ' + req.params.plugin_id
+ });
+ }
+ } else {
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: navigation_manager.getNavigation()
+ });
+ }
+ });
+};
+
+/**
+ * Create the navigation object stored in this instance for a plugin.
+ * To be used across framework instances for informing each other of
+ * inter-instance navigation options.
+ * @param {Object} req - the Express request object
+ * @return {Function} Promise that resolves with success object or rejects
+ * with error
+ */
+navigationAPI.post = function(req) {
+ return new Promise(function(resolve, reject) {
+ var plugin_nav = navigation_manager.getNavigation()[req.params.plugin_id] || {};
+ if (plugin_nav != {}) {
+ reject({
+ statusCode: constants.HTTP_RESPONSE_CODES.ERROR.CONFLICT,
+ errorMessage: 'Navigation for ' + req.params.plugin_id + ' already exist'
+ });
+ } else {
+ navigation_manager.addNavigation(req.params.plugin_id, req.body);
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.CREATED,
+ data: {}
+ });
+ }
+ });
+};
+
+/**
+ * Update the navigation object stored in this instance for a plugin.
+ * @param {Object} req - the Express request object with a plugin_id and route_id
+ * @return {Function} Promise that resolves with success object or rejects
+ * with error
+ */
+navigationAPI.put = function(req) {
+ return new Promise(function(resolve, reject) {
+ var plugin_nav = navigation_manager.getNavigation()[req.params.plugin_id]['routes'][req.params.route_id];
+ if (plugin_nav == null || plugin_nav == undefined) {
+ reject({
+ statusCode: constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST,
+ errorMessage: 'Navigation for route' + req.params.route_id + ' under plugin ' + req.params.plugin_id + ' does not exist'
+ });
+ } else {
+ navigation_manager.addNavigation(req.plugin_id, req.body);
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: {}
+ });
+ }
+ });
+};
+
+/**
+ * Delete a particulat route navigation object stored in this instance for a plugin.
+ * @param {Object} req - the Express request object with a plugin_id and route_id
+ * @return {Function} Promise that resolves with success object or rejects
+ * with error
+ */
+navigationAPI.delete = function(req) {
+ return new Promise(function(resolve, reject) {
+ var plugin_nav = navigation_manager.getNavigation()[req.params.plugin_id]['routes'[req.params.route_id]];
+ if (plugin_nav == null || plugin_nav == undefined) {
+ reject({
+ statusCode: constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST,
+ errorMessage: 'Navigation for route' + req.params.route_id + ' under plugin ' + req.params.plugin_id + ' does not exist'
+ });
+ } else {
+ navigation_manager.deleteNavigation(req.params.plugin_id, req.params.route_id);
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: {}
+ });
+ }
+ });
+};
+
+
+module.exports = navigationAPI;
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * restconf api module. Provides API functions to interact with RESTCONF
+ * @module framework/core/modules/api/restconf
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var Promise = require('bluebird');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var request = utils.request;
+var restconfAPI = {};
+var _ = require('lodash');
+restconfAPI['streams'] = {};
+
+/**
+ * Get the RESTCONF/Netconf streams from the RESTCONF endpoint
+ * @param {Object} req - the Express request object with or without a plugin_id
+ * @return {Function} Promise that resolves with success object or rejects
+ * with error
+ */
+restconfAPI['streams'].get = function(req) {
+ var api_server = req.query["api_server"];
+ var uri = utils.confdPort(api_server);
+ var url = req.path;
+ return new Promise(function(resolve, reject) {
+ request({
+ url: uri + url + '?deep',
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.get('Authorization')
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ }, function(error, response, body) {
+ if (utils.validateResponse('restconfAPI.streams', error, response, body, resolve, reject)) {
+ // resolve(JSON.parse(response.body))
+ resolve({
+ statusCode: response.statusCode,
+ data: JSON.parse(response.body)
+ })
+ };
+ })
+ })
+};
+
+
+module.exports = restconfAPI;
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * navigation_manager module. manages navigation state for a skyquake instance
+ * @module framework/core/modules/navigation_manager
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var path = require('path');
+var fs = require('fs');
+var util = require('util');
+var skyquakeEmitter = require('./skyquakeEmitter');
+require('require-json');
+var _ = require('lodash');
+
+/**
+ * navigation looks like:
+ * {
+ * "plugin_name": <{routes>}>
+ * }
+ */
+
+var NAVIGATION = {};
+
+function addNavigation(plugin_name, routes) {
+ if (!NAVIGATION[plugin_name]) {
+ NAVIGATION[plugin_name] = {};
+ }
+
+ if (!NAVIGATION[plugin_name].routes) {
+ NAVIGATION[plugin_name].routes = routes;
+ } else {
+ _.extend(NAVIGATION[plugin_name].routes, routes);
+ }
+}
+function addOrder(plugin_name, order) {
+ if (!NAVIGATION[plugin_name]) {
+ NAVIGATION[plugin_name] = {};
+ }
+ NAVIGATION[plugin_name].order = order || 5;
+}
+function addPriority(plugin_name, priority) {
+ if (!NAVIGATION[plugin_name]) {
+ NAVIGATION[plugin_name] = {};
+ }
+ NAVIGATION[plugin_name].priority = priority || 1;
+}
+
+function addLabel(plugin_name, label) {
+ if (!NAVIGATION[plugin_name]) {
+ NAVIGATION[plugin_name] = {};
+ }
+ NAVIGATION[plugin_name].label = label || 'RW.UI Plugin';
+}
+
+function getNavigation() {
+ return NAVIGATION;
+}
+
+function deleteNavigation(plugin_name, route_id) {
+ delete NAVIGATION[plugin_name]['routes'][route_id];
+}
+
+function onNavigationDiscovered(plugin_name, plugin) {
+ addNavigation(plugin_name, plugin.routes);
+ addOrder(plugin_name, plugin.order);
+ addPriority(plugin_name, plugin.priority);
+ addLabel(plugin_name, plugin.name);
+}
+
+function init() {
+ skyquakeEmitter.on('config_discoverer.navigation_discovered', onNavigationDiscovered);
+}
+
+function config(config) {
+
+}
+
+function run() {
+
+}
+
+
+module.exports = {
+ init: init,
+ config: config,
+ run: run,
+ addNavigation: addNavigation,
+ getNavigation: getNavigation
+};
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * plugin_discoverer module. Static plugin discovery at bootstrap
+ * and dynamic plugin discovery during runtime.
+ * @module framework/core/modules/plugin_discoverer
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+var path = require('path');
+var fs = require('fs');
+var util = require('util');
+var skyquakeEmitter = require('./skyquakeEmitter');
+
+
+var plugins_path = '';
+
+/**
+ * Module initialization function
+ */
+function init() {
+
+}
+
+/**
+ * Module configuration function
+ * @param {Object} config - configuration object
+ */
+function config(config) {
+ plugins_path = config.plugins_path;
+}
+
+/**
+ * Module run function.
+ */
+function run() {
+
+ fs.readdir(plugins_path, function(err, filenames) {
+ if (!err) {
+ filenames.forEach(function(filename) {
+ fs.stat(plugins_path + '/' + filename, function(err, stats) {
+ if (!err) {
+ if (stats.isDirectory()) {
+ skyquakeEmitter.emit('plugin_discoverer.plugin_discovered', filename, path.join(plugins_path, path.sep, filename));
+ }
+ }
+ });
+ });
+ }
+ });
+
+ // Watch for modifications and new plugins
+ fs.watch(plugins_path, {persistent: true, recursive: true}, function(event, filename) {
+ var splitPath = filename.split(path.sep)
+ skyquakeEmitter.emit('plugin_discoverer.plugin_updated', splitPath[0], filename);
+ });
+}
+
+
+module.exports = {
+ init: init,
+ config: config,
+ run: run
+};
\ No newline at end of file
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * Node configuration routes module.
+ * Provides a RESTful API to provide configuration
+ * details such as api_server.
+ * @module framework/core/modules/routes/configuration
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var configurationAPI = require('../api/configuration');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.put('/server-configuration', cors(), function(req, res) {
+ configurationAPI.update(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.get('/server-configuration', cors(), function(req, res) {
+ configurationAPI.get(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+module.exports = Router;
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * inactivity routes module. Provides a RESTful API for this
+ * skyquake instance's inactivity state.
+ * @module framework/core/modules/routes/inactivity
+ * @author Laurence Maultsby <laurence.maultsby@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var descriptorModelMetaAPI = require('../api/descriptorModelMetaAPI.js');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.get('/descriptor-model-meta', cors(), function(req, res) {
+ descriptorModelMetaAPI.get(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+module.exports = Router;
+
+
+
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * inactivity routes module. Provides a RESTful API for this
+ * skyquake instance's inactivity state.
+ * @module framework/core/modules/routes/inactivity
+ * @author Laurence Maultsby <laurence.maultsby@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.get('/inactivity-timeout', cors(), function(req, res) {
+ var response = {
+ statusCode: 200,
+ data: {
+ 'inactivity-timeout': process.env.UI_TIMEOUT_SECS || 600000
+ }
+ }
+ utils.sendSuccessResponse(response, res);
+});
+
+module.exports = Router;
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * navigation routes module. Provides a RESTful API for this
+ * skyquake instance's navigation state.
+ * @module framework/core/modules/routes/navigation
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var navAPI = require('../api/navigation');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var configurationAPI = require('../api/configuration');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.get('/', cors(), function(req, res, next) {
+ res.redirect('/launchpad/?api_server=' + req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname));
+});
+
+Router.get('/nav', cors(), function(req, res) {
+ navAPI.get(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.get('/nav/:plugin_id', cors(), function(req, res) {
+ navAPI.get(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.post('/nav/:plugin_id', cors(), function(req, res) {
+ navAPI.create(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.put('/nav/:plugin_id/:route_id', cors(), function(req, res) {
+ navAPI.update(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.delete('/nav/:plugin_id/:route_id', cors(), function(req, res) {
+ navAPI.delete(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+
+module.exports = Router;
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * restconf specific routes module. Provides a RESTful API for RESTCONF APIs.
+ * @module framework/core/modules/routes/restconf
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var restconfAPI = require('../api/restconf');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.get('/api/operational/restconf-state/streams', cors(), function(req, res) {
+ restconfAPI['streams'].get(req).then(function(data) {
+ // res.send(data);
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+module.exports = Router;
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * socket routes module. Provides a RESTful API for this
+ * skyquake instance's navigation state.
+ * @module framework/core/modules/routes/socket
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+
+var sockets = {};
+
+sockets.routes = function(socketManager) {
+ console.log('Configuring socket routes');
+ Router.use(bodyParser.json());
+ Router.use(cors());
+ Router.use(bodyParser.urlencoded({
+ extended: true
+ }));
+
+ Router.post('/socket-polling', cors(), function(req, res) {
+ socketManager.subscribe(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+ });
+
+ Router.get('/socket-polling-test', cors(), function(req, res) {
+ utils.sendSuccessResponse({
+ statusCode: 200,
+ data: {
+ message: 'Hello socket polling'
+ }
+ }, res);
+ });
+}
+
+sockets.router = Router;
+
+module.exports = sockets;
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/**
+ * skyquakeEmitter module. global channel for communication
+ * @module framework/core/modules/skyquakeEmitter
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var events = require('events');
+var skyquakeEmitter = new events.EventEmitter();
+
+module.exports = skyquakeEmitter;
\ No newline at end of file
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+// (function(window){
+ /**!
+ * @license
+ * HTML5 Canvas Gauge implementation
+ *
+ * This code is subject to MIT license.
+ *
+ * Copyright (c) 2012 Mykhailo Stadnyk <mikhus@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @authors: Mykhailo Stadnyk <mikhus@gmail.com>
+ * Chris Poile <poile@edwards.usask.ca>
+ * Luca Invernizzi <http://www.lucainvernizzi.net>
+ * Robert Blackburn <http://www.rwblackburn.com>
+ */
+
+ /**
+ * @param {Object} config
+ * @constructor
+ */
+ var Gauge = function (config) {
+ Gauge.Collection.push(this);
+
+ /**
+ * Default gauge configuration
+ * @struct
+ */
+ this.config = {
+ isAggregate: false,
+ renderTo: null,
+ width: 200,
+ height: 200,
+ title: false,
+ maxValue: 100,
+ minValue: 0,
+ majorTicks: [],
+ minorTicks: 10,
+ strokeTicks: true,
+ units: false,
+ valueFormat: { "int": 3, "dec": 2 },
+ majorTicksFormat: { "int": 1, "dec": 0 },
+ glow: true,
+ animation: {
+ delay: 10,
+ duration: 250,
+ fn: 'cycle'
+ },
+ colors: {
+ plate: '#fff',
+ majorTicks: '#444',
+ minorTicks: '#666',
+ title: '#888',
+ units: '#888',
+ numbers: '#444',
+ needle: { start: 'rgba(240, 128, 128, 1)', end: 'rgba(255, 160, 122, .9)' }
+ },
+ highlights: [
+ {
+ from: 20,
+ to: 60,
+ color: '#eee'
+ },
+ {
+ from: 60,
+ to: 80,
+ color: '#ccc'
+ },
+ {
+ from: 80,
+ to: 100,
+ color: '#999'
+ }
+ ]
+ };
+
+ var
+ value = 0,
+ self = this,
+ fromValue = 0,
+ toValue = 0,
+ imready = false
+ ;
+
+ /**
+ * Sets a new value to gauge and updates the gauge view
+ *
+ * @param {number} val - the new value to set to the gauge
+ * @return {Gauge} this - returns self
+ */
+ this.setValue = function (val) {
+
+ fromValue = config.animation ? value : val;
+
+ var dv = (config.maxValue - config.minValue) / 100;
+
+ toValue = val > config.maxValue ?
+ config.maxValue + dv :
+ val < config.minValue ?
+ config.minValue - dv :
+ val
+ ;
+
+ value = val;
+
+ if (toValue >= fromValue) {
+ config.arrow = 'up';
+ } else {
+ config.arrow = 'down';
+ }
+
+ config.animation ? animate() : this.draw();
+
+ return this;
+ };
+
+ /**
+ * Sets a new value to gauge and updates the gauge view without
+ * any animation (even if configured)
+ *
+ * @param {number} val - the new value to set to the gauge
+ * @return {Gauge} this - returns self
+ */
+ this.setRawValue = function (val) {
+ fromValue = value = val;
+ this.draw();
+ return this;
+ };
+
+ /**
+ * Clears the value of the gauge
+ * @return {Gauge}
+ */
+ this.clear = function () {
+ value = fromValue = toValue = this.config.minValue;
+ this.draw();
+ return this;
+ };
+
+
+ /**
+ * Returns the current value been set to the gauge
+ *
+ * @return {number} value - current gauge's value
+ */
+ this.getValue = function () {
+ return value;
+ };
+
+ /**
+ * Ready event for the gauge. Use it whenever you
+ * initialize the gauge to be assured it was fully drawn
+ * before you start the update on it
+ *
+ * @event {Function} onready
+ */
+ this.onready = function () {
+ };
+
+ function applyRecursive(dst, src) {
+ for (var i in src) {
+ // modification by Chris Poile, Oct 08, 2012. More correct check of an Array instance
+ if (typeof src[i] == "object" && !(Object.prototype.toString.call(src[i]) === '[object Array]') && i != 'renderTo') {
+ if (typeof dst[i] != "object") {
+ dst[i] = {};
+ }
+
+ applyRecursive(dst[i], src[i]);
+ } else {
+ dst[i] = src[i];
+ }
+ }
+ };
+
+ applyRecursive(this.config, config);
+
+ this.config.minValue = parseFloat(this.config.minValue);
+ this.config.maxValue = parseFloat(this.config.maxValue);
+
+ config = this.config;
+ fromValue = value = config.minValue;
+
+ if (!config.renderTo) {
+ throw Error("Canvas element was not specified when creating the Gauge object!");
+ }
+
+ var
+ canvas = config.renderTo.tagName ? config.renderTo : document.getElementById(config.renderTo),
+ ctx = canvas.getContext('2d'),
+ cache, CW, CH, CX, CY, max, cctx
+ ;
+
+ function baseInit() {
+ canvas.width = config.width;
+ canvas.height = config.height;
+
+ cache = canvas.cloneNode(true);
+ cctx = cache.getContext('2d');
+ CW = canvas.width;
+ CH = canvas.height;
+ CX = CW / 2;
+ CY = CH / 2;
+ max = CX < CY ? CX : CY;
+
+ cache.i8d = false;
+
+ // translate cache to have 0, 0 in center
+ cctx.translate(CX, CY);
+ cctx.save();
+
+ // translate canvas to have 0,0 in center
+ ctx.translate(CX, CY);
+ ctx.save();
+ };
+
+ // do basic initialization
+ baseInit();
+
+ /**
+ * Updates the gauge config
+ *
+ * @param {Object} config
+ * @return {Gauge}
+ */
+ this.updateConfig = function (config) {
+ applyRecursive(this.config, config);
+ baseInit();
+ this.draw();
+ return this;
+ };
+
+ var animateFx = {
+ linear: function (p) {
+ return p;
+ },
+ quad: function (p) {
+ return Math.pow(p, 2);
+ },
+ quint: function (p) {
+ return Math.pow(p, 5);
+ },
+ cycle: function (p) {
+ return 1 - Math.sin(Math.acos(p));
+ },
+ bounce: function (p) {
+ return 1 - (function (p) {
+ for (var a = 0, b = 1; 1; a += b, b /= 2) {
+ if (p >= (7 - 4 * a) / 11) {
+ return -Math.pow((11 - 6 * a - 11 * p) / 4, 2) + Math.pow(b, 2);
+ }
+ }
+ })(1 - p);
+ },
+ elastic: function (p) {
+ return 1 - (function (p) {
+ var x = 1.5;
+ return Math.pow(2, 10 * (p - 1)) * Math.cos(20 * Math.PI * x / 3 * p);
+ })(1 - p);
+ }
+ };
+
+ var animateInterval = null;
+
+ function _animate(opts) {
+ var start = new Date;
+
+ animateInterval = setInterval(function () {
+ var
+ timePassed = new Date - start,
+ progress = timePassed / opts.duration
+ ;
+
+ if (progress > 1) {
+ progress = 1;
+ }
+
+ var animateFn = typeof opts.delta == "function" ?
+ opts.delta :
+ animateFx[opts.delta]
+ ;
+
+ var delta = animateFn(progress);
+ opts.step(delta);
+
+ if (progress == 1) {
+ clearInterval(animateInterval);
+ }
+ }, opts.delay || 10);
+ };
+
+ function animate() {
+ animateInterval && clearInterval(animateInterval); // stop previous animation
+ var
+ path = (toValue - fromValue),
+ from = fromValue,
+ cfg = config.animation
+ ;
+
+ _animate({
+ delay: cfg.delay,
+ duration: cfg.duration,
+ delta: cfg.fn,
+ step: function (delta) {
+ fromValue = parseFloat(from) + path * delta;
+ self.draw();
+ }
+ });
+ };
+
+ // defaults
+ ctx.lineCap = "round";
+
+ /**
+ * Drows the gauge. Normally this function should be used to
+ * initally draw the gauge
+ *
+ * @return {Gauge} this - returns the self Gauge object
+ */
+ this.draw = function () {
+ if (!cache.i8d) {
+ // clear the cache
+ cctx.clearRect(-CX, -CY, CW, CH);
+ cctx.save();
+
+ var tmp = {ctx: ctx};
+ ctx = cctx;
+
+ drawPlate();
+ drawHighlights();
+ drawMinorTicks();
+ drawMajorTicks();
+ drawNumbers();
+ drawTitle();
+ drawUnits();
+
+ cache.i8d = true;
+ ctx = tmp.ctx;
+ delete tmp.ctx;
+ }
+
+ // clear the canvas
+ ctx.clearRect(-CX, -CY, CW, CH);
+ ctx.save();
+
+ ctx.drawImage(cache, -CX, -CY, CW, CH);
+
+ if (!Gauge.initialized) {
+ var iv = setInterval(function () {
+ if (!Gauge.initialized) {
+ return;
+ }
+
+ clearInterval(iv);
+
+ drawValueBox();
+ drawNeedle();
+ // drawArrow();
+
+ if (!imready) {
+ self.onready && self.onready();
+ imready = true;
+ }
+ }, 10);
+ } else {
+ drawValueBox();
+ drawNeedle();
+ // drawArrow();
+
+ if (!imready) {
+ self.onready && self.onready();
+ imready = true;
+ }
+ }
+
+ return this;
+ };
+
+ /**
+ * Transforms degrees to radians
+ */
+ function radians(degrees) {
+ return degrees * Math.PI / 180;
+ };
+
+ /**
+ * Linear gradient
+ */
+ function lgrad(clrFrom, clrTo, len) {
+ var grad = ctx.createLinearGradient(0, 0, 0, len);
+ grad.addColorStop(0, clrFrom);
+ grad.addColorStop(1, clrTo);
+
+ return grad;
+ };
+
+ function drawPlate() {
+ var
+ r0 = max / 100 * 93,
+ d0 = max - r0,
+ r1 = max / 100 * 91,
+ d1 = max - r1,
+ r2 = max / 100 * 88,
+ d2 = max - r2,
+ r3 = max / 100 * 85;
+
+ ctx.save();
+
+ if (config.glow) {
+ ctx.shadowBlur = d0;
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
+ }
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r0, 0, Math.PI * 2, true);
+ // ctx.fillStyle = lgrad( '#ddd', '#aaa', r0);
+ ctx.fillStyle = lgrad('hsla(0, 0%, 17%, 1)', 'hsla(0, 0%, 0%, 1)', r0);
+ //ctx.fill();
+
+ //ctx.restore();
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r1, 0, Math.PI * 2, true);
+ // ctx.fillStyle = lgrad( '#fafafa', '#ccc', r1);
+ ctx.fillStyle = lgrad('hsla(0, 0%, 47%, 1)', 'hsla(0, 0%, 33%, 1)', r1);
+ //ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r2, 0, Math.PI * 2, true);
+ // ctx.fillStyle = lgrad( '#eee', '#f0f0f0', r2);
+ ctx.fillStyle = lgrad('hsla(0, 0%, 33%, 1)', 'hsla(0, 0%, 43%, 1)', r2);
+ //ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r3, 0, Math.PI * 2, true);
+ ctx.fillStyle = config.colors.plate;
+ //ctx.fill();
+
+ ctx.save();
+ };
+
+ /**
+ * Formats a number for display on the dial's plate using the majorTicksFormat config option.
+ *
+ * @param {number} num The number to format
+ * @returns {string} The formatted number
+ */
+ function formatMajorTickNumber(num) {
+ var r, isDec = false;
+
+ // First, force the correct number of digits right of the decimal.
+ if (config.majorTicksFormat.dec === 0) {
+ r = Math.round(num).toString();
+ } else {
+ r = num.toFixed(config.majorTicksFormat.dec);
+ }
+
+ // Second, force the correct number of digits left of the decimal.
+ if (config.majorTicksFormat["int"] > 1) {
+ // Does this number have a decimal?
+ isDec = (r.indexOf('.') > -1);
+
+ // Is this number a negative number?
+ if (r.indexOf('-') > -1) {
+ return '-' + [
+ config.majorTicksFormat["int"] + config.majorTicksFormat.dec + 2 + (isDec ? 1 : 0) - r.length
+ ].join('0') + r.replace('-', '');
+ } else {
+ return [
+ config.majorTicksFormat["int"] + config.majorTicksFormat.dec + 1 + (isDec ? 1 : 0) - r.length
+ ].join('0') + r;
+ }
+ } else {
+ return r;
+ }
+ }
+
+ // major ticks draw
+ function drawMajorTicks() {
+ var r = max / 100 * 81;
+
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = config.colors.majorTicks;
+ ctx.save();
+
+ if (config.majorTicks.length === 0) {
+ var numberOfDefaultTicks = 5;
+ var tickSize = (config.maxValue - config.minValue) / numberOfDefaultTicks;
+
+ for (var i = 0; i < numberOfDefaultTicks; i++) {
+ config.majorTicks.push(formatMajorTickNumber(config.minValue + (tickSize * i)));
+ }
+ config.majorTicks.push(formatMajorTickNumber(config.maxValue));
+ }
+
+ for (var i = 0; i < config.majorTicks.length; ++i) {
+ var a = 45 + i * (270 / (config.majorTicks.length - 1));
+ ctx.rotate(radians(a));
+
+ ctx.beginPath();
+ ctx.moveTo(0, r);
+ ctx.lineTo(0, r - max / 100 * 15);
+ ctx.stroke();
+
+ ctx.restore();
+ ctx.save();
+ }
+
+ if (config.strokeTicks) {
+ ctx.rotate(radians(90));
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r, radians(45), radians(315), false);
+ ctx.stroke();
+ ctx.restore();
+
+ ctx.save();
+ }
+ };
+
+ // minor ticks draw
+ function drawMinorTicks() {
+ var r = max / 100 * 81;
+
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = config.colors.minorTicks;
+
+ ctx.save();
+
+ var len = config.minorTicks * (config.majorTicks.length - 1);
+
+ for (var i = 0; i < len; ++i) {
+ var a = 45 + i * (270 / len);
+ ctx.rotate(radians(a));
+
+ ctx.beginPath();
+ ctx.moveTo(0, r);
+ ctx.lineTo(0, r - max / 100 * 7.5);
+ ctx.stroke();
+
+ ctx.restore();
+ ctx.save();
+ }
+ };
+
+ // tick numbers draw
+ function drawNumbers() {
+ //var r = max / 100 * 55;
+ //
+ //for (var i = 0; i < config.majorTicks.length; ++i) {
+ // var
+ // a = 45 + i * (270 / (config.majorTicks.length - 1)),
+ // p = rpoint(r, radians(a))
+ // ;
+ //
+ // ctx.font = 20 * (max / 200) + "px Arial";
+ // ctx.fillStyle = config.colors.numbers;
+ // ctx.lineWidth = 0;
+ // ctx.textAlign = "center";
+ // ctx.fillText(config.majorTicks[i], p.x, p.y + 3);
+ //}
+ };
+
+ // title draw
+ function drawTitle() {
+ if (!config.title) {
+ return;
+ }
+
+ ctx.save();
+ ctx.font = 24 * (max / 200) + "px Arial";
+ ctx.fillStyle = config.colors.title;
+ ctx.textAlign = "center";
+ ctx.fillText(config.title, 0, -max / 4.25);
+ ctx.restore();
+ };
+
+ // units draw
+ function drawUnits() {
+ if (!config.units) {
+ return;
+ }
+
+ ctx.save();
+ ctx.font = 22 * (max / 200) + "px Arial";
+ ctx.fillStyle = config.colors.units;
+ ctx.textAlign = "center";
+ ctx.fillText(config.units, 0, max / 3.25);
+ ctx.restore();
+ };
+
+
+ function drawArrow() {
+
+ if (config.arrow != "false") {
+
+ if (config.arrow == "up") {
+ var r = max / 100 * 2.0;
+ y0 = max / 100 * 64;
+ y1 = max / 100 * 55;
+ y2 = max / 100 * 53;
+ y3 = max / 100 * 50;
+ y4 = max / 100 * 45;
+ arrow_color = "rgba(107, 184, 20, 1)";
+
+ var x0 = max / 100 * -8
+ var x1 = max / 100 * -6
+ var x2 = max / 100 * -1.5
+ var x3 = max / 100 * 0
+ var x4 = max / 100 * 1.5
+ var x5 = max / 100 * 6
+ var x6 = max / 100 * 8
+
+ ctx.beginPath();
+ ctx.moveTo(x0, y2);
+ ctx.lineTo(x3 - r, y4 + r);
+ ctx.arcTo(x3, y4 - r, x3 + r, y4 + r, r * 1.09);
+ ctx.lineTo(x6, y2);
+ ctx.arcTo(x6 + r/2.0, y2 + r/1.0, x5, y1, r*.9)
+ ctx.lineTo(x4, y3);
+ ctx.lineTo(x4, y0);
+ ctx.arcTo(x3, y0 + r, x2, y0, r*.9);
+ ctx.lineTo(x2, y3);
+ ctx.lineTo(x1, y1);
+ ctx.arcTo(x1 - r, y1 - r/2.0, x0, y2, r*1.09)
+
+
+ ctx.closePath();
+ ctx.fillStyle = arrow_color;
+ ctx.fill();
+ } else {
+ var r = max / 100 * 2.0;
+ var y0 = max / 100 * 45;
+ var y1 = max / 100 * 54;
+ var y2 = max / 100 * 56;
+ var y3 = max / 100 * 59;
+ var y4 = max / 100 * 64;
+ var arrow_color = "rgba(252, 38, 50, 1)";
+
+ var x0 = max / 100 * -8
+ var x1 = max / 100 * -6
+ var x2 = max / 100 * -1.5
+ var x3 = max / 100 * 0
+ var x4 = max / 100 * 1.5
+ var x5 = max / 100 * 6
+ var x6 = max / 100 * 8
+
+ ctx.beginPath();
+ ctx.moveTo(x0, y2);
+ ctx.lineTo(x3 - r, y4 - r);
+ ctx.arcTo(x3, y4 + r, x3 + r, y4 - r, r * 1.09);
+ ctx.lineTo(x6, y2);
+ ctx.arcTo(x6 + r/2.0, y2 - r/1.0, x5, y1, r*.9)
+ ctx.lineTo(x4, y3);
+ ctx.lineTo(x4, y0);
+ ctx.arcTo(x3, y0 - r, x2, y0, r*.9);
+ ctx.lineTo(x2, y3);
+ ctx.lineTo(x1, y1);
+ ctx.arcTo(x1 - r, y1 + r/2.0, x0, y2, r*1.09)
+
+
+ ctx.closePath();
+ ctx.fillStyle = arrow_color;
+ ctx.fill();
+ }
+ ctx.save();
+ ctx.restore();
+ }
+ }
+
+ function padValue(val) {
+ var cdec = config.valueFormat['dec']
+ var cint = config.valueFormat['int']
+
+ val = parseFloat(val);
+ var n = (val < 0);
+
+ val = Math.abs(val);
+
+ if (cdec > 0) {
+ val = val.toFixed(cdec).toString().split('.');
+
+ for (var i = 0, s = cint - val[0].length; i < s; ++i) {
+ val[0] = '0' + val[0];
+ }
+
+ val = (n ? '-' : '') + val[0] + '.' + val[1];
+ } else {
+ val = Math.round(val).toString();
+
+ for (var i = 0, s = cint - val.length; i < s; ++i) {
+ val = '0' + val;
+ }
+
+ val = (n ? '-' : '') + val
+ }
+
+ return val;
+ };
+
+ function rpoint(r, a) {
+ var
+ x = 0, y = r,
+
+ sin = Math.sin(a),
+ cos = Math.cos(a),
+
+ X = x * cos - y * sin,
+ Y = x * sin + y * cos
+ ;
+
+ return { x: X, y: Y };
+ };
+ function clearCircle(x, y, radius)
+ {
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ ctx.clip();
+ ctx.clearRect(x - radius - 1, y - radius - 1,
+ radius * 2 + 2, radius * 2 + 2);
+
+ };
+ // draws the highlight colors
+ function drawHighlights() {
+ ctx.save();
+
+ var r1 = max / 100 * 81;
+ var r2 = r1 - max / 100 * 15;
+
+ for (var i = 0, s = config.highlights.length; i < s; i++) {
+ var
+ hlt = config.highlights[i],
+ vd = (config.maxValue - config.minValue) / 270,
+ sa = radians(45 + (hlt.from - config.minValue) / vd),
+ ea = radians(45 + (hlt.to - config.minValue) / vd)
+ ;
+
+ ctx.beginPath();
+
+ ctx.rotate(radians(90));
+ ctx.arc(0, 0, r1, sa, ea, false);
+ ctx.restore();
+ ctx.save();
+
+ var
+ ps = rpoint(r2, sa),
+ pe = rpoint(r1, sa)
+ ;
+ ctx.moveTo(ps.x, ps.y);
+ ctx.lineTo(pe.x, pe.y);
+
+ var
+ ps1 = rpoint(r1, ea),
+ pe1 = rpoint(r2, ea)
+ ;
+
+ ctx.lineTo(ps1.x, ps1.y);
+ ctx.lineTo(pe1.x, pe1.y);
+ ctx.lineTo(ps.x, ps.y);
+
+ ctx.closePath();
+
+ ctx.fillStyle = hlt.color;
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.rotate(radians(90));
+ ctx.arc(0, 0, r2, sa - 0.2, ea + 0.2, false);
+ ctx.restore();
+
+ ctx.closePath();
+
+ ctx.fillStyle = config.colors.plate;
+ ctx.fill();
+ ctx.save();
+ ctx.imageSmoothingEnabled = true
+ //clearCircle(0, 0, 100)
+
+ }
+ };
+
+ // drows the gauge needle
+ function drawNeedle() {
+ var
+ r1 = 0 ,
+ r2 = 0 ,
+ rIn = max / 100 * 85,
+ rOut = max / 100 * 63,
+ rP = max / 100 * 59,
+ pad1 = max / 100 * 3,
+ pad2 = max / 100 * 2.5,
+
+ shad = function () {
+ ctx.shadowOffsetX = 2;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowBlur = 10;
+ // ctx.shadowColor = 'rgba(188, 143, 143, 0.45)';
+ ctx.shadowColor = 'rgba(50, 50, 50, .3)';
+ }
+ ;
+
+ shad();
+
+ ctx.save();
+
+ if (fromValue < 0) {
+ fromValue = Math.abs(config.minValue - fromValue);
+ } else if (config.minValue > 0) {
+ fromValue -= config.minValue
+ } else {
+ fromValue = Math.abs(config.minValue) + fromValue;
+ }
+
+ ctx.rotate(radians(45 + fromValue / ((config.maxValue - config.minValue) / 270)));
+
+ ctx.beginPath();
+ ctx.lineTo(-pad2, rOut);
+ ctx.lineTo(-pad2, rIn);
+ ctx.lineTo(pad2, rIn);
+ ctx.lineTo(pad2, rOut);
+ ctx.lineTo(0, rP);
+ ctx.closePath();
+ ctx.strokeStyle = "#999"
+ ctx.stroke();
+
+ ctx.fillStyle = lgrad(
+ config.colors.needle.start,
+ config.colors.needle.end,
+ rIn - rOut
+ );
+ ctx.fill();
+
+ // ctx.beginPath();
+ // ctx.lineTo(-_pad2, _rOut);
+ // ctx.lineTo(-_pad2, _rIn);
+ // ctx.lineTo(_pad2, _rIn);
+ // ctx.lineTo(_pad2, _rOut);
+ // ctx.lineTo(0, _rOut - 5);
+ // ctx.closePath();
+
+ // ctx.fillStyle = "#ccc"
+ // ctx.fill();
+
+ ctx.beginPath();
+ ctx.lineTo(-pad2, rIn);
+ ctx.lineTo(-pad2, rIn);
+ ctx.lineTo(-pad1, 0);
+ ctx.lineTo(-pad2, rOut);
+ ctx.lineTo(pad2 / 2 - 2, rOut);
+ ctx.closePath();
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
+
+ //ctx.fill();
+
+ ctx.restore();
+
+ //shad();
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r2 +.5, 0, Math.PI * 2, true);
+ // ctx.fillStyle = lgrad( '#f0f0f0', '#ccc', r1);
+ ctx.fillStyle = lgrad('#3b3b3b', '#121212', r1);
+ //ctx.fill();
+
+ ctx.restore();
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r2, 0, Math.PI * 2, true);
+ // ctx.fillStyle = lgrad( "#e8e8e8", "#f5f5f5", r2);
+ ctx.fillStyle = 'rgba(255,255,255,1)';
+ //ctx.fill();
+ };
+
+ function roundRect(x, y, w, h, r) {
+ ctx.beginPath();
+
+ ctx.moveTo(x + r, y);
+ ctx.lineTo(x + w - r, y);
+
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r);
+ ctx.lineTo(x + w, y + h - r);
+
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
+ ctx.lineTo(x + r, y + h);
+
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r);
+ ctx.lineTo(x, y + r);
+
+ ctx.quadraticCurveTo(x, y, x + r, y);
+
+ ctx.closePath();
+ };
+
+ // value box draw
+ function drawValueBox() {
+ ctx.save();
+
+ ctx.font = 100 + " " + 73 * (max / 200) + "px 'roboto-light";
+ var
+ text = padValue(value),
+ tw = ctx.measureText('-' + padValue(0)).width,
+ y = max - max / 100 * 96,
+ x = 0,
+ th = 0.12 * max
+ ;
+
+ ctx.save();
+
+ roundRect(
+ -tw / 2 - 0.025 * max,
+ y - th - 0.04 * max,
+ tw + 0.05 * max,
+ th + 0.07 * max,
+ 0.025 * max
+ );
+
+ var grd = ctx.createRadialGradient(
+ x,
+ y - 0.12 * max - 0.025 * max + (0.12 * max + 0.045 * max) / 2,
+ max / 10,
+ x,
+ y - 0.12 * max - 0.025 * max + (0.12 * max + 0.045 * max) / 2,
+ max / 5
+ );
+
+ // grd.addColorStop( 0, "#888");
+ // grd.addColorStop( 1, "#666");
+
+ // ctx.strokeStyle = grd;
+ // ctx.lineWidth = 0.05 * max;
+ // ctx.stroke();
+
+ // ctx.shadowBlur = 0.012 * max;
+ // ctx.shadowColor = 'rgba(0, 0, 0, 1)';
+
+ // ctx.fillStyle = "#babab2";
+ // ctx.fill();
+
+ // ctx.restore();
+
+ // ctx.shadowOffsetX = 0.004 * max;
+ // ctx.shadowOffsetY = 0.004 * max;
+ // ctx.shadowBlur = 0.012 * max;
+ // ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
+
+ // ctx.fillStyle = "#444";
+ ctx.fillStyle = "rgba(50,50,50,1)";
+ // ctx.fillStyle = "rgba(50,50,50,1)";
+ ctx.textAlign = "center";
+
+ ctx.fillText(text, -x, y);
+
+
+ ctx.save();
+ ctx.font = 100 + " " + 20 * (max / 200) + "px 'roboto-light";
+ //ctx.fillText(config.unit, -x, y + 30);
+ ctx.restore();
+
+ };
+ };
+
+// initialize
+ Gauge.initialized = false;
+ (function () {
+ var
+ d = document,
+ h = d.getElementsByTagName('head')[0],
+ ie = navigator.userAgent.toLocaleLowerCase().indexOf('msie') != -1,
+ url = 'fonts/digital-7-mono.' + (ie ? 'eot' : 'ttf'),
+
+ // RW: don't use mono font, this was causing err in js console
+ text = '',
+// text = "@font-face {" +
+// "font-family: 'Led';" +
+// "src: url('" + url + "');" +
+// "}",
+ ss,
+ r = d.createElement('style')
+ ;
+
+ r.type = 'text/css';
+
+ if (ie) {
+ h.appendChild(r);
+ ss = r.styleSheet;
+ ss.cssText = text;
+ } else {
+ try {
+ r.appendChild(d.createTextNode(text));
+ } catch (e) {
+ r.cssText = text;
+ }
+
+ h.appendChild(r);
+
+ ss = r.styleSheet ? r.styleSheet :
+ (r.sheet || d.styleSheets[d.styleSheets.length - 1])
+ ;
+ }
+
+ var iv = setInterval(function () {
+ if (!d.body) {
+ return;
+ }
+
+ clearInterval(iv);
+
+ var dd = d.createElement('div');
+
+ dd.style.fontFamily = 'Led';
+ dd.style.position = 'absolute';
+ dd.style.height = dd.style.width = 0;
+ dd.style.overflow = 'hidden';
+
+ dd.innerHTML = '.';
+
+ d.body.appendChild(dd);
+
+ setTimeout(function () { // no other way to handle font is rendered by a browser
+ // just give the browser around 250ms to do that :(
+ Gauge.initialized = true;
+ dd.parentNode.removeChild(dd);
+ }, 250);
+ }, 1);
+ })();
+
+ Gauge.Collection = [];
+ Gauge.Collection.get = function (id) {
+ var self = this;
+
+ if (typeof(id) == 'string') {
+ for (var i = 0, s = self.length; i < s; i++) {
+ var canvas = self[i].config.renderTo.tagName ? self[i].config.renderTo : document.getElementById(self[i].config.renderTo);
+ if (canvas.getAttribute('id') == id) {
+ return self[i];
+ }
+ }
+ } else if (typeof(id) == 'number') {
+ return self[id];
+ } else {
+ return null;
+ }
+ };
+
+ function domReady(handler) {
+ if (window.addEventListener) {
+ window.addEventListener('DOMContentLoaded', handler, false);
+ } else {
+ window.attachEvent('onload', handler);
+ }
+ }
+
+ domReady(function () {
+ function toCamelCase(arr) {
+ var str = arr[0];
+ for (var i = 1, s = arr.length; i < s; i++) {
+ str += arr[i].substr(0, 1).toUpperCase() + arr[i].substr(1, arr[i].length - 1);
+ }
+ return str;
+ };
+
+ function trim(str) {
+ return str.replace(/^\s+|\s+$/g, '');
+ };
+
+ var c = document.getElementsByTagName('canvas');
+
+ for (var i = 0, s = c.length; i < s; i++) {
+
+ if (c[i].getAttribute('data-type') == 'canv-gauge') {
+ var
+ gauge = c[i],
+ config = {},
+ prop,
+ w = parseInt(gauge.getAttribute('width'), 10),
+ h = parseInt(gauge.getAttribute('height'), 10)
+ ;
+
+ config.renderTo = gauge;
+
+ if (w) {
+ config.width = w;
+ }
+
+ if (h) {
+ config.height = h;
+ }
+
+ for (var ii = 0, ss = gauge.attributes.length; ii < ss; ii++) {
+ prop = gauge.attributes.item(ii).nodeName;
+
+ if (prop != 'data-type' && prop.substr(0, 5) == 'data-') {
+ var
+ cfgProp = prop.substr(5, prop.length - 5).toLowerCase().split('-'),
+ attrValue = gauge.getAttribute(prop)
+ ;
+
+ if (!attrValue) {
+ continue;
+ }
+
+ switch (cfgProp[0]) {
+ case 'colors' :
+ {
+ if (cfgProp[1]) {
+ if (!config.colors) {
+ config.colors = {};
+ }
+
+ if (cfgProp[1] == 'needle') {
+ var parts = attrValue.split(/\s+/);
+
+ if (parts[0] && parts[1]) {
+ config.colors.needle = { start: parts[0], end: parts[1] };
+ }
+ else {
+ config.colors.needle = attrValue;
+ }
+ }
+ else {
+ cfgProp.shift();
+ config.colors[toCamelCase(cfgProp)] = attrValue;
+ }
+ }
+ break;
+ }
+ case 'highlights' :
+ {
+ if (!config.highlights) {
+ config.highlights = [];
+ }
+
+ var hls = attrValue.match(/(?:(?:-?\d*\.)?(-?\d+){1,2} ){2}(?:(?:#|0x)?(?:[0-9A-F|a-f]){3,8}|rgba?\(.*?\))/g);
+
+ for (var j = 0, l = hls.length; j < l; j++) {
+ var
+ cfg = trim(hls[j]).split(/\s+/),
+ hlCfg = {}
+ ;
+
+ if (cfg[0] && cfg[0] != '') {
+ hlCfg.from = cfg[0];
+ }
+
+ if (cfg[1] && cfg[1] != '') {
+ hlCfg.to = cfg[1];
+ }
+
+ if (cfg[2] && cfg[2] != '') {
+ hlCfg.color = cfg[2];
+ }
+
+ config.highlights.push(hlCfg);
+ }
+ break;
+ }
+ case 'animation' :
+ {
+ if (cfgProp[1]) {
+ if (!config.animation) {
+ config.animation = {};
+ }
+
+ if (cfgProp[1] == 'fn' && /^\s*function\s*\(/.test(attrValue)) {
+ attrValue = eval('(' + attrValue + ')');
+ }
+
+ config.animation[cfgProp[1]] = attrValue;
+ }
+ break;
+ }
+ default :
+ {
+ var cfgName = toCamelCase(cfgProp);
+
+ if (cfgName == 'onready') {
+ continue;
+ }
+
+ if (cfgName == 'majorTicks') {
+ attrValue = attrValue.split(/\s+/);
+ }
+ else if (cfgName == 'strokeTicks' || cfgName == 'glow') {
+ attrValue = attrValue == 'true' ? true : false;
+ }
+ else if (cfgName == 'valueFormat') {
+ var val = attrValue.split('.');
+
+ if (val.length == 2) {
+ attrValue = {
+ 'int': parseInt(val[0], 10),
+ 'dec': parseInt(val[1], 10)
+ }
+ }
+ else {
+ continue;
+ }
+ }
+
+ config[cfgName] = attrValue;
+ break;
+ }
+ }
+ }
+ }
+
+ var g = new Gauge(config);
+
+ if (gauge.getAttribute('data-value')) {
+ g.setRawValue(parseFloat(gauge.getAttribute('data-value')));
+ }
+
+ if (gauge.getAttribute('data-onready')) {
+ g.onready = function () {
+ eval(this.config.renderTo.getAttribute('data-onready'));
+ };
+ }
+
+ g.draw();
+ }
+ }
+ });
+module.exports = Gauge;
+ // window['Gauge'] = Gauge;
+
+// })(window);
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed 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.
+ *
+ */
+
+/*
+line-chart - v1.1.9 - 21 June 2015
+https://github.com/n3-charts/line-chart
+Copyright (c) 2015 n3-charts
+ */
+var directive, m, mod, old_m,
+ __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+old_m = angular.module('n3-charts.linechart', ['n3charts.utils']);
+
+m = angular.module('n3-line-chart', ['n3charts.utils']);
+
+directive = function(name, conf) {
+ old_m.directive(name, conf);
+ return m.directive(name, conf);
+};
+
+directive('linechart', [
+ 'n3utils', '$window', '$timeout', function(n3utils, $window, $timeout) {
+ var link;
+ link = function(scope, element, attrs, ctrl) {
+ var dispatch, id, initialHandlers, isUpdatingOptions, promise, updateEvents, window_resize, _u;
+ _u = n3utils;
+ dispatch = _u.getEventDispatcher();
+ id = _u.uuid();
+ element[0].style['font-size'] = 0;
+ scope.redraw = function() {
+ scope.update();
+ };
+ isUpdatingOptions = false;
+ initialHandlers = {
+ onSeriesVisibilityChange: function(_arg) {
+ var index, newVisibility, series;
+ series = _arg.series, index = _arg.index, newVisibility = _arg.newVisibility;
+ scope.options.series[index].visible = newVisibility;
+ return scope.$apply();
+ }
+ };
+ scope.update = function() {
+ var axes, columnWidth, dataPerSeries, dimensions, fn, handlers, isThumbnail, options, svg;
+ options = _u.sanitizeOptions(scope.options, attrs.mode);
+ handlers = angular.extend(initialHandlers, _u.getTooltipHandlers(options));
+ dataPerSeries = _u.getDataPerSeries(scope.data, options);
+ dimensions = _u.getDimensions(options, element, attrs);
+ isThumbnail = attrs.mode === 'thumbnail';
+ _u.clean(element[0]);
+ svg = _u.bootstrap(element[0], id, dimensions);
+ fn = function(key) {
+ return (options.series.filter(function(s) {
+ return s.axis === key && s.visible !== false;
+ })).length > 0;
+ };
+ axes = _u.createAxes(svg, dimensions, options.axes).andAddThemIf({
+ all: !isThumbnail,
+ x: true,
+ y: fn('y'),
+ y2: fn('y2')
+ });
+ if (dataPerSeries.length) {
+ _u.setScalesDomain(axes, scope.data, options.series, svg, options);
+ }
+ _u.createContent(svg, id, options, handlers);
+ if (dataPerSeries.length) {
+ columnWidth = _u.getBestColumnWidth(axes, dimensions, dataPerSeries, options);
+ _u.drawArea(svg, axes, dataPerSeries, options, handlers).drawColumns(svg, axes, dataPerSeries, columnWidth, options, handlers, dispatch).drawLines(svg, axes, dataPerSeries, options, handlers);
+ if (options.drawDots) {
+ _u.drawDots(svg, axes, dataPerSeries, options, handlers, dispatch);
+ }
+ }
+ if (options.drawLegend) {
+ _u.drawLegend(svg, options.series, dimensions, handlers, dispatch);
+ }
+ if (options.tooltip.mode === 'scrubber') {
+ return _u.createGlass(svg, dimensions, handlers, axes, dataPerSeries, options, dispatch, columnWidth);
+ } else if (options.tooltip.mode !== 'none') {
+ return _u.addTooltips(svg, dimensions, options.axes);
+ }
+ };
+ updateEvents = function() {
+ if (scope.oldclick) {
+ dispatch.on('click', scope.oldclick);
+ } else if (scope.click) {
+ dispatch.on('click', scope.click);
+ } else {
+ dispatch.on('click', null);
+ }
+ if (scope.oldhover) {
+ dispatch.on('hover', scope.oldhover);
+ } else if (scope.hover) {
+ dispatch.on('hover', scope.hover);
+ } else {
+ dispatch.on('hover', null);
+ }
+ if (scope.oldfocus) {
+ dispatch.on('focus', scope.oldfocus);
+ } else if (scope.focus) {
+ dispatch.on('focus', scope.focus);
+ } else {
+ dispatch.on('focus', null);
+ }
+ if (scope.toggle) {
+ return dispatch.on('toggle', scope.toggle);
+ } else {
+ return dispatch.on('toggle', null);
+ }
+ };
+ promise = void 0;
+ window_resize = function() {
+ if (promise != null) {
+ $timeout.cancel(promise);
+ }
+ return promise = $timeout(scope.redraw, 1);
+ };
+ $window.addEventListener('resize', window_resize);
+ scope.$watch('data', scope.redraw, true);
+ scope.$watch('options', scope.redraw, true);
+ scope.$watchCollection('[click, hover, focus, toggle]', updateEvents);
+ scope.$watchCollection('[oldclick, oldhover, oldfocus]', updateEvents);
+ console.log('data', scope.data)
+ window_resize();
+ };
+ return {
+ replace: true,
+ restrict: 'E',
+ scope: {
+ data: '=',
+ options: '=',
+ oldclick: '=click',
+ oldhover: '=hover',
+ oldfocus: '=focus',
+ click: '=onClick',
+ hover: '=onHover',
+ focus: '=onFocus',
+ toggle: '=onToggle'
+ },
+ template: '<div></div>',
+ link: link
+ };
+ }
+]);
+
+mod = angular.module('n3charts.utils', []);
+
+mod.factory('n3utils', [
+ '$window', '$log', '$rootScope', function($window, $log, $rootScope) {
+ return {
+ addPatterns: function(svg, series) {
+ var pattern;
+ pattern = svg.select('defs').selectAll('pattern').data(series.filter(function(s) {
+ return s.striped;
+ })).enter().append('pattern').attr({
+ id: function(s) {
+ return s.type + 'Pattern_' + s.index;
+ },
+ patternUnits: "userSpaceOnUse",
+ x: 0,
+ y: 0,
+ width: 60,
+ height: 60
+ }).append('g').style({
+ 'fill': function(s) {
+ return s.color;
+ },
+ 'fill-opacity': 0.3
+ });
+ pattern.append('rect').style('fill-opacity', 0.3).attr('width', 60).attr('height', 60);
+ pattern.append('path').attr('d', "M 10 0 l10 0 l -20 20 l 0 -10 z");
+ pattern.append('path').attr('d', "M40 0 l10 0 l-50 50 l0 -10 z");
+ pattern.append('path').attr('d', "M60 10 l0 10 l-40 40 l-10 0 z");
+ return pattern.append('path').attr('d', "M60 40 l0 10 l-10 10 l -10 0 z");
+ },
+ drawArea: function(svg, scales, data, options) {
+ var areaSeries, drawers;
+ areaSeries = data.filter(function(series) {
+ return series.type === 'area';
+ });
+ this.addPatterns(svg, areaSeries);
+ drawers = {
+ y: this.createLeftAreaDrawer(scales, options.lineMode, options.tension),
+ y2: this.createRightAreaDrawer(scales, options.lineMode, options.tension)
+ };
+ svg.select('.content').selectAll('.areaGroup').data(areaSeries).enter().append('g').attr('class', function(s) {
+ return 'areaGroup ' + 'series_' + s.index;
+ }).append('path').attr('class', 'area').style('fill', function(s) {
+ if (s.striped !== true) {
+ return s.color;
+ }
+ return "url(#areaPattern_" + s.index + ")";
+ }).style('opacity', function(s) {
+ if (s.striped) {
+ return '1';
+ } else {
+ return '0.3';
+ }
+ }).attr('d', function(d) {
+ return drawers[d.axis](d.values);
+ });
+ return this;
+ },
+ createLeftAreaDrawer: function(scales, mode, tension) {
+ return d3.svg.area().x(function(d) {
+ return scales.xScale(d.x);
+ }).y0(function(d) {
+ return scales.yScale(d.y0);
+ }).y1(function(d) {
+ return scales.yScale(d.y0 + d.y);
+ }).interpolate(mode).tension(tension);
+ },
+ createRightAreaDrawer: function(scales, mode, tension) {
+ return d3.svg.area().x(function(d) {
+ return scales.xScale(d.x);
+ }).y0(function(d) {
+ return scales.y2Scale(d.y0);
+ }).y1(function(d) {
+ return scales.y2Scale(d.y0 + d.y);
+ }).interpolate(mode).tension(tension);
+ },
+ getPseudoColumns: function(data, options) {
+ var keys, pseudoColumns;
+ data = data.filter(function(s) {
+ return s.type === 'column';
+ });
+ pseudoColumns = {};
+ keys = [];
+ data.forEach(function(series) {
+ var i, inAStack, index;
+ inAStack = false;
+ options.stacks.forEach(function(stack, index) {
+ var _ref;
+ if ((series.id != null) && (_ref = series.id, __indexOf.call(stack.series, _ref) >= 0)) {
+ pseudoColumns[series.id] = index;
+ if (__indexOf.call(keys, index) < 0) {
+ keys.push(index);
+ }
+ return inAStack = true;
+ }
+ });
+ if (inAStack === false) {
+ i = pseudoColumns[series.id] = index = keys.length;
+ return keys.push(i);
+ }
+ });
+ return {
+ pseudoColumns: pseudoColumns,
+ keys: keys
+ };
+ },
+ getMinDelta: function(seriesData, key, scale, range) {
+ return d3.min(seriesData.map(function(series) {
+ return series.values.map(function(d) {
+ return scale(d[key]);
+ }).filter(function(e) {
+ if (range) {
+ return e >= range[0] && e <= range[1];
+ } else {
+ return true;
+ }
+ }).reduce(function(prev, cur, i, arr) {
+ var diff;
+ diff = i > 0 ? cur - arr[i - 1] : Number.MAX_VALUE;
+ if (diff < prev) {
+ return diff;
+ } else {
+ return prev;
+ }
+ }, Number.MAX_VALUE);
+ }));
+ },
+ getBestColumnWidth: function(axes, dimensions, seriesData, options) {
+ var colData, delta, innerWidth, keys, nSeries, pseudoColumns, _ref;
+ if (!(seriesData && seriesData.length !== 0)) {
+ return 10;
+ }
+ if ((seriesData.filter(function(s) {
+ return s.type === 'column';
+ })).length === 0) {
+ return 10;
+ }
+ _ref = this.getPseudoColumns(seriesData, options), pseudoColumns = _ref.pseudoColumns, keys = _ref.keys;
+ innerWidth = dimensions.width - dimensions.left - dimensions.right;
+ colData = seriesData.filter(function(d) {
+ return pseudoColumns.hasOwnProperty(d.id);
+ });
+ delta = this.getMinDelta(colData, 'x', axes.xScale, [0, innerWidth]);
+ if (delta > innerWidth) {
+ delta = 0.25 * innerWidth;
+ }
+ nSeries = keys.length;
+ return parseInt((delta - options.columnsHGap) / nSeries);
+ },
+ getColumnAxis: function(data, columnWidth, options) {
+ var keys, pseudoColumns, x1, _ref;
+ _ref = this.getPseudoColumns(data, options), pseudoColumns = _ref.pseudoColumns, keys = _ref.keys;
+ x1 = d3.scale.ordinal().domain(keys).rangeBands([0, keys.length * columnWidth], 0);
+ return function(s) {
+ var index;
+ if (pseudoColumns[s.id] == null) {
+ return 0;
+ }
+ index = pseudoColumns[s.id];
+ return x1(index) - keys.length * columnWidth / 2;
+ };
+ },
+ drawColumns: function(svg, axes, data, columnWidth, options, handlers, dispatch) {
+ var colGroup, x1;
+ data = data.filter(function(s) {
+ return s.type === 'column';
+ });
+ x1 = this.getColumnAxis(data, columnWidth, options);
+ data.forEach(function(s) {
+ return s.xOffset = x1(s) + columnWidth * .5;
+ });
+ colGroup = svg.select('.content').selectAll('.columnGroup').data(data).enter().append("g").attr('class', function(s) {
+ return 'columnGroup series_' + s.index;
+ }).attr('transform', function(s) {
+ return "translate(" + x1(s) + ",0)";
+ });
+ colGroup.each(function(series) {
+ return d3.select(this).selectAll("rect").data(series.values).enter().append("rect").style({
+ 'stroke': series.color,
+ 'fill': series.color,
+ 'stroke-opacity': function(d) {
+ if (d.y === 0) {
+ return '0';
+ } else {
+ return '1';
+ }
+ },
+ 'stroke-width': '1px',
+ 'fill-opacity': function(d) {
+ if (d.y === 0) {
+ return 0;
+ } else {
+ return 0.7;
+ }
+ }
+ }).attr({
+ width: columnWidth,
+ x: function(d) {
+ return axes.xScale(d.x);
+ },
+ height: function(d) {
+ if (d.y === 0) {
+ return axes[d.axis + 'Scale'].range()[0];
+ }
+ return Math.abs(axes[d.axis + 'Scale'](d.y0 + d.y) - axes[d.axis + 'Scale'](d.y0));
+ },
+ y: function(d) {
+ if (d.y === 0) {
+ return 0;
+ } else {
+ return axes[d.axis + 'Scale'](Math.max(0, d.y0 + d.y));
+ }
+ }
+ }).on({
+ 'click': function(d, i) {
+ return dispatch.click(d, i);
+ }
+ }).on('mouseover', function(d, i) {
+ dispatch.hover(d, i);
+ return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, {
+ series: series,
+ x: axes.xScale(d.x),
+ y: axes[d.axis + 'Scale'](d.y0 + d.y),
+ datum: d
+ }, options.axes) : void 0;
+ }).on('mouseout', function(d) {
+ return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0;
+ });
+ });
+ return this;
+ },
+ drawDots: function(svg, axes, data, options, handlers, dispatch) {
+ var dotGroup;
+ dotGroup = svg.select('.content').selectAll('.dotGroup').data(data.filter(function(s) {
+ var _ref;
+ return ((_ref = s.type) === 'line' || _ref === 'area') && s.drawDots;
+ })).enter().append('g');
+ dotGroup.attr({
+ "class": function(s) {
+ return "dotGroup series_" + s.index;
+ },
+ fill: function(s) {
+ return s.color;
+ }
+ }).selectAll('.dot').data(function(d) {
+ return d.values;
+ }).enter().append('circle').attr({
+ 'class': 'dot',
+ 'r': function(d) {
+ return d.dotSize;
+ },
+ 'cx': function(d) {
+ return axes.xScale(d.x);
+ },
+ 'cy': function(d) {
+ return axes[d.axis + 'Scale'](d.y + d.y0);
+ }
+ }).style({
+ 'stroke': 'white',
+ 'stroke-width': '2px'
+ }).on({
+ 'click': function(d, i) {
+ return dispatch.click(d, i);
+ }
+ }).on({
+ 'mouseover': function(d, i) {
+ return dispatch.hover(d, i);
+ }
+ });
+ if (options.tooltip.mode !== 'none') {
+ dotGroup.on('mouseover', function(series) {
+ var d, target;
+ target = d3.select(d3.event.target);
+ d = target.datum();
+ target.attr('r', function(s) {
+ return s.dotSize + 2;
+ });
+ return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, {
+ series: series,
+ x: target.attr('cx'),
+ y: target.attr('cy'),
+ datum: d
+ }, options.axes) : void 0;
+ }).on('mouseout', function(d) {
+ d3.select(d3.event.target).attr('r', function(s) {
+ return s.dotSize;
+ });
+ return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0;
+ });
+ }
+ return this;
+ },
+ getEventDispatcher: function() {
+ var events;
+ events = ['focus', 'hover', 'click', 'toggle'];
+ return d3.dispatch.apply(this, events);
+ },
+ computeLegendLayout: function(svg, series, dimensions) {
+ var cumul, i, j, leftLayout, leftWidths, padding, rightLayout, rightWidths, that, w;
+ padding = 10;
+ that = this;
+ leftWidths = this.getLegendItemsWidths(svg, 'y');
+ leftLayout = [0];
+ i = 1;
+ while (i < leftWidths.length) {
+ leftLayout.push(leftWidths[i - 1] + leftLayout[i - 1] + padding);
+ i++;
+ }
+ rightWidths = this.getLegendItemsWidths(svg, 'y2');
+ if (!(rightWidths.length > 0)) {
+ return [leftLayout];
+ }
+ w = dimensions.width - dimensions.right - dimensions.left;
+ cumul = 0;
+ rightLayout = [];
+ j = rightWidths.length - 1;
+ while (j >= 0) {
+ rightLayout.push(w - cumul - rightWidths[j]);
+ cumul += rightWidths[j] + padding;
+ j--;
+ }
+ rightLayout.reverse();
+ return [leftLayout, rightLayout];
+ },
+ getLegendItemsWidths: function(svg, axis) {
+ var bbox, i, items, that, widths;
+ that = this;
+ bbox = function(t) {
+ return that.getTextBBox(t).width;
+ };
+ items = svg.selectAll(".legendItem." + axis);
+ if (!(items.length > 0)) {
+ return [];
+ }
+ widths = [];
+ i = 0;
+ while (i < items[0].length) {
+ widths.push(bbox(items[0][i]));
+ i++;
+ }
+ return widths;
+ },
+ drawLegend: function(svg, series, dimensions, handlers, dispatch) {
+ var d, groups, legend, that, translateLegends;
+ that = this;
+ legend = svg.append('g').attr('class', 'legend');
+ d = 16;
+ svg.select('defs').append('svg:clipPath').attr('id', 'legend-clip').append('circle').attr('r', d / 2);
+ groups = legend.selectAll('.legendItem').data(series);
+ groups.enter().append('g').on('click', function(s, i) {
+ var visibility;
+ visibility = !(s.visible !== false);
+ dispatch.toggle(s, i, visibility);
+ return typeof handlers.onSeriesVisibilityChange === "function" ? handlers.onSeriesVisibilityChange({
+ series: s,
+ index: i,
+ newVisibility: visibility
+ }) : void 0;
+ });
+ groups.attr({
+ 'class': function(s, i) {
+ return "legendItem series_" + i + " " + s.axis;
+ },
+ 'opacity': function(s, i) {
+ if (s.visible === false) {
+ that.toggleSeries(svg, i);
+ return '0.2';
+ }
+ return '1';
+ }
+ }).each(function(s) {
+ var item, _ref;
+ item = d3.select(this);
+ item.append('circle').attr({
+ 'fill': s.color,
+ 'stroke': s.color,
+ 'stroke-width': '2px',
+ 'r': d / 2
+ });
+ item.append('path').attr({
+ 'clip-path': 'url(#legend-clip)',
+ 'fill-opacity': (_ref = s.type) === 'area' || _ref === 'column' ? '1' : '0',
+ 'fill': 'white',
+ 'stroke': 'white',
+ 'stroke-width': '2px',
+ 'd': that.getLegendItemPath(s, d, d)
+ });
+ item.append('circle').attr({
+ 'fill-opacity': 0,
+ 'stroke': s.color,
+ 'stroke-width': '2px',
+ 'r': d / 2
+ });
+ return item.append('text').attr({
+ 'class': function(d, i) {
+ return "legendText series_" + i;
+ },
+ 'font-family': 'Courier',
+ 'font-size': 10,
+ 'transform': 'translate(13, 4)',
+ 'text-rendering': 'geometric-precision'
+ }).text(s.label || s.y);
+ });
+ translateLegends = function() {
+ var left, right, _ref;
+ _ref = that.computeLegendLayout(svg, series, dimensions), left = _ref[0], right = _ref[1];
+ return groups.attr({
+ 'transform': function(s, i) {
+ if (s.axis === 'y') {
+ return "translate(" + (left.shift()) + "," + (dimensions.height - 40) + ")";
+ } else {
+ return "translate(" + (right.shift()) + "," + (dimensions.height - 40) + ")";
+ }
+ }
+ });
+ };
+ translateLegends();
+ setTimeout(translateLegends, 0);
+ return this;
+ },
+ getLegendItemPath: function(series, w, h) {
+ var base_path, path;
+ if (series.type === 'column') {
+ path = 'M' + (-w / 3) + ' ' + (-h / 8) + ' l0 ' + h + ' ';
+ path += 'M0' + ' ' + (-h / 3) + ' l0 ' + h + ' ';
+ path += 'M' + w / 3 + ' ' + (-h / 10) + ' l0 ' + h + ' ';
+ return path;
+ }
+ base_path = 'M-' + w / 2 + ' 0' + h / 3 + ' l' + w / 3 + ' -' + h / 3 + ' l' + w / 3 + ' ' + h / 3 + ' l' + w / 3 + ' -' + 2 * h / 3;
+ if (series.type === 'area') {
+ base_path + ' l0 ' + h + ' l-' + w + ' 0z';
+ }
+ return base_path;
+ },
+ toggleSeries: function(svg, index) {
+ var isVisible;
+ isVisible = false;
+ svg.select('.content').selectAll('.series_' + index).style('display', function(s) {
+ if (d3.select(this).style('display') === 'none') {
+ isVisible = true;
+ return 'initial';
+ } else {
+ isVisible = false;
+ return 'none';
+ }
+ });
+ return isVisible;
+ },
+ drawLines: function(svg, scales, data, options, handlers) {
+ var drawers, interpolateData, lineGroup;
+ drawers = {
+ y: this.createLeftLineDrawer(scales, options.lineMode, options.tension),
+ y2: this.createRightLineDrawer(scales, options.lineMode, options.tension)
+ };
+ lineGroup = svg.select('.content').selectAll('.lineGroup').data(data.filter(function(s) {
+ var _ref;
+ return (_ref = s.type) === 'line' || _ref === 'area';
+ })).enter().append('g');
+ lineGroup.style('stroke', function(s) {
+ return s.color;
+ }).attr('class', function(s) {
+ return "lineGroup series_" + s.index;
+ }).append('path').attr({
+ "class": 'line',
+ d: function(d) {
+ return drawers[d.axis](d.values);
+ }
+ }).style({
+ 'fill': 'none',
+ 'stroke-width': function(s) {
+ return s.thickness;
+ },
+ 'stroke-dasharray': function(s) {
+ if (s.lineMode === 'dashed') {
+ return '10,3';
+ }
+ return void 0;
+ }
+ });
+ if (options.tooltip.interpolate) {
+ interpolateData = function(series) {
+ var datum, error, i, interpDatum, maxXPos, maxXValue, maxYPos, maxYValue, minXPos, minXValue, minYPos, minYValue, mousePos, target, valuesData, x, xPercentage, xVal, y, yPercentage, yVal, _i, _len;
+ target = d3.select(d3.event.target);
+ try {
+ mousePos = d3.mouse(this);
+ } catch (_error) {
+ error = _error;
+ mousePos = [0, 0];
+ }
+ valuesData = target.datum().values;
+ for (i = _i = 0, _len = valuesData.length; _i < _len; i = ++_i) {
+ datum = valuesData[i];
+ x = scales.xScale(datum.x);
+ y = scales.yScale(datum.y);
+ if ((typeof minXPos === "undefined" || minXPos === null) || x < minXPos) {
+ minXPos = x;
+ minXValue = datum.x;
+ }
+ if ((typeof maxXPos === "undefined" || maxXPos === null) || x > maxXPos) {
+ maxXPos = x;
+ maxXValue = datum.x;
+ }
+ if ((typeof minYPos === "undefined" || minYPos === null) || y < minYPos) {
+ minYPos = y;
+ }
+ if ((typeof maxYPos === "undefined" || maxYPos === null) || y > maxYPos) {
+ maxYPos = y;
+ }
+ if ((typeof minYValue === "undefined" || minYValue === null) || datum.y < minYValue) {
+ minYValue = datum.y;
+ }
+ if ((typeof maxYValue === "undefined" || maxYValue === null) || datum.y > maxYValue) {
+ maxYValue = datum.y;
+ }
+ }
+ xPercentage = (mousePos[0] - minXPos) / (maxXPos - minXPos);
+ yPercentage = (mousePos[1] - minYPos) / (maxYPos - minYPos);
+ xVal = Math.round(xPercentage * (maxXValue - minXValue) + minXValue);
+ yVal = Math.round((1 - yPercentage) * (maxYValue - minYValue) + minYValue);
+ interpDatum = {
+ x: xVal,
+ y: yVal
+ };
+ return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, {
+ series: series,
+ x: mousePos[0],
+ y: mousePos[1],
+ datum: interpDatum
+ }, options.axes) : void 0;
+ };
+ lineGroup.on('mousemove', interpolateData).on('mouseout', function(d) {
+ return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0;
+ });
+ }
+ return this;
+ },
+ createLeftLineDrawer: function(scales, mode, tension) {
+ return d3.svg.line().x(function(d) {
+ return scales.xScale(d.x);
+ }).y(function(d) {
+ return scales.yScale(d.y + d.y0);
+ }).interpolate(mode).tension(tension);
+ },
+ createRightLineDrawer: function(scales, mode, tension) {
+ return d3.svg.line().x(function(d) {
+ return scales.xScale(d.x);
+ }).y(function(d) {
+ return scales.y2Scale(d.y + d.y0);
+ }).interpolate(mode).tension(tension);
+ },
+ getPixelCssProp: function(element, propertyName) {
+ var string;
+ string = $window.getComputedStyle(element, null).getPropertyValue(propertyName);
+ return +string.replace(/px$/, '');
+ },
+ getDefaultMargins: function() {
+ return {
+ top: 20,
+ right: 50,
+ bottom: 60,
+ left: 50
+ };
+ },
+ getDefaultThumbnailMargins: function() {
+ return {
+ top: 1,
+ right: 1,
+ bottom: 2,
+ left: 0
+ };
+ },
+ getElementDimensions: function(element, width, height) {
+ var bottom, dim, left, parent, right, top;
+ dim = {};
+ parent = element;
+ top = this.getPixelCssProp(parent, 'padding-top');
+ bottom = this.getPixelCssProp(parent, 'padding-bottom');
+ left = this.getPixelCssProp(parent, 'padding-left');
+ right = this.getPixelCssProp(parent, 'padding-right');
+ dim.width = +(width || parent.offsetWidth || 900) - left - right;
+ dim.height = +(height || parent.offsetHeight || 500) - top - bottom;
+ return dim;
+ },
+ getDimensions: function(options, element, attrs) {
+ var dim;
+ dim = this.getElementDimensions(element[0].parentElement, attrs.width, attrs.height);
+ dim = angular.extend(options.margin, dim);
+ return dim;
+ },
+ clean: function(element) {
+ return d3.select(element).on('keydown', null).on('keyup', null).select('svg').remove();
+ },
+ uuid: function() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r, v;
+ r = Math.random() * 16 | 0;
+ v = c === 'x' ? r : r & 0x3 | 0x8;
+ return v.toString(16);
+ });
+ },
+ bootstrap: function(element, id, dimensions) {
+ var defs, height, svg, width;
+ d3.select(element).classed('chart', true);
+ width = dimensions.width;
+ height = dimensions.height;