summaryrefslogtreecommitdiffstats
path: root/framework/src/onos/web/gui/src/main/webapp/app/fw
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/onos/web/gui/src/main/webapp/app/fw')
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/README.txt44
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.css49
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.js165
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/layer.js25
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.css50
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.js214
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.css64
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.js387
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.css50
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.js99
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.css102
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.html6
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.js56
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.css90
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.js103
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/remote/remote.js25
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/remote/rest.js72
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/remote/urlfn.js63
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/remote/websocket.js329
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsevent.js49
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsock.js42
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/geodata.js186
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.css34
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.js646
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.css92
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.js265
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/map.js129
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svg.js25
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svgUtil.js311
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/svg/zoom.js132
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/util/fn.js292
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/util/keys.js215
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/util/prefs.js128
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/util/random.js51
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/util/theme.js121
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/util/util.js25
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.css120
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.js263
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.css215
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.js272
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js165
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.css77
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.js268
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.css44
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.js146
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/app/fw/widget/widget.js25
46 files changed, 6331 insertions, 0 deletions
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/README.txt b/framework/src/onos/web/gui/src/main/webapp/app/fw/README.txt
new file mode 100644
index 00000000..626547b2
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/README.txt
@@ -0,0 +1,44 @@
+# Framework related code
+
+- layer
+ - Flash Service (transient messages)
+ - Panel Service (floating panels)
+ - Quick Help Service (key bindings, mouse gestures)
+ - Veil Service (loss of server connection)
+
+- mast
+ - Masthead Service
+
+- nav
+ - Navigation Service (navigation menu)
+
+- remote
+ - REST Service
+ - URL functin Service
+ - Web Socket Service
+ - Web Socket Event Service
+ - Web Socket encapsulation
+
+ - (Login Service) << planned
+
+- svg
+ - GeoData Service (TopoJSON map functions)
+ - Glyph Service
+ - Icon Service
+ - Map Service
+ - SVG Utilities Service
+ - Zoom Service
+
+- util
+ - General Functions
+ - Key Handler
+ - User Preference Service
+ - Randomization Service
+ - Theme Service
+
+- widget
+ - Button Service
+ - Table Service (table styling directives)
+ - Table Builder Service
+ - Toolbar Service
+ - Tooltip Service
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.css
new file mode 100644
index 00000000..02064625
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.css
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Flash Service -- CSS file
+ */
+
+#flash {
+ z-index: 1400;
+}
+
+#flash svg {
+ position: absolute;
+ bottom: 0;
+ opacity: 0.8;
+}
+
+.light #flash svg g.flashItem rect {
+ fill: #ccc;
+}
+.dark #flash svg g.flashItem rect {
+ fill: #555;
+}
+
+#flash svg g.flashItem text {
+ stroke: none;
+ text-anchor: middle;
+ alignment-baseline: middle;
+ font-size: 16pt;
+}
+.light #flash svg g.flashItem text {
+ fill: #333;
+}
+.dark #flash svg g.flashItem text {
+ fill: #999;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.js
new file mode 100644
index 00000000..0d95b774
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/flash.js
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Layer -- Flash Service
+
+ Provides a mechanism to flash short informational messages to the screen
+ to alert the user of something, e.g. "Hosts visible" or "Hosts hidden".
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $timeout;
+
+ // configuration
+ var defaultSettings = {
+ fade: 200,
+ showFor: 1200
+ },
+ w = '100%',
+ h = 200,
+ xpad = 20,
+ ypad = 10,
+ rx = 10,
+ vbox = '-200 -' + (h/2) + ' 400 ' + h;
+
+ // internal state
+ var settings,
+ timer = null,
+ data = [],
+ enabled;
+
+ // DOM elements
+ var flashDiv, svg;
+
+
+ function computeBox(el) {
+ var text = el.select('text'),
+ box = text.node().getBBox();
+
+ // center
+ box.x = -box.width / 2;
+ box.y = -box.height / 2;
+
+ // add some padding
+ box.x -= xpad;
+ box.width += xpad * 2;
+ box.y -= ypad;
+ box.height += ypad * 2;
+
+ return box;
+ }
+
+ function updateFlash() {
+ if (!svg) {
+ svg = flashDiv.append('svg').attr({
+ width: w,
+ height: h,
+ viewBox: vbox
+ });
+ }
+
+ var items = svg.selectAll('.flashItem')
+ .data(data);
+
+ // this is when there is an existing item
+ items.each(function (msg) {
+ var el = d3.select(this),
+ box;
+
+ el.select('text').text(msg);
+ box = computeBox(el);
+ el.select('rect').attr(box);
+ });
+
+
+ // this is when there is no existing item
+ var entering = items.enter()
+ .append('g')
+ .attr({
+ class: 'flashItem',
+ opacity: 0
+ })
+ .transition()
+ .duration(settings.fade)
+ .attr('opacity', 1);
+
+ entering.each(function (msg) {
+ var el = d3.select(this),
+ box;
+
+ el.append('rect').attr('rx', rx);
+ el.append('text').text(msg);
+ box = computeBox(el);
+ el.select('rect').attr(box);
+ });
+
+ items.exit()
+ .transition()
+ .duration(settings.fade)
+ .attr('opacity', 0)
+ .remove();
+
+ if (svg && data.length === 0) {
+ svg.transition()
+ .delay(settings.fade + 10)
+ .remove();
+ svg = null;
+ }
+ }
+
+ function flash(msg) {
+ if (!enabled) return;
+
+ if (timer) {
+ $timeout.cancel(timer);
+ }
+
+ timer = $timeout(function () {
+ data = [];
+ updateFlash();
+ }, settings.showFor);
+
+ data = [msg];
+ updateFlash();
+ }
+
+ function enable(b) {
+ enabled = !!b;
+ }
+
+ angular.module('onosLayer')
+ .factory('FlashService', ['$log', '$timeout',
+ function (_$log_, _$timeout_) {
+ $log = _$log_;
+ $timeout = _$timeout_;
+
+ function initFlash(opts) {
+ settings = angular.extend({}, defaultSettings, opts);
+ flashDiv = d3.select('#flash');
+ enabled = true;
+ }
+
+ return {
+ initFlash: initFlash,
+ flash: flash,
+ enable: enable
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/layer.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/layer.js
new file mode 100644
index 00000000..a47f53e0
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/layer.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Layers Module
+ */
+(function () {
+ 'use strict';
+
+ angular.module('onosLayer', ['onosUtil']);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.css
new file mode 100644
index 00000000..46dbdb52
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.css
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Panel Service -- CSS file
+ */
+
+.floatpanel {
+ position: absolute;
+ z-index: 100;
+ display: block;
+ top: 64px;
+ width: 200px;
+ right: -220px;
+ opacity: 0;
+
+ padding: 10px;
+ font-size: 10pt;
+
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+html[data-platform='iPad'] .floatpanel {
+ top: 80px;
+}
+
+.light .floatpanel {
+ background-color: rgba(255,255,255,0.8);
+ color: black;
+ box-shadow: 0 2px 12px #777;
+}
+.dark .floatpanel {
+ background-color: rgba(50,50,50,0.8);
+ color: #ccc;
+ box-shadow: 0 2px 12px #000;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.js
new file mode 100644
index 00000000..aef71eee
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/panel.js
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Layer -- Panel Service
+ */
+(function () {
+ 'use strict';
+
+ var $log, fs;
+
+ var defaultSettings = {
+ edge: 'right',
+ width: 200,
+ margin: 20,
+ hideMargin: 20,
+ xtnTime: 750,
+ fade: true
+ };
+
+ var panels,
+ panelLayer;
+
+
+ function init() {
+ panelLayer = d3.select('#floatpanels');
+ panelLayer.html('');
+ panels = {};
+ }
+
+ // helpers for panel
+ function noop() {}
+
+ function margin(p) {
+ return p.settings.margin;
+ }
+ function hideMargin(p) {
+ return p.settings.hideMargin;
+ }
+ function noPx(p, what) {
+ return Number(p.el.style(what).replace(/px$/, ''));
+ }
+ function widthVal(p) {
+ return noPx(p, 'width');
+ }
+ function heightVal(p) {
+ return noPx(p, 'height');
+ }
+ function pxShow(p) {
+ return margin(p) + 'px';
+ }
+ function pxHide(p) {
+ return (-hideMargin(p) - widthVal(p) - (noPx(p, 'padding') * 2)) + 'px';
+ }
+
+ function makePanel(id, settings) {
+ var p = {
+ id: id,
+ settings: settings,
+ on: false,
+ el: null
+ },
+ api = {
+ show: showPanel,
+ hide: hidePanel,
+ toggle: togglePanel,
+ empty: emptyPanel,
+ append: appendPanel,
+ width: panelWidth,
+ height: panelHeight,
+ isVisible: panelIsVisible,
+ classed: classed,
+ el: panelEl
+ };
+
+ p.el = panelLayer.append('div')
+ .attr('id', id)
+ .attr('class', 'floatpanel')
+ .style('opacity', 0);
+
+ // has to be called after el is set
+ p.el.style(p.settings.edge, pxHide(p));
+ panelWidth(p.settings.width);
+ if (p.settings.height) {
+ panelHeight(p.settings.height);
+ }
+
+ panels[id] = p;
+
+ function showPanel(cb) {
+ var endCb = fs.isF(cb) || noop;
+ p.on = true;
+ p.el.transition().duration(p.settings.xtnTime)
+ .each('end', endCb)
+ .style(p.settings.edge, pxShow(p))
+ .style('opacity', 1);
+ }
+
+ function hidePanel(cb) {
+ var endCb = fs.isF(cb) || noop,
+ endOpacity = p.settings.fade ? 0 : 1;
+ p.on = false;
+ p.el.transition().duration(p.settings.xtnTime)
+ .each('end', endCb)
+ .style(p.settings.edge, pxHide(p))
+ .style('opacity', endOpacity);
+ }
+
+ function togglePanel(cb) {
+ if (p.on) {
+ hidePanel(cb);
+ } else {
+ showPanel(cb);
+ }
+ return p.on;
+ }
+
+ function emptyPanel() {
+ return p.el.html('');
+ }
+
+ function appendPanel(what) {
+ return p.el.append(what);
+ }
+
+ function panelWidth(w) {
+ if (w === undefined) {
+ return widthVal(p);
+ }
+ p.el.style('width', w + 'px');
+ }
+
+ function panelHeight(h) {
+ if (h === undefined) {
+ return heightVal(p);
+ }
+ p.el.style('height', h + 'px');
+ }
+
+ function panelIsVisible() {
+ return p.on;
+ }
+
+ function classed(cls, bool) {
+ return p.el.classed(cls, bool);
+ }
+
+ function panelEl() {
+ return p.el;
+ }
+
+ return api;
+ }
+
+ function removePanel(id) {
+ panelLayer.select('#' + id).remove();
+ delete panels[id];
+ }
+
+ angular.module('onosLayer')
+ .factory('PanelService', ['$log', 'FnService', function (_$log_, _fs_) {
+ $log = _$log_;
+ fs = _fs_;
+
+ function createPanel(id, opts) {
+ var settings = angular.extend({}, defaultSettings, opts);
+ if (!id) {
+ $log.warn('createPanel: no ID given');
+ return null;
+ }
+ if (panels[id]) {
+ $log.warn('Panel with ID "' + id + '" already exists');
+ return null;
+ }
+ if (fs.debugOn('widget')) {
+ $log.debug('creating panel:', id, settings);
+ }
+ return makePanel(id, settings);
+ }
+
+ function destroyPanel(id) {
+ if (panels[id]) {
+ if (fs.debugOn('widget')) {
+ $log.debug('destroying panel:', id);
+ }
+ removePanel(id);
+ } else {
+ if (fs.debugOn('widget')) {
+ $log.debug('no panel to destroy:', id);
+ }
+ }
+ }
+
+ return {
+ init: init,
+ createPanel: createPanel,
+ destroyPanel: destroyPanel
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.css
new file mode 100644
index 00000000..bb806d8a
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.css
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Quick Help Service -- CSS file
+ */
+
+#quickhelp {
+ z-index: 1300;
+}
+
+#quickhelp svg {
+ position: absolute;
+ top: 180px;
+ opacity: 1;
+}
+
+#quickhelp svg g.help rect {
+ fill: black;
+ opacity: 0.7;
+}
+
+#quickhelp svg text.title {
+ font-size: 10pt;
+ font-style: italic;
+ text-anchor: middle;
+ fill: #999;
+}
+
+#quickhelp svg g.keyItem {
+ fill: white;
+}
+
+#quickhelp svg g line.qhrowsep {
+ stroke: #888;
+ stroke-dasharray: 2 2;
+}
+
+#quickhelp svg text {
+ font-size: 7pt;
+ alignment-baseline: middle;
+}
+
+#quickhelp svg text.key {
+ fill: #add;
+}
+
+#quickhelp svg text.desc {
+ fill: #ddd;
+}
+
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.js
new file mode 100644
index 00000000..a25cf68d
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/quickhelp.js
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Layer -- Quick Help Service
+
+ Provides a mechanism to display key bindings and mouse gesture notes.
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, fs, sus;
+
+ // configuration
+ var defaultSettings = {
+ fade: 500
+ },
+ w = '100%',
+ h = '80%',
+ vbox = '-200 0 400 400',
+ pad = 10,
+ offy = 45,
+ sepYDelta = 20,
+ colXDelta = 16,
+ yTextSpc = 12,
+ offDesc = 8;
+
+ // internal state
+ var settings,
+ data = [],
+ yCount;
+
+ // DOM elements
+ var qhdiv, svg, pane, rect, items;
+
+ // key-logical-name to key-display lookup..
+ var keyDisp = {
+ equals: '=',
+ slash: '/',
+ backSlash: '\\',
+ backQuote: '`',
+ leftArrow: 'L-arrow',
+ upArrow: 'U-arrow',
+ rightArrow: 'R-arrow',
+ downArrow: 'D-arrow'
+ };
+
+ // list of needed bindings to use in aggregateData
+ var neededBindings = [
+ 'globalKeys', 'globalFormat', 'viewKeys', 'viewGestures'
+ ];
+
+ // ===========================================
+ // === Function Definitions ===
+
+ function mkKeyDisp(id) {
+ var v = keyDisp[id] || id;
+ return fs.cap(v);
+ }
+
+ function addSeparator(el, i) {
+ var y = sepYDelta/2 - 5;
+ el.append('line')
+ .attr({ 'class': 'qhrowsep', x1: 0, y1: y, x2: 0, y2: y });
+ }
+
+ function addContent(el, data, ri) {
+ var xCount = 0,
+ clsPfx = 'qh-r' + ri + '-c';
+
+ function addColumn(el, c, i) {
+ var cls = clsPfx + i,
+ oy = 0,
+ aggKey = el.append('g').attr('visibility', 'hidden'),
+ gcol = el.append('g').attr({
+ 'class': cls,
+ transform: sus.translate(xCount, 0)
+ });
+
+ c.forEach(function (j) {
+ var k = j[0],
+ v = j[1];
+
+ if (k !== '-') {
+ aggKey.append('text').text(k);
+
+ gcol.append('text').text(k)
+ .attr({
+ 'class': 'key',
+ y: oy
+ });
+ gcol.append('text').text(v)
+ .attr({
+ 'class': 'desc',
+ y: oy
+ });
+ }
+
+ oy += yTextSpc;
+ });
+
+ // adjust position of descriptions, based on widest key
+ var kbox = aggKey.node().getBBox(),
+ ox = kbox.width + offDesc;
+ gcol.selectAll('.desc').attr('x', ox);
+ aggKey.remove();
+
+ // now update x-offset for next column
+ var bbox = gcol.node().getBBox();
+ xCount += bbox.width + colXDelta;
+ }
+
+ data.forEach(function (d, i) {
+ addColumn(el, d, i);
+ });
+
+ // finally, return the height of the row..
+ return el.node().getBBox().height;
+ }
+
+ function updateKeyItems() {
+ var rows = items.selectAll('.qhRow').data(data);
+
+ yCount = offy;
+
+ var entering = rows.enter()
+ .append('g')
+ .attr({
+ 'class': 'qhrow'
+ });
+
+ entering.each(function (r, i) {
+ var el = d3.select(this),
+ sep = r.type === 'sep',
+ dy;
+
+ el.attr('transform', sus.translate(0, yCount));
+
+ if (sep) {
+ addSeparator(el, i);
+ yCount += sepYDelta;
+ } else {
+ dy = addContent(el, r.data, i);
+ yCount += dy;
+ }
+ });
+
+ // size the backing rectangle
+ var ibox = items.node().getBBox(),
+ paneW = ibox.width + pad * 2,
+ paneH = ibox.height + offy;
+
+ items.selectAll('.qhrowsep').attr('x2', ibox.width);
+ items.attr('transform', sus.translate(-paneW/2, -pad));
+ rect.attr({
+ width: paneW,
+ height: paneH,
+ transform: sus.translate(-paneW/2-pad, 0)
+ });
+
+ }
+
+ function checkFmt(fmt) {
+ // should be a single array of keys,
+ // or array of arrays of keys (one per column).
+ // return null if there is a problem.
+ var a = fs.isA(fmt),
+ n = a && a.length,
+ ns = 0,
+ na = 0;
+
+ if (n) {
+ // it is an array which has some content
+ a.forEach(function (d) {
+ fs.isA(d) && na++;
+ fs.isS(d) && ns++;
+ });
+ if (na === n || ns === n) {
+ // all arrays or all strings...
+ return a;
+ }
+ }
+ return null;
+ }
+
+ function buildBlock(map, fmt) {
+ var b = [];
+ fmt.forEach(function (k) {
+ var v = map.get(k),
+ a = fs.isA(v),
+ d = (a && a[1]);
+
+ // '-' marks a separator; d is the description
+ if (k === '-' || d) {
+ b.push([mkKeyDisp(k), d]);
+ }
+ });
+ return b;
+ }
+
+ function emptyRow() {
+ return { type: 'row', data: [] };
+ }
+
+ function mkArrRow(fmt) {
+ var d = emptyRow();
+ d.data.push(fmt);
+ return d;
+ }
+
+ function mkColumnarRow(map, fmt) {
+ var d = emptyRow();
+ fmt.forEach(function (a) {
+ d.data.push(buildBlock(map, a));
+ });
+ return d;
+ }
+
+ function mkMapRow(map, fmt) {
+ var d = emptyRow();
+ d.data.push(buildBlock(map, fmt));
+ return d;
+ }
+
+ function addRow(row) {
+ var d = row || { type: 'sep' };
+ data.push(d);
+ }
+
+ function aggregateData(bindings) {
+ var hf = '_helpFormat',
+ gmap = d3.map(bindings.globalKeys),
+ gfmt = bindings.globalFormat,
+ vmap = d3.map(bindings.viewKeys),
+ vgest = bindings.viewGestures,
+ vfmt, vkeys;
+
+ // filter out help format entry
+ vfmt = checkFmt(vmap.get(hf));
+ vmap.remove(hf);
+
+ // if bad (or no) format, fallback to sorted keys
+ if (!vfmt) {
+ vkeys = vmap.keys();
+ vfmt = vkeys.sort();
+ }
+
+ data = [];
+
+ addRow(mkMapRow(gmap, gfmt));
+ addRow();
+ addRow(fs.isA(vfmt[0]) ? mkColumnarRow(vmap, vfmt) : mkMapRow(vmap, vfmt));
+ addRow();
+ addRow(mkArrRow(vgest));
+ }
+
+
+ function popBind(bindings) {
+ pane = svg.append('g')
+ .attr({
+ class: 'help',
+ opacity: 0
+ });
+
+ rect = pane.append('rect')
+ .attr('rx', 8);
+
+ pane.append('text')
+ .text('Quick Help')
+ .attr({
+ class: 'title',
+ dy: '1.2em',
+ transform: sus.translate(-pad,0)
+ });
+
+ items = pane.append('g');
+ aggregateData(bindings);
+ updateKeyItems();
+
+ _fade(1);
+ }
+
+ function fadeBindings() {
+ _fade(0);
+ }
+
+ function _fade(o) {
+ svg.selectAll('g.help')
+ .transition()
+ .duration(settings.fade)
+ .attr('opacity', o);
+ }
+
+ function addSvg() {
+ svg = qhdiv.append('svg')
+ .attr({
+ width: w,
+ height: h,
+ viewBox: vbox
+ });
+ }
+
+ function removeSvg() {
+ svg.transition()
+ .delay(settings.fade + 20)
+ .remove();
+ }
+
+ function goodBindings(bindings) {
+ var warnPrefix = 'Quickhelp Service: showQuickHelp(), ';
+ if (!bindings || !fs.isO(bindings) || fs.isEmptyObject(bindings)) {
+ $log.warn(warnPrefix + 'invalid bindings object');
+ return false;
+ }
+ if (!(neededBindings.every(function (key) { return key in bindings; }))) {
+ $log.warn(
+ warnPrefix +
+ 'needed bindings for help panel not provided:',
+ neededBindings
+ );
+ return false
+ }
+ return true;
+ }
+
+ // ===========================================
+ // === Module Definition ===
+
+ angular.module('onosLayer')
+ .factory('QuickHelpService',
+ ['$log', 'FnService', 'SvgUtilService',
+
+ function (_$log_, _fs_, _sus_) {
+ $log = _$log_;
+ fs = _fs_;
+ sus = _sus_;
+
+ function initQuickHelp(opts) {
+ settings = angular.extend({}, defaultSettings, fs.isO(opts));
+ qhdiv = d3.select('#quickhelp');
+ }
+
+ function showQuickHelp(bindings) {
+ svg = qhdiv.select('svg');
+ if (!goodBindings(bindings)) {
+ return null;
+ }
+ if (svg.empty()) {
+ addSvg();
+ popBind(bindings);
+ } else {
+ hideQuickHelp();
+ }
+ }
+
+ function hideQuickHelp() {
+ svg = qhdiv.select('svg');
+ if (!svg.empty()) {
+ fadeBindings();
+ removeSvg();
+ return true;
+ }
+ return false;
+ }
+
+ return {
+ initQuickHelp: initQuickHelp,
+ showQuickHelp: showQuickHelp,
+ hideQuickHelp: hideQuickHelp
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.css
new file mode 100644
index 00000000..afef94ac
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.css
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Veil Service -- CSS file
+ */
+
+#veil {
+ z-index: 5000;
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.light #veil {
+ background-color: rgba(0,0,0,0.75);
+}
+.dark #veil {
+ background-color: rgba(64,64,64,0.75);
+}
+
+#veil .msg {
+ position: absolute;
+ padding: 60px;
+}
+
+#veil .msg p {
+ display: block;
+ font-size: 14pt;
+ font-style: italic;
+ color: #ddd;
+}
+
+#veil svg .glyph {
+ fill: #222;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.js
new file mode 100644
index 00000000..fc0530af
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/layer/veil.js
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Layer -- Veil Service
+
+ Provides a mechanism to display an overlaying div with information.
+ Used mainly for web socket connection interruption.
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $route, fs, ks, gs;
+
+ var veil;
+
+ function init() {
+ var wSize = fs.windowSize(),
+ ww = wSize.width,
+ wh = wSize.height,
+ shrink = wh * 0.3,
+ birdDim = wh - shrink,
+ birdCenter = (ww - birdDim) / 2,
+ svg;
+
+ veil = d3.select('#veil');
+
+ svg = veil.select('svg').attr({
+ width: ww,
+ height: wh
+ }).style('opacity', 0.2);
+
+ gs.addGlyph(svg, 'bird', birdDim, false, [birdCenter, shrink/2]);
+ }
+
+ // msg should be an array of strings
+ function show(msg) {
+ var msgs = fs.isA(msg) || [msg],
+ pdiv = veil.select('.msg');
+ pdiv.selectAll('p').remove();
+
+ msgs.forEach(function (line) {
+ pdiv.append('p').text(line);
+ });
+
+ veil.style('display', 'block');
+ ks.enableKeys(false);
+ }
+
+ function hide() {
+ veil.style('display', 'none');
+ ks.enableKeys(true);
+ }
+
+ // function that only invokes the veil if the caller is the current view
+ // TODO: review - is this deprecated ?
+ function lostServer(ctrlName, msg) {
+ if ($route.current.$$route.controller === ctrlName) {
+ $log.debug('VEIL-service: ', ctrlName);
+ show(msg)
+ } else {
+ $log.debug('VEIL-service: IGNORING ', ctrlName);
+ }
+ }
+
+ angular.module('onosLayer')
+ .factory('VeilService',
+ ['$log', '$route', 'FnService', 'KeyService', 'GlyphService',
+
+ function (_$log_, _$route_, _fs_, _ks_, _gs_) {
+ $log = _$log_;
+ $route = _$route_;
+ fs = _fs_;
+ ks = _ks_;
+ gs = _gs_;
+
+ return {
+ init: init,
+ show: show,
+ hide: hide,
+ lostServer: lostServer
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.css
new file mode 100644
index 00000000..2e86e86d
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.css
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Masthead -- CSS file
+ */
+
+#mast {
+ height: 36px;
+ padding: 4px;
+ vertical-align: baseline;
+}
+
+html[data-platform='iPad'] #mast {
+ padding-top: 16px;
+}
+
+.light #mast {
+ background-color: #bbb;
+ box-shadow: 0 2px 8px #777;
+}
+.dark #mast {
+ background-color: #444;
+ box-shadow: 0 2px 8px #222;
+}
+
+#mast .nav-menu-button {
+ width: 30px;
+ height: 30px;
+ margin-left: 8px;
+ margin-bottom: 4px;
+ cursor: pointer;
+}
+.light #mast .nav-menu-button:hover {
+ background-color: #ddd;
+}
+.dark #mast .nav-menu-button:hover {
+ background-color: #777;
+}
+
+#mast img.logo {
+ height: 38px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+#mast img.logo:hover {
+ /* need something better */
+ /*background-color: #888;*/
+}
+
+#mast .title {
+ font-size: 14pt;
+ font-style: italic;
+ vertical-align: 12px;
+}
+
+.light #mast .title {
+ color: #369;
+}
+.dark #mast .title {
+ color: #eee;
+}
+
+#mast-right {
+ display: inline-block;
+ padding-top: 8px;
+ padding-right: 16px;
+ float: right;
+ /*border: 1px solid red;*/
+}
+
+#mast-right a {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ text-decoration: none;
+}
+
+.light #mast-right a {
+ color: #369;
+}
+.dark #mast-right a {
+ color: #eee;
+}
+
+#mast-right a:hover {
+ color: #CE5650;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.html b/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.html
new file mode 100644
index 00000000..5bb488a8
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.html
@@ -0,0 +1,6 @@
+<!-- Masthead partial HTML -->
+<img class="nav-menu-button" src="data/img/nav-menu.png"
+ ng-click="mastCtrl.toggleNav()"></div>
+<img class="logo" src="data/img/onos-logo.png">
+<span class="title">Open Network Operating System</span>
+<div id="mast-right"><a href="rs/logout">logout</a></div>
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.js
new file mode 100644
index 00000000..1cde58c7
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/mast/mast.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Masthead Module
+ */
+(function () {
+ 'use strict';
+
+ // injected services
+ var $log;
+
+ // configuration
+ var mastHeight = 36,
+ padMobile = 16;
+
+ angular.module('onosMast', ['onosNav'])
+ .controller('MastCtrl', ['$log', 'NavService', function (_$log_, ns) {
+ var self = this;
+
+ $log = _$log_;
+
+ // initialize mast controller here...
+ self.radio = null;
+
+ // delegate to NavService
+ self.toggleNav = function () {
+ ns.toggleNav();
+ };
+
+ $log.log('MastCtrl has been created');
+ }])
+
+ // also define a service to allow lookup of mast height.
+ .factory('MastService', ['FnService', function (fs) {
+ return {
+ mastHeight: function () {
+ return fs.isMobile() ? mastHeight + padMobile : mastHeight;
+ }
+ }
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.css
new file mode 100644
index 00000000..4d2c4e91
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.css
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Navigation -- CSS file
+ */
+
+#nav {
+ position: absolute;
+ top: 45px;
+ left: 1px;
+ padding: 0;
+ z-index: 3000;
+ visibility: hidden;
+}
+
+html[data-platform='iPad'] #nav {
+ top: 57px;
+}
+
+.light #nav {
+ background-color: #bbb;
+ box-shadow: 0 2px 8px #777;
+}
+.dark #nav {
+ background-color: #444;
+ box-shadow: 0 2px 8px #111;
+}
+
+#nav .nav-hdr {
+ font-style: italic;
+ padding: 6px 8px 6px 8px;
+}
+
+.light #nav .nav-hdr {
+ color: #ddd;
+ border-bottom: solid 1px #999;
+ background-color: #aaa;
+}
+.dark #nav .nav-hdr {
+ color: #888;
+ border-bottom: solid 1px #444;
+ background-color: #555;
+}
+
+#nav a {
+ text-decoration: none;
+ font-size: 14pt;
+ display: block;
+ padding: 8px 16px 6px 12px;
+}
+
+.light #nav a {
+ color: #369;
+ border-bottom: solid #ccc 1px;
+}
+.dark #nav a {
+ color: #eee;
+ border-bottom: solid #333 1px;
+}
+
+.light #nav a:hover {
+ background-color: #ddd;
+}
+.dark #nav a:hover {
+ background-color: #777;
+}
+
+#nav a div {
+ display: inline-block;
+ vertical-align: middle;
+ padding-right: 4px;
+}
+
+#nav a div svg.embeddedIcon g.icon .glyph {
+ fill: #c66;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.js
new file mode 100644
index 00000000..36ef599e
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/nav/nav.js
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Navigation Module
+ */
+(function () {
+ 'use strict';
+
+ // injected dependencies
+ var $log, $location, $window, fs;
+
+ // internal state
+ var navShown = false;
+
+ function updatePane() {
+ var vis = navShown ? 'visible' : 'hidden';
+ d3.select('#nav').style('visibility', vis);
+ }
+
+
+ function showNav() {
+ navShown = true;
+ updatePane();
+ }
+ function hideNav() {
+ navShown = false;
+ updatePane();
+ }
+ function toggleNav() {
+ navShown = !navShown;
+ updatePane();
+ }
+ function hideIfShown() {
+ if (navShown) {
+ hideNav();
+ return true;
+ }
+ return false;
+ }
+
+ function navTo(path, params) {
+ var url;
+ if (!path) {
+ $log.warn('Not a valid navigation path');
+ return null;
+ }
+ $location.url('/' + path);
+
+ if (fs.isO(params)) {
+ $location.search(params);
+ } else if (params !== undefined) {
+ $log.warn('Query params not an object', params);
+ }
+
+ url = $location.absUrl();
+ $log.log('Navigating to ', url);
+ $window.location.href = url;
+ }
+
+ angular.module('onosNav', [])
+ .controller('NavCtrl', ['$log',
+
+ function (_$log_) {
+ var self = this;
+ $log = _$log_;
+
+ self.hideNav = hideNav;
+ $log.log('NavCtrl has been created');
+ }
+ ])
+ .factory('NavService',
+ ['$log', '$location', '$window', 'FnService',
+
+ function (_$log_, _$location_, _$window_, _fs_) {
+ $log = _$log_;
+ $location = _$location_;
+ $window = _$window_;
+ fs = _fs_;
+
+ return {
+ showNav: showNav,
+ hideNav: hideNav,
+ toggleNav: toggleNav,
+ hideIfShown: hideIfShown,
+ navTo: navTo
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/remote.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/remote.js
new file mode 100644
index 00000000..453d08f9
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/remote.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Remote Communications Module
+ */
+(function () {
+ 'use strict';
+
+ angular.module('onosRemote', ['onosUtil']);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/rest.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/rest.js
new file mode 100644
index 00000000..04b9fe02
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/rest.js
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Remote Communications Module -- REST Service
+ */
+(function () {
+ 'use strict';
+
+ var $log;
+
+ angular.module('onosRemote')
+ .factory('RestService',
+ ['$log', '$http', 'FnService', 'UrlFnService',
+
+ function (_$log_, $http, fs, ufs) {
+ $log = _$log_;
+
+ function get(url, callback, errorCb) {
+ var fullUrl = ufs.rsUrl(url);
+
+ $http.get(fullUrl).then(function (response) {
+ // success
+ callback(response.data);
+ }, function (response) {
+ // error
+ var emsg = 'Failed to retrieve JSON data: ' + fullUrl;
+ $log.warn(emsg, response.status, response.data);
+ if (errorCb) {
+ errorCb(emsg);
+ }
+ });
+ }
+
+ // TODO: test this
+ function post(url, data, callbacks) {
+ var fullUrl = ufs.rsUrl(url);
+ $http.post(fullUrl, data).then(function (response) {
+ // success
+ if (callbacks && fs.isF(callbacks.success)) {
+ callbacks.success(response.data);
+ }
+ }, function (response) {
+ // error
+ var msg = 'Problem with $http post request: ' + fullUrl;
+ $log.warn(msg, response.status, response.data);
+
+ if (callbacks && fs.isF(callbacks.error)) {
+ callbacks.error(msg);
+ }
+ });
+ }
+
+ return {
+ get: get,
+ post: post
+ };
+ }]);
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/urlfn.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/urlfn.js
new file mode 100644
index 00000000..6ec01b41
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/urlfn.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Remote -- General Purpose Functions
+ */
+(function () {
+ 'use strict';
+
+ var uiContext = '/onos/ui/',
+ rsSuffix = uiContext + 'rs/',
+ wsSuffix = uiContext + 'websock/';
+
+ angular.module('onosRemote')
+ .factory('UrlFnService', ['$location', function ($loc) {
+
+ function matchSecure(protocol) {
+ var p = $loc.protocol(),
+ secure = (p === 'https' || p === 'wss');
+ return secure ? protocol + 's' : protocol;
+ }
+
+ function urlBase(protocol, port, host) {
+ return matchSecure(protocol) + '://' +
+ (host || $loc.host()) + ':' + (port || $loc.port());
+ }
+
+ function httpPrefix(suffix) {
+ return urlBase('http') + suffix;
+ }
+
+ function wsPrefix(suffix, wsport, host) {
+ return urlBase('ws', wsport, host) + suffix;
+ }
+
+ function rsUrl(path) {
+ return httpPrefix(rsSuffix) + path;
+ }
+
+ function wsUrl(path, wsport, host) {
+ return wsPrefix(wsSuffix, wsport, host) + path;
+ }
+
+ return {
+ rsUrl: rsUrl,
+ wsUrl: wsUrl
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/websocket.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/websocket.js
new file mode 100644
index 00000000..1c03d6fd
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/websocket.js
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Remote -- Web Socket Service
+ */
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, $loc, fs, ufs, wsock, vs;
+
+ // internal state
+ var webSockOpts, // web socket options
+ ws = null, // web socket reference
+ wsUp = false, // web socket is good to go
+ sid = 0, // event sequence identifier
+ handlers = {}, // event handler bindings
+ pendingEvents = [], // events TX'd while socket not up
+ host, // web socket host
+ url, // web socket URL
+ clusterNodes = [], // ONOS instances data for failover
+ clusterIndex = -1, // the instance to which we are connected
+ connectRetries = 0, // limit our attempts at reconnecting
+ openListeners = {}, // registered listeners for websocket open()
+ nextListenerId = 1; // internal ID for open listeners
+
+ // =======================
+ // === Bootstrap Handler
+
+ var builtinHandlers = {
+ bootstrap: function (data) {
+ clusterNodes = data.clusterNodes;
+ clusterNodes.forEach(function (d, i) {
+ if (d.uiAttached) {
+ clusterIndex = i;
+ $log.info('Connected to cluster node ' + d.ip);
+ // TODO: add connect info to masthead somewhere
+ }
+ });
+ }
+ };
+
+ // ==========================
+ // === Web socket callbacks
+
+ function handleOpen() {
+ $log.info('Web socket open - ', url);
+ vs.hide();
+
+ if (fs.debugOn('txrx')) {
+ $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
+ }
+ pendingEvents.forEach(function (ev) {
+ _send(ev);
+ });
+ pendingEvents = [];
+
+ connectRetries = 0;
+ wsUp = true;
+ informListeners(host, url);
+ }
+
+ // Handles the specified (incoming) message using handler bindings.
+ function handleMessage(msgEvent) {
+ var ev, h;
+
+ try {
+ ev = JSON.parse(msgEvent.data);
+ } catch (e) {
+ $log.error('Message.data is not valid JSON', msgEvent.data, e);
+ return null;
+ }
+ if (fs.debugOn('txrx')) {
+ $log.debug(' << *Rx* ', ev.event, ev.payload);
+ }
+
+ if (h = handlers[ev.event]) {
+ try {
+ h(ev.payload);
+ } catch (e) {
+ $log.error('Problem handling event:', ev, e);
+ return null;
+ }
+ } else {
+ $log.warn('Unhandled event:', ev);
+ }
+
+ }
+
+ function handleClose() {
+ var gsucc;
+
+ $log.info('Web socket closed');
+ wsUp = false;
+
+ if (gsucc = findGuiSuccessor()) {
+ createWebSocket(webSockOpts, gsucc);
+ } else {
+ // If no controllers left to contact, show the Veil...
+ vs.show([
+ 'Oops!',
+ 'Web-socket connection to server closed...',
+ 'Try refreshing the page.'
+ ]);
+ }
+ }
+
+
+ // ==============================
+ // === Private Helper Functions
+
+ function findGuiSuccessor() {
+ var ncn = clusterNodes.length,
+ ip, node;
+
+ while (connectRetries < ncn && !ip) {
+ connectRetries++;
+ clusterIndex = (clusterIndex + 1) % ncn;
+ node = clusterNodes[clusterIndex];
+ ip = node && node.ip;
+ }
+
+ return ip;
+ }
+
+ function informListeners(host, url) {
+ angular.forEach(openListeners, function (lsnr) {
+ lsnr.cb(host, url);
+ });
+ }
+
+ function _send(ev) {
+ if (fs.debugOn('txrx')) {
+ $log.debug(' *Tx* >> ', ev.event, ev.payload);
+ }
+ ws.send(JSON.stringify(ev));
+ }
+
+ function noHandlersWarn(handlers, caller) {
+ if (!handlers || fs.isEmptyObject(handlers)) {
+ $log.warn('WSS.' + caller + '(): no event handlers');
+ return true;
+ }
+ return false;
+ }
+
+ // ===================
+ // === API Functions
+
+ // Required for unit tests to set to known state
+ function resetSid() {
+ sid = 0;
+ }
+ function resetState() {
+ webSockOpts = undefined;
+ ws = null;
+ wsUp = false;
+ host = undefined;
+ url = undefined;
+ pendingEvents = [];
+ handlers = {};
+ sid = 0;
+ clusterNodes = [];
+ clusterIndex = -1;
+ connectRetries = 0;
+ openListeners = {};
+ nextListenerId = 1;
+ }
+
+ // Currently supported opts:
+ // wsport: web socket port (other than default 8181)
+ // host: if defined, is the host address to use
+ function createWebSocket(opts, _host_) {
+ var wsport = (opts && opts.wsport) || null;
+
+ webSockOpts = opts; // preserved for future calls
+
+ host = _host_ || $loc.host();
+ url = ufs.wsUrl('core', wsport, _host_);
+
+ $log.debug('Attempting to open websocket to: ' + url);
+ ws = wsock.newWebSocket(url);
+ if (ws) {
+ ws.onopen = handleOpen;
+ ws.onmessage = handleMessage;
+ ws.onclose = handleClose;
+ }
+ // Note: Wsock logs an error if the new WebSocket call fails
+ return url;
+ }
+
+ // Binds the message handlers to their message type (event type) as
+ // specified in the given map. Note that keys are the event IDs; values
+ // are either:
+ // * the event handler function, or
+ // * an API object which has an event handler for the key
+ //
+ function bindHandlers(handlerMap) {
+ var m,
+ dups = [];
+
+ if (noHandlersWarn(handlerMap, 'bindHandlers')) {
+ return null;
+ }
+ m = d3.map(handlerMap);
+
+ m.forEach(function (eventId, api) {
+ var fn = fs.isF(api) || fs.isF(api[eventId]);
+ if (!fn) {
+ $log.warn(eventId + ' handler not a function');
+ return;
+ }
+
+ if (handlers[eventId]) {
+ dups.push(eventId);
+ } else {
+ handlers[eventId] = fn;
+ }
+ });
+ if (dups.length) {
+ $log.warn('duplicate bindings ignored:', dups);
+ }
+ }
+
+ // Unbinds the specified message handlers.
+ // Expected that the same map will be used, but we only care about keys
+ function unbindHandlers(handlerMap) {
+ var m;
+
+ if (noHandlersWarn(handlerMap, 'unbindHandlers')) {
+ return null;
+ }
+ m = d3.map(handlerMap);
+
+ m.forEach(function (eventId) {
+ delete handlers[eventId];
+ });
+ }
+
+ function addOpenListener(callback) {
+ var id = nextListenerId++,
+ cb = fs.isF(callback),
+ o = { id: id, cb: cb };
+
+ if (cb) {
+ openListeners[id] = o;
+ } else {
+ $log.error('WSS.addOpenListener(): callback not a function');
+ o.error = 'No callback defined';
+ }
+ return o;
+ }
+
+ function removeOpenListener(lsnr) {
+ var id = fs.isO(lsnr) && lsnr.id,
+ o;
+ if (!id) {
+ $log.warn('WSS.removeOpenListener(): invalid listener', lsnr);
+ return null;
+ }
+ o = openListeners[id];
+
+ if (o) {
+ delete openListeners[id];
+ }
+ }
+
+ // Formulates an event message and sends it via the web-socket.
+ // If the websocket is not up yet, we store it in a pending list.
+ function sendEvent(evType, payload) {
+ var ev = {
+ event: evType,
+ sid: ++sid,
+ payload: payload || {}
+ };
+
+ if (wsUp) {
+ _send(ev);
+ } else {
+ pendingEvents.push(ev);
+ }
+ }
+
+
+ // ============================
+ // ===== Definition of module
+ angular.module('onosRemote')
+ .factory('WebSocketService',
+ ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
+ 'VeilService',
+
+ function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
+ $log = _$log_;
+ $loc = _$loc_;
+ fs = _fs_;
+ ufs = _ufs_;
+ wsock = _wsock_;
+ vs = _vs_;
+
+ bindHandlers(builtinHandlers);
+
+ return {
+ resetSid: resetSid,
+ resetState: resetState,
+ createWebSocket: createWebSocket,
+ bindHandlers: bindHandlers,
+ unbindHandlers: unbindHandlers,
+ addOpenListener: addOpenListener,
+ removeOpenListener: removeOpenListener,
+ sendEvent: sendEvent
+ };
+ }
+ ]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsevent.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsevent.js
new file mode 100644
index 00000000..02d71b52
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsevent.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ DEPRECATED: to be deleted
+ ONOS GUI -- Remote -- Web Socket Event Service
+ */
+(function () {
+ 'use strict';
+
+ var sid = 0;
+
+ angular.module('onosRemote')
+ .factory('WsEventService', [function () {
+
+ function sendEvent(ws, evType, payload) {
+ var p = payload || {};
+
+ ws.send({
+ event: evType,
+ sid: ++sid,
+ payload: p
+ });
+ }
+
+ function resetSid() {
+ sid = 0;
+ }
+
+ return {
+ sendEvent: sendEvent,
+ resetSid: resetSid
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsock.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsock.js
new file mode 100644
index 00000000..0c58a798
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/remote/wsock.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Remote -- Web Socket Wrapper Service
+
+ This service provided specifically so that it can be mocked in unit tests.
+ */
+(function () {
+ 'use strict';
+
+ angular.module('onosRemote')
+ .factory('WSock', ['$log', function ($log) {
+
+ function newWebSocket(url) {
+ var ws = null;
+ try {
+ ws = new WebSocket(url);
+ } catch (e) {
+ $log.error('Unable to create web socket:', e);
+ }
+ return ws;
+ }
+
+ return {
+ newWebSocket: newWebSocket
+ };
+ }]);
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/geodata.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/geodata.js
new file mode 100644
index 00000000..6c54bb45
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/geodata.js
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- SVG -- GeoData Service
+ */
+
+/*
+ The GeoData Service facilitates the fetching and caching of TopoJSON data
+ from the server, as well as providing a way of creating a path generator
+ for that data, to be used to render the map in an SVG layer.
+
+ A TopoData object can be fetched by ID. IDs that start with an asterisk
+ identify maps bundled with the GUI. IDs that do not start with an
+ asterisk are assumed to be URLs to externally provided data.
+
+ var topodata = GeoDataService.fetchTopoData('*continental-us');
+
+ The path generator can then be created for that data-set:
+
+ var gen = GeoDataService.createPathGenerator(topodata, opts);
+
+ opts is an optional argument that allows the override of default settings:
+ {
+ objectTag: 'states',
+ projection: d3.geo.mercator(),
+ logicalSize: 1000,
+ mapFillScale: .95
+ };
+
+ The returned object (gen) comprises transformed data (TopoJSON -> GeoJSON),
+ the D3 path generator function, and the settings used ...
+
+ {
+ geodata: { ... },
+ pathgen: function (...) { ... },
+ settings: { ... }
+ }
+ */
+
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $http, fs;
+
+ // internal state
+ var cache = d3.map(),
+ bundledUrlPrefix = 'data/map/';
+
+ function getUrl(id) {
+ if (id[0] === '*') {
+ return bundledUrlPrefix + id.slice(1) + '.topojson';
+ }
+ return id + '.topojson';
+ }
+
+
+ // start afresh...
+ function clearCache() {
+ cache = d3.map();
+ }
+
+ // returns a promise decorated with:
+ // .meta -- id, url, and whether the data was cached
+ // .topodata -- TopoJSON data (on response from server)
+
+ function fetchTopoData(id) {
+ if (!fs.isS(id)) {
+ return null;
+ }
+ var url = getUrl(id),
+ promise = cache.get(id);
+
+ if (!promise) {
+ // need to fetch the data, build the object,
+ // cache it, and return it.
+ promise = $http.get(url);
+
+ promise.meta = {
+ id: id,
+ url: url,
+ wasCached: false
+ };
+
+ promise.then(function (response) {
+ // success
+ promise.topodata = response.data;
+ }, function (response) {
+ // error
+ $log.warn('Failed to retrieve map TopoJSON data: ' + url,
+ response.status, response.data);
+ });
+
+ cache.set(id, promise);
+
+ } else {
+ promise.meta.wasCached = true;
+ }
+
+ return promise;
+ }
+
+ var defaultGenSettings = {
+ objectTag: 'states',
+ projection: d3.geo.mercator(),
+ logicalSize: 1000,
+ mapFillScale: .95
+ };
+
+ // converts given TopoJSON-format data into corresponding GeoJSON
+ // data, and creates a path generator for that data.
+ function createPathGenerator(topoData, opts) {
+ var settings = angular.extend({}, defaultGenSettings, opts),
+ topoObject = topoData.objects[settings.objectTag],
+ geoData = topojson.feature(topoData, topoObject),
+ proj = settings.projection,
+ dim = settings.logicalSize,
+ mfs = settings.mapFillScale,
+ path = d3.geo.path().projection(proj);
+
+ rescaleProjection(proj, mfs, dim, path, geoData);
+
+ // return the results
+ return {
+ geodata: geoData,
+ pathgen: path,
+ settings: settings
+ };
+ }
+
+ function rescaleProjection(proj, mfs, dim, path, geoData) {
+ // adjust projection scale and translation to fill the view
+ // with the map
+
+ // start with unit scale, no translation..
+ proj.scale(1).translate([0, 0]);
+
+ // figure out dimensions of map data..
+ var b = path.bounds(geoData),
+ x1 = b[0][0],
+ y1 = b[0][1],
+ x2 = b[1][0],
+ y2 = b[1][1],
+ dx = x2 - x1,
+ dy = y2 - y1,
+ x = (x1 + x2) / 2,
+ y = (y1 + y2) / 2;
+
+ // size map to 95% of minimum dimension to fill space..
+ var s = mfs / Math.min(dx / dim, dy / dim),
+ t = [dim / 2 - s * x, dim / 2 - s * y];
+
+ // set new scale, translation on the projection..
+ proj.scale(s).translate(t);
+ }
+
+ angular.module('onosSvg')
+ .factory('GeoDataService', ['$log', '$http', 'FnService',
+ function (_$log_, _$http_, _fs_) {
+ $log = _$log_;
+ $http = _$http_;
+ fs = _fs_;
+
+
+ return {
+ clearCache: clearCache,
+ fetchTopoData: fetchTopoData,
+ createPathGenerator: createPathGenerator,
+ rescaleProjection: rescaleProjection
+ };
+ }]);
+}()); \ No newline at end of file
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.css
new file mode 100644
index 00000000..82910654
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.css
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Glyph Service -- CSS file
+ */
+
+svg .glyph {
+ stroke: none;
+ fill-rule: evenodd;
+}
+
+.light svg .glyph,
+.dark svg .glyph.overlay {
+ fill: black;
+}
+
+.dark svg .glyph,
+.light svg .glyph.overlay {
+ fill: white;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.js
new file mode 100644
index 00000000..838a2ac0
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/glyph.js
@@ -0,0 +1,646 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- SVG -- Glyph Service
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, fs, sus;
+
+ // internal state
+ var glyphs = d3.map();
+
+ // ----------------------------------------------------------------------
+ // Base set of Glyphs...
+
+ var birdData = {
+ _bird: "352 224 113 112",
+ bird: "M427.7,300.4 c-6.9,0.6-13.1,5-19.2,7.1c-18.1,6.2-33.9," +
+ "9.1-56.5,4.7c24.6,17.2,36.6,13,63.7,0.1c-0.5,0.6-0.7,1.3-1.3," +
+ "1.9c1.4-0.4,2.4-1.7,3.4-2.2c-0.4,0.7-0.9,1.5-1.4,1.9c2.2-0.6," +
+ "3.7-2.3,5.9-3.9c-2.4,2.1-4.2,5-6,8c-1.5,2.5-3.1,4.8-5.1,6.9c-1," +
+ "1-1.9,1.9-2.9,2.9c-1.4,1.3-2.9,2.5-5.1,2.9c1.7,0.1,3.6-0.3,6.5" +
+ "-1.9c-1.6,2.4-7.1,6.2-9.9,7.2c10.5-2.6,19.2-15.9,25.7-18c18.3" +
+ "-5.9,13.8-3.4,27-14.2c1.6-1.3,3-1,5.1-0.8c1.1,0.1,2.1,0.3,3.2," +
+ "0.5c0.8,0.2,1.4,0.4,2.2,0.8l1.8,0.9c-1.9-4.5-2.3-4.1-5.9-6c-2.3" +
+ "-1.3-3.3-3.8-6.2-4.9c-7.1-2.6-11.9,11.7-11.7-5c0.1-8,4.2-14.4," +
+ "6.4-22c1.1-3.8,2.3-7.6,2.4-11.5c0.1-2.3,0-4.7-0.4-7c-2-11.2-8.4" +
+ "-21.5-19.7-24.8c-1-0.3-1.1-0.3-0.9,0c9.6,17.1,7.2,38.3,3.1,54.2" +
+ "C429.9,285.5,426.7,293.2,427.7,300.4z"
+ },
+
+ // TODO: ONOS-2566 glyphs for device types:
+ // otn, roadm_otn, firewall, balancer, ips, ids,
+ // controller, virtual, fiber_switch, other
+
+ glyphDataSet = {
+ _viewbox: "0 0 110 110",
+
+ unknown: "M35,40a5,5,0,0,1,5-5h30a5,5,0,0,1,5,5v30a5,5,0,0,1-5,5" +
+ "h-30a5,5,0,0,1-5-5z",
+
+ node: "M15,100a5,5,0,0,1-5-5v-65a5,5,0,0,1,5-5h80a5,5,0,0,1,5,5" +
+ "v65a5,5,0,0,1-5,5zM14,22.5l11-11a10,3,0,0,1,10-2h40a10,3,0,0,1," +
+ "10,2l11,11zM16,35a5,5,0,0,1,10,0a5,5,0,0,1-10,0z",
+
+ switch: "M10,20a10,10,0,0,1,10-10h70a10,10,0,0,1,10,10v70a10,10," +
+ "0,0,1-10,10h-70a10,10,0,0,1-10-10zM60,26l12,0,0-8,18,13-18,13,0" +
+ "-8-12,0zM60,60l12,0,0-8,18,13-18,13,0-8-12,0zM50,40l-12,0,0-8" +
+ "-18,13,18,13,0-8,12,0zM50,74l-12,0,0-8-18,13,18,13,0-8,12,0z",
+
+ roadm: "M10,35l25-25h40l25,25v40l-25,25h-40l-25-25zM58,26l12,0,0" +
+ "-8,18,13-18,13,0-8-12,0zM58,60l12,0,0-8,18,13-18,13,0-8-12,0z" +
+ "M52,40l-12,0,0-8-18,13,18,13,0-8,12,0zM52,74l-12,0,0-8-18,13," +
+ "18,13,0-8,12,0z",
+
+ endstation: "M10,15a5,5,0,0,1,5-5h65a5,5,0,0,1,5,5v80a5,5,0,0,1" +
+ "-5,5h-65a5,5,0,0,1-5-5zM87.5,14l11,11a3,10,0,0,1,2,10v40a3,10," +
+ "0,0,1,-2,10l-11,11zM17,19a2,2,0,0,1,2-2h56a2,2,0,0,1,2,2v26a2," +
+ "2,0,0,1-2,2h-56a2,2,0,0,1-2-2zM20,20h54v10h-54zM20,33h54v10h" +
+ "-54zM42,70a5,5,0,0,1,10,0a5,5,0,0,1-10,0z",
+
+ router: "M10,55A45,45,0,0,1,100,55A45,45,0,0,1,10,55M20,50l12,0," +
+ "0-8,18,13-18,13,0-8-12,0zM90,50l-12,0,0-8-18,13,18,13,0-8,12,0z" +
+ "M50,47l0-12-8,0,13-18,13,18-8,0,0,12zM50,63l0,12-8,0,13,18,13" +
+ "-18-8,0,0-12z",
+
+ bgpSpeaker: "M10,40a45,35,0,0,1,90,0Q100,77,55,100Q10,77,10,40z" +
+ "M50,29l12,0,0-8,18,13-18,13,0-8-12,0zM60,57l-12,0,0-8-18,13," +
+ "18,13,0-8,12,0z",
+
+ chain: "M60.4,77.6c-4.9,5.2-9.6,11.3-15.3,16.3c-8.6,7.5-20.4,6.8" +
+ "-28-0.8c-7.7-7.7-8.4-19.6-0.8-28.4c6.5-7.4,13.5-14.4,20.9-20.9" +
+ "c7.5-6.7,19.2-6.7,26.5-0.8c3.5,2.8,4.4,6.1,2.2,8.7c-2.7,3.1" +
+ "-5.5,2.5-8.5,0.3c-4.7-3.4-9.7-3.2-14,0.9C37.1,58.7,31,64.8," +
+ "25.2,71c-4.2,4.4-4.2,10.6-0.6,14.3c3.7,3.7,9.7,3.7,14.3-0.4" +
+ "c2.9-2.5,5.3-5.5,8.3-8c1-0.9,3-1.1,4.4-0.9C54.8,76.3,57.9,77.1," +
+ "60.4,77.6zM49.2,32.2c5-5.2,9.7-10.9,15.2-15.7c12.8-11,31.2" +
+ "-4.9,34.3,11.2C100,34.2,98.3,40.2,94,45c-6.7,7.4-13.7,14.6" +
+ "-21.2,21.2C65.1,73,53.2,72.7,46,66.5c-3.2-2.8-3.9-5.8-1.6-8.4" +
+ "c2.6-2.9,5.3-2.4,8.2-0.3c5.2,3.7,10,3.3,14.7-1.1c5.8-5.6,11.6" +
+ "-11.3,17.2-17.2c4.6-4.8,4.9-11.1,0.9-15c-3.9-3.9-10.1-3.4-15," +
+ "1.2c-3.1,2.9-5.7,7.4-9.3,8.5C57.6,35.3,53,33,49.2,32.2z",
+
+ crown: "M99.5,21.6c0,3-2.3,5.4-5.1,5.4c-0.3,0-0.7,0-1-0.1c-1.8," +
+ "4-4.8,10-7.2,17.3c-3.4,10.6-0.9,26.2,2.7,27.3C90.4,72,91.3," +
+ "75,88,75H22.7c-3.3,0-2.4-3-0.9-3.5c3.6-1.1,6.1-16.7,2.7-27.3" +
+ "c-2.4-7.4-5.4-13.5-7.2-17.5c-0.5,0.2-1,0.3-1.6,0.3c-2.8,0" +
+ "-5.1-2.4-5.1-5.4c0-3,2.3-5.4,5.1-5.4c2.8,0,5.1,2.4,5.1,5.4c0," +
+ "1-0.2,1.9-0.7,2.7c0.7,0.8,1.4,1.6,2.4,2.6c8.8,8.9,11.9,12.7," +
+ "18.1,11.7c6.5-1,11-8.2,13.3-14.1c-2-0.8-3.3-2.7-3.3-5.1c0-3," +
+ "2.3-5.4,5.1-5.4c2.8,0,5.1,2.4,5.1,5.4c0,2.5-1.6,4.5-3.7,5.2" +
+ "c2.3,5.9,6.8,13,13.2,14c6.2,1,9.3-2.7,18.1-11.7c0.7-0.7,1.4" +
+ "-1.5,2-2.1c-0.6-0.9-1-2-1-3.1c0-3,2.3-5.4,5.1-5.4C97.2,16.2," +
+ "99.5,18.6,99.5,21.6zM92,87.9c0,2.2-1.7,4.1-3.8,4.1H22.4c" +
+ "-2.1,0-4.4-1.9-4.4-4.1v-3.3c0-2.2,2.3-4.5,4.4-4.5h65.8c2.1," +
+ "0,3.8,2.3,3.8,4.5V87.9z",
+
+ lock: "M79.4,48.6h-2.7c0.2-5.7-0.2-20.4-7.9-28.8c-3.6-3.9-8.3" +
+ "-5.9-13.7-5.9c-5.4,0-10.2,2-13.8,5.9c-7.8,8.4-8.3,23.2-8.1,28.8" +
+ "h-2.7c-4.4,0-8,2.6-8,5.9v35.7c0,3.3,3.6,5.9,8,5.9h48.9c4.4,0," +
+ "8-2.6,8-5.9V54.5C87.5,51.3,83.9,48.6,79.4,48.6z M48.1,26.1c1.9" +
+ "-2,4.1-2.9,7-2.9c2.9,0,5.1,0.9,6.9,2.9c5,5.4,5.6,17.1,5.4,22.6" +
+ "h-25C42.3,43.1,43.1,31.5,48.1,26.1z",
+
+ topo: 'M97.2,76.3H86.6l-7.7-21.9H82c1,0,1.9-0.8,1.9-1.9V35.7c' +
+ '0-1-0.8-1.9-1.9-1.9H65.2c-1,0-1.9,0.8-1.9,1.9v2.6L33.4,26.1v-11' +
+ 'c0-1-0.8-1.9-1.9-1.9H14.7c-1,0-1.9,0.8-1.9,1.9v16.8c0,1,0.8,' +
+ '1.9,1.9,1.9h16.8c1,0,1.9-0.8,1.9-1.9v-2.6l29.9,12.2v9L30.5,76.9' +
+ 'c-0.3-0.3-0.8-0.5-1.3-0.5H12.4c-1,0-1.9,0.8-1.9,1.9V95c0,1,0.8,' +
+ '1.9,1.9,1.9h16.8c1,0,1.9-0.8,1.9-1.9v-6.9h47.4V95c0,1,0.8,1.9,' +
+ '1.9,1.9h16.8c1,0,1.9-0.8,1.9-1.9V78.2C99.1,77.2,98.2,76.3,97.2,' +
+ '76.3z M31.1,85.1v-4.9l32.8-26.4c0.3,0.3,0.8,0.5,1.3,0.5h10.5l' +
+ '7.7,21.9h-3c-1,0-1.9,0.8-1.9,1.9v6.9H31.1z',
+
+ refresh: 'M102.6,40.8L88.4,70.5L69.8,43.4L80,42.6c-0.7-2.3-1.7-' +
+ '5.1-3.4-7.8C71.8,27,64,23.1,53.5,23.1c-19.5,0-24.8,11.2-24.8,' +
+ '11.3l-10.1-4.3c0.3-0.7,7.9-18,35-18c24.8,0,35,17.3,37.7,29.6L' +
+ '102.6,40.8z M81.5,74.5c-0.2,0.5-5.5,11.4-24.9,11.4c-10.5,0-18.3' +
+ '-3.9-23.1-11.7c-1.7-2.8-2.8-5.6-3.4-7.8l10.2-0.8L21.7,38.5L7.5,' +
+ '68.2l11.4-0.9c2.7,12.3,12.9,29.6,37.7,29.6c26.9,0,34.6-17.2,34.9' +
+ '-18L81.5,74.5z',
+
+ garbage: 'M94.6,20.2c0,2.7-2.1,4.8-4.8,4.8H19.2c-2.7,0-4.8-2.1-' +
+ '4.8-4.8s2.1-4.8,4.8-4.8h27.6c-0.8-0.7-1.4-1.7-1.4-2.9c0-2.1,1.7-' +
+ '3.9,3.9-3.9h10.4c2.1,0,3.9,1.7,3.9,3.9c0,1.2-0.5,2.2-1.4,2.9h' +
+ '27.6C92.5,15.4,94.6,17.6,94.6,20.2z M91,33.4v64.8c0,2-1.7,3.6-' +
+ '3.8,3.6h-65c-2.1,0-3.8-1.6-3.8-3.6V33.4c0-2,1.7-3.6,3.8-3.6h65C' +
+ '89.3,29.8,91,31.4,91,33.4z M31.5,37.7c0-2.1-1.2-3.8-2.7-3.8h-0.7' +
+ 'c-1.5,0-2.7,1.7-2.7,3.8v55.9c0,2.1,1.2,3.8,2.7,3.8h0.7c1.5,0,2.7' +
+ '-1.7,2.7-3.8V37.7z M58.5,37.7c0-2.1-1.8-3.8-4-3.8h-1c-2.2,0-4,' +
+ '1.7-4,3.8v55.9c0,2.1,1.8,3.8,4,3.8h1c2.2,0,4-1.7,4-3.8V37.7z M' +
+ '83.5,37.7c0-2.1-1.2-3.8-2.7-3.8h-0.7c-1.5,0-2.7,1.7-2.7,3.8v55.9' +
+ 'c0,2.1,1.2,3.8,2.7,3.8h0.7c1.5,0,2.7-1.7,2.7-3.8V37.7z',
+
+ loading: 'M103.1,53.1c0,0,0,0.2,0,0.5c0,0.2,0,0.4,0,0.7c0,0.3,0,0' +
+ '.6,0,0.9c-0.1,1.3-0.2,3-0.5,4.8c-0.5,3.4-1.6,6.8-1.6,6.8l-9.2-2.' +
+ '7c0,0,0.8-2.7,1.1-5.5c0.2-1.4,0.3-2.8,0.3-3.8c0-0.3,0-0.5,0-0.7c' +
+ '0-0.2,0-0.4,0-0.6c0-0.3,0-0.5,0-0.5L103.1,53.1z M87.3,74.3c0,0-0' +
+ '.1,0.2-0.3,0.5c-0.2,0.3-0.4,0.6-0.7,1.1c-0.6,0.9-1.4,2-2.3,3.1c-' +
+ '1.8,2.2-3.9,4.1-3.9,4.1l5.7,6.5c0,0,0.7-0.5,1.6-1.4c1-0.9,2.2-2.' +
+ '1,3.3-3.4c1.1-1.3,2.2-2.6,3-3.7c0.4-0.5,0.7-1,0.9-1.3c0.2-0.3,0.' +
+ '3-0.4,0.3-0.4L87.3,74.3z M70.8,89.2c0,0-0.2,0.1-0.5,0.2c-0.3,0.2' +
+ '-0.6,0.3-1.2,0.5c-1,0.4-2.3,0.9-3.7,1.4c-2.7,0.9-5.5,1.3-5.5,1.3' +
+ 'l1.1,7.6c0,0,0.8-0.1,2.1-0.3c1.3-0.2,2.9-0.6,4.6-1c1.6-0.5,3.3-1' +
+ ',4.5-1.5c0.6-0.2,1.2-0.5,1.5-0.6c0.3-0.1,0.5-0.2,0.5-0.2L70.8,89' +
+ '.2z M48.6,92.9c0,0-0.2,0-0.5-0.1c-0.4,0-0.7-0.1-1.3-0.2c-1.1-0.2' +
+ '-2.5-0.5-3.9-0.8c-2.8-0.7-5.4-1.9-5.4-1.9L34.6,96c0,0,3,1.5,6.3,' +
+ '2.5c1.6,0.5,3.3,0.9,4.5,1.2c0.6,0.1,1.2,0.2,1.5,0.3c0.3,0.1,0.5,' +
+ '0.1,0.5,0.1L48.6,92.9z M27.6,83.8c0,0-0.1-0.1-0.4-0.3c-0.3-0.2-0' +
+ '.6-0.5-1-0.9c-0.8-0.7-1.8-1.8-2.8-2.8c-2-2.2-3.6-4.6-3.6-4.6l-5,' +
+ '3.2c0,0,0.4,0.7,1.1,1.7c0.7,1,1.7,2.4,2.8,3.7c1.1,1.3,2.2,2.5,3.' +
+ '1,3.4c0.4,0.4,0.9,0.8,1.1,1.1c0.3,0.2,0.4,0.4,0.4,0.4L27.6,83.8z' +
+ 'M14.8,64.7c0,0-0.1-0.2-0.2-0.5c-0.1-0.3-0.2-0.7-0.4-1.3c-0.3-1.1' +
+ '-0.6-2.5-0.8-4c-0.5-2.9-0.5-5.9-0.5-5.9l-5,0c0,0,0,0.8,0,2.1c0,1' +
+ '.2,0.1,2.9,0.3,4.5c0.2,1.6,0.5,3.3,0.8,4.5c0.1,0.6,0.3,1.2,0.4,1' +
+ '.5c0.1,0.3,0.1,0.5,0.1,0.5L14.8,64.7z M14.3,41.4c0,0,0.1-0.2,0.1' +
+ '-0.5c0.1-0.3,0.2-0.7,0.4-1.3c0.3-1.1,0.8-2.5,1.4-3.8c1.2-2.7,2.8' +
+ '-5.3,2.8-5.3l-3.4-2.2c0,0-1.8,2.7-3.2,5.7c-0.7,1.5-1.3,3-1.7,4.2' +
+ 'c-0.2,0.6-0.4,1.1-0.5,1.4C10,39.9,10,40.1,10,40.1L14.3,41.4z M26' +
+ '.7,21.3c0,0,0.1-0.1,0.4-0.4c0.3-0.2,0.6-0.5,1-0.9c0.9-0.7,2.1-1.' +
+ '6,3.3-2.5c1.2-0.8,2.5-1.6,3.5-2.1c1-0.5,1.7-0.9,1.7-0.9l-1.3-2.9' +
+ 'c0,0-0.7,0.3-1.8,0.9c-1.1,0.5-2.5,1.3-3.9,2.2c-1.4,0.9-2.7,1.8-3' +
+ '.7,2.5c-0.5,0.4-0.9,0.7-1.2,0.9c-0.3,0.2-0.4,0.3-0.4,0.3L26.7,21' +
+ '.3z M48.2,11c0,0,0.2,0,0.5-0.1c0.3,0,0.8-0.1,1.4-0.2c1.1-0.1,2.6' +
+ '-0.3,4.2-0.3c3-0.1,6.1,0.3,6.1,0.3l0.3-2.3c0,0-0.8-0.1-2-0.3C57.' +
+ '4,8.1,55.8,8,54.2,8c-1.6,0-3.2,0-4.4,0.1c-0.6,0-1.1,0.1-1.5,0.1c' +
+ '-0.3,0-0.5,0.1-0.5,0.1L48.2,11z M72,14c0,0,0.7,0.3,1.7,0.8c1,0.5' +
+ ',2.4,1.2,3.7,2c2.6,1.6,5,3.6,5,3.6l0.9-1c0,0-2.4-2.1-5-3.9c-1.3-' +
+ '0.9-2.7-1.7-3.8-2.3c-1.1-0.6-1.8-0.9-1.8-0.9L72,14zM90.7,29.6c0,' +
+ '0,0.4,0.6,1,1.6c0.6,1,1.4,2.3,2,3.7c0.7,1.4,1.3,2.8,1.7,3.9c0.4,' +
+ '1.1,0.6,1.8,0.6,1.8l0.4-0.1c0,0-0.2-0.8-0.6-1.9c-0.4-1.1-0.9-2.6' +
+ '-1.5-4c-0.6-1.4-1.3-2.9-1.9-3.9c-0.6-1-1-1.7-1-1.7L90.7,29.6z',
+
+ // --- Navigation glyphs ------------------------------------
+
+ flowTable: 'M15.9,19.1h-8v-13h8V19.1z M90.5,6.1H75.6v13h14.9V6.1z' +
+ ' M71.9,6.1H56.9v13h14.9V6.1z M53.2,6.1H38.3v13h14.9V6.1z M34.5,' +
+ '6.1H19.6v13h14.9V6.1z M102.2,6.1h-8v13h8V6.1z M102.2,23.6H7.9v' +
+ '78.5h94.4V23.6z M86,63.2c0,3.3-2.7,6-6,6c-2.8,0-5.1-1.9-5.8-' +
+ '4.5H63.3v5.1c0,0.9-0.7,1.5-1.5,1.5h-5.2v10.6c2.6,0.7,4.5,3,4.5,' +
+ '5.8c0,3.3-2.7,6-6,6c-3.3,0-6-2.7-6-6c0-2.8,1.9-5.1,4.4-5.8V71.3' +
+ 'H48c-0.9,0-1.5-0.7-1.5-1.5v-5.1H36c-0.7,2.6-3,4.4-5.8,4.4c-3.3,' +
+ '0-6-2.7-6-6s2.7-6,6-6c2.8,0,5.2,1.9,5.8,4.5h10.5V56c0-0.9,0.7-' +
+ '1.5,1.5-1.5h5.5V43.8c-2.6-0.7-4.5-3-4.5-5.8c0-3.3,2.7-6,6-6s6,' +
+ '2.7,6,6c0,2.8-1.9,5.1-4.5,5.8v10.6h5.2c0.9,0,1.5,0.7,1.5,1.5v' +
+ '5.6h10.8c0.7-2.6,3-4.5,5.8-4.5C83.3,57.1,86,59.8,86,63.2z M55.1,' +
+ '42.3c2.3,0,4.3-1.9,4.3-4.3c0-2.3-1.9-4.3-4.3-4.3s-4.3,1.9-4.3,' +
+ '4.3C50.8,40.4,52.7,42.3,55.1,42.3z M34.4,63.1c0-2.3-1.9-4.3-4.3' +
+ '-4.3s-4.3,1.9-4.3,4.3s1.9,4.3,4.3,4.3S34.4,65.5,34.4,63.1z ' +
+ 'M55.1,83.5c-2.3,0-4.3,1.9-4.3,4.3s1.9,4.3,4.3,4.3s4.3-1.9,4.3-' +
+ '4.3S57.5,83.5,55.1,83.5zM84.2,63.2c0-2.3-1.9-4.3-4.3-4.3s-4.3,' +
+ '1.9-4.3,4.3s1.9,4.3,4.3,4.3S84.2,65.5,84.2,63.2z',
+
+ portTable: 'M15.9,19.1h-8v-13h8V19.1z M90.5,6.1H75.6v13h14.9V6.1' +
+ 'z M71.9,6.1H56.9v13h14.9V6.1z M53.2,6.1H38.3v13h14.9V6.1z M34.5,' +
+ '6.1H19.6v13h14.9V6.1z M102.2,6.1h-8v13h8V6.1z M102.6,23.6v78.5H' +
+ '8.2V23.6H102.6z M85.5,37.7c0-0.7-0.4-1.3-0.9-1.3H26.2c-0.5,0-' +
+ '0.9,0.6-0.9,1.3v34.6c0,0.7,0.4,1.3,0.9,1.3h11v9.6c0,1.1,0.5,2,' +
+ '1.2,2h9.1c0,0.2,0,0.3,0,0.5v3c0,1.1,0.5,2,1.2,2h13.5c0.6,0,1.2-' +
+ '0.9,1.2-2v-3c0-0.2,0-0.3,0-0.5h9.1c0.6,0,1.2-0.9,1.2-2v-9.6h11' +
+ 'c0.5,0,0.9-0.6,0.9-1.3V37.7z M30.2,40h-1v8h1V40zM75.2,40h-2.1v8' +
+ 'h2.1V40z M67.7,40h-2.1v8h2.1V40z M60.2,40h-2.1v8h2.1V40z M52.7,' +
+ '40h-2.1v8h2.1V40z M45.2,40h-2.1v8h2.1V40zM37.7,40h-2.1v8h2.1V40' +
+ 'z M81.6,40h-1v8h1V40z',
+
+ groupTable: 'M16,19.1H8v-13h8V19.1z M90.6,6.1H75.7v13h14.9V6.1z ' +
+ 'M71.9,6.1H57v13h14.9V6.1z M53.3,6.1H38.4v13h14.9V6.1z M34.6,6.1' +
+ 'H19.7v13h14.9V6.1z M102.3,6.1h-8v13h8V6.1z M45.7,52.7c0.2-5.6,' +
+ '2.6-10.7,6.2-14.4c-2.6-1.5-5.7-2.5-8.9-2.5c-9.8,0-17.7,7.9-17.7,' +
+ '17.7c0,6.3,3.3,11.9,8.3,15C34.8,61.5,39.4,55.6,45.7,52.7z M51.9,' +
+ '68.8c-3.1-3.1-5.2-7.2-6-11.7c-4.7,2.8-7.9,7.6-8.6,13.2c1.8,0.6,' +
+ '3.6,0.9,5.6,0.9C46.2,71.2,49.3,70.3,51.9,68.8z M55.2,71.5c-3.5,' +
+ '2.4-7.7,3.7-12.2,3.7c-1.9,0-3.8-0.3-5.6-0.7C38.5,83.2,45.9,90,' +
+ '54.9,90c9,0,16.4-6.7,17.5-15.4c-1.6,0.4-3.4,0.6-5.1,0.6C62.8,' +
+ '75.2,58.6,73.8,55.2,71.5z M54.9,50.6c1.9,0,3.8,0.3,5.6,0.7c-0.5' +
+ '-4.1-2.5-7.9-5.4-10.6c-2.9,2.7-4.8,6.4-5.3,10.5C51.5,50.8,53.2,' +
+ '50.6,54.9,50.6z M49.7,55.4c0.5,4.3,2.4,8.1,5.4,10.9c2.9-2.8,4.9' +
+ '-6.6,5.4-10.8c-1.8-0.6-3.6-0.9-5.6-0.9C53.1,54.6,51.4,54.9,49.7,' +
+ '55.4z M102.3,23.6v78.5H8V23.6H102.3z M89,53.5c0-12-9.7-21.7-' +
+ '21.7-21.7c-4.5,0-8.7,1.4-12.2,3.7c-3.5-2.4-7.7-3.7-12.2-3.7c-12,' +
+ '0-21.7,9.7-21.7,21.7c0,8.5,4.9,15.9,12,19.4C33.6,84.6,43.2,94,' +
+ '54.9,94c11.7,0,21.2-9.3,21.7-20.9C84,69.7,89,62.2,89,53.5z M' +
+ '64.3,57.3c-0.8,4.4-2.9,8.4-5.9,11.5c2.6,1.5,5.7,2.5,8.9,2.5c1.8,' +
+ '0,3.6-0.3,5.2-0.8C72,64.9,68.8,60.1,64.3,57.3z M67.3,35.8c-3.3,0' +
+ '-6.3,0.9-8.9,2.5c3.7,3.8,6.1,8.9,6.2,14.6c6.1,3.1,10.6,8.9,11.7,' +
+ '15.8C81.5,65.6,85,60,85,53.5C85,43.8,77.1,35.8,67.3,35.8z',
+
+ // --- Topology toolbar specific glyphs ----------------------
+
+ summary: "M95.8,9.2H14.2c-2.8,0-5,2.2-5,5v81.5c0,2.8,2.2,5,5," +
+ "5h81.5c2.8,0,5-2.2,5-5V14.2C100.8,11.5,98.5,9.2,95.8,9.2z " +
+ "M16.7,22.2c0-1.1,0.9-2,2-2h20.1c1.1,0,2,0.9,2,2v20.1c0,1.1-0.9," +
+ "2-2,2H18.7c-1.1,0-2-0.9-2-2V22.2z M93,87c0,1.1-0.9,2-2,2H18.9" +
+ "c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2H91c1.1,0,2,0.9,2,2V87z " +
+ "M93,65c0,1.1-0.9,2-2,2H18.9c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2," +
+ "2-2H91c1.1,0,2,0.9,2,2V65z",
+
+ details: "M95.8,9.2H14.2c-2.8,0-5,2.2-5,5v81.5c0,2.8,2.2,5,5," +
+ "5h81.5c2.8,0,5-2.2,5-5V14.2C100.8,11.5,98.5,9.2,95.8,9.2z M16.9," +
+ "22.2c0-1.1,0.9-2,2-2H91c1.1,0,2,0.9,2,2v7c0,1.1-0.9,2-2,2H18.9c" +
+ "-1.1,0-2-0.9-2-2V22.2z M93,87.8c0,1.1-0.9,2-2,2H18.9c-1.1," +
+ "0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2H91c1.1,0,2,0.9,2,2V87.8z M93,68.2" +
+ "c0,1.1-0.9,2-2,2H18.9c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2H91" +
+ "c1.1,0,2,0.9,2,2V68.2z M93,48.8c0,1.1-0.9,2-2,2H19c-1.1,0-2-" +
+ "0.9-2-2v-7c0-1.1,0.9-2,2-2H91c1.1,0,2,0.9,2,2V48.8z",
+
+ ports: "M98,9.2H79.6c-1.1,0-2.1,0.9-2.1,2.1v17.6l-5.4,5.4c-1.7" +
+ "-1.1-3.8-1.8-6-1.8c-6,0-10.9,4.9-10.9,10.9c0,2.2,0.7,4.3,1.8,6" +
+ "l-7.5,7.5c-1.8-1.2-3.9-1.9-6.2-1.9c-6,0-10.9,4.9-10.9,10.9c0," +
+ "2.3,0.7,4.4,1.9,6.2l-6.2,6.2H11.3c-1.1,0-2.1,0.9-2.1,2.1v18.4" +
+ "c0,1.1,0.9,2.1,2.1,2.1h18.4c1.1,0,2.1-0.9,2.1-2.1v-16l7-6.9" +
+ "c1.4,0.7,3,1.1,4.7,1.1c6,0,10.9-4.9,10.9-10.9c0-1.7-0.4-3.3-" +
+ "1.1-4.7l8-8c1.5,0.7,3.1,1.1,4.8,1.1c6,0,10.9-4.9,10.9-10.9c0" +
+ "-1.7-0.4-3.4-1.1-4.8l6.9-6.9H98c1.1,0,2.1-0.9,2.1-2.1V11.3" +
+ "C100.1,10.2,99.2,9.2,98,9.2z M43.4,72c-3.3,0-6-2.7-6-6s2.7-6," +
+ "6-6s6,2.7,6,6S46.7,72,43.4,72z M66.1,49.5c-3.3,0-6-2.7-6-6" +
+ "c0-3.3,2.7-6,6-6s6,2.7,6,6C72.2,46.8,69.5,49.5,66.1,49.5z",
+
+ map: "M95.8,9.2H14.2c-2.8,0-5,2.2-5,5v66c0.3-1.4,0.7-2.8," +
+ "1.1-4.1l1.6,0.5c-0.9,2.4-1.6,4.8-2.2,7.3l-0.5-0.1v12c0,2.8,2.2," +
+ "5,5,5h81.5c2.8,0,5-2.2,5-5V14.2C100.8,11.5,98.5,9.2,95.8,9.2z " +
+ "M16.5,67.5c-0.4,0.5-0.7,1-1,1.5c-0.3,0.5-0.6,1-0.9,1.6l-1.9-0.9" +
+ "c0.3-0.6,0.6-1.2,0.9-1.8c0.3-0.6,0.6-1.2,1-1.7c0.7-1.1,1.5-2.2," +
+ "2.5-3.2l1.8,1.8C18,65.6,17.2,66.5,16.5,67.5z M29.7,64.1" +
+ "c-0.4-0.4-0.8-0.8-1.2-1.1c-0.1-0.1-0.2-0.1-0.2-0.1c0,0-0.1," +
+ "0-0.1-0.1l-0.1,0l0,0l-0.1,0c-0.3-0.1-0.5-0.2-0.8-0.2c-0.5-0.1" +
+ "-1.1-0.2-1.6-0.3c-0.6,0-1.1,0-1.6,0l-0.4-2.8c0.7-0.1,1.5-0.2,2.2" +
+ "-0.1c0.7,0,1.4,0.1,2.2,0.3c0.4,0.1,0.7,0.2,1,0.3l0.1,0l0,0l0.1," +
+ "0l0.1,0c0.1,0,0.1,0,0.3,0.1c0.3,0.1,0.5,0.2,0.7,0.4c0.7,0.5," +
+ "1.2,0.9,1.7,1.4L29.7,64.1z M39.4,74.7c-1.8-1.8-3.6-3.8-5.3-5.7" +
+ "l2.6-2.4c0.9,0.9,1.8,1.8,2.7,2.7c0.9,0.9,1.8,1.7,2.7,2.6L39.4," +
+ "74.7z M50.8,84.2c-1.1-0.7-2.2-1.5-3.3-2.3c-0.5-0.4-1.1-0.8-1.6" +
+ "-1.2c-0.5-0.4-1-0.8-1.5-1.2l2.7-3.4c0.5,0.4,1,0.8,1.5,1.1c0.5," +
+ "0.3,1,0.7,1.5,1c1,0.7,2.1,1.3,3.1,1.9L50.8,84.2z M61.3," +
+ "88.7c-0.7-0.1-1.4-0.3-2.1-0.5c-0.7-0.2-1.4-0.5-2-0.7l1.8" +
+ "-4.8c0.6,0.2,1.1,0.4,1.6,0.5c0.5,0.2,1.1,0.3,1.6,0.4c1,0.2,2.1," +
+ "0.2,3,0.1l0.7,5.1C64.3,89.1,62.7,88.9,61.3,88.7z M75.1,80.4c" +
+ "-0.2,0.7-0.5,1.4-0.9,2c-0.2,0.3-0.3,0.7-0.5,1l-0.3,0.5l-0.3," +
+ "0.4l-3.9-2.8l0.3-0.4l0.2-0.3c0.1-0.2,0.3-0.4,0.4-0.7c0.3-0.5," +
+ "0.5-0.9,0.7-1.5c0.4-1,0.8-2.1,1.1-3.3l4.2,0.9C75.9,77.7,75.6," +
+ "79,75.1,80.4z M73,69.2l0.2-1.9l0.1-1.9c0.1-1.2,0.1-2.5,0.1-" +
+ "3.8l2.5-0.2c0.2,1.3,0.4,2.6,0.5,3.9l0.1,2l0.1,2L73,69.2z " +
+ "M73,51l0.5-0.1c0.4,1.3,0.8,2.6,1.1,3.9L73.2,55C73.1,53.7,73.1," +
+ "52.3,73,51z M91.9,20.4c-0.7,1.4-3.6,3.6-4.2,3.9c-1.5,0.8-5," +
+ "2.8-10.1,7.7c3,2.9,5.8,5.4,7.3,6.4c2.6,1.8,3.4,4.3,3.6,6.1c0.1," +
+ "1.1-0.1,2.5-0.4,3c-0.5,0.9-1.6,2-3,1.4c-2-0.8-11.5-9.6-13-11c" +
+ "-3.5,3.9-7.4,8.9-11.7,15.1c0,0-3.1,3.4-5.2,0.9C52.9,51.5,61," +
+ "39.3,61,39.3s2.2-3.1,5.6-7c-2.9-3-5.9-6.3-6.6-7.3c0,0-3.7-5-1.3" +
+ "-6.6c3.2-2.1,6.3,0.8,6.3,0.8s3.1,3.3,7,7.2c4.7-4.7,10.1-9.2," +
+ "14.7-10c0,0,3.3-1,5.2,1.7C92.5,18.8,92.4,19.6,91.9,20.4z",
+
+ cycleLabels: "M72.5,33.9c0,0.6-0.2,1-0.5,1H40c-0.3,0-0.5-0.4" +
+ "-0.5-1V20.7c0-0.6,0.2-1,0.5-1h32c0.3,0,0.5,0.4,0.5,1V33.9z " +
+ "M41.2,61.8c0-0.6-0.2-1-0.5-1h-32c-0.3,0-0.5,0.4-0.5,1V75c0,0.6," +
+ "0.2,1,0.5,1h32c0.3,0,0.5-0.4,0.5-1V61.8z M101.8,61.8c0-0.6-0.2" +
+ "-1-0.5-1h-32c-0.3,0-0.5,0.4-0.5,1V75c0,0.6,0.2,1,0.5,1h32c0.3," +
+ "0,0.5-0.4,0.5-1V61.8z M17.2,52.9c0-0.1-0.3-7.1,4.6-13.6l-2.4-1.8" +
+ "c-5.4,7.3-5.2,15.2-5.1,15.5L17.2,52.9z M12.7,36.8l7.4,2.5l1.5," +
+ "7.6L29.5,31L12.7,36.8z M94.2,42.3c-2.1-8.9-8.3-13.7-8.6-13.9l" +
+ "-1.8,2.4c0.1,0,5.6,4.3,7.5,12.2L94.2,42.3z M99,37.8l-6.6,4.1l" +
+ "-6.8-3.7l7.1,16.2L99,37.8z M69,90.2l-1.2-2.8c-0.1,0-6.6,2.8" +
+ "-14.3,0.6l-0.8,2.9c2.5,0.7,4.9,1,7,1C65,91.8,68.7,90.2,69,90.2z " +
+ "M54.3,97.3L54,89.5l6.6-4.1l-17.6-1.7L54.3,97.3z",
+
+ oblique: "M80.9,30.2h4.3l15-16.9H24.8l-15,16.9h19v48.5h-4l-15," +
+ "16.9h75.3l15-16.9H80.9V30.2z M78.6,78.7H56.1V30.2h22.5V78.7z" +
+ "M79.7,17.4c2.4,0,4.3,1.9,4.3,4.3c0,2.4-1.9,4.3-4.3,4.3s-4.3" +
+ "-1.9-4.3-4.3C75.4,19.3,77.4,17.4,79.7,17.4z M55,17.4c2.4,0," +
+ "4.3,1.9,4.3,4.3c0,2.4-1.9,4.3-4.3,4.3s-4.3-1.9-4.3-4.3C50.7," +
+ "19.3,52.6,17.4,55,17.4z M26.1,21.7c0-2.4,1.9-4.3,4.3-4.3c2.4," +
+ "0,4.3,1.9,4.3,4.3c0,2.4-1.9,4.3-4.3,4.3C28,26,26.1,24.1,26.1," +
+ "21.7z M31.1,30.2h22.6v48.5H31.1V30.2z M30.3,91.4c-2.4,0-4.3" +
+ "-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c2.4,0,4.3,1.9,4.3,4.3C34.6," +
+ "89.5,32.7,91.4,30.3,91.4z M54.9,91.4c-2.4,0-4.3-1.9-4.3-4.3c0" +
+ "-2.4,1.9-4.3,4.3-4.3c2.4,0,4.3,1.9,4.3,4.3C59.2,89.5,57.3," +
+ "91.4,54.9,91.4z M84,87.1c0,2.4-1.9,4.3-4.3,4.3c-2.4,0-4.3-1.9" +
+ "-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3C82.1,82.8,84,84.7,84,87.1z",
+
+ filters: "M24.8,13.3L9.8,40.5h75.3l15.0-27.2H24.8z M72.8,32.1l-" +
+ "9.7-8.9l-19.3,8.9l-6.0-7.4L24.1,30.9l-1.2-2.7l15.7-7.1l6.0,7.4" +
+ "l19.0-8.8l9.7,8.8l11.5-5.6l1.3,2.7L72.8,32.1zM24.3,68.3L9.3," +
+ "95.5h75.3l15.0-27.2H24.3z M84.3,85.9L70.7,79.8l-6.0,7.4l-19.3" +
+ "-8.9l-9.7,8.9l-13.3-6.5l1.3-2.7l11.5,5.6l9.7-8.8l19.0,8.8l6.0" +
+ "-7.4l15.7,7.1L84.3,85.9z M15.3,57h-6v-4h6V57zM88.1,57H76.0v-4h" +
+ "12.1V57z M69.9,57H57.8v-4h12.1V57z M51.7,57H39.6v-4H51.7V57z " +
+ "M33.5,57H21.4v-4h12.1V57zM100.2,57h-6v-4h6V57z",
+
+ resetZoom: "M86,79.8L61.7,54.3c1.8-2.9,2.8-6.3,2.9-10c0.3-11.2" +
+ "-8.6-20.5-19.8-20.8C33.7,23.2,24.3,32,24.1,43.2c-0.3,11.2,8.6," +
+ "20.5,19.8,20.8c4,0.1,8.9-0.8,11.9-3.6l23.7,25c1.5,1.6,4,2.3," +
+ "5.3,1l1.6-1.6C87.7,83.7,87.5,81.4,86,79.8z M31.4,43.4c0.2-7.1," +
+ "6.1-12.8,13.2-12.6C51.8,31,57.5,37,57.3,44.1c-0.2,7.1-6.1,12.8" +
+ "-13.2,12.6C36.9,56.5,31.3,50.6,31.4,43.4zM22.6,104H6V86.4c0" +
+ "-1.7,1.4-3.1,3.1-3.1s3.1,1.4,3.1,3.1v11.4h10.4c1.7,0,3.1,1.4," +
+ "3.1,3.1S24.3,104,22.6,104z M25.7,9.1c0,1.7-1.4,3.1-3.1,3.1" +
+ "H12.2v11.4c0,1.7-1.4,3.1-3.1,3.1S6,25.3,6,23.6V6h16.6C24.3,6," +
+ "25.7,7.4,25.7,9.1z M84.3,100.9c0-1.7,1.4-3.1,3.1-3.1h10.4V86.4" +
+ "c0-1.7,1.4-3.1,3.1-3.1s3.1,1.4,3.1,3.1V104H87.4C85.7,104,84.3," +
+ "102.6,84.3,100.9z M87.4,6H104v17.6c0,1.7-1.4,3.1-3.1,3.1s-3.1" +
+ "-1.4-3.1-3.1V12.2H87.4c-1.7,0-3.1-1.4-3.1-3.1S85.7,6,87.4,6z",
+
+ relatedIntents: "M99.9,43.7v22.6c0,1.9-1.5,3.4-3.4,3.4H73.9c" +
+ "-1.9,0-3.4-1.5-3.4-3.4V43.7c0-1.9,1.5-3.4,3.4-3.4h22.6C98.4," +
+ "40.3,99.9,41.8,99.9,43.7z M48.4,46.3l6.2,6.7h-4.6L38.5,38v9.7" +
+ "l4.7,5.3H10.1V57h33.2l-4.8,5.3v9.5L49.8,57h5.1v0l-6.5,7v11.5" +
+ "L64.1,55L48.4,34.4V46.3z",
+
+ nextIntent: "M88.1,55.7L34.6,13.1c0,0-1.6-0.5-2.1-0.2c-1.9,1.2" +
+ "-6.5,13.8-3.1,17.2c7,6.9,30.6,24.5,32.4,25.9c-1.8,1.4-25.4,19" +
+ "-32.4,25.9c-3.4,3.4,1.2,16,3.1,17.2c0.6,0.4,2.1-0.2,2.1-0.2" +
+ "s53.1-42.4,53.5-42.7C88.5,56,88.1,55.7,88.1,55.7z",
+
+ prevIntent: "M22.5,55.6L76,12.9c0,0,1.6-0.5,2.2-0.2c1.9,1.2," +
+ "6.5,13.8,3.1,17.2c-7,6.9-30.6,24.5-32.4,25.9c1.8,1.4,25.4,19," +
+ "32.4,25.9c3.4,3.4-1.2,16-3.1,17.2c-0.6,0.4-2.2-0.2-2.2-0.2" +
+ "S22.9,56.3,22.5,56C22.2,55.8,22.5,55.6,22.5,55.6z",
+
+ intentTraffic: "M14.7,71.5h-6v-33h6V71.5z M88.5,38.5H76.9v33" +
+ "h11.7V38.5z M70.1,38.5H58.4v33h11.7V38.5z M51.6,38.5H39.9v33" +
+ "h11.7V38.5z M33.1,38.5H21.5v33h11.7V38.5z M101.3,38.5h-6v33h6" +
+ "V38.5z",
+
+ allTraffic: "M15.7,64.5h-7v-19h7V64.5z M78.6,45.5H62.9v19h15.7" +
+ "V45.5z M47.1,45.5H31.4v19h15.7V45.5z M101.3,45.5h-7v19h7V45.5z" +
+ "M14.7,14.1h-6v19h6V14.1z M88.5,14.1H76.9v19h11.7V14.1z M70.1," +
+ "14.1H58.4v19h11.7V14.1z M51.6,14.1H39.9v19h11.7V14.1z M33.1,14.1" +
+ "H21.5v19h11.7V14.1z M101.3,14.1h-6v19h6V14.1z M14.7,76.9h-6v19" +
+ "h6V76.9z M88.5,76.9H76.9v19h11.7V76.9z M70.1,76.9H58.4v19h11.7" +
+ "V76.9z M51.6,76.9H39.9v19h11.7V76.9z M33.1,76.9H21.5v19h11.7" +
+ "V76.9z M101.3,76.9h-6v19h6V76.9z",
+
+ flows: "M93.8,46.1c-4.3,0-8,3-9,7H67.9v-8.8c0-1.3-1.1-2.4-2.4" +
+ "-2.4h-8.1V25.3c4-1,7-4.7,7-9.1c0-5.2-4.2-9.4-9.4-9.4c-5.2,0" +
+ "-9.4,4.2-9.4,9.4c0,4.3,3,8,7,9v16.5H44c-1.3,0-2.4,1.1-2.4,2.4" +
+ "v8.8H25.3c-1-4.1-4.7-7.1-9.1-7.1c-5.2,0-9.4,4.2-9.4,9.4s4.2," +
+ "9.4,9.4,9.4c4.3,0,8-2.9,9-6.9h16.4v7.9c0,1.3,1.1,2.4,2.4,2.4" +
+ "h8.6v16.6c-4,1.1-6.9,4.7-6.9,9c0,5.2,4.2,9.4,9.4,9.4c5.2,0," +
+ "9.4-4.2,9.4-9.4c0-4.4-3-8-7.1-9.1V68.2h8.1c1.3,0,2.4-1.1,2.4" +
+ "-2.4v-7.9h16.8c1.1,4,4.7,7,9,7c5.2,0,9.4-4.2,9.4-9.4S99,46.1," +
+ "93.8,46.1z M48.7,16.3c0-3.5,2.9-6.4,6.4-6.4c3.5,0,6.4,2.9,6.4," +
+ "6.4s-2.9,6.4-6.4,6.4C51.5,22.6,48.7,19.8,48.7,16.3zM16.2,61.7c" +
+ "-3.5,0-6.4-2.9-6.4-6.4c0-3.5,2.9-6.4,6.4-6.4s6.4,2.9,6.4,6.4" +
+ "C22.6,58.9,19.7,61.7,16.2,61.7z M61.4,93.7c0,3.5-2.9,6.4-6.4," +
+ "6.4c-3.5,0-6.4-2.9-6.4-6.4c0-3.5,2.9-6.4,6.4-6.4C58.6,87.4," +
+ "61.4,90.2,61.4,93.7z M93.8,61.8c-3.5,0-6.4-2.9-6.4-6.4c0-3.5," +
+ "2.9-6.4,6.4-6.4s6.4,2.9,6.4,6.4C100.1,58.9,97.3,61.8,93.8,61.8z",
+
+ eqMaster: "M100.1,46.9l-10.8-25h0.2c0.5,0,0.8-0.5,0.8-1.1v-3.2" +
+ "c0-0.6-0.4-1.1-0.8-1.1H59.2v-5.1c0-0.5-0.8-1-1.7-1h-5.1c-0.9,0" +
+ "-1.7,0.4-1.7,1v5.1l-30.2,0c-0.5,0-0.8,0.5-0.8,1.1v3.2c0,0.6," +
+ "0.4,1.1,0.8,1.1h0.1l-10.8,25C9,47.3,8.4,48,8.4,48.8v1.6l0,0h0" +
+ "v6.4c0,1.3,1.4,2.3,3.2,2.3h21.7c1.8,0,3.2-1,3.2-2.3v-8c0-0.9" +
+ "-0.7-1.6-1.7-2L22.9,21.9h27.9v59.6l-29,15.9c0,1.2,1.8,2.2,4.1," +
+ "2.2h58.3c2.3,0,4.1-1,4.1-2.2l-29-15.9V21.9h27.8L75.2,46.8c-1," +
+ "0.4-1.7,1.1-1.7,2v8c0,1.3,1.4,2.3,3.2,2.3h21.7c1.8,0,3.2-1,3.2" +
+ "-2.3v-8C101.6,48,101,47.3,100.1,46.9z M22,23.7l10.8,22.8H12.1" +
+ "L22,23.7z M97.9,46.5H77.2L88,23.7L97.9,46.5z"
+ },
+
+ badgeDataSet = {
+ _viewbox: "0 0 10 10",
+
+ uiAttached: "M2,2.5a.5,.5,0,0,1,.5-.5h5a.5,.5,0,0,1,.5,.5v3" +
+ "a.5,.5,0,0,1-.5,.5h-5a.5,.5,0,0,1-.5-.5zM2.5,2.8a.3,.3,0,0,1," +
+ ".3-.3h4.4a.3,.3,0,0,1,.3,.3v2.4a.3,.3,0,0,1-.3,.3h-4.4" +
+ "a.3,.3,0,0,1-.3-.3zM2,6.55h6l1,1.45h-8z",
+
+ checkMark: "M2.6,4.5c0,0,0.7-0.4,1.2,0.3l1.0," +
+ "1.8c0,0,2.7-5.4,2.8-5.7c0,0,0.5-0.9,1.4-0.1c0," +
+ "0,0.5,0.5,0,1.3S6.8,7.3,5.6,9.2c0,0-0.4," +
+ "0.5-1.2,0.1S2.2,5.4,2.2,5.4S2.2,4.7,2.6,4.5z",
+
+ xMark: "M9.0,7.2C8.2,6.9,7.4,6.1,6.7,5.2c0.4-0.5," +
+ "0.7-0.8,0.8-1.0C7.8,3.5,9.4,1.6,8.1,1.1" +
+ "C6.8,0.6,6.6,1.7,6.6,1.7C6.4,2.1,6.0,2.7,5.4," +
+ "3.4C4.9,2.5,4.5,1.9,4.5,1.9" +
+ "S3.8,0.2,2.9,0.7C1.9,1.1,2.3,2.3,2.3,2.3c0.3,1.1,0.8,2.1,1.4,2.9" +
+ "C2.5,6.4,1.3,7.4,1.3,7.4S0.8,7.8,0.8,8.1C0.9,8.3,0.9,9.6,2.4,9.1" +
+ "C3.1,8.8,4.1,7.9,5.1,7.0c1.3,1.3,2.5,1.9,2.5,1.9s0.5,0.5,1.4-0.2" +
+ "C9.8,7.9,9.0,7.2,9.0,7.2z",
+
+ triangleUp: "M0.5,6.2c0,0,3.8-3.8,4.2-4.2C5,1.7,5.3,2,5.3,2l4.3," +
+ "4.3c0,0,0.4,0.4-0.1,0.4c-1.7,0-8.2,0-8.8,0C0,6.6,0.5,6.2,0.5,6.2z",
+
+ triangleDown: "M9.5,4.2c0,0-3.8,3.8-4.2,4.2c-0.3,0.3-0.5,0-0.5," +
+ "0L0.5,4.2c0,0-0.4-0.4,0.1-0.4c1.7,0,8.2,0,8.8,0C10,3.8,9.5,4.2," +
+ "9.5,4.2z",
+
+ plus: "M4,2h2v2h2v2h-2v2h-2v-2h-2v-2h2z",
+
+ minus: "M2,4h6v2h-6z",
+
+ play: "M2.5,2l5.5,3l-5.5,3z",
+
+ stop: "M2.5,2.5h5v5h-5z"
+ },
+
+ spriteData = {
+ _cloud: '0 0 110 110',
+ cloud: "M37.6,79.5c-6.9,8.7-20.4,8.6-22.2-2.7" +
+ "M16.3,41.2c-0.8-13.9,19.4-19.2,23.5-7.8" +
+ "M38.9,30.9c5.1-9.4,15.1-8.5,16.9-1.3" +
+ "M54.4,32.9c4-12.9,14.8-9.6,18.6-3.8" +
+ "M95.8,58.5c10-4.1,11.7-17.8-0.9-19.8" +
+ "M18.1,76.4C5.6,80.3,3.8,66,13.8,61.5" +
+ "M16.2,62.4C2.1,58.4,3.5,36,16.8,36.6" +
+ "M93.6,74.7c10.2-2,10.7-14,5.8-18.3" +
+ "M71.1,79.3c11.2,7.6,24.6,6.4,22.1-11.7" +
+ "M36.4,76.8c3.4,13.3,35.4,11.6,36.1-1.4" +
+ "M70.4,31c11.8-10.4,26.2-5.2,24.7,10.1"
+ };
+
+ // ----------------------------------------------------------------------
+ // === Constants
+
+ var msgGS = 'GlyphService.',
+ rg = "registerGlyphs(): ",
+ rgs = "registerGlyphSet(): ";
+
+ // ----------------------------------------------------------------------
+
+ function warn(msg) {
+ $log.warn(msgGS + msg);
+ }
+
+ function addToMap(key, value, vbox, overwrite, dups) {
+ if (!overwrite && glyphs.get(key)) {
+ dups.push(key);
+ } else {
+ glyphs.set(key, {id: key, vb: vbox, d: value});
+ }
+ }
+
+ function reportDups(dups, which) {
+ var ok = (dups.length == 0),
+ msg = 'ID collision: ';
+
+ if (!ok) {
+ dups.forEach(function (id) {
+ warn(which + msg + '"' + id + '"');
+ });
+ }
+ return ok;
+ }
+
+ function reportMissVb(missing, which) {
+ var ok = (missing.length == 0),
+ msg = 'Missing viewbox property: ';
+
+ if (!ok) {
+ missing.forEach(function (vbk) {
+ warn(which + msg + '"' + vbk + '"');
+ });
+ }
+ return ok;
+ }
+
+ // ----------------------------------------------------------------------
+ // === API functions ===
+
+ function clear() {
+ // start with a fresh map
+ glyphs = d3.map();
+ }
+
+ function init() {
+ registerGlyphs(birdData);
+ registerGlyphSet(glyphDataSet);
+ registerGlyphSet(badgeDataSet);
+ registerGlyphs(spriteData);
+ }
+
+ function registerGlyphs(data, overwrite) {
+ var dups = [],
+ missvb = [];
+
+ angular.forEach(data, function (value, key) {
+ var vbk = '_' + key,
+ vb = data[vbk];
+
+ if (key[0] !== '_') {
+ if (!vb) {
+ missvb.push(vbk);
+ return;
+ }
+ addToMap(key, value, vb, overwrite, dups);
+ }
+ });
+ return reportDups(dups, rg) && reportMissVb(missvb, rg);
+ }
+
+ function registerGlyphSet(data, overwrite) {
+ var dups = [],
+ vb = data._viewbox;
+
+ if (!vb) {
+ warn(rgs + 'no "_viewbox" property found');
+ return false;
+ }
+
+ angular.forEach(data, function (value, key) {
+ if (key[0] !== '_') {
+ addToMap(key, value, vb, overwrite, dups);
+ }
+ });
+ return reportDups(dups, rgs);
+ }
+
+ function ids() {
+ return glyphs.keys();
+ }
+
+ function glyph(id) {
+ return glyphs.get(id);
+ }
+
+ // Note: defs should be a D3 selection of a single <defs> element
+ function loadDefs(defs, glyphIds, noClear) {
+ var list = fs.isA(glyphIds) || ids(),
+ clearCache = !noClear;
+
+ if (clearCache) {
+ // remove all existing content
+ defs.html(null);
+ }
+
+ // load up the requested glyphs
+ list.forEach(function (id) {
+ var g = glyph(id);
+ if (g) {
+ if (noClear) {
+ // quick exit if symbol is already present
+ if (defs.select('symbol#' + g.id).size() > 0) {
+ return;
+ }
+ }
+ defs.append('symbol')
+ .attr({ id: g.id, viewBox: g.vb })
+ .append('path').attr('d', g.d);
+ }
+ });
+ }
+
+ // trans can specify translation [x,y]
+ function addGlyph(elem, glyphId, size, overlay, trans) {
+ var sz = size || 40,
+ ovr = !!overlay,
+ xns = fs.isA(trans),
+ atr = {
+ width: sz,
+ height: sz,
+ 'class': 'glyph',
+ 'xlink:href': '#' + glyphId
+ };
+
+ if (xns) {
+ atr.transform = sus.translate(trans);
+ }
+ return elem.append('use').attr(atr).classed('overlay', ovr);
+ }
+
+ // ----------------------------------------------------------------------
+
+ angular.module('onosSvg')
+ .factory('GlyphService',
+ ['$log', 'FnService', 'SvgUtilService',
+
+ function (_$log_, _fs_, _sus_) {
+ $log = _$log_;
+ fs = _fs_;
+ sus = _sus_;
+
+ return {
+ clear: clear,
+ init: init,
+ registerGlyphs: registerGlyphs,
+ registerGlyphSet: registerGlyphSet,
+ ids: ids,
+ glyph: glyph,
+ loadDefs: loadDefs,
+ addGlyph: addGlyph
+ };
+ }]
+ )
+ .run(['$log', function ($log) {
+ $log.debug('Clearing glyph cache');
+ clear();
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.css
new file mode 100644
index 00000000..6ade7c7e
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.css
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Icon Service -- CSS file
+ */
+
+svg#IconLibDefs {
+ display: none;
+}
+
+svg .svgIcon {
+ fill-rule: evenodd;
+}
+
+svg.embeddedIcon g.icon {
+ fill: none;
+}
+
+svg.embeddedIcon g.icon rect {
+ stroke: none;
+ fill: none;
+}
+
+svg.embeddedIcon g.icon .glyph {
+ stroke: none;
+ fill: white;
+ fill-rule: evenodd;
+}
+
+
+/* Sortable table headers */
+.light div.tableColSort svg.embeddedIcon .icon .glyph {
+ fill: black;
+}
+.dark div.tableColSort svg.embeddedIcon .icon .glyph {
+ fill: #ccc;
+}
+
+
+/* color schemes for specific icon classes */
+
+svg.embeddedIcon .icon.appInactive .glyph {
+ fill: none;
+}
+
+.light svg.embeddedIcon .icon.active .glyph {
+ fill: green;
+}
+.dark svg.embeddedIcon .icon.active .glyph {
+ fill: #308C10;
+}
+
+
+.light table svg.embeddedIcon {
+ fill: #ccc;
+}
+.dark table svg.embeddedIcon {
+ fill: #222;
+}
+.light table svg.embeddedIcon .glyph {
+ fill: #333;
+}
+.dark table svg.embeddedIcon .glyph {
+ fill: #ccc;
+}
+
+.light svg.embeddedIcon .icon.active .glyph {
+ fill: green;
+}
+.light svg.embeddedIcon .icon.inactive .glyph {
+ fill: darkred;
+}
+.dark svg.embeddedIcon .icon.active .glyph {
+ fill: #308C10;
+}
+.dark svg.embeddedIcon .icon.inactive .glyph {
+ fill: #AD2D2D;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.js
new file mode 100644
index 00000000..ba794313
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- SVG -- Icon Service
+ */
+(function () {
+ 'use strict';
+
+ var $log, fs, gs, sus;
+
+ var vboxSize = 50,
+ cornerSize = vboxSize / 10,
+ viewBox = '0 0 ' + vboxSize + ' ' + vboxSize;
+
+ // Maps icon ID to the glyph ID it uses.
+ // NOTE: icon ID maps to a CSS class for styling that icon
+ var glyphMapping = {
+ active: 'checkMark',
+ inactive: 'xMark',
+
+ plus: 'plus',
+ minus: 'minus',
+ play: 'play',
+ stop: 'stop',
+
+ refresh: 'refresh',
+ garbage: 'garbage',
+
+ upArrow: 'triangleUp',
+ downArrow: 'triangleDown',
+
+ loading: 'loading',
+
+ appInactive: 'unknown',
+
+ devIcon_SWITCH: 'switch',
+ devIcon_ROADM: 'roadm',
+ flowTable: 'flowTable',
+ portTable: 'portTable',
+ groupTable: 'groupTable',
+
+ hostIcon_endstation: 'endstation',
+ hostIcon_router: 'router',
+ hostIcon_bgpSpeaker: 'bgpSpeaker',
+
+ nav_apps: 'bird',
+ nav_settings: 'chain',
+ nav_cluster: 'node',
+ nav_topo: 'topo',
+ nav_devs: 'switch',
+ nav_links: 'ports',
+ nav_hosts: 'endstation',
+ nav_intents: 'relatedIntents'
+ };
+
+ function ensureIconLibDefs() {
+ var body = d3.select('body'),
+ svg = body.select('svg#IconLibDefs'),
+ defs;
+
+ if (svg.empty()) {
+ svg = body.append('svg').attr('id', 'IconLibDefs');
+ defs = svg.append('defs');
+ }
+ return svg.select('defs');
+ }
+
+ // div is a D3 selection of the <DIV> element into which icon should load
+ // glyphId identifies the glyph to use
+ // size is dimension of icon in pixels. Defaults to 20.
+ // installGlyph, if truthy, will cause the glyph to be added to
+ // well-known defs element. Defaults to false.
+ // svgClass is the CSS class used to identify the SVG layer.
+ // Defaults to 'embeddedIcon'.
+ function loadIcon(div, glyphId, size, installGlyph, svgClass) {
+ var dim = size || 20,
+ svgCls = svgClass || 'embeddedIcon',
+ gid = glyphId || 'unknown',
+ svg, g;
+
+ if (installGlyph) {
+ gs.loadDefs(ensureIconLibDefs(), [gid], true);
+ }
+
+ svg = div.append('svg').attr({
+ 'class': svgCls,
+ width: dim,
+ height: dim,
+ viewBox: viewBox
+ });
+
+ g = svg.append('g').attr({
+ 'class': 'icon'
+ });
+
+ g.append('rect').attr({
+ width: vboxSize,
+ height: vboxSize,
+ rx: cornerSize
+ });
+
+ g.append('use').attr({
+ width: vboxSize,
+ height: vboxSize,
+ 'class': 'glyph',
+ 'xlink:href': '#' + gid
+ });
+ }
+
+ // div is a D3 selection of the <DIV> element into which icon should load
+ // iconCls is the CSS class used to identify the icon
+ // size is dimension of icon in pixels. Defaults to 20.
+ // installGlyph, if truthy, will cause the glyph to be added to
+ // well-known defs element. Defaults to false.
+ // svgClass is the CSS class used to identify the SVG layer.
+ // Defaults to 'embeddedIcon'.
+ function loadIconByClass(div, iconCls, size, installGlyph, svgClass) {
+ loadIcon(div, glyphMapping[iconCls], size, installGlyph, svgClass);
+ div.select('svg g').classed(iconCls, true);
+ }
+
+ function loadEmbeddedIcon(div, iconCls, size) {
+ loadIconByClass(div, iconCls, size, true);
+ }
+
+
+ // configuration for device and host icons in the topology view
+ var config = {
+ device: {
+ dim: 36,
+ rx: 4
+ },
+ host: {
+ radius: {
+ noGlyph: 9,
+ withGlyph: 14
+ },
+ glyphed: {
+ endstation: 1,
+ bgpSpeaker: 1,
+ router: 1
+ }
+ }
+ };
+
+
+ // Adds a device icon to the specified element, using the given glyph.
+ // Returns the D3 selection of the icon.
+ function addDeviceIcon(elem, glyphId) {
+ var cfg = config.device,
+ g = elem.append('g')
+ .attr('class', 'svgIcon deviceIcon');
+
+ g.append('rect').attr({
+ x: 0,
+ y: 0,
+ rx: cfg.rx,
+ width: cfg.dim,
+ height: cfg.dim
+ });
+
+ g.append('use').attr({
+ 'xlink:href': '#' + glyphId,
+ width: cfg.dim,
+ height: cfg.dim
+ });
+
+ g.dim = cfg.dim;
+ return g;
+ }
+
+ function addHostIcon(elem, radius, glyphId) {
+ var dim = radius * 1.5,
+ xlate = -dim / 2,
+ g = elem.append('g')
+ .attr('class', 'svgIcon hostIcon');
+
+ g.append('circle').attr('r', radius);
+
+ g.append('use').attr({
+ 'xlink:href': '#' + glyphId,
+ width: dim,
+ height: dim,
+ transform: sus.translate(xlate,xlate)
+ });
+ return g;
+ }
+
+ function sortIcons() {
+ function sortAsc(div) {
+ div.style('display', 'inline-block');
+ loadEmbeddedIcon(div, 'upArrow', 10);
+ div.classed('tableColSort', true);
+ }
+
+ function sortDesc(div) {
+ div.style('display', 'inline-block');
+ loadEmbeddedIcon(div, 'downArrow', 10);
+ div.classed('tableColSort', true);
+ }
+
+ function sortNone(div) {
+ div.remove();
+ }
+
+ return {
+ sortAsc: sortAsc,
+ sortDesc: sortDesc,
+ sortNone: sortNone
+ };
+ }
+
+
+ // =========================
+ // === DEFINE THE MODULE
+
+ angular.module('onosSvg')
+ .directive('icon', ['IconService', function (is) {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attrs) {
+ attrs.$observe('iconId', function () {
+ var div = d3.select(element[0]);
+ div.selectAll('*').remove();
+ is.loadEmbeddedIcon(div, attrs.iconId, attrs.iconSize);
+ });
+ }
+ };
+ }])
+
+ .factory('IconService', ['$log', 'FnService', 'GlyphService',
+ 'SvgUtilService',
+
+ function (_$log_, _fs_, _gs_, _sus_) {
+ $log = _$log_;
+ fs = _fs_;
+ gs = _gs_;
+ sus = _sus_;
+
+ return {
+ loadIcon: loadIcon,
+ loadIconByClass: loadIconByClass,
+ loadEmbeddedIcon: loadEmbeddedIcon,
+ addDeviceIcon: addDeviceIcon,
+ addHostIcon: addHostIcon,
+ iconConfig: function () { return config; },
+ sortIcons: sortIcons
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/map.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/map.js
new file mode 100644
index 00000000..f8d40f9c
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/map.js
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- SVG -- Map Service
+ */
+
+/*
+ The Map Service provides a simple API for loading geographical maps into
+ an SVG layer. For example, as a background to the Topology View.
+
+ e.g. var promise = MapService.loadMapInto(svgLayer, '*continental-us');
+
+ The Map Service makes use of the GeoDataService to load the required data
+ from the server and to create the appropriate geographical projection.
+
+ A promise is returned to the caller, which is resolved with the
+ map projection once created.
+*/
+
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $q, fs, gds;
+
+ // NOTE: This method assumes the datafile has exactly the map data
+ // that you want to load; for example id="*continental_us"
+ // mapping to ~/data/map/continental_us.topojson contains
+ // exactly the paths for the continental US.
+
+ function loadMapInto(mapLayer, id, opts) {
+ var promise = gds.fetchTopoData(id),
+ deferredProjection = $q.defer();
+
+ if (!promise) {
+ $log.warn('Failed to load map: ' + id);
+ return false;
+ }
+
+ promise.then(function () {
+ var gen = gds.createPathGenerator(promise.topodata, opts);
+
+ deferredProjection.resolve(gen.settings.projection);
+
+ mapLayer.selectAll('path')
+ .data(gen.geodata.features)
+ .enter()
+ .append('path')
+ .attr('d', gen.pathgen);
+ });
+ return deferredProjection.promise;
+ }
+
+ // ---
+
+ // NOTE: This method uses the countries.topojson data file, and then
+ // filters the results based on the supplied options.
+ // Usage:
+ // promise = loadMapRegionInto(svgGroup, {
+ // countryFilter: function (country) {
+ // return country.properties.continent === 'South America';
+ // }
+ // });
+
+ function loadMapRegionInto(mapLayer, filterOpts) {
+ var promise = gds.fetchTopoData("*countries"),
+ deferredProjection = $q.defer();
+
+ if (!promise) {
+ $log.warn('Failed to load countries TopoJSON data');
+ return false;
+ }
+
+ promise.then(function () {
+ var width = 1000,
+ height = 1000,
+ proj = d3.geo.mercator().translate([width/2, height/2]),
+ pathGen = d3.geo.path().projection(proj),
+ data = promise.topodata,
+ features = topojson.feature(data, data.objects.countries).features,
+ country = features.filter(filterOpts.countryFilter),
+ countryFeature = {
+ type: 'FeatureCollection',
+ features: country
+ },
+ path = d3.geo.path().projection(proj);
+
+ gds.rescaleProjection(proj, 0.95, 1000, path, countryFeature);
+
+ deferredProjection.resolve(proj);
+
+ mapLayer.selectAll('path.country')
+ .data([countryFeature])
+ .enter()
+ .append('path').classed('country', true)
+ .attr('d', pathGen);
+ });
+ return deferredProjection.promise;
+ }
+
+ angular.module('onosSvg')
+ .factory('MapService', ['$log', '$q', 'FnService', 'GeoDataService',
+ function (_$log_, _$q_, _fs_, _gds_) {
+ $log = _$log_;
+ $q = _$q_;
+ fs = _fs_;
+ gds = _gds_;
+
+ return {
+ loadMapRegionInto: loadMapRegionInto,
+ loadMapInto: loadMapInto
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svg.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svg.js
new file mode 100644
index 00000000..8fd441fe
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svg.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Scalable Vector Graphics Module
+ */
+(function () {
+ 'use strict';
+
+ angular.module('onosSvg', ['onosUtil']);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svgUtil.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
new file mode 100644
index 00000000..cb67ae83
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- SVG -- Util Service
+ */
+
+/*
+ The SVG Util Service provides a miscellany of utility functions.
+ */
+
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, fs;
+
+ angular.module('onosSvg')
+ .factory('SvgUtilService', ['$log', 'FnService',
+ function (_$log_, _fs_) {
+ $log = _$log_;
+ fs = _fs_;
+
+ // TODO: change 'force' ref to be 'force.alpha' ref.
+ function createDragBehavior(force, selectCb, atDragEnd,
+ dragEnabled, clickEnabled) {
+ var draggedThreshold = d3.scale.linear()
+ .domain([0, 0.1])
+ .range([5, 20])
+ .clamp(true),
+ drag,
+ fSel = fs.isF(selectCb),
+ fEnd = fs.isF(atDragEnd),
+ fDEn = fs.isF(dragEnabled),
+ fCEn = fs.isF(clickEnabled),
+ bad = [];
+
+ function naf(what) {
+ return 'SvgUtilService: createDragBehavior(): ' + what +
+ ' is not a function';
+ }
+
+ if (!force) {
+ bad.push('SvgUtilService: createDragBehavior(): ' +
+ 'Bad force reference');
+ }
+ if (!fSel) {
+ bad.push(naf('selectCb'));
+ }
+ if (!fEnd) {
+ bad.push(naf('atDragEnd'));
+ }
+ if (!fDEn) {
+ bad.push(naf('dragEnabled'));
+ }
+ if (!fCEn) {
+ bad.push(naf('clickEnabled'));
+ }
+
+ if (bad.length) {
+ $log.error(bad.join('\n'));
+ return null;
+ }
+
+ function dragged(d) {
+ var threshold = draggedThreshold(force.alpha()),
+ dx = d.oldX - d.px,
+ dy = d.oldY - d.py;
+ if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
+ d.dragged = true;
+ }
+ return d.dragged;
+ }
+
+ drag = d3.behavior.drag()
+ .origin(function(d) { return d; })
+ .on('dragstart', function(d) {
+ if (clickEnabled() || dragEnabled()) {
+ d3.event.sourceEvent.stopPropagation();
+
+ d.oldX = d.x;
+ d.oldY = d.y;
+ d.dragged = false;
+ d.fixed |= 2;
+ d.dragStarted = true;
+ }
+ })
+ .on('drag', function(d) {
+ if (dragEnabled()) {
+ d.px = d3.event.x;
+ d.py = d3.event.y;
+ if (dragged(d)) {
+ if (!force.alpha()) {
+ force.alpha(.025);
+ }
+ }
+ }
+ })
+ .on('dragend', function(d) {
+ if (d.dragStarted) {
+ d.dragStarted = false;
+ if (!dragged(d)) {
+ // consider this the same as a 'click'
+ // (selection of a node)
+ if (clickEnabled()) {
+ selectCb.call(this, d);
+ }
+ }
+ d.fixed &= ~6;
+
+ // hook at the end of a drag gesture
+ if (dragEnabled()) {
+ atDragEnd.call(this, d);
+ }
+ }
+ });
+
+ return drag;
+ }
+
+
+ function loadGlow(defs, r, g, b, id) {
+ var glow = defs.append('filter')
+ .attr('x', '-50%')
+ .attr('y', '-50%')
+ .attr('width', '200%')
+ .attr('height', '200%')
+ .attr('id', id);
+
+ glow.append('feColorMatrix')
+ .attr('type', 'matrix')
+ .attr('values',
+ '0 0 0 0 ' + r + ' ' +
+ '0 0 0 0 ' + g + ' ' +
+ '0 0 0 0 ' + b + ' ' +
+ '0 0 0 1 0 ');
+
+ glow.append('feGaussianBlur')
+ .attr('stdDeviation', 3)
+ .attr('result', 'coloredBlur');
+
+ glow.append('feMerge').selectAll('feMergeNode')
+ .data(['coloredBlur', 'SourceGraphic'])
+ .enter().append('feMergeNode')
+ .attr('in', String);
+ }
+
+ function loadGlowDefs(defs) {
+ loadGlow(defs, 0.0, 0.0, 0.7, 'blue-glow');
+ loadGlow(defs, 1.0, 1.0, 0.3, 'yellow-glow');
+ }
+
+ // --- Ordinal scales for 7 values.
+
+ // blue brown brick red sea green purple dark teal lime
+ var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
+ lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
+
+ darkNorm = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'],
+ darkMute = ['#304860', '#664631', '#A8391B', '#00754B', '#77206D', '#005959', '#428700'];
+
+ var colors= {
+ light: {
+ norm: d3.scale.ordinal().range(lightNorm),
+ mute: d3.scale.ordinal().range(lightMute)
+ },
+ dark: {
+ norm: d3.scale.ordinal().range(darkNorm),
+ mute: d3.scale.ordinal().range(darkMute)
+ }
+ };
+
+ function cat7() {
+ var tcid = 'd3utilTestCard';
+
+ function getColor(id, muted, theme) {
+ // NOTE: since we are lazily assigning domain ids, we need to
+ // get the color from all 4 scales, to keep the domains
+ // in sync.
+ var ln = colors.light.norm(id),
+ lm = colors.light.mute(id),
+ dn = colors.dark.norm(id),
+ dm = colors.dark.mute(id);
+ if (theme === 'dark') {
+ return muted ? dm : dn;
+ } else {
+ return muted ? lm : ln;
+ }
+ }
+
+ function testCard(svg) {
+ var g = svg.select('g#' + tcid),
+ dom = d3.range(7),
+ k, muted, theme, what;
+
+ if (!g.empty()) {
+ g.remove();
+
+ } else {
+ g = svg.append('g')
+ .attr('id', tcid)
+ .attr('transform', 'scale(4)translate(20,20)');
+
+ for (k=0; k<4; k++) {
+ muted = k%2;
+ what = muted ? ' muted' : ' normal';
+ theme = k < 2 ? 'light' : 'dark';
+ dom.forEach(function (id, i) {
+ var x = i * 20,
+ y = k * 20,
+ f = get(id, muted, theme);
+ g.append('circle').attr({
+ cx: x,
+ cy: y,
+ r: 5,
+ fill: f
+ });
+ });
+ g.append('rect').attr({
+ x: 140,
+ y: k * 20 - 5,
+ width: 32,
+ height: 10,
+ rx: 2,
+ fill: '#888'
+ });
+ g.append('text').text(theme + what)
+ .attr({
+ x: 142,
+ y: k * 20 + 2,
+ fill: 'white'
+ })
+ .style('font-size', '4pt');
+ }
+ }
+ }
+
+ return {
+ testCard: testCard,
+ getColor: getColor
+ };
+ }
+
+ function translate(x, y) {
+ if (fs.isA(x) && x.length === 2 && !y) {
+ return 'translate(' + x[0] + ',' + x[1] + ')';
+ }
+ return 'translate(' + x + ',' + y + ')';
+ }
+
+ function scale(x, y) {
+ return 'scale(' + x + ',' + y + ')';
+ }
+
+ function skewX(x) {
+ return 'skewX(' + x + ')';
+ }
+
+ function rotate(deg) {
+ return 'rotate(' + deg + ')';
+ }
+
+ function stripPx(s) {
+ return s.replace(/px$/,'');
+ }
+
+ function safeId(s) {
+ return s.replace(/[^a-z0-9]/gi, '-');
+ }
+
+ function makeVisible(el, b) {
+ el.style('visibility', (b ? 'visible' : 'hidden'));
+ }
+
+ function isVisible(el) {
+ return el.style('visibility') === 'visible';
+ }
+
+ return {
+ createDragBehavior: createDragBehavior,
+ loadGlowDefs: loadGlowDefs,
+ cat7: cat7,
+ translate: translate,
+ scale: scale,
+ skewX: skewX,
+ rotate: rotate,
+ stripPx: stripPx,
+ safeId: safeId,
+ visible: function (el, x) {
+ if (x === undefined) {
+ return isVisible(el);
+ } else {
+ makeVisible(el, x);
+ }
+ }
+ };
+ }]);
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/zoom.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/zoom.js
new file mode 100644
index 00000000..6acab794
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/svg/zoom.js
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- SVG -- Zoom Service
+ */
+(function () {
+ 'use strict';
+
+ // configuration
+ var defaultSettings = {
+ zoomMin: 0.05,
+ zoomMax: 10,
+ zoomEnabled: function (ev) { return true; },
+ zoomCallback: function () {}
+ };
+
+ // injected references to services
+ var $log;
+
+ angular.module('onosSvg')
+ .factory('ZoomService', ['$log',
+
+ function (_$log_) {
+ $log = _$log_;
+
+/*
+ NOTE: opts is an object:
+ {
+ svg: svg, D3 selection of <svg> element
+ zoomLayer: zoomLayer, D3 selection of <g> element
+ zoomEnabled: function (ev) { ... },
+ zoomCallback: function () { ... }
+ }
+
+ where:
+ * svg and zoomLayer should be D3 selections of DOM elements.
+ * zoomLayer <g> is a child of <svg> element.
+ * zoomEnabled is an optional predicate based on D3 event.
+ * default is always enabled.
+ * zoomCallback is an optional callback invoked each time we pan/zoom.
+ * default is do nothing.
+
+ Optionally, zoomMin and zoomMax also can be defined.
+ These default to 0.25 and 10 respectively.
+*/
+ function createZoomer(opts) {
+ var cz = 'ZoomService.createZoomer(): ',
+ d3s = ' (D3 selection) property defined',
+ settings = angular.extend({}, defaultSettings, opts),
+ zoom = d3.behavior.zoom()
+ .translate([0, 0])
+ .scale(1)
+ .scaleExtent([settings.zoomMin, settings.zoomMax])
+ .on('zoom', zoomed),
+ fail = false,
+ zoomer;
+
+ if (!settings.svg) {
+ $log.error(cz + 'No "svg" (svg tag)' + d3s);
+ fail = true;
+ }
+ if (!settings.zoomLayer) {
+ $log.error(cz + 'No "zoomLayer" (g tag)' + d3s);
+ fail = true;
+ }
+
+ if (fail) {
+ return null;
+ }
+
+ // zoom events from mouse gestures...
+ function zoomed() {
+ var ev = d3.event.sourceEvent;
+ if (settings.zoomEnabled(ev)) {
+ adjustZoomLayer(d3.event.translate, d3.event.scale);
+ }
+ }
+
+ function adjustZoomLayer(translate, scale) {
+ settings.zoomLayer.attr('transform',
+ 'translate(' + translate + ')scale(' + scale + ')');
+ settings.zoomCallback();
+ }
+
+ zoomer = {
+ panZoom: function (translate, scale) {
+ zoom.translate(translate).scale(scale);
+ adjustZoomLayer(translate, scale);
+ },
+
+ reset: function () {
+ zoomer.panZoom([0,0], 1);
+ },
+
+ translate: function () {
+ return zoom.translate();
+ },
+
+ scale: function () {
+ return zoom.scale();
+ },
+
+ scaleExtent: function () {
+ return zoom.scaleExtent();
+ }
+ };
+
+ // apply the zoom behavior to the SVG element
+ settings.svg && settings.svg.call(zoom);
+ return zoomer;
+ }
+
+ return {
+ createZoomer: createZoomer
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/util/fn.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/fn.js
new file mode 100644
index 00000000..defb8458
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Util -- General Purpose Functions
+ */
+(function () {
+ 'use strict';
+
+ // injected services
+ var $window, $log;
+
+ // internal state
+ var debugFlags = {};
+
+
+ function _parseDebugFlags(dbgstr) {
+ var bits = dbgstr ? dbgstr.split(",") : [];
+ bits.forEach(function (key) {
+ debugFlags[key] = true;
+ });
+ $log.debug('Debug flags:', dbgstr);
+ }
+
+ function isF(f) {
+ return typeof f === 'function' ? f : null;
+ }
+
+ function isA(a) {
+ // NOTE: Array.isArray() is part of EMCAScript 5.1
+ return Array.isArray(a) ? a : null;
+ }
+
+ function isS(s) {
+ return typeof s === 'string' ? s : null;
+ }
+
+ function isO(o) {
+ return (o && typeof o === 'object' && o.constructor === Object) ? o : null;
+ }
+
+ function contains(a, x) {
+ return isA(a) && a.indexOf(x) > -1;
+ }
+
+ // Returns true if all names in the array are defined as functions
+ // on the given api object; false otherwise.
+ // Also returns false if there are properties on the api that are NOT
+ // listed in the array of names.
+ function areFunctions(api, fnNames) {
+ var fnLookup = {},
+ extraFound = false;
+
+ if (!isA(fnNames)) {
+ return false;
+ }
+ var n = fnNames.length,
+ i, name;
+ for (i=0; i<n; i++) {
+ name = fnNames[i];
+ if (!isF(api[name])) {
+ return false;
+ }
+ fnLookup[name] = true;
+ }
+
+ // check for properties on the API that are not listed in the array,
+ angular.forEach(api, function (value, key) {
+ if (!fnLookup[key]) {
+ extraFound = true;
+ }
+ });
+ return !extraFound;
+ }
+
+ // Returns true if all names in the array are defined as functions
+ // on the given api object; false otherwise. This is a non-strict version
+ // that does not care about other properties on the api.
+ function areFunctionsNonStrict(api, fnNames) {
+ if (!isA(fnNames)) {
+ return false;
+ }
+ var n = fnNames.length,
+ i, name;
+ for (i=0; i<n; i++) {
+ name = fnNames[i];
+ if (!isF(api[name])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Returns width and height of window inner dimensions.
+ // offH, offW : offset width/height are subtracted, if present
+ function windowSize(offH, offW) {
+ var oh = offH || 0,
+ ow = offW || 0;
+ return {
+ height: $window.innerHeight - oh,
+ width: $window.innerWidth - ow
+ };
+ }
+
+ // Returns true if current browser determined to be a mobile device
+ function isMobile() {
+ var ua = $window.navigator.userAgent,
+ patt = /iPhone|iPod|iPad|Silk|Android|BlackBerry|Opera Mini|IEMobile/;
+ return patt.test(ua);
+ }
+
+ // Returns true if the current browser determined to be Chrome
+ function isChrome() {
+ var isChromium = $window.chrome,
+ vendorName = $window.navigator.vendor,
+ isOpera = $window.navigator.userAgent.indexOf("OPR") > -1;
+ return (isChromium !== null &&
+ isChromium !== undefined &&
+ vendorName === "Google Inc." &&
+ isOpera == false);
+ }
+
+ // Returns true if the current browser determined to be Safari
+ function isSafari() {
+ return ($window.navigator.userAgent.indexOf('Safari') !== -1 &&
+ $window.navigator.userAgent.indexOf('Chrome') === -1);
+ }
+
+ // Returns true if the current browser determined to be Firefox
+ function isFirefox() {
+ return typeof InstallTrigger !== 'undefined';
+ }
+
+ // search through an array of objects, looking for the one with the
+ // tagged property matching the given key. tag defaults to 'id'.
+ // returns the index of the matching object, or -1 for no match.
+ function find(key, array, tag) {
+ var _tag = tag || 'id',
+ idx, n, d;
+ for (idx = 0, n = array.length; idx < n; idx++) {
+ d = array[idx];
+ if (d[_tag] === key) {
+ return idx;
+ }
+ }
+ return -1;
+ }
+
+ // search through array to find (the first occurrence of) item,
+ // returning its index if found; otherwise returning -1.
+ function inArray(item, array) {
+ var i;
+ if (isA(array)) {
+ for (i=0; i<array.length; i++) {
+ if (array[i] === item) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ // remove (the first occurrence of) the specified item from the given
+ // array, if any. Return true if the removal was made; false otherwise.
+ function removeFromArray(item, array) {
+ var found = false,
+ i = inArray(item, array);
+ if (i >= 0) {
+ array.splice(i, 1);
+ found = true;
+ }
+ return found;
+ }
+
+ // return true if the object is empty, return false otherwise
+ function isEmptyObject(obj) {
+ var key;
+ for (key in obj) {
+ return false;
+ }
+ return true;
+ }
+
+ // returns true if the two objects have all the same properties
+ function sameObjProps(obj1, obj2) {
+ var key;
+ for (key in obj1) {
+ if (obj1.hasOwnProperty(key)) {
+ if (!(obj1[key] === obj2[key])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // returns true if the array contains the object
+ // does NOT use strict object reference equality,
+ // instead checks each property individually for equality
+ function containsObj(arr, obj) {
+ var i,
+ len = arr.length;
+ for (i = 0; i < len; i++) {
+ if (sameObjProps(arr[i], obj)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // return the given string with the first character capitalized.
+ function cap(s) {
+ return s.toLowerCase().replace(/^[a-z]/, function (m) {
+ return m.toUpperCase();
+ });
+ }
+
+ // return the parameter without a px suffix
+ function noPx(num) {
+ return Number(num.replace(/px$/, ''));
+ }
+
+ // return an element's given style property without px suffix
+ function noPxStyle(elem, prop) {
+ return Number(elem.style(prop).replace(/px$/, ''));
+ }
+
+ function endsWith(str, suffix) {
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ }
+
+ function parseBitRate(str) {
+ return Number(str.replace(/,/, '')
+ .replace(/\s+.bps/i, '')
+ .replace(/\.\d*/, ''));
+ }
+
+ // return true if the given debug flag was specified in the query params
+ function debugOn(tag) {
+ return debugFlags[tag];
+ }
+
+ angular.module('onosUtil')
+ .factory('FnService',
+ ['$window', '$location', '$log', function (_$window_, $loc, _$log_) {
+ $window = _$window_;
+ $log = _$log_;
+
+ _parseDebugFlags($loc.search().debug);
+
+ return {
+ isF: isF,
+ isA: isA,
+ isS: isS,
+ isO: isO,
+ contains: contains,
+ areFunctions: areFunctions,
+ areFunctionsNonStrict: areFunctionsNonStrict,
+ windowSize: windowSize,
+ isMobile: isMobile,
+ isChrome: isChrome,
+ isSafari: isSafari,
+ isFirefox: isFirefox,
+ debugOn: debugOn,
+ find: find,
+ inArray: inArray,
+ removeFromArray: removeFromArray,
+ isEmptyObject: isEmptyObject,
+ sameObjProps: sameObjProps,
+ containsObj: containsObj,
+ cap: cap,
+ noPx: noPx,
+ noPxStyle: noPxStyle,
+ endsWith: endsWith,
+ parseBitRate: parseBitRate
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/util/keys.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/keys.js
new file mode 100644
index 00000000..2985565c
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/keys.js
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Util -- Key Handler Service
+ */
+(function () {
+ 'use strict';
+
+ // references to injected services
+ var $log, fs, ts, ns, qhs;
+
+ // internal state
+ var enabled = true,
+ keyHandler = {
+ globalKeys: {},
+ maskedKeys: {},
+ viewKeys: {},
+ viewFn: null,
+ viewGestures: []
+ };
+
+ function whatKey(code) {
+ switch (code) {
+ case 13: return 'enter';
+ case 16: return 'shift';
+ case 17: return 'ctrl';
+ case 18: return 'alt';
+ case 27: return 'esc';
+ case 32: return 'space';
+ case 37: return 'leftArrow';
+ case 38: return 'upArrow';
+ case 39: return 'rightArrow';
+ case 40: return 'downArrow';
+ case 91: return 'cmdLeft';
+ case 93: return 'cmdRight';
+ case 187: return 'equals';
+ case 188: return 'comma';
+ case 189: return 'dash';
+ case 190: return 'dot';
+ case 191: return 'slash';
+ case 192: return 'backQuote';
+ case 220: return 'backSlash';
+ default:
+ if ((code >= 48 && code <= 57) ||
+ (code >= 65 && code <= 90)) {
+ return String.fromCharCode(code);
+ } else if (code >= 112 && code <= 123) {
+ return 'F' + (code - 111);
+ }
+ return '.';
+ }
+ }
+
+ function keyIn() {
+ var event = d3.event,
+ keyCode = event.keyCode,
+ key = whatKey(keyCode),
+ kh = keyHandler,
+ gk = kh.globalKeys[key],
+ gcb = fs.isF(gk) || (fs.isA(gk) && fs.isF(gk[0])),
+ vk = kh.viewKeys[key],
+ kl = fs.isF(kh.viewKeys._keyListener),
+ vcb = fs.isF(vk) || (fs.isA(vk) && fs.isF(vk[0])) || fs.isF(kh.viewFn),
+ token = 'keyev'; // indicate this was a key-pressed event
+
+ d3.event.stopPropagation();
+
+ if (enabled) {
+ // global callback?
+ if (gcb && gcb(token, key, keyCode, event)) {
+ // if the event was 'handled', we are done
+ return;
+ }
+ // otherwise, let the view callback have a shot
+ if (vcb) {
+ vcb(token, key, keyCode, event);
+ }
+ if (kl) {
+ kl(key);
+ }
+ }
+ }
+
+ function setupGlobalKeys() {
+ angular.extend(keyHandler, {
+ globalKeys: {
+ backSlash: [quickHelp, 'Show / hide Quick Help'],
+ slash: [quickHelp, 'Show / hide Quick Help'],
+ esc: [escapeKey, 'Dismiss dialog or cancel selections'],
+ T: [toggleTheme, "Toggle theme"]
+ },
+ globalFormat: ['backSlash', 'slash', 'esc', 'T'],
+
+ // Masked keys are global key handlers that always return true.
+ // That is, the view will never see the event for that key.
+ maskedKeys: {
+ slash: true,
+ backSlash: true,
+ T: true
+ }
+ });
+ }
+
+ function quickHelp(view, key, code, ev) {
+ qhs.showQuickHelp(keyHandler);
+ return true;
+ }
+
+ // returns true if we 'consumed' the ESC keypress, false otherwise
+ function escapeKey(view, key, code, ev) {
+ return ns.hideIfShown() || qhs.hideQuickHelp();
+ }
+
+ function toggleTheme(view, key, code, ev) {
+ ts.toggleTheme();
+ return true;
+ }
+
+ function setKeyBindings(keyArg) {
+ var viewKeys,
+ masked = [];
+
+ if (fs.isF(keyArg)) {
+ // set general key handler callback
+ keyHandler.viewFn = keyArg;
+ } else {
+ // set specific key filter map
+ viewKeys = d3.map(keyArg).keys();
+ viewKeys.forEach(function (key) {
+ if (keyHandler.maskedKeys[key]) {
+ masked.push('setKeyBindings(): Key "' + key + '" is reserved');
+ }
+ });
+
+ if (masked.length) {
+ $log.warn(masked.join('\n'));
+ }
+ keyHandler.viewKeys = keyArg;
+ }
+ }
+
+ function getKeyBindings() {
+ var gkeys = d3.map(keyHandler.globalKeys).keys(),
+ masked = d3.map(keyHandler.maskedKeys).keys(),
+ vkeys = d3.map(keyHandler.viewKeys).keys(),
+ vfn = !!fs.isF(keyHandler.viewFn);
+
+ return {
+ globalKeys: gkeys,
+ maskedKeys: masked,
+ viewKeys: vkeys,
+ viewFunction: vfn
+ };
+ }
+
+ function unbindKeys() {
+ keyHandler.viewKeys = {};
+ keyHandler.viewFn = null;
+ keyHandler.viewGestures = [];
+ }
+
+ angular.module('onosUtil')
+ .factory('KeyService',
+ ['$log', 'FnService', 'ThemeService', 'NavService',
+
+ function (_$log_, _fs_, _ts_, _ns_) {
+ $log = _$log_;
+ fs = _fs_;
+ ts = _ts_;
+ ns = _ns_;
+
+ return {
+ bindQhs: function (_qhs_) {
+ qhs = _qhs_;
+ },
+ installOn: function (elem) {
+ elem.on('keydown', keyIn);
+ setupGlobalKeys();
+ },
+ keyBindings: function (x) {
+ if (x === undefined) {
+ return getKeyBindings();
+ } else {
+ setKeyBindings(x);
+ }
+ },
+ unbindKeys: unbindKeys,
+ gestureNotes: function (g) {
+ if (g === undefined) {
+ return keyHandler.viewGestures;
+ } else {
+ keyHandler.viewGestures = fs.isA(g) || [];
+ }
+ },
+ enableKeys: function (b) {
+ enabled = b;
+ }
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/util/prefs.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/prefs.js
new file mode 100644
index 00000000..02a23909
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/prefs.js
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Util -- User Preference Service
+ */
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, $cookies, fs;
+
+ // internal state
+ var cache = {};
+
+ // NOTE: in Angular 1.3.5, $cookies is just a simple object, and
+ // cookie values are just strings. From the 1.3.5 docs:
+ //
+ // "Only a simple Object is exposed and by adding or removing
+ // properties to/from this object, new cookies are created/deleted
+ // at the end of current $eval. The object's properties can only
+ // be strings."
+ //
+ // We may want to upgrade the version of Angular sometime soon
+ // since later version support objects as cookie values.
+
+ // NOTE: prefs represented as simple name/value pairs
+ // => a temporary restriction while we are encoding into cookies
+ /*
+ {
+ foo: 1,
+ bar: 0,
+ goo: 2
+ }
+
+ stored as "foo:1,bar:0,goo:2"
+ */
+
+ // reads cookie with given name and returns an object rep of its value
+ // or null if no such cookie is set
+ function getPrefs(name) {
+ var cook = $cookies[name],
+ bits,
+ obj = {};
+
+ if (cook) {
+ bits = cook.split(',');
+ bits.forEach(function (value) {
+ var x = value.split(':');
+ obj[x[0]] = x[1];
+ });
+
+ // update the cache
+ cache[name] = obj;
+ return obj;
+ }
+ // perhaps we have a cached copy..
+ return cache[name];
+ }
+
+ // converts string values to numbers for selected (or all) keys
+ function asNumbers(obj, keys) {
+ if (!obj) return null;
+
+ if (!keys) {
+ // do them all
+ angular.forEach(obj, function (v, k) {
+ obj[k] = Number(obj[k]);
+ });
+ } else {
+ keys.forEach(function (k) {
+ obj[k] = Number(obj[k]);
+ });
+ }
+ return obj;
+ }
+
+ function setPrefs(name, obj) {
+ var bits = [],
+ str;
+
+ angular.forEach(obj, function (value, key) {
+ bits.push(key + ':' + value);
+ });
+ str = bits.join(',');
+
+ // keep a cached copy of the object
+ cache[name] = obj;
+
+ // The angular way of doing this...
+ // $cookies[name] = str;
+ // ...but it appears that this gets delayed, and doesn't 'stick' ??
+
+ // FORCE cookie to be set by writing directly to document.cookie...
+ document.cookie = name + '=' + encodeURIComponent(str);
+ if (fs.debugOn('prefs')) {
+ $log.debug('<<>> Wrote cookie <'+name+'>:', str);
+ }
+ }
+
+ angular.module('onosUtil')
+ .factory('PrefsService', ['$log', '$cookies', 'FnService',
+ function (_$log_, _$cookies_, _fs_) {
+ $log = _$log_;
+ $cookies = _$cookies_;
+ fs = _fs_;
+
+ return {
+ getPrefs: getPrefs,
+ asNumbers: asNumbers,
+ setPrefs: setPrefs
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/util/random.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/random.js
new file mode 100644
index 00000000..2298a944
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/random.js
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Random -- Encapsulated randomness
+ */
+(function () {
+ 'use strict';
+
+ var $log, fs;
+
+ var halfRoot2 = 0.7071;
+
+ // given some value, s, returns an integer between -s/2 and s/2
+ // e.g. s = 100; result in the range [-50..50)
+ function spread(s) {
+ return Math.floor((Math.random() * s) - s / 2);
+ }
+
+ // for a given dimension, d, choose a random value somewhere between
+ // 0 and d where the value is within (d / (2 * sqrt(2))) of d/2.
+ function randDim(d) {
+ return d / 2 + spread(d * halfRoot2);
+ }
+
+ angular.module('onosUtil')
+ .factory('RandomService', ['$log', 'FnService',
+
+ function (_$log_, _fs_) {
+ $log = _$log_;
+ fs = _fs_;
+
+ return {
+ spread: spread,
+ randDim: randDim
+ };
+ }]);
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/util/theme.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/theme.js
new file mode 100644
index 00000000..0e0ee649
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/theme.js
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Util -- Theme Service
+ */
+(function () {
+ 'use strict';
+
+ var $log, fs;
+
+ var themes = ['light', 'dark'],
+ themeStr = themes.join(' '),
+ thidx,
+ listeners = {},
+ nextListenerId = 1;
+
+ function init() {
+ thidx = 0;
+ updateBodyClass();
+ }
+
+ function getTheme() {
+ return themes[thidx];
+ }
+
+ function setTheme(t) {
+ var idx = themes.indexOf(t);
+ if (idx > -1 && idx !== thidx) {
+ thidx = idx;
+ updateBodyClass();
+ themeEvent('set');
+ }
+ }
+
+ function toggleTheme() {
+ var i = thidx + 1;
+ thidx = (i===themes.length) ? 0 : i;
+ updateBodyClass();
+ themeEvent('toggle');
+ return getTheme();
+ }
+
+ function updateBodyClass() {
+ var body = d3.select('body');
+ body.classed(themeStr, false);
+ body.classed(getTheme(), true);
+ }
+
+ function themeEvent(w) {
+ var t = getTheme(),
+ m = 'Theme-Change-('+w+'): ' + t;
+ $log.debug(m);
+ angular.forEach(listeners, function(value) {
+ value.cb(
+ {
+ event: 'themeChange',
+ value: t
+ }
+ );
+ });
+ }
+
+ function addListener(callback) {
+ var id = nextListenerId++,
+ cb = fs.isF(callback),
+ o = { id: id, cb: cb };
+
+ if (cb) {
+ listeners[id] = o;
+ } else {
+ $log.error('ThemeService.addListener(): callback not a function');
+ o.error = 'No callback defined';
+ }
+ return o;
+ }
+
+ function removeListener(lsnr) {
+ var id = lsnr && lsnr.id,
+ o = listeners[id];
+ if (o) {
+ delete listeners[id];
+ }
+ }
+
+ angular.module('onosUtil')
+ .factory('ThemeService', ['$log', 'FnService',
+ function (_$log_, _fs_) {
+ $log = _$log_;
+ fs = _fs_;
+ thidx = 0;
+
+ return {
+ init: init,
+ theme: function (x) {
+ if (x === undefined) {
+ return getTheme();
+ } else {
+ setTheme(x);
+ }
+ },
+ toggleTheme: toggleTheme,
+ addListener: addListener,
+ removeListener: removeListener
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/util/util.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/util.js
new file mode 100644
index 00000000..dc3b09c2
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/util/util.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Utilities Module
+ */
+(function () {
+ 'use strict';
+
+ angular.module('onosUtil', []);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.css
new file mode 100644
index 00000000..9ae03595
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.css
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Button Service -- CSS file
+ */
+
+.button,
+.toggleButton,
+.radioSet {
+ display: inline-block;
+ padding: 0 4px;
+}
+.radioButton {
+ display: inline-block;
+ padding: 0 2px;
+}
+
+.button svg.embeddedIcon,
+.toggleButton svg.embeddedIcon,
+.radioButton svg.embeddedIcon {
+ cursor: pointer;
+}
+.button svg.embeddedIcon .icon rect,
+.toggleButton svg.embeddedIcon .icon rect,
+.radioButton svg.embeddedIcon .icon rect{
+ stroke: none;
+}
+
+/* Selected button color */
+.light .button svg.embeddedIcon .icon rect,
+.light .toggleButton.selected svg.embeddedIcon .icon rect,
+.light .radioButton.selected svg.embeddedIcon .icon rect {
+ fill: #838383;
+}
+
+.light .button:hover svg.embeddedIcon .icon rect,
+.light .toggleButton.selected:hover svg.embeddedIcon .icon rect
+/* NOTE: selected radio button should NOT have hover highlight */
+{
+ fill: #444444;
+}
+
+.light .button svg.embeddedIcon .glyph,
+.light .toggleButton.selected svg.embeddedIcon .glyph,
+.light .radioButton.selected svg.embeddedIcon .glyph {
+ fill: white;
+}
+
+.dark .button svg.embeddedIcon .icon rect,
+.dark .toggleButton.selected svg.embeddedIcon .icon rect,
+.dark .radioButton.selected svg.embeddedIcon .icon rect {
+ fill: #151515;
+}
+
+.dark .button:hover svg.embeddedIcon .icon rect,
+.dark .toggleButton.selected:hover svg.embeddedIcon .icon rect
+/* NOTE: selected radio button should NOT have hover highlight */
+{
+ fill: #444;
+}
+
+.dark .button svg.embeddedIcon .glyph,
+.dark .toggleButton.selected svg.embeddedIcon .glyph,
+.dark .radioButton.selected svg.embeddedIcon .glyph {
+ fill: #B2B2B2;
+}
+
+/* Unselected button color */
+.light .toggleButton svg.embeddedIcon .icon rect,
+.light .radioButton svg.embeddedIcon .icon rect {
+ fill: #eee;
+}
+
+.light .toggleButton:hover svg.embeddedIcon .icon rect,
+.light .radioButton:hover:not(.selected) svg.embeddedIcon .icon rect {
+ fill: #ccc;
+}
+
+.light .toggleButton svg.embeddedIcon .glyph,
+.light .radioButton svg.embeddedIcon .glyph {
+ fill: #bbb;
+}
+.light .toggleButton:hover:not(.selected) svg.embeddedIcon .glyph,
+.light .radioButton:hover:not(.selected) svg.embeddedIcon .glyph {
+ fill: #999;
+}
+
+.dark .toggleButton svg.embeddedIcon .icon rect,
+.dark .radioButton svg.embeddedIcon .icon rect {
+ fill: #303030;
+}
+
+.dark .toggleButton:hover:not(.selected) svg.embeddedIcon .icon rect,
+.dark .radioButton:hover:not(.selected) svg.embeddedIcon .icon rect {
+ fill: #555;
+}
+
+.dark .toggleButton svg.embeddedIcon .glyph,
+.dark .radioButton svg.embeddedIcon .glyph {
+ fill: #565656;
+}
+
+.dark .toggleButton:hover:not(.selected) svg.embeddedIcon .glyph,
+.dark .radioButton:hover:not(.selected) svg.embeddedIcon .glyph {
+ fill: #777;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.js
new file mode 100644
index 00000000..09cdd43a
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/button.js
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Widget -- Button Service
+ */
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, fs, is, tts;
+
+ // configuration
+ var btnSize = 25,
+ btnPadding = 4;
+
+
+ // === Helper Functions
+
+ function divExists(div, msg) {
+ if (!div) {
+ $log.warn('div undefined (' + msg + ')');
+ }
+ return !!div;
+ }
+
+ function createDiv(div, cls, id) {
+ return div.append('div')
+ .classed(cls, true)
+ .attr('id', id);
+ }
+
+ function noop() {}
+
+ function buttonWidth() {
+ return btnSize + 2 * btnPadding;
+ }
+
+ // === BUTTON =================================================
+
+ // div is where to put the button (d3.selection of a DIV element)
+ // id should be globally unique
+ // gid is glyph ID (from Glyph Service)
+ // cb is callback function on click
+ // tooltip is text for tooltip
+ function button(div, id, gid, cb, tooltip) {
+ if (!divExists(div, 'button')) return null;
+
+ var btnDiv = createDiv(div, 'button', id),
+ cbFnc = fs.isF(cb) || noop;
+
+ is.loadIcon(btnDiv, gid, btnSize, true);
+ if (tooltip) { tts.addTooltip(btnDiv, tooltip); }
+
+ btnDiv.on('click', cbFnc);
+
+ return {
+ id: id,
+ width: buttonWidth
+ }
+ }
+
+
+ // === TOGGLE BUTTON ==========================================
+
+ // div is where to put the button (d3.selection of a DIV element)
+ // id should be globally unique
+ // gid is glyph ID (from Glyph Service)
+ // initState is whether the toggle is on or not to begin
+ // cb is callback function on click
+ // tooltip is text for tooltip
+ function toggle(div, id, gid, initState, cb, tooltip) {
+ if (!divExists(div, 'toggle button')) return null;
+
+ var sel = !!initState,
+ togDiv = createDiv(div, 'toggleButton', id),
+ cbFnc = fs.isF(cb) || noop;
+
+ is.loadIcon(togDiv, gid, btnSize, true);
+ togDiv.classed('selected', sel);
+ if (tooltip) { tts.addTooltip(togDiv, tooltip); }
+
+ function _toggle(b, nocb) {
+ sel = (b === undefined) ? !sel : !!b;
+ togDiv.classed('selected', sel);
+ nocb || cbFnc(sel);
+ }
+
+ // toggle the button state without invoking the callback
+ function toggleNoCb() {
+ _toggle(undefined, true);
+ }
+
+ togDiv.on('click', _toggle);
+
+ return {
+ id: id,
+ width: buttonWidth,
+ selected: function () { return sel; },
+ toggle: _toggle,
+ toggleNoCb: toggleNoCb
+ }
+ }
+
+
+ // === RADIO BUTTON SET =======================================
+
+
+ // div is where to put the button (d3.selection of a DIV element)
+ // id should be globally unique
+ // rset is an array of button descriptors of the following form:
+ // {
+ // gid: glyphId,
+ // tooltip: tooltipText,
+ // cb: callbackFunction
+ // }
+ function radioSet(div, id, rset) {
+ if (!divExists(div, 'radio button set')) return null;
+
+ if (!fs.isA(rset) || !rset.length) {
+ $log.warn('invalid array (radio button set)');
+ return null;
+ }
+
+ var rDiv = createDiv(div, 'radioSet', id),
+ rads = [],
+ idxByKey = {},
+ currIdx = 0;
+
+ function rsetWidth() {
+ return ((btnSize + btnPadding) * rads.length) + btnPadding;
+ }
+
+ function rbclick() {
+ var id = d3.select(this).attr('id'),
+ m = /^.*-(\d+)$/.exec(id),
+ idx = Number(m[1]);
+
+ if (idx !== currIdx) {
+ rads[currIdx].el.classed('selected', false);
+ currIdx = idx;
+ rads[currIdx].el.classed('selected', true);
+ invokeCurrent();
+ }
+ }
+
+ // {
+ // gid: gid,
+ // tooltip: ..., (optional)
+ // key: ..., (optional)
+ // cb: cb
+ // id: ... (added by us)
+ // index: ... (added by us)
+ // }
+
+ rset.forEach(function (btn, index) {
+
+ if (!fs.isO(btn)) {
+ $log.warn('radio button descriptor at index ' + index +
+ ' not an object');
+ return;
+ }
+
+ var rid = id + '-' + index,
+ initSel = (index === 0),
+ rbdiv = createDiv(rDiv, 'radioButton', rid);
+
+ rbdiv.classed('selected', initSel);
+ rbdiv.on('click', rbclick);
+ is.loadIcon(rbdiv, btn.gid, btnSize, true);
+ if (btn.tooltip) { tts.addTooltip(rbdiv, btn.tooltip); }
+ angular.extend(btn, {
+ el: rbdiv,
+ id: rid,
+ cb: fs.isF(btn.cb) || noop,
+ index: index
+ });
+
+ if (btn.key) {
+ idxByKey[btn.key] = index;
+ }
+
+ rads.push(btn);
+ });
+
+
+ function invokeCurrent() {
+ var curr = rads[currIdx];
+ curr.cb(curr.index, curr.key);
+ }
+
+ function selected(x) {
+ var curr = rads[currIdx],
+ idx;
+
+ if (x === undefined) {
+ return curr.key || curr.index;
+ } else {
+ idx = idxByKey[x];
+ if (idx === undefined) {
+ $log.warn('no radio button with key:', x);
+ } else {
+ selectedIndex(idx);
+ }
+ }
+ }
+
+ function selectedIndex(x) {
+ if (x === undefined) {
+ return currIdx;
+ } else {
+ if (x >= 0 && x < rads.length) {
+ if (currIdx !== x) {
+ currIdx = x;
+ invokeCurrent();
+ } else {
+ $log.warn('current index already selected:', x);
+ }
+ } else {
+ $log.warn('invalid radio button index:', x);
+ }
+ }
+ }
+
+ return {
+ width: rsetWidth,
+ selected: selected,
+ selectedIndex: selectedIndex
+ }
+ }
+
+
+ angular.module('onosWidget')
+ .factory('ButtonService',
+ ['$log', 'FnService', 'IconService', 'TooltipService',
+
+ function (_$log_, _fs_, _is_, _tts_) {
+ $log = _$log_;
+ fs = _fs_;
+ is = _is_;
+ tts = _tts_;
+
+ return {
+ button: button,
+ toggle: toggle,
+ radioSet: radioSet
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.css
new file mode 100644
index 00000000..18b81ba6
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.css
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/* ------ for summary-list tables ------ */
+
+div.summary-list {
+ margin: 0 20px 16px 10px;
+ font-size: 10pt;
+ border-spacing: 0;
+}
+
+div.loading-wheel {
+ display: inline-block;
+ position: absolute;
+ margin-top: 40px;
+ left: 47%;
+ animation: spin reverse 2s ease infinite;
+ z-index: 1000;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+div.loading-wheel svg.embeddedIcon g.icon .glyph {
+ opacity: .8;
+}
+.light div.loading-wheel svg.embeddedIcon g.icon .glyph {
+ fill: #964949;
+}
+.dark div.loading-wheel svg.embeddedIcon g.icon .glyph {
+ fill: whitesmoke;
+}
+
+div.summary-list table {
+ border-collapse: collapse;
+ table-layout: fixed;
+ empty-cells: show;
+ margin: 0;
+}
+
+div.summary-list div.table-body {
+ overflow-y: scroll;
+}
+
+div.summary-list div.table-body::-webkit-scrollbar {
+ display: none;
+}
+
+div.summary-list tr.no-data td {
+ text-align: center;
+ font-style: italic;
+}
+
+.light div.summary-list tr:nth-child(even) {
+ background-color: #ddd;
+}
+.light div.summary-list tr:nth-child(odd) {
+ background-color: #eee;
+}
+.dark div.summary-list tr:nth-child(even) {
+ background-color: #333;
+}
+.dark div.summary-list tr:nth-child(odd) {
+ background-color: #444;
+}
+
+.light div.summary-list tr.selected {
+ background-color: deepskyblue;
+}
+
+.dark div.summary-list tr.selected {
+ background-color: #304860;
+}
+
+/* highlighting */
+div.summary-list tr {
+ transition: background-color 500ms;
+}
+.light div.summary-list tr.data-change {
+ background-color: #FDFFDC;
+}
+.dark div.summary-list tr.data-change {
+ background-color: #5A5600;
+}
+
+div.summary-list td {
+ padding: 6px;
+ text-align: left;
+ word-wrap: break-word;
+}
+
+div.summary-list .table-header td {
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ font-weight: bold;
+}
+div.summary-list .table-header td:first-child {
+ border-radius: 8px 0 0 0;
+}
+div.summary-list .table-header td:last-child {
+ border-radius: 0 8px 0 0;
+}
+
+.light div.summary-list .table-header td {
+ background-color: #bbb;
+}
+.dark div.summary-list .table-header td {
+ background-color: #222;
+ color: #ccc;
+}
+
+/* rows are selectable */
+div.summary-list .table-body td {
+ cursor: pointer;
+}
+
+.dark div.summary-list td {
+ color: #ccc;
+}
+
+/* Tabular view upper right control buttons */
+
+div.ctrl-btns {
+ display: inline-block;
+ float: right;
+ height: 44px;
+ margin-right: 24px;
+ margin-top: 7px;
+}
+
+
+div.ctrl-btns div {
+ display: inline-block;
+ padding: 4px;
+ cursor: pointer;
+}
+
+div.ctrl-btns div.separator {
+ cursor: auto;
+ width: 24px;
+ border: none;
+}
+
+/* Inactive */
+.light .ctrl-btns div g.icon rect,
+.light .ctrl-btns div:hover g.icon rect {
+ fill: #eee;
+}
+.dark .ctrl-btns div g.icon rect,
+.dark .ctrl-btns div:hover g.icon rect {
+ fill: #222;
+}
+
+.light .ctrl-btns div g.icon use {
+ fill: #ddd;
+}
+.dark .ctrl-btns div g.icon use {
+ fill: #333;
+}
+
+/* Active hover */
+.light .ctrl-btns div.active:hover g.icon rect {
+ fill: #800;
+}
+
+.dark .ctrl-btns div.active:hover g.icon rect {
+ fill: #CE5650;
+}
+
+/* Active */
+.light .ctrl-btns div.active g.icon use {
+ fill: #fff;
+}
+.dark .ctrl-btns div.active g.icon use {
+ fill: #eee;
+}
+
+.light .ctrl-btns div.active g.icon rect {
+ fill: #bbb;
+}
+.dark .ctrl-btns div.active g.icon rect {
+ fill: #444;
+}
+
+/* Refresh button specific */
+.light .ctrl-btns div.refresh.active g.icon rect {
+ fill: #964949;
+}
+
+.dark .ctrl-btns div.refresh.active g.icon rect {
+ fill: #9B4641;
+}
+.light .ctrl-btns div.refresh:hover g.icon rect {
+ fill: #964949;
+}
+
+.dark .ctrl-btns div.refresh:hover g.icon rect {
+ fill: #9B4641;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.js
new file mode 100644
index 00000000..327aedb9
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/table.js
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Widget -- Table Service
+ */
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, $window, fs, mast, is;
+
+ // constants
+ var tableIconTdSize = 33,
+ pdg = 22,
+ flashTime = 1500,
+ colWidth = 'col-width',
+ tableIcon = 'table-icon',
+ asc = 'asc',
+ desc = 'desc',
+ none = 'none';
+
+ // internal state
+ var currCol = {},
+ prevCol = {},
+ cstmWidths = {},
+ sortIconAPI;
+
+ // Functions for resizing a tabular view to the window
+
+ function _width(elem, width) {
+ elem.style('width', width);
+ }
+
+ function findCstmWidths(table) {
+ var headers = table.select('.table-header').selectAll('td');
+
+ headers.each(function (d, i) {
+ var h = d3.select(this),
+ index = i.toString();
+ if (h.classed(tableIcon)) {
+ cstmWidths[index] = tableIconTdSize + 'px';
+ }
+ if (h.attr(colWidth)) {
+ cstmWidths[index] = h.attr(colWidth);
+ }
+ });
+ if (fs.debugOn('widget')) {
+ $log.debug('Headers with custom widths: ', cstmWidths);
+ }
+ }
+
+ function setTdWidths(elem, width) {
+ var tds = elem.select('tr:first-child').selectAll('td');
+ _width(elem, width + 'px');
+
+ tds.each(function (d, i) {
+ var td = d3.select(this),
+ index = i.toString();
+ if (cstmWidths.hasOwnProperty(index)) {
+ _width(td, cstmWidths[index]);
+ }
+ });
+ }
+
+ function setHeight(thead, body, height) {
+ var h = height - (mast.mastHeight() +
+ fs.noPxStyle(d3.select('.tabular-header'), 'height') +
+ fs.noPxStyle(thead, 'height') + pdg);
+ body.style('height', h + 'px');
+ }
+
+ function adjustTable(haveItems, tableElems, width, height) {
+ if (haveItems) {
+ setTdWidths(tableElems.thead, width);
+ setTdWidths(tableElems.tbody, width);
+ setHeight(tableElems.thead, tableElems.table.select('.table-body'), height);
+ } else {
+ setTdWidths(tableElems.thead, width);
+ _width(tableElems.tbody, width + 'px');
+ }
+ }
+
+ // Functions for sorting table rows by header
+
+ function updateSortDirection(thElem) {
+ sortIconAPI.sortNone(thElem.select('div'));
+ currCol.div = thElem.append('div');
+ currCol.colId = thElem.attr('colId');
+
+ if (currCol.colId === prevCol.colId) {
+ (currCol.dir === desc) ? currCol.dir = asc : currCol.dir = desc;
+ prevCol.dir = currCol.dir;
+ } else {
+ currCol.dir = asc;
+ prevCol.dir = none;
+ }
+ (currCol.dir === asc) ?
+ sortIconAPI.sortAsc(currCol.div) : sortIconAPI.sortDesc(currCol.div);
+
+ if (prevCol.colId && prevCol.dir === none) {
+ sortIconAPI.sortNone(prevCol.div);
+ }
+
+ prevCol.colId = currCol.colId;
+ prevCol.div = currCol.div;
+ }
+
+ function sortRequestParams() {
+ return {
+ sortCol: currCol.colId,
+ sortDir: currCol.dir
+ };
+ }
+
+ function resetSort() {
+ if (currCol.div) {
+ sortIconAPI.sortNone(currCol.div);
+ }
+ if (prevCol.div) {
+ sortIconAPI.sortNone(prevCol.div);
+ }
+ currCol = {};
+ prevCol = {};
+ }
+
+ angular.module('onosWidget')
+ .directive('onosTableResize', ['$log','$window',
+ 'FnService', 'MastService',
+
+ function (_$log_, _$window_, _fs_, _mast_) {
+ return function (scope, element) {
+ $log = _$log_;
+ $window = _$window_;
+ fs = _fs_;
+ mast = _mast_;
+
+ var table = d3.select(element[0]),
+ tableElems = {
+ table: table,
+ thead: table.select('.table-header').select('table'),
+ tbody: table.select('.table-body').select('table')
+ },
+ wsz;
+
+ findCstmWidths(table);
+
+ // adjust table on window resize
+ scope.$watchCollection(function () {
+ return {
+ h: $window.innerHeight,
+ w: $window.innerWidth
+ };
+ }, function () {
+ wsz = fs.windowSize(0, 30);
+ adjustTable(
+ scope.tableData.length,
+ tableElems,
+ wsz.width, wsz.height
+ );
+ });
+
+ // adjust table when data changes
+ scope.$watchCollection('tableData', function () {
+ adjustTable(
+ scope.tableData.length,
+ tableElems,
+ wsz.width, wsz.height
+ );
+ });
+
+ scope.$on('$destroy', function () {
+ cstmWidths = {};
+ });
+ };
+ }])
+
+ .directive('onosSortableHeader', ['$log', 'IconService',
+ function (_$log_, _is_) {
+ return function (scope, element) {
+ $log = _$log_;
+ is = _is_;
+ var header = d3.select(element[0]);
+ sortIconAPI = is.sortIcons();
+
+ header.selectAll('td').on('click', function () {
+ var col = d3.select(this);
+
+ if (col.attr('sortable') === '') {
+ updateSortDirection(col);
+ scope.sortParams = sortRequestParams();
+ scope.sortCallback(scope.sortParams);
+ }
+ });
+
+ scope.$on('$destroy', function () {
+ resetSort();
+ });
+ };
+ }])
+
+ .directive('onosFlashChanges',
+ ['$log', '$parse', '$timeout', 'FnService',
+ function ($log, $parse, $timeout, fs) {
+
+ return function (scope, element, attrs) {
+ var idProp = attrs.idProp,
+ table = d3.select(element[0]),
+ trs, promise;
+
+ function highlightRows() {
+ var changedRows = [];
+ function classRows(b) {
+ if (changedRows.length) {
+ angular.forEach(changedRows, function (tr) {
+ tr.classed('data-change', b);
+ });
+ }
+ }
+ // timeout because 'row-id' was the un-interpolated value
+ // "{{link.one}}" for example, instead of link.one evaluated
+ // timeout executes on the next digest -- after evaluation
+ $timeout(function () {
+ if (scope.tableData.length) {
+ trs = table.selectAll('tr');
+ }
+
+ if (trs && !trs.empty()) {
+ trs.each(function () {
+ var tr = d3.select(this);
+ if (fs.find(tr.attr('row-id'),
+ scope.changedData,
+ idProp) > -1) {
+ changedRows.push(tr);
+ }
+ });
+ classRows(true);
+ promise = $timeout(function () {
+ classRows(false);
+ }, flashTime);
+ trs = undefined;
+ }
+ });
+ }
+
+ // new items added:
+ scope.$on('ngRepeatComplete', highlightRows);
+ // items changed in existing set:
+ scope.$watchCollection('changedData', highlightRows);
+
+ scope.$on('$destroy', function () {
+ if (promise) {
+ $timeout.cancel(promise);
+ }
+ });
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
new file mode 100644
index 00000000..24161bbb
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Widget -- Table Service
+ */
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, $interval, $timeout, fs, wss;
+
+ // constants
+ var refreshInterval = 2000,
+ loadingWait = 500;
+
+ // example params to buildTable:
+ // {
+ // scope: $scope, <- controller scope
+ // tag: 'device', <- table identifier
+ // selCb: selCb, <- row selection callback (optional)
+ // respCb: respCb, <- websocket response callback (optional)
+ // query: params <- query parameters in URL (optional)
+ // }
+ // Note: selCb() is passed the row data model of the selected row,
+ // or null when no row is selected.
+ // Note: query is always an object (empty or containing properties)
+ // it comes from $location.search()
+
+ function buildTable(o) {
+ var handlers = {},
+ root = o.tag + 's',
+ req = o.tag + 'DataRequest',
+ resp = o.tag + 'DataResponse',
+ onSel = fs.isF(o.selCb),
+ onResp = fs.isF(o.respCb),
+ oldTableData = [],
+ loaded = false,
+ refreshPromise, loadingPromise;
+
+ o.scope.tableData = [];
+ o.scope.changedData = [];
+ o.scope.sortParams = {};
+ o.scope.loading = true;
+ o.scope.autoRefresh = true;
+ o.scope.autoRefreshTip = 'Toggle auto refresh';
+
+ // === websocket functions --------------------
+ // response
+ function respCb(data) {
+ loaded = true;
+ o.scope.loading = false;
+ o.scope.tableData = data[root];
+ onResp && onResp();
+
+ // checks if data changed for row flashing
+ if (!angular.equals(o.scope.tableData, oldTableData)) {
+ o.scope.changedData = [];
+ // only flash the row if the data already exists
+ if (oldTableData.length) {
+ angular.forEach(o.scope.tableData, function (item) {
+ if (!fs.containsObj(oldTableData, item)) {
+ o.scope.changedData.push(item);
+ }
+ });
+ }
+ angular.copy(o.scope.tableData, oldTableData);
+ }
+ o.scope.$apply();
+ }
+ handlers[resp] = respCb;
+ wss.bindHandlers(handlers);
+
+ // request
+ function sortCb(params) {
+ var p = angular.extend({}, params, o.query);
+ wss.sendEvent(req, p);
+ stillLoading();
+ }
+ o.scope.sortCallback = sortCb;
+
+ // show loading wheel if it's taking a while for the server to respond
+ function stillLoading() {
+ loaded = false;
+ loadingPromise = $timeout(function () {
+ if (!loaded) {
+ o.scope.loading = true;
+ }
+ }, loadingWait);
+ }
+
+ // === selecting a row functions ----------------
+ function selCb($event, selRow) {
+ o.scope.selId = (o.scope.selId === selRow.id) ? null : selRow.id;
+ onSel && onSel($event, selRow);
+ }
+ o.scope.selectCallback = selCb;
+
+ // === autoRefresh functions ------------------
+ function startRefresh() {
+ refreshPromise = $interval(function () {
+ if (fs.debugOn('widget')) {
+ $log.debug('Refreshing ' + root + ' page');
+ }
+ sortCb(o.scope.sortParams);
+ }, refreshInterval);
+ }
+
+ function stopRefresh() {
+ if (angular.isDefined(refreshPromise)) {
+ $interval.cancel(refreshPromise);
+ refreshPromise = undefined;
+ }
+ }
+
+ function toggleRefresh() {
+ o.scope.autoRefresh = !o.scope.autoRefresh;
+ o.scope.autoRefresh ? startRefresh() : stopRefresh();
+ }
+ o.scope.toggleRefresh = toggleRefresh;
+
+ // === Cleanup on destroyed scope -----------------
+ o.scope.$on('$destroy', function () {
+ wss.unbindHandlers(handlers);
+ stopRefresh();
+ if (angular.isDefined(loadingPromise)) {
+ $timeout.cancel(loadingPromise);
+ loadingPromise = undefined;
+ }
+ });
+
+ sortCb();
+ startRefresh();
+ }
+
+ angular.module('onosWidget')
+ .factory('TableBuilderService',
+ ['$log', '$interval', '$timeout', 'FnService', 'WebSocketService',
+
+ function (_$log_, _$interval_, _$timeout_, _fs_, _wss_) {
+ $log = _$log_;
+ $interval = _$interval_;
+ $timeout = _$timeout_;
+ fs = _fs_;
+ wss = _wss_;
+
+ return {
+ buildTable: buildTable
+ };
+ }]);
+
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.css
new file mode 100644
index 00000000..36a5971b
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.css
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Toolbar Service -- CSS file
+ */
+
+.light .tbar-arrow svg.embeddedIcon .icon .glyph {
+ fill: #838383;
+}
+.dark .tbar-arrow svg.embeddedIcon .icon .glyph {
+ fill: #B2B2B2;
+}
+
+
+div.tbar-arrow {
+ position: absolute;
+ top: 53%;
+ left: 96%;
+ margin-right: -4%;
+ transform: translate(-50%, -50%);
+ cursor: pointer;
+}
+.safari div.tbar-arrow {
+ top: 46%;
+}
+.firefox div.tbar-arrow {
+ left: 97%;
+ margin-right: -3%;
+}
+
+.tbar-arrow svg.embeddedIcon .icon rect {
+ stroke: none;
+}
+.light .tbar-arrow svg.embeddedIcon .icon rect {
+ fill: none;
+}
+.dark .tbar-arrow svg.embeddedIcon .icon rect {
+ fill: none;
+}
+
+
+.toolbar {
+ line-height: 125%;
+}
+.tbar-row {
+ display: inline-block;
+}
+
+
+
+.separator {
+ border: 1px solid;
+ margin: 0 4px 0 4px;
+ display: inline-block;
+ height: 23px;
+ width: 0;
+}
+.light .separator {
+ border-color: #ddd;
+}
+.dark .separator {
+ border-color: #1A1A1A;
+}
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.js
new file mode 100644
index 00000000..050afd0f
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/toolbar.js
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Widget -- Toolbar Service
+ */
+// TODO: Augment service to allow toolbars to exist on right edge of screen
+// TODO: also - make toolbar more object aware (rows etc.)
+
+
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, fs, ps, bns, is;
+
+ // configuration
+ var arrowSize = 10,
+ sepWidth = 6,
+ defaultSettings = {
+ edge: 'left',
+ width: 20,
+ margin: 0,
+ hideMargin: -20,
+ top: 'auto',
+ bottom: '10px',
+ fade: false,
+ shown: false
+ };
+
+ // internal state
+ var tbars = {};
+
+
+ // === Helper functions --------------------------------------
+
+ // translate uses 50 because the svg viewbox is 50
+ function rotateArrowLeft(adiv) {
+ adiv.select('g')
+ .attr('transform', 'translate(0 50) rotate(-90)');
+ }
+ function rotateArrowRight(adiv) {
+ adiv.select('g')
+ .attr('transform', 'translate(50 0) rotate(90)');
+ }
+
+ function createArrow(panel) {
+ var arrowDiv = panel.append('div')
+ .classed('tbar-arrow', true);
+ is.loadIcon(arrowDiv, 'triangleUp', arrowSize, true);
+ return arrowDiv;
+ }
+
+ function warn(msg, id) {
+ $log.warn('createToolbar: ' + msg + ': [' + id + ']');
+ return null;
+ }
+
+ // ==================================
+
+ function createToolbar(id, opts) {
+ if (!id) return warn('no ID given');
+ if (tbars[id]) return warn('duplicate ID given', id);
+
+ var settings = angular.extend({}, defaultSettings, fs.isO(opts)),
+ items = {},
+ tbid = 'toolbar-' + id,
+ panel = ps.createPanel(tbid, settings),
+ arrowDiv = createArrow(panel),
+ currentRow = panel.append('div').classed('tbar-row', true),
+ rowButtonIds = [], // for removable buttons
+ tbWidth = arrowSize + 2, // empty toolbar width
+ maxWidth = panel.width();
+
+ arrowDiv.on('click', toggle);
+
+ // add a descriptor for this toolbar
+ tbars[id] = {
+ settings: settings,
+ items: items,
+ panel: panel,
+ panelId: tbid
+ };
+
+ panel.classed('toolbar', true)
+ .style('top', settings.top)
+ .style('bottom', settings.bottom);
+
+ // Helper functions
+
+ function dupId(id, caller) {
+ if (items[id]) {
+ $log.warn(caller + ': duplicate ID:', id);
+ return true;
+ }
+ return false;
+ }
+
+ function adjustWidth(btnWidth) {
+ if (fs.noPxStyle(currentRow, 'width') >= maxWidth) {
+ tbWidth += btnWidth;
+ maxWidth = tbWidth;
+ }
+ panel.width(tbWidth);
+ }
+
+ // API functions
+
+ function addButton(id, gid, cb, tooltip) {
+ if (dupId(id, 'addButton')) return null;
+
+ var bid = tbid + '-' + id,
+ btn = bns.button(currentRow, bid, gid, cb, tooltip);
+
+ items[id] = btn;
+ adjustWidth(btn.width());
+ return btn;
+ }
+
+ function addToggle(id, gid, initState, cb, tooltip) {
+ if (dupId(id, 'addToggle')) return null;
+
+ var tid = tbid + '-' + id,
+ tog = bns.toggle(currentRow, tid, gid, initState, cb, tooltip);
+
+ items[id] = tog;
+ adjustWidth(tog.width());
+ return tog;
+ }
+
+ function addRadioSet(id, rset) {
+ if (dupId(id, 'addRadioSet')) return null;
+
+ var rid = tbid + '-' + id,
+ rad = bns.radioSet(currentRow, rid, rset);
+
+ items[id] = rad;
+ adjustWidth(rad.width());
+ return rad;
+ }
+
+ function addSeparator() {
+ currentRow.append('div')
+ .classed('separator', true);
+ tbWidth += sepWidth;
+ }
+
+ function addRow() {
+ if (currentRow.select('div').empty()) {
+ return null;
+ } else {
+ panel.append('br');
+ currentRow = panel.append('div').classed('tbar-row', true);
+
+ // return API to allow caller more access to the row
+ return {
+ clear: rowClear,
+ setText: rowSetText,
+ addButton: rowAddButton,
+ classed: rowClassed
+ };
+ }
+ }
+
+ function rowClear() {
+ currentRow.selectAll('*').remove();
+ rowButtonIds.forEach(function (bid) {
+ delete items[bid];
+ });
+ rowButtonIds = [];
+ }
+
+ // installs a div with text into the button row
+ function rowSetText(text) {
+ rowClear();
+ currentRow.append('div').classed('tbar-row-text', true)
+ .html(text);
+ }
+
+ function rowAddButton(id, gid, cb, tooltip) {
+ var b = addButton(id, gid, cb, tooltip);
+ if (b) {
+ rowButtonIds.push(id);
+ }
+ }
+
+ function rowClassed(classes, bool) {
+ currentRow.classed(classes, bool);
+ }
+
+ function show(cb) {
+ rotateArrowLeft(arrowDiv);
+ panel.show(cb);
+ }
+
+ function hide(cb) {
+ rotateArrowRight(arrowDiv);
+ panel.hide(cb);
+ }
+
+ function toggle(cb) {
+ if (panel.isVisible()) {
+ hide(cb);
+ } else {
+ show(cb);
+ }
+ }
+
+ return {
+ addButton: addButton,
+ addToggle: addToggle,
+ addRadioSet: addRadioSet,
+ addSeparator: addSeparator,
+ addRow: addRow,
+
+ show: show,
+ hide: hide,
+ toggle: toggle
+ };
+ }
+
+ function destroyToolbar(id) {
+ var tb = tbars[id];
+ delete tbars[id];
+
+ if (tb) {
+ ps.destroyPanel(tb.panelId);
+ }
+ }
+
+ // === Module Definition ===
+
+ angular.module('onosWidget')
+ .factory('ToolbarService',
+ ['$log', 'FnService', 'PanelService', 'ButtonService', 'IconService',
+
+ function (_$log_, _fs_, _ps_, _bns_, _is_) {
+ $log = _$log_;
+ fs = _fs_;
+ ps = _ps_;
+ bns = _bns_;
+ is = _is_;
+
+ // this function is only used in testing
+ function init() {
+ tbars = {};
+ }
+
+ return {
+ init: init,
+ createToolbar: createToolbar,
+ destroyToolbar: destroyToolbar
+ };
+ }]);
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.css b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.css
new file mode 100644
index 00000000..f967793c
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.css
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Tooltip Service -- CSS file
+ */
+
+#tooltip {
+ text-align: center;
+ font-size: 80%;
+ border: 1px solid;
+ padding: 5px;
+ position: absolute;
+ z-index: 5000;
+ display: none;
+ pointer-events: none;
+}
+
+/* colors subject to change */
+
+.light #tooltip {
+ background-color: #ddd;
+ color: #444;
+ border-color: #ccc;
+}
+
+.dark #tooltip {
+ background-color: #151515;
+ color: #B2B2B2;
+ border-color: #252525;
+} \ No newline at end of file
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.js
new file mode 100644
index 00000000..dd8a6950
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/tooltip.js
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Widget -- Tooltip Service
+ */
+
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $rootScope, fs;
+
+ // constants
+ var hoverHeight = 35,
+ hoverDelay = 150,
+ exitDelay = 150;
+
+ // internal state
+ var tooltip, currElemId;
+
+ // === Helper functions ---------------------------------------------
+
+ function init() {
+ tooltip = d3.select('#tooltip');
+ tooltip.html('');
+ }
+
+ function tipStyle(mouseX, mouseY) {
+ var winWidth = fs.windowSize().width,
+ winHeight = fs.windowSize().height,
+ style = {
+ display: 'inline-block',
+ left: 'auto',
+ right: 'auto'
+ };
+
+ if (mouseX <= (winWidth / 2)) {
+ style.left = mouseX + 'px';
+ } else {
+ style.right = (winWidth - mouseX) + 'px';
+ }
+
+ if (mouseY <= (winHeight / 2)) {
+ style.top = (mouseY + (hoverHeight - 10)) + 'px';
+ } else {
+ style.top = (mouseY - hoverHeight) + 'px';
+ }
+
+ return style;
+ }
+
+ // === API functions ------------------------------------------------
+
+ function addTooltip(elem, tooltip) {
+ elem.on('mouseover', function () { showTooltip(this, tooltip); });
+ elem.on('mouseout', function () { cancelTooltip(this); });
+ $rootScope.$on('$routeChangeStart', function () {
+ cancelTooltip(elem.node());
+ });
+ }
+
+ function showTooltip(el, msg) {
+ // tooltips don't make sense on mobile devices
+ if (!el || !msg || fs.isMobile()) {
+ return;
+ }
+
+ var elem = d3.select(el),
+ mouseX = d3.event.pageX,
+ mouseY = d3.event.pageY,
+ style = tipStyle(mouseX, mouseY);
+ currElemId = elem.attr('id');
+
+ tooltip.transition()
+ .delay(hoverDelay)
+ .each('start', function () {
+ d3.select(this).style('display', 'none');
+ })
+ .each('end', function () {
+ d3.select(this).style(style)
+ .text(msg);
+ });
+ }
+
+ function cancelTooltip(el) {
+ if (!el) {
+ return;
+ }
+ var elem = d3.select(el);
+
+ if (elem.attr('id') === currElemId) {
+ tooltip.transition()
+ .delay(exitDelay)
+ .style({
+ display: 'none'
+ })
+ .text('');
+ }
+ }
+
+ angular.module('onosWidget')
+
+ .directive('tooltip', ['$rootScope', 'FnService',
+ function (_$rootScope_, _fs_) {
+ $rootScope = _$rootScope_;
+ fs = _fs_;
+
+ init();
+
+ return {
+ restrict: 'A',
+ link: function (scope, elem, attrs) {
+ addTooltip(d3.select(elem[0]), scope[attrs.ttMsg]);
+ }
+ };
+ }])
+
+ .factory('TooltipService', ['$log', '$rootScope', 'FnService',
+ function (_$log_, _$rootScope_, _fs_) {
+ $log = _$log_;
+ $rootScope = _$rootScope_;
+ fs = _fs_;
+
+ init();
+
+ return {
+ addTooltip: addTooltip,
+ showTooltip: showTooltip,
+ cancelTooltip: cancelTooltip
+ };
+ }]);
+}());
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/widget.js b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/widget.js
new file mode 100644
index 00000000..d11c8287
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/app/fw/widget/widget.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Widgets Module
+ */
+(function () {
+ 'use strict';
+
+ angular.module('onosWidget', []);
+
+}());