diff options
author | Koren Lev <korenlev@gmail.com> | 2017-07-27 16:42:15 +0300 |
---|---|---|
committer | Koren Lev <korenlev@gmail.com> | 2017-07-27 16:42:15 +0300 |
commit | b88c78e3cf2bef22aa2f1c4d0bf305e303bc15f0 (patch) | |
tree | ffa30a6e1511d72562d8772b8700cda52b2752a1 /ui/imports/lib | |
parent | b70483739d1f6f4f0d31987ed2e4d1e30d71d579 (diff) |
adding calipso ui
Change-Id: Ifa6f63daebb07f45580f747341960e898fdb00c4
Signed-off-by: Koren Lev <korenlev@gmail.com>
Diffstat (limited to 'ui/imports/lib')
-rw-r--r-- | ui/imports/lib/d3-graph.js | 573 | ||||
-rw-r--r-- | ui/imports/lib/d3three.js | 789 | ||||
-rw-r--r-- | ui/imports/lib/general-regex.js | 15 | ||||
-rw-r--r-- | ui/imports/lib/icon.js | 14 | ||||
-rw-r--r-- | ui/imports/lib/images-for-node-type.js | 22 | ||||
-rw-r--r-- | ui/imports/lib/regex-utils.js | 11 | ||||
-rw-r--r-- | ui/imports/lib/simple-schema-utils.js | 15 | ||||
-rw-r--r-- | ui/imports/lib/utilities.js | 54 |
8 files changed, 1493 insertions, 0 deletions
diff --git a/ui/imports/lib/d3-graph.js b/ui/imports/lib/d3-graph.js new file mode 100644 index 0000000..311ad95 --- /dev/null +++ b/ui/imports/lib/d3-graph.js @@ -0,0 +1,573 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +import { Inventory } from '/imports/api/inventories/inventories'; +import { Cliques } from '/imports/api/cliques/cliques'; +import { Links } from '/imports/api/links/links'; +import { NodeHoverAttr } from '/imports/api/attributes_for_hover_on_data/attributes_for_hover_on_data'; +import * as cola from 'webcola'; +import { store } from '/imports/ui/store/store'; +import { activateGraphTooltipWindow } from '/imports/ui/actions/graph-tooltip-window.actions'; +import { closeGraphTooltipWindow } from '/imports/ui/actions/graph-tooltip-window.actions'; +import { activateVedgeInfoWindow } from '/imports/ui/actions/vedge-info-window.actions'; +import * as R from 'ramda'; + +let d3Graph = { + color:'', + + zoomer:function(){ + var width = '100%', + height = '100%'; + var xScale = d3.scale.linear() + .domain([0,width]).range([0,width]); + var yScale = d3.scale.linear() + .domain([0,height]).range([0, height]); + return d3.behavior.zoom(). + scaleExtent([0.1,10]). + x(xScale). + y(yScale). + on('zoomstart', d3Graph.zoomstart). + on('zoom', d3Graph.redraw); + }, + + svg:'', + force:'', + link:'', + node:'', + linkText:'', + + graph:{ + nodes:[], + links:[], + }, + + zoomstart:function () { + var node = d3Graph.svg.selectAll('.node'); + node.each(function(d) { + d.selected = false; + d.previouslySelected = false; + }); + node.classed('selected', false); + }, + + /* depreacted - not used ? + getGraphData:function(nodeId){ + + var invNodes = Inventory.find({ 'type': 'instance', $and: [ { 'host': nodeId } ] }); + + var edges = []; + var nodes = []; + + invNodes.forEach(function(n){ + nodes = n['Entities']; + edges = n['Relations']; + }); + + nodes.forEach(function(n){ + n.name = n.object_name; + }); + + var edges_new = []; + edges.forEach(function(e) { + var sourceNode = nodes.filter(function(n) { return n.id === e.from; })[0], + targetNode = nodes.filter(function(n) { return n.id === e.to; })[0]; + + edges_new.push({source: sourceNode, target: targetNode, value: 1,label: e.label,attributes: e.attributes}); + }); +//any links with duplicate source and target get an incremented 'linknum' + for (var i=0; i<edges_new.length; i++) { + if (i != 0 && + edges_new[i].source == edges_new[i-1].source && + edges_new[i].target == edges_new[i-1].target) { + + edges_new[i].linknum = edges_new[i-1].linknum + 1; + } + else {edges_new[i].linknum = 1;} + } + //var graph = {}; + this.graph.nodes = nodes; + this.graph.links = edges_new; + + }, + */ + + getGraphDataByClique:function(nodeObjId){ + // Clique: one instance per graph. A focal point describing a node, and with links data. + // TODO: findOne or .each. + var cliques = Cliques.find({ focal_point: new Mongo.ObjectID(nodeObjId) }).fetch(); + + + var nodes = []; + var edges_new = []; + + if (R.length(cliques) === 0) { + return; + } + + // CliquesLinks: All the links for a specific clique + var cliquesLinks = []; + cliques[0].links.forEach(function(n){ + cliquesLinks.push(n); + }); + + // LinksList = Map(Clique.links -> links collection) + var linksList = Links.find({ _id: {$in: cliquesLinks}}).fetch(); + //console.log(linksList); + + // Create nodes from the links endpoints. + // Nodes = link source & target (objectid) + linksList.forEach(function(linkItem){ + nodes.push(linkItem['source']); + nodes.push(linkItem['target']); + }); + + // NodesList = Nodes exapneded. + var nodesList = Inventory.find({ _id: {$in: nodes}}).fetch(); + + // Links list: expanend source/target nodes to create in memory data graph - links,nodes. + linksList.forEach(function(linkItem){ + var sourceNode = nodesList.filter(function(n) { + return n._id._str === linkItem.source._str; + })[0]; + + var targetNode = nodesList.filter(function(n) { + return n._id._str === linkItem.target._str; + })[0]; + + edges_new.push({ + source: sourceNode, + target: targetNode, + value: 1, + label: linkItem.link_name, + attributes: linkItem + }); + + }); + + // Expend nodeslist to include linked attributes. + nodesList.forEach(function(nodeItem){ + nodeItem.attributes = []; + var attrHoverFields = NodeHoverAttr.find({ 'type': nodeItem['type']}).fetch(); + if(attrHoverFields.length){ + attrHoverFields[0].attributes.forEach(function(field){ + if(nodeItem[field]){ + var object = {}; + object[field] = nodeItem[field]; + nodeItem.attributes.push(object); + } + }); + } + }); + + this.graph.nodes = nodesList; + this.graph.links = edges_new; + + }, + + createGraphData: function (width, height){ + //var self = this; + //var width = 500; + //var height = 900; + + this.color = d3.scale.category20(); + /* + this.svg = d3.select('#dgraphid').append('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('pointer-events', 'all') + //.attr('transform', 'translate(250,250) scale(0.3)') + .call(d3.behavior.zoom().on('zoom', this.redraw)) + .append('svg:g'); + + //.append('g'); + + this.force = cola.d3adaptor().convergenceThreshold(0.1) + //.linkDistance(200) + .size([width, height]); + */ + //var focused = null; + + this.force = cola.d3adaptor().convergenceThreshold(0.1) + //.linkDistance(200) + .size([width, height]); + + var outer = d3.select('#dgraphid') + .append('svg') + .attr({ + width: '100%', + height: '100%', + 'pointer-events': 'all', + class: 'os-d3-graph' + }); + + outer.append('rect') + .attr({ class: 'background', width: '100%', height: '100%' }) + .call(this.zoomer()); + /*.call(d3.behavior.zoom() + .on('zoom', function(d) { + d3Graph.svg.attr('transform', 'translate(' + d3.event.translate + ')' + ' scale(' + d3.event.scale + ')'); + }))*/ + //.on('mouseover', function () { focused = this; }); + + //d3.select('body').on('keydown', function () { d3.select(focused); /* then do something with it here */ }); + //d3.select('#dgraphid').on('keydown', d3Graph.keydown()); + + let scale = 0.5; + + this.svg = outer + .append('g') + //.attr('transform', 'translate(250,250) scale(0.3)'); + .attr('transform', 'translate(250,250) scale(' + scale.toString() + ')'); + + let fontSize = Math.floor(16 / scale); + d3Graph.svg.selectAll('.link-group text') + .style('font-size', fontSize + 'px'); + d3Graph.svg.selectAll('.node text') + .style('font-size', fontSize + 'px'); + + }, + + redraw: function(){ + //console.log('here', d3.event.translate, d3.event.scale); + + d3Graph.svg.attr('transform', + 'translate(' + d3.event.translate + ')' + + ' scale(' + d3.event.scale + ')'); + + let fontSize = Math.floor(16 / d3.event.scale); + d3Graph.svg.selectAll('.link-group text') + .style('font-size', fontSize + 'px'); + d3Graph.svg.selectAll('.node text') + .style('font-size', fontSize + 'px'); + + }, + + updateNetworkGraph:function (){ + var self = this; + + if (R.isNil(this.svg)) { + return; + } + + this.svg.selectAll('g').remove(); + //this.svg.exit().remove(); + + this.force + .nodes(this.graph.nodes) + .links(this.graph.links) + .symmetricDiffLinkLengths(250) + //.jaccardLinkLengths(300) + //.jaccardLinkLengths(80,0.7) + .handleDisconnected(true) + .avoidOverlaps(true) + .start(50, 100, 200); + + /* + this.force + .on('dragstart', function (d) { d3.event.sourceEvent.stopPropagation(); d3.select(this).classed('dragging', true); } ) + .on('drag', function (d) { d3.select(this).attr('cx', d.x = d3.event.x).attr('cy', d.y = d3.event.y); } ) + .on('dragend', function (d) { d3.select(this).classed('dragging', false); }); + */ + + + // Define the div for the tooltip + + //svg.exit().remove(); + //graph.constraints = [{'axis':'y', 'left':0, 'right':1, 'gap':25},]; + + //.start(10,15,20); + /*var path = svg.append('svg:g') + .selectAll('path') + .data(force.links()) + .enter().append('svg:path') + .attr('class', 'link');; + */ + var link = this.svg.selectAll('.link') + .data(this.force.links()) + .enter() + .append('g') + .attr('class', 'link-group') + .append('line') + .attr('class', 'link') + .style('stroke-width', function(_d) { return 3; }) + //.style('stroke-width', function(d) { return Math.sqrt(d.stroke); }) + .attr('stroke', function (d) { + if(d.attributes.state == 'error'){ + self.blinkLink(d); + return 'red'; + } + else if(d.attributes.state == 'warn'){ + self.blinkLink(d); + return 'orange'; + } + else if(d.source.level === d.target.level) { + return self.color(d.source.level); + } + else { + return self.color(d.level); + //d3.select(this).classed('different-groups', true); + } + }); + /*.style('stroke', function(d) { + if(d.label == 'net-103'){ + self.blinkLink(d); + return 'red'; + } + //return 'red'; + //return self.color(d.level); + })*/ + + var linkText = this.svg.selectAll('.link-group') + .append('text') + .data(this.force.links()) + .text(function(d) { return d.label; }) + .attr('x', function(d) { return (d.source.x + (d.target.x - d.source.x) * 0.5); }) + .attr('y', function(d) { return (d.source.y + (d.target.y - d.source.y) * 0.5); }) + .attr('dy', '.25em') + .attr('text-anchor', 'right') + .on('mouseover', function(d) { + store.dispatch(activateGraphTooltipWindow( + d.label, + d.attributes, + d3.event.pageX, + d3.event.pageY + )); + }) + .on('mouseout', function(_d) { + store.dispatch(closeGraphTooltipWindow()); + }); + + var node = this.svg.selectAll('.node') + .data(this.force.nodes()) + .enter().append('g') + .attr('class', 'node') + .call(this.force.drag); + + // A map from group ID to image URL. + var imageByGroup = { + 'instance': 'ic_computer_black_48dp_2x.png', + 'pnic': 'ic_dns_black_48dp_2x.png', + 'vconnector': 'ic_settings_input_composite_black_48dp_2x.png', + // 'network': 'ic_cloud_queue_black_48dp_2x.png', + 'network': 'ic_cloud_queue_black_48dp_2x.png', + 'vedge': 'ic_gamepad_black_48dp_2x.png', + 'vservice': 'ic_storage_black_48dp_2x.png', + 'vnic': 'ic_settings_input_hdmi_black_48dp_2x.png', + 'otep':'ic_keyboard_return_black_48dp_2x.png', + 'default':'ic_lens_black_48dp_2x.png' + }; + + node.append('image') + //.attr('xlink:href', 'https://github.com/favicon.ico') + .attr('xlink:href', function(d) { + if(imageByGroup[d.type]){ + return `/${imageByGroup[d.type]}`; + } + else{ + return `/${imageByGroup['default']}`; + } + + }) + .attr('x', -8) + .attr('y', -8) + .attr('width', 36) + .attr('height', 36) + //node.append('circle') + .attr('class', 'node') + //.attr('r', function(d){return 13;}) + .on('mouseover', function(d) { + store.dispatch(activateGraphTooltipWindow( + d.name, + d.attributes, + d3.event.pageX, + d3.event.pageY)); + }) + .on('mouseout', function(_d) { + store.dispatch(closeGraphTooltipWindow()); + }) + .on('click', function(d) { + if (d.type === 'vedge') { + store.dispatch(activateVedgeInfoWindow( + d, + d3.event.pageX, + d3.event.pageY)); + } + }) + .style('fill', function(d) { + if(d.state == 'error'){ + self.blinkNode(d); + return 'red'; + } + return self.color(d.group); + }) + .call(this.force.drag); + + + /* + .each(function() { + var sel = d3.select(this); + var state = false; + sel.on('dblclick', function () { + state = !state; + if (state) { + sel.style('fill', 'black'); + } else { + sel.style('fill', function (d) { + return d.colr; + }); + } + }); + }); + */ + + node.append('text') + .attr('dx', 0) + .attr('dy', 40) + .text(function(d) { return d.object_name; }); + + + this.force.on('tick', function() { + link.attr('x1', function(d) { return d.source.x; }) + .attr('y1', function(d) { return d.source.y; }) + .attr('x2', function(d) { return d.target.x; }) + .attr('y2', function(d) { return d.target.y; }); + /* + .attr('dr1', function(d) { return 75/d.source.linknum; }) + .attr('dr2', function(d) { return 75/d.target.linknum; }); + */ + + node.attr('transform', function(d) { + return 'translate(' + d.x + ',' + d.y + ')'; + }); + + linkText + .attr('x', function(d) { + return (d.source.x + (d.target.x - d.source.x) * 0.5); + }) + .attr('y', function(d) { + return (d.source.y + (d.target.y - d.source.y) * 0.5); + }); + }); + + }, + + centerview: function () { + // Center the view on the molecule(s) and scale it so that everything + // fits in the window + var width = 500; + var height = 500; + + if (this.graph === null) return; + + var nodes = this.graph.nodes; + + //no molecules, nothing to do + if (nodes.length === 0) return; + + // Get the bounding box + var min_x = d3.min(nodes.map(function(d) {return d.x;})); + var min_y = d3.min(nodes.map(function(d) {return d.y;})); + + var max_x = d3.max(nodes.map(function(d) {return d.x;})); + var max_y = d3.max(nodes.map(function(d) {return d.y;})); + + + // The width and the height of the graph + var mol_width = max_x - min_x; + var mol_height = max_y - min_y; + + // how much larger the drawing area is than the width and the height + var width_ratio = width / mol_width; + var height_ratio = height / mol_height; + + // we need to fit it in both directions, so we scale according to + // the direction in which we need to shrink the most + var min_ratio = Math.min(width_ratio, height_ratio) * 0.8; + + // the new dimensions of the molecule + var new_mol_width = mol_width * min_ratio; + var new_mol_height = mol_height * min_ratio; + + // translate so that it's in the center of the window + var x_trans = -(min_x) * min_ratio + (width - new_mol_width) / 2; + var y_trans = -(min_y) * min_ratio + (height - new_mol_height) / 2; + + + // do the actual moving + d3Graph.svg.attr('transform', + 'translate(' + [x_trans, y_trans] + ')' + ' scale(' + min_ratio + ')'); + + // tell the zoomer what we did so that next we zoom, it uses the + // transformation we entered here + //d3Graph.zoomer.translate([x_trans, y_trans ]); + //d3Graph.zoomer.scale(min_ratio); + }, + + keydown:function() { +/* + shiftKey = d3.event.shiftKey || d3.event.metaKey; + ctrlKey = d3.event.ctrlKey; +*/ + if(d3.event===null) return; + + console.log('d3.event', d3.event); + + if (d3.event.keyCode == 67) { //the 'c' key + this.centerview(); + } + + }, + + blinkNode: function(node){ + var nodeList = this.svg.selectAll('.node'); + var selected = nodeList.filter(function (d, _i) { + return d.id == node.id; + //return d.name != findFromParent; + }); + selected.forEach(function(n){ + for (var i = 0; i != 30; i++) { + $(n[1]).fadeTo('slow', 0.1).fadeTo('slow', 5.0); + } + }); + }, + + blinkLink: function(link){ + var linkList = this.svg.selectAll('.link'); + var selected = linkList.filter(function (d, _i) { + return d.label == link.label; + //return d.id == link.id; + //return d.name != findFromParent; + }); + selected.forEach(function(n){ + for (var i = 0; i != 30; i++) { + $(n[0]).fadeTo('slow', 0.1).fadeTo('slow', 5.0); + } + }); + }, + + tick:function(obj){ + obj.link.attr('x1', function(d) { return d.source.x; }) + .attr('y1', function(d) { return d.source.y; }) + .attr('x2', function(d) { return d.target.x; }) + .attr('y2', function(d) { return d.target.y; }); + + obj.node.attr('transform', function(d) { + return 'translate(' + d.x + ',' + d.y + ')'; + }); + + obj.linkText + .attr('x', function(d) { + return (d.source.x + (d.target.x - d.source.x) * 0.5); + }) + .attr('y', function(d) { + return (d.source.y + (d.target.y - d.source.y) * 0.5); + }); + } +}; + +export { d3Graph }; diff --git a/ui/imports/lib/d3three.js b/ui/imports/lib/d3three.js new file mode 100644 index 0000000..51493f2 --- /dev/null +++ b/ui/imports/lib/d3three.js @@ -0,0 +1,789 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +var chartOffset = 0; + +// D3.layout.force3d.js +// (C) 2012 ziggy.jonsson.nyc@gmail.com +// BSD license (http://opensource.org/licenses/BSD-3-Clause) + +d3.layout.force3d = function() { + var forceXY = d3.layout.force() + ,forceZ = d3.layout.force() + ,zNodes = {} + ,zLinks = {} + ,nodeID = 1 + ,linkID = 1 + ,tickFunction = Object + + var force3d = {} + + Object.keys(forceXY).forEach(function(d) { + force3d[d] = function() { + var result = forceXY[d].apply(this,arguments) + if (d !="nodes" && d!="links") forceZ[d].apply(this,arguments) + return (result == forceXY) ? force3d : result + } + }) + + + force3d.on = function(name,fn) { + tickFunction = fn + return force3d + } + + + forceXY.on("tick",function() { + + // Refresh zNodes add new, delete removed + var _zNodes = {} + forceXY.nodes().forEach(function(d,i) { + if (!d.id) d.id = nodeID++ + _zNodes[d.id] = zNodes[d.id] || {x:d.z,px:d.z,py:d.z,y:d.z,id:d.id} + d.z = _zNodes[d.id].x + }) + zNodes = _zNodes + + // Refresh zLinks add new, delete removed + var _zLinks = {} + forceXY.links().forEach(function(d) { + var nytt = false + if (!d.linkID) { d.linkID = linkID++;nytt=true} + _zLinks[d.linkID] = zLinks[d.linkID] || {target:zNodes[d.target.id],source:zNodes[d.source.id]} + + }) + zLinks = _zLinks + + // Update the nodes/links in forceZ + forceZ.nodes(d3.values(zNodes)) + forceZ.links(d3.values(zLinks)) + forceZ.start() // Need to kick forceZ so we don't lose the update mechanism + + // And run the user defined function, if defined + tickFunction() + }) + + // Expose the sub-forces for debugging purposes + force3d.xy = forceXY + force3d.z = forceZ + + return force3d +} +// end of d3.layout.force3d.js + +// Override default functions for d3 +THREE.Object3D.prototype.appendChild = function (c) { + this.add(c); + return c; +}; +THREE.Object3D.prototype.querySelectorAll = function () { return []; }; + +// this one is to use D3's .attr() on THREE's objects +THREE.Object3D.prototype.setAttribute = function (name, value) { + var chain = name.split('.'); + var object = this; + for (var i = 0; i < chain.length - 1; i++) { + object = object[chain[i]]; + } + object[chain[chain.length - 1]] = value; +} + +// d3three object +D3THREE = function(singleton) { + this.labelGroup = new THREE.Object3D(); + this.maxY = 0; + this.axisObjects = {}; + + this.running = true; + + if (singleton) { + if (typeof(d3three) !== 'undefined') { + d3three.stop(); + } + d3three = this; + } + + //if (!singleton) { + // d3threes.push(this); + //} +} + +D3THREE.prototype.init = function(divId) { + // standard THREE stuff, straight from examples + this.renderer = new THREE.WebGLRenderer({antialias: true, alpha : true, preserveDrawingBuffer: true}); + this.renderer.shadowMap.enabled = true; + this.renderer.shadowMap.type = THREE.PCFSoftShadow; + this.renderer.shadowMapSoft = true; + this.renderer.shadowCameraNear = 1000; + this.renderer.shadowCameraFar = 10000; + this.renderer.shadowCameraFov = 50; + this.renderer.shadowMapBias = 0.0039; + this.renderer.shadowMapDarkness = 0.25; + this.renderer.shadowMapWidth = 10000; + this.renderer.shadowMapHeight = 10000; + this.renderer.physicallyBasedShading = true; + + this.divId = divId; + this.width = document.getElementById(divId).offsetWidth; + this.height = document.getElementById(divId).offsetHeight; + + this.renderer.setSize( this.width, this.height ); + + document.getElementById(divId).appendChild( this.renderer.domElement ); + + this.camera = new THREE.PerspectiveCamera( 30, this.width / this.height, 1, 100000 ); + this.camera.position.z = -1000; + this.camera.position.x = -800; + this.camera.position.y = 600; + + this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement ); + + this.scene = new THREE.Scene(); + + this.defaultLight = new THREE.AmbientLight( 0xbbbbb ); // soft white light + this.scene.add( this.defaultLight ); + + this.scene.add(this.labelGroup); + + var self = this; + window.addEventListener( 'resize', self.onWindowResize.bind(self), false ); +} + +D3THREE.prototype.onWindowResize = function() { + var self = this; + self.camera.aspect = self.width / self.height; + self.camera.updateProjectionMatrix(); + + self.renderer.setSize( self.width, self.height ); +} + +D3THREE.prototype.animate = function() { + var self = this; + if (this.running) { + setTimeout( function() { + this.requestId = requestAnimationFrame( self.animate.bind(self) ); + }, 1000 / 15 ); + + self.renderer.render( self.scene, self.camera ); + self.controls.update(); + + self.labelGroup.children.forEach(function(l){ + l.rotation.setFromRotationMatrix(self.camera.matrix, "YXZ"); + l.rotation.x = 0; + l.rotation.z = 0; + }); + } else { + window.removeEventListener( 'resize', self.onWindowResize.bind(self) ); + while (self.scene.children.length > 0) { + var childObject = self.scene.children[0]; + if (childObject.geometry) { + childObject.geometry.dispose(); + } + if (childObject.material) { + childObject.material.dispose(); + } + self.scene.remove(childObject); + delete(childObject); + } + + self.renderer.context = null; + self.renderer.domElement = null; + self.renderer = null; + + self.camera = null; + self.controls = null; + self.scene = null; + self.labelGroup = null; + + cancelAnimationFrame(self.requestId); + } +} + +D3THREE.prototype.stop = function() { + this.running = false; +} + +D3THREE.prototype.render = function(element, data) { + element.render(data); +} + +D3THREE.createAxis = function(dt) { + return new D3THREE.Axis(dt); +} + +// d3three axis +D3THREE.Axis = function(dt) { + this._scale = d3.scale.linear(); + this._orient = "x"; + this._tickFormat = function(d) { return d }; + this._dt = dt; +} + +D3THREE.Axis.prototype.orient = function(o) { + if (o) { + this._dt.axisObjects[o] = this; + this._orient = o; + } + return this; +} + +D3THREE.Axis.prototype.scale = function(s) { + if (s) { + this._scale = s; + } + return this; +} + +D3THREE.Axis.prototype.tickFormat = function(f) { + if (f) { + this._tickFormat = f; + } + return this; +} + +D3THREE.Axis.prototype.interval = function() { + var interval; + if (typeof(this._scale.rangeBand) === 'function') { + // ordinal scale + interval = this._scale.range()[1]; + } else { + interval = this._scale.range()[1] / (this._scale.ticks().length - 1); + } + return interval; +} + +D3THREE.Axis.prototype.ticks = function() { + var ticks; + if (typeof(this._scale.rangeBand) === 'function') { + // ordinal scale + ticks = this._scale.domain(); + } else { + ticks = this._scale.ticks(); + } + return ticks; +} + +D3THREE.Axis.prototype.getRotationShift = function() { + return this.interval() * (this.ticks().length - 1) / 2; +} + +D3THREE.Axis.prototype.render = function() { + var material = new THREE.LineBasicMaterial({ + color: 0xbbbbbb, + linewidth: 2 + }); + + var tickMaterial = new THREE.LineBasicMaterial({ + color: 0xbbbbbb, + linewidth: 1 + }); + + var geometry = new THREE.Geometry(); + + interval = this.interval(); + + var interval = this.interval(), ticks = this.ticks(); + + // x,y axis shift, so rotation is from center of screen + var xAxisShift = this._dt.axisObjects.x.getRotationShift(), + yAxisShift = this._dt.axisObjects.y.getRotationShift(); + + for (var i = 0; i < ticks.length; i++) { + var tickMarGeometry = new THREE.Geometry(); + + var shape = new THREE.TextGeometry(this._tickFormat(ticks[i]), + { + size: 5, + height: 1, + curveSegments: 20 + }); + var wrapper = new THREE.MeshBasicMaterial({color: 0xbbbbbb}); + var words = new THREE.Mesh(shape, wrapper); + + if (this._orient === "y") { + // tick + geometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, chartOffset, 0 - xAxisShift)); + + tickMarGeometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, chartOffset, 0 - xAxisShift)); + tickMarGeometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, -10 + chartOffset, 0 - xAxisShift)); + var tickLine = new THREE.Line(tickMarGeometry, tickMaterial); + this._dt.scene.add(tickLine); + + if (i * interval > this._dt.maxY) { + this._dt.maxY = i * interval; + } + + words.position.set(i * interval - yAxisShift, -20 + chartOffset, 0 - xAxisShift); + } else if (this._orient === "z") { + // tick + geometry.vertices.push(new THREE.Vector3(0 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift)); + + tickMarGeometry.vertices.push(new THREE.Vector3(0 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift)); + tickMarGeometry.vertices.push(new THREE.Vector3(10 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift)); + var tickLine = new THREE.Line(tickMarGeometry, tickMaterial); + this._dt.scene.add(tickLine); + + words.position.set(20 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift); + } else if (this._orient === "x") { + // tick + geometry.vertices.push(new THREE.Vector3(0 - yAxisShift, chartOffset, i * interval - xAxisShift)); + + tickMarGeometry.vertices.push(new THREE.Vector3(0 - yAxisShift, 0 + chartOffset, i * interval - xAxisShift)); + tickMarGeometry.vertices.push(new THREE.Vector3(0 - yAxisShift, -10 + chartOffset, i * interval - xAxisShift)); + var tickLine = new THREE.Line(tickMarGeometry, tickMaterial); + this._dt.scene.add(tickLine); + + words.position.set(0 - yAxisShift, -20 + chartOffset, i * interval - xAxisShift); + } + + this._dt.labelGroup.add(words); + } + + var line = new THREE.Line(geometry, material); + + this._dt.scene.add(line); +} + +// Chart object +D3THREE.Chart = function() { +} + +D3THREE.Chart.prototype.config = function(c) { + this._config = $.extend(this._config, c); +} + +D3THREE.Chart.prototype.init = function(dt) { + this._dt = dt; + // mouse move + var self = this; + this._dt.renderer.domElement.addEventListener( 'mousemove', function(e) { + self.onDocumentMouseMove(e); + }, false ); +} + +var cumulativeOffset = function(element) { + var top = 0, left = 0; + do { + top += element.offsetTop || 0; + left += element.offsetLeft || 0; + element = element.offsetParent; + } while(element); + + return { + top: top, + left: left + }; +}; + +D3THREE.Chart.prototype.detectNodeHover = function(e) { + var boundingRect = this._dt.renderer.domElement.getBoundingClientRect(); + + var vector = new THREE.Vector3(); + vector.x = ( (e.clientX - boundingRect.left) / this._dt.renderer.domElement.width ) * 2 - 1; + vector.y = 1 - ( (e.clientY - boundingRect.top) / this._dt.renderer.domElement.height ) * 2; + vector.z = 1; + + // create a check ray + vector.unproject( this._dt.camera ); + var ray = new THREE.Raycaster( this._dt.camera.position, + vector.sub( this._dt.camera.position ).normalize() ); + + var intersects = ray.intersectObjects( this._nodeGroup.children ); + + for (var i = 0; i < this._nodeGroup.children.length; i++) { + this._nodeGroup.children[i].material.opacity = 1; + } + + if (intersects.length > 0) { + var obj = intersects[0].object; + obj.material.opacity = 0.5; + + var html = ""; + + html += "<div class=\"tooltip_kv\">"; + html += "<span>"; + html += "x: " + this._dt.axisObjects.x._tickFormat(obj.userData.x); + html += "</span><br>"; + html += "<span>"; + html += "y: " + this._dt.axisObjects.y._tickFormat(obj.userData.y); + html += "</span><br>"; + html += "<span>"; + html += "z: " + this._dt.axisObjects.z._tickFormat(obj.userData.z); + html += "</span><br>"; + html += "</div>"; + + document.getElementById("tooltip-container").innerHTML = html; + document.getElementById("tooltip-container").style.display = "block"; + + document.getElementById("tooltip-container").style.top = (e.pageY + 10) + "px"; + document.getElementById("tooltip-container").style.left = (e.pageX + 10) + "px"; + } else { + document.getElementById("tooltip-container").style.display = "none"; + } +} + +// Scatter plot +D3THREE.Scatter = function(dt) { + this.init(dt); + + this._nodeGroup = new THREE.Object3D(); + + this._config = {color: 0x4682B4, pointRadius: 5}; +} + +D3THREE.Scatter.prototype = new D3THREE.Chart(); + +D3THREE.Scatter.prototype.onDocumentMouseMove = function(e) { + // detect intersected spheres + this.detectNodeHover(e); +} + +D3THREE.Scatter.prototype.render = function(data) { + var geometry = new THREE.SphereGeometry( this._config.pointRadius, 32, 32 ); + + this._dt.scene.add(this._nodeGroup); + + // x,y axis shift, so rotation is from center of screen + var xAxisShift = this._dt.axisObjects.x.getRotationShift(), + yAxisShift = this._dt.axisObjects.y.getRotationShift(); + + var self = this; + d3.select(this._nodeGroup) + .selectAll() + .data(data) + .enter().append( function(d) { + var material = new THREE.MeshBasicMaterial( { + color: self._config.color } ); + var mesh = new THREE.Mesh( geometry, material ); + mesh.userData = {x: d.x, y: d.y, z: d.z}; + return mesh; + } ) + .attr("position.z", function(d) { + return self._dt.axisObjects.x._scale(d.x) - xAxisShift; + }) + .attr("position.x", function(d) { + return self._dt.axisObjects.y._scale(d.y) - yAxisShift; + }) + .attr("position.y", function(d) { + return self._dt.axisObjects.z._scale(d.z) + chartOffset; + }); +} + +// Surface plot +D3THREE.Surface = function(dt) { + this.init(dt); + + this._nodeGroup = new THREE.Object3D(); + + this._config = {color: 0x4682B4, pointColor: 0xff7f0e, pointRadius: 2}; +} + +D3THREE.Surface.prototype = new D3THREE.Chart(); + +D3THREE.Surface.prototype.onDocumentMouseMove = function(e) { + // detect intersected spheres + var boundingRect = this._dt.renderer.domElement.getBoundingClientRect(); + + var vector = new THREE.Vector3(); + vector.x = ( (e.clientX - boundingRect.left) / this._dt.renderer.domElement.width ) * 2 - 1; + vector.y = 1 - ( (e.clientY - boundingRect.top) / this._dt.renderer.domElement.height ) * 2; + vector.z = 1; + + // create a check ray + vector.unproject( this._dt.camera ); + var ray = new THREE.Raycaster( this._dt.camera.position, + vector.sub( this._dt.camera.position ).normalize() ); + + var meshIntersects = ray.intersectObjects( [this._meshSurface] ); + + if (meshIntersects.length > 0) { + for (var i = 0; i < this._nodeGroup.children.length; i++) { + this._nodeGroup.children[i].visible = true; + this._nodeGroup.children[i].material.opacity = 1; + } + + this.detectNodeHover(e); + } else { + // hide nodes + for (var i = 0; i < this._nodeGroup.children.length; i++) { + this._nodeGroup.children[i].visible = false; + } + } +} + +D3THREE.Surface.prototype.render = function(threeData) { + /* render data points */ + var geometry = new THREE.SphereGeometry( this._config.pointRadius, 32, 32 ); + + this._dt.scene.add(this._nodeGroup); + + // x,y axis shift, so rotation is from center of screen + var xAxisShift = this._dt.axisObjects.x.getRotationShift(), + yAxisShift = this._dt.axisObjects.y.getRotationShift(); + + var self = this; + d3.select(this._nodeGroup) + .selectAll() + .data(threeData) + .enter().append( function(d) { + var material = new THREE.MeshBasicMaterial( { + color: self._config.pointColor } ); + var mesh = new THREE.Mesh( geometry, material ); + mesh.userData = {x: d.x, y: d.y, z: d.z}; + mesh.visible = false; + return mesh; + } ) + .attr("position.z", function(d) { + return self._dt.axisObjects.x._scale(d.x) - xAxisShift; + }) + .attr("position.x", function(d) { + return self._dt.axisObjects.y._scale(d.y) - yAxisShift; + }) + .attr("position.y", function(d) { + return self._dt.axisObjects.z._scale(d.z) + chartOffset; + }); + + /* custom surface */ + function distance (v1, v2) + { + var dx = v1.x - v2.x; + var dy = v1.y - v2.y; + var dz = v1.z - v2.z; + + return Math.sqrt(dx*dx+dz*dz); + } + + var vertices = []; + var holes = []; + var triangles, mesh; + var geometry = new THREE.Geometry(); + var material = new THREE.MeshBasicMaterial({color: this._config.color}); + + for (var i = 0; i < threeData.length; i++) { + vertices.push(new THREE.Vector3( + self._dt.axisObjects.y._scale(threeData[i].y) - yAxisShift, + self._dt.axisObjects.z._scale(threeData[i].z) + chartOffset, + self._dt.axisObjects.x._scale(threeData[i].x) - xAxisShift)); + } + + geometry.vertices = vertices; + + for (var i = 0; i < vertices.length; i++) { + // find three closest vertices to generate surface + var v1, v2, v3; + var distances = []; + + // find vertices in same y or y + 1 row + var minY = Number.MAX_VALUE; + for (var j = i + 1; j < vertices.length; j++) { + if (i !== j && vertices[j].x > vertices[i].x) { + if (vertices[j].x < minY) { + minY = vertices[j].x; + } + } + } + + var rowVertices = [], row2Vertices = []; + for (var j = i + 1; j < vertices.length; j++) { + if (i !== j && (vertices[j].x === vertices[i].x)) { + rowVertices.push({index: j, v: vertices[j]}); + } + if (i !== j && (vertices[j].x === minY)) { + row2Vertices.push({index: j, v: vertices[j]}); + } + } + + if (rowVertices.length >= 1 && row2Vertices.length >= 2) { + // find smallest x + rowVertices.sort(function(a, b) { + if (a.v.z < b.v.z) { + return -1; + } else if (a.v.z === b.v.z) { + return 0; + } else { + return 1; + } + }); + + v1 = rowVertices[0].index; + + row2Vertices.sort(function(a, b) { + if (a.v.z < b.v.z) { + return -1; + } else if (a.v.z === b.v.z) { + return 0; + } else { + return 1; + } + }); + + v2 = row2Vertices[0].index; + v3 = row2Vertices[1].index; + + var fv = [i, v1, v2, v3]; + fv = fv.sort(function(a, b) { + if (a < b) return -1; + else if (a === b) return 0; + else return 1; + }); + + geometry.faces.push( new THREE.Face3(fv[1], fv[0], fv[3])); + geometry.faces.push( new THREE.Face3(fv[0], fv[2], fv[3])); + } + } + + this._meshSurface = new THREE.Mesh( geometry, material ); + this._dt.scene.add(this._meshSurface); +} + +// Bar plot +D3THREE.Bar = function(dt) { + this.init(dt); + + this._nodeGroup = new THREE.Object3D(); + + this._config = {color: 0x4682B4, barSize: 5}; +} + +D3THREE.Bar.prototype = new D3THREE.Chart(); + +D3THREE.Bar.prototype.onDocumentMouseMove = function(e) { + this.detectNodeHover(e); +} + +D3THREE.Bar.prototype.render = function(threeData) { + /* render data points */ + this._dt.scene.add(this._nodeGroup); + + // x,y axis shift, so rotation is from center of screen + var xAxisShift = this._dt.axisObjects.x.getRotationShift(), + yAxisShift = this._dt.axisObjects.y.getRotationShift(); + + var self = this; + d3.select(this._nodeGroup) + .selectAll() + .data(threeData) + .enter().append( function(d) { + var height = self._dt.axisObjects.z._scale(d.z) + chartOffset; + var geometry = new THREE.BoxGeometry( self._config.barSize, height, self._config.barSize ); + var material = new THREE.MeshBasicMaterial( { + color: self._config.color } ); + var mesh = new THREE.Mesh( geometry, material ); + mesh.userData = {x: d.x, y: d.y, z: d.z}; + return mesh; + } ) + .attr("position.z", function(d) { + return self._dt.axisObjects.x._scale(d.x) - xAxisShift; + }) + .attr("position.x", function(d) { + return self._dt.axisObjects.y._scale(d.y) - yAxisShift; + }) + .attr("position.y", function(d) { + var height = self._dt.axisObjects.z._scale(d.z) + chartOffset; + return height / 2; + }); +} + +// Force layout plot +D3THREE.Force = function(dt) { + this.init(dt); + + this._nodeGroup = new THREE.Object3D(); + + this._config = {color: 0x4682B4, linkColor: 0xcccccc, linkWidth: 1}; +} + +D3THREE.Force.prototype = new D3THREE.Chart(); + +D3THREE.Force.prototype.onDocumentMouseMove = function(e) { +} + +D3THREE.Force.prototype.render = function(threeData) { + var spheres = [], three_links = []; + // Define the 3d force + var force = d3.layout.force3d() + .nodes(sort_data=[]) + .links(links=[]) + .size([50, 50]) + .gravity(0.3) + .charge(-400) + + var DISTANCE = 1; + + for (var i = 0; i < threeData.nodes.length; i++) { + sort_data.push({x:threeData.nodes.x + DISTANCE,y:threeData.nodes.y + DISTANCE,z:0}) + + // set up the sphere vars + var radius = 5, + segments = 16, + rings = 16; + + // create the sphere's material + var sphereMaterial = new THREE.MeshLambertMaterial({ color: this._config.color }); + + var sphere = new THREE.Mesh( + new THREE.SphereGeometry( + radius, + segments, + rings), + sphereMaterial); + + spheres.push(sphere); + + // add the sphere to the scene + this._dt.scene.add(sphere); + } + + for (var i = 0; i < threeData.links.length; i++) { + links.push({target:sort_data[threeData.links[i].target],source:sort_data[threeData.links[i].source]}); + + var material = new THREE.LineBasicMaterial({ color: this._config.linkColor, + linewidth: this._config.linkWidth}); + var geometry = new THREE.Geometry(); + + geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); + geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); + var line = new THREE.Line( geometry, material ); + line.userData = { source: threeData.links[i].source, + target: threeData.links[i].target }; + three_links.push(line); + this._dt.scene.add(line); + + force.start(); + } + + // set up the axes + var x = d3.scale.linear().domain([0, 350]).range([0, 10]), + y = d3.scale.linear().domain([0, 350]).range([0, 10]), + z = d3.scale.linear().domain([0, 350]).range([0, 10]); + + var self = this; + force.on("tick", function(e) { + for (var i = 0; i < sort_data.length; i++) { + spheres[i].position.set(x(sort_data[i].x) * 40 - 40, y(sort_data[i].y) * 40 - 40,z(sort_data[i].z) * 40 - 40); + + for (var j = 0; j < three_links.length; j++) { + var line = three_links[j]; + var vi = -1; + if (line.userData.source === i) { + vi = 0; + } + if (line.userData.target === i) { + vi = 1; + } + + if (vi >= 0) { + line.geometry.vertices[vi].x = x(sort_data[i].x) * 40 - 40; + line.geometry.vertices[vi].y = y(sort_data[i].y) * 40 - 40; + line.geometry.vertices[vi].z = y(sort_data[i].z) * 40 - 40; + line.geometry.verticesNeedUpdate = true; + } + } + } + }); +} diff --git a/ui/imports/lib/general-regex.js b/ui/imports/lib/general-regex.js new file mode 100644 index 0000000..184a63a --- /dev/null +++ b/ui/imports/lib/general-regex.js @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +export const portRegEx = /^0*(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/; + +export const pathRegEx = /^(\/){1}([^\/\0]+(\/)?)+$/; + +export const hostnameRegex= new RegExp('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$'); + +export const ipAddressRegex = new RegExp('(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}'); diff --git a/ui/imports/lib/icon.js b/ui/imports/lib/icon.js new file mode 100644 index 0000000..1653bc2 --- /dev/null +++ b/ui/imports/lib/icon.js @@ -0,0 +1,14 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +export class Icon { + constructor({type, name}) { + this.type = type; + this.name = name; + } +} diff --git a/ui/imports/lib/images-for-node-type.js b/ui/imports/lib/images-for-node-type.js new file mode 100644 index 0000000..5846f46 --- /dev/null +++ b/ui/imports/lib/images-for-node-type.js @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +export let imagesForNodeType = { + 'instance': 'ic_computer_black_48dp_2x.png', + 'pnic': 'ic_dns_black_48dp_2x.png', + 'vconnector': 'ic_settings_input_composite_black_48dp_2x.png', + // 'network': 'ic_cloud_queue_black_48dp_2x.png', + 'network': 'ic_cloud_queue_black_48dp_2x.png', + 'vedge': 'ic_gamepad_black_48dp_2x.png', + 'vservice': 'ic_storage_black_48dp_2x.png', + 'vnic': 'ic_settings_input_hdmi_black_48dp_2x.png', + 'otep':'ic_keyboard_return_black_48dp_2x.png', +}; + +export let defaultNodeTypeImage = 'ic_lens_black_48dp_2x.png'; + diff --git a/ui/imports/lib/regex-utils.js b/ui/imports/lib/regex-utils.js new file mode 100644 index 0000000..fd9bce2 --- /dev/null +++ b/ui/imports/lib/regex-utils.js @@ -0,0 +1,11 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +export function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +} diff --git a/ui/imports/lib/simple-schema-utils.js b/ui/imports/lib/simple-schema-utils.js new file mode 100644 index 0000000..3f2840b --- /dev/null +++ b/ui/imports/lib/simple-schema-utils.js @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +import { SimpleSchema } from 'meteor/aldeed:simple-schema'; + +export let _idFieldDef = { + type: { + _str: { type: String, regEx: SimpleSchema.RegEx.Id } + } +}; diff --git a/ui/imports/lib/utilities.js b/ui/imports/lib/utilities.js new file mode 100644 index 0000000..e1143a3 --- /dev/null +++ b/ui/imports/lib/utilities.js @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others / +// / +// All rights reserved. This program and the accompanying materials / +// are made available under the terms of the Apache License, Version 2.0 / +// which accompanies this distribution, and is available at / +// http://www.apache.org/licenses/LICENSE-2.0 / +///////////////////////////////////////////////////////////////////////////////////////// +import * as R from 'ramda'; + +export function idToStr(orgId) { + return R.ifElse(R.is(Mongo.ObjectID), + function (id) { return id.toHexString() + ':' + 'objectid'; }, + R.identity + )(orgId); +} + +export function parseReqId(pId) { + let idMatch = R.match(/(.*):objectid$/, pId); + if (idMatch.length === 0) { + return { + type: 'string', + id: pId + }; + } else { + return { + type: 'objectid', + id: new Mongo.ObjectID(idMatch[1]) + }; + } +} + +function calcColor(level) { + let r = 11; + let g = 122; + let b = 209; + //let a = 1; + let factor = level / 15; + factor = factor < 0 ? 0 : 1 - factor; + + let nR = Math.floor(r * factor); + let nG = Math.floor(g * factor); + let nB = Math.floor(b * factor); + //let nA = a; + let colorStr = R.reduce((acc, colorPart) => { + let digits = colorPart.toString(16); + if (colorPart < 16) { digits = '0' + digits; } + return acc + digits; + }, '#', [nR, nG, nB]); + + return colorStr; +} + +export let calcColorMem = R.memoize(calcColor); |