Added Collections and Models for a Region.
Change-Id: Ic033b2890dad18e47b057e6b1d1c8535d812590d
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js b/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
new file mode 100644
index 0000000..31c8886
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
@@ -0,0 +1,301 @@
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log,
+ ps,
+ sus,
+ gs,
+ ts,
+ fs,
+ flash;
+
+ // api from topo
+ var api;
+
+ // configuration
+ var showLogicErrors = true,
+ idIns = 'topo-p-instance',
+ instOpts = {
+ edge: 'left',
+ width: 20
+ };
+
+ // internal state
+ var onosInstances,
+ onosOrder,
+ oiShowMaster,
+ oiBox;
+
+
+ function addInstance(data) {
+ var id = data.id;
+
+ if (onosInstances[id]) {
+ updateInstance(data);
+ return;
+ }
+ onosInstances[id] = data;
+ onosOrder.push(data);
+ updateInstances();
+ }
+
+ function updateInstance(data) {
+ var id = data.id,
+ d = onosInstances[id];
+ if (d) {
+ angular.extend(d, data);
+ updateInstances();
+ } else {
+ logicError('updateInstance: lookup fail: ID = "' + id + '"');
+ }
+ }
+
+ function removeInstance(data) {
+ var id = data.id,
+ d = onosInstances[id];
+ if (d) {
+ var idx = fs.find(id, onosOrder);
+ if (idx >= 0) {
+ onosOrder.splice(idx, 1);
+ }
+ delete onosInstances[id];
+ updateInstances();
+ } else {
+ logicError('removeInstance lookup fail. ID = "' + id + '"');
+ }
+ }
+
+ // ==========================
+
+ function clickInst(d) {
+ var el = d3.select(this),
+ aff = el.classed('affinity');
+ if (!aff) {
+ setAffinity(el, d);
+ } else {
+ cancelAffinity();
+ }
+ }
+
+ function setAffinity(el, d) {
+ d3.selectAll('.onosInst')
+ .classed('mastership', true)
+ .classed('affinity', false);
+ el.classed('affinity', true);
+
+ // suppress all elements except nodes whose master is this instance
+ api.showMastership(d.id);
+ oiShowMaster = true;
+ }
+
+ function cancelAffinity() {
+ d3.selectAll('.onosInst')
+ .classed('mastership affinity', false);
+
+ api.showMastership(null);
+ oiShowMaster = false;
+ }
+
+ function attachUiBadge(svg) {
+ gs.addGlyph(svg, 'uiAttached', 24, true, [14, 54])
+ .classed('badgeIcon uiBadge', true);
+ }
+
+ function attachReadyBadge(svg) {
+ gs.addGlyph(svg, 'checkMark', 16, true, [18, 40])
+ .classed('badgeIcon readyBadge', true);
+ }
+
+ function instColor(id, online) {
+ return sus.cat7().getColor(id, !online, ts.theme());
+ }
+
+ // ==============================
+
+ function updateInstances() {
+ var rox = 5,
+ roy = 5,
+ rw = 160,
+ rhh = 30,
+ rbh = 45,
+ tx = 48,
+ instSvg = {
+ width: 170,
+ height: 85,
+ viewBox: '0 0 170 85'
+ },
+ headRect = {
+ x: rox,
+ y: roy,
+ width: rw,
+ height: rhh
+ },
+ bodyRect = {
+ x: rox,
+ y: roy + rhh,
+ width: rw,
+ height: rbh
+ },
+ titleAttr = {
+ class: 'instTitle',
+ x: tx,
+ y: 27
+ };
+
+ var onoses = oiBox.el().selectAll('.onosInst')
+ .data(onosOrder, function (d) { return d.id; });
+
+ function nSw(n) {
+ return 'Devices: ' + n;
+ }
+
+ // operate on existing onos instances if necessary
+ onoses.each(function (d) {
+ var el = d3.select(this),
+ svg = el.select('svg');
+
+ // update online state
+ el.classed('online', d.online);
+ el.classed('ready', d.ready);
+
+ // update ui-attached state
+ svg.select('use.uiBadge').remove();
+ if (d.uiAttached) {
+ attachUiBadge(svg);
+ }
+
+ function updAttr(id, value) {
+ svg.select('text.instLabel.' + id).text(value);
+ }
+
+ updAttr('ip', d.ip);
+ updAttr('ns', nSw(d.switches));
+ });
+
+
+ // operate on new onos instances
+ var entering = onoses.enter()
+ .append('div')
+ .classed('onosInst', true)
+ .classed('online', function (d) { return d.online; })
+ .classed('ready', function (d) { return d.ready; })
+ .on('click', clickInst);
+
+ entering.each(function (d) {
+ var el = d3.select(this),
+ svg = el.append('svg').attr(instSvg);
+
+ svg.append('rect').attr(headRect);
+ svg.append('rect').attr(bodyRect);
+
+ gs.addGlyph(svg, 'bird', 20, false, [15, 10])
+ .classed('badgeIcon bird', true);
+
+ attachReadyBadge(svg);
+
+ if (d.uiAttached) {
+ attachUiBadge(svg);
+ }
+
+ svg.append('text')
+ .attr(titleAttr)
+ .text(d.id);
+
+ var ty = 55;
+ function addAttr(id, label) {
+ svg.append('text').attr({
+ class: 'instLabel ' + id,
+ x: tx,
+ y: ty
+ }).text(label);
+ ty += 18;
+ }
+
+ addAttr('ip', d.ip);
+ addAttr('ns', nSw(d.switches));
+ });
+
+ // operate on existing + new onoses here
+ // set the affinity colors...
+ onoses.each(function (d) {
+
+ var el = d3.select(this),
+ rect = el.select('svg').select('rect'),
+ col = instColor(d.id, d.online);
+
+ rect.style('fill', col);
+ });
+
+ // adjust the panel size appropriately...
+ oiBox.width(instSvg.width * onosOrder.length);
+ oiBox.height(instSvg.height);
+
+ // remove any outgoing instances
+ onoses.exit().remove();
+ }
+
+
+ // ==========================
+
+ function logicError(msg) {
+ if (showLogicErrors) {
+ $log.warn('TopoInstService: ' + msg);
+ }
+ }
+
+ function initInst(_api_) {
+ api = _api_;
+ oiBox = ps.createPanel(idIns, instOpts);
+ oiBox.show();
+
+ onosInstances = {};
+ onosOrder = [];
+ oiShowMaster = false;
+
+ // we want to update the instances, each time the theme changes
+ ts.addListener(updateInstances);
+ }
+
+ function destroyInst() {
+ ts.removeListener(updateInstances);
+
+ ps.destroyPanel(idIns);
+ oiBox = null;
+
+ onosInstances = {};
+ onosOrder = [];
+ oiShowMaster = false;
+ }
+
+ function allInstances(data) {
+ $log.debug('Update all instances', data);
+
+ var members = data.members;
+
+ members.forEach(function (member) {
+ addInstance(member);
+ });
+ }
+
+ angular.module('ovTopo2')
+ .factory('Topo2InstanceService',
+ ['$log', 'PanelService', 'SvgUtilService', 'GlyphService',
+ 'ThemeService', 'FnService', 'FlashService',
+
+ function (_$log_, _ps_, _sus_, _gs_, _ts_, _fs_, _flash_) {
+ $log = _$log_;
+ ps = _ps_;
+ sus = _sus_;
+ gs = _gs_;
+ ts = _ts_;
+ fs = _fs_;
+ flash = _flash_;
+
+ return {
+ initInst: initInst,
+ allInstances: allInstances
+ };
+ }]);
+
+}());