Added webservices forwarding script for UI
diff --git a/web/js/main.js b/web/js/main.js
new file mode 100644
index 0000000..9fb6f66
--- /dev/null
+++ b/web/js/main.js
@@ -0,0 +1,130 @@
+/*
+ Copyright 2012 IBM
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+var hackBase = "http://localhost:9000"; // put a URL here to access a different REST server
+
+var AppRouter = Backbone.Router.extend({
+
+ routes:{
+ "":"home",
+ "topology":"topology",
+ "switches":"switchList",
+ "switch/:id":"switchDetails",
+ "switch/:id/port/:p":"portDetails", // not clear if needed
+ "hosts":"hostList",
+ "host/:id":"hostDetails",
+ // "vlans":"vlanList" // maybe one day
+ // "vlan/:id":"vlanDetails"
+ },
+
+ initialize:function () {
+ this.headerView = new HeaderView();
+ $('.header').html(this.headerView.render().el);
+
+ // Close the search dropdown on click anywhere in the UI
+ $('body').click(function () {
+ $('.dropdown').removeClass("open");
+ });
+ },
+
+ home:function () {
+ $('#content').html(new HomeView().render().el);
+ $('ul[class="nav"] > li').removeClass('active');
+ $('a[href="/"]').parent().addClass('active');
+ },
+
+ topology:function () {
+ //console.log("switching to topology view");
+ var topo = new Topology();
+ $('#content').html(new TopologyView({model:topo, hosts:hl}).render().el);
+ // TODO factor this code out
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="topology"]').parent().addClass('active');
+ },
+
+ switchDetails:function (id) {
+ //console.log("switching [sic] to single switch view");
+ var sw = swl.get(id);
+ $('#content').html(new SwitchView({model:sw}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/switches"]').parent().addClass('active');
+ },
+
+ switchList:function () {
+ //console.log("switching [sic] to switch list view");
+ $('#content').html(new SwitchListView({model:swl}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/switches"]').parent().addClass('active');
+ },
+
+ hostDetails:function (id) {
+ //console.log("switching to single host view");
+ var h = hl.get(id);
+ $('#content').html(new HostView({model:h}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/hosts"]').parent().addClass('active');
+ },
+
+ hostList:function () {
+ //console.log("switching to host list view");
+ $('#content').html(new HostListView({model:hl}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/hosts"]').parent().addClass('active');
+ },
+
+});
+
+// load global models and reuse them
+var swl = new SwitchCollection();
+var hl = new HostCollection();
+
+var updating = true;
+
+tpl.loadTemplates(['home', 'status', 'topology', 'header', 'switch', 'switch-list', 'switch-list-item', 'host', 'host-list', 'host-list-item', 'port-list', 'port-list-item', 'flow-list', 'flow-list-item'],
+ function () {
+ app = new AppRouter();
+ Backbone.history.start({pushState: true});
+ //console.log("started history")
+
+ $(document).ready(function () {
+ // trigger Backbone routing when clicking on links, thanks to Atinux and pbnv
+ app.navigate("", true);
+
+ window.document.addEventListener('click', function(e) {
+ e = e || window.event
+ var target = e.target || e.srcElement
+ if ( target.nodeName.toLowerCase() === 'a' ) {
+ e.preventDefault()
+ var uri = target.getAttribute('href')
+ app.navigate(uri.substr(1), true)
+ }
+ });
+ window.addEventListener('popstate', function(e) {
+ app.navigate(location.pathname.substr(1), true);
+ });
+
+ // wait for the page to be rendered before loading any data
+ swl.fetch();
+ hl.fetch();
+
+ setInterval(function () {
+ if(updating) {
+ swl.fetch();
+ hl.fetch();
+ }
+ }, 3000);
+ });
+ });
diff --git a/web/js/models/flowmodel.js b/web/js/models/flowmodel.js
new file mode 100644
index 0000000..80777c3
--- /dev/null
+++ b/web/js/models/flowmodel.js
@@ -0,0 +1,37 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Flow = Backbone.Model.extend({
+
+ defaults: {
+ receiveBytes: 0,
+ receivePackets: 0,
+ transmitBytes: 0,
+ transmitPackets: 0,
+ },
+
+ // initialize:function () {}
+
+});
+
+window.FlowCollection = Backbone.Collection.extend({
+
+ model:Flow,
+
+ // instead of the collection loading its children, the switch will load them
+ // initialize:function () {}
+
+});
\ No newline at end of file
diff --git a/web/js/models/hostmodel.js b/web/js/models/hostmodel.js
new file mode 100644
index 0000000..8de3dd6
--- /dev/null
+++ b/web/js/models/hostmodel.js
@@ -0,0 +1,77 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Host = Backbone.Model.extend({
+
+ defaults: {
+ // vlan: -1,
+ lastSeen: 'never',
+ ip: ' ',
+ swport: ' ',
+ },
+
+ // initialize:function () {}
+
+});
+
+window.HostCollection = Backbone.Collection.extend({
+
+ model:Host,
+
+ fetch:function () {
+ var self = this;
+ //console.log("fetching host list")
+ $.ajax({
+ url:hackBase + "/wm/device/",
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched host list: " + data.length);
+ // console.log(data);
+ // data is a list of device hashes
+ var old_ids = self.pluck('id');
+ //console.log("old_ids" + old_ids);
+ _.each(data, function(h) {
+ h.id = h.mac[0];
+ old_ids = _.without(old_ids, h.id);
+ if (h['attachmentPoint'].length > 0) {
+ h.swport = _.reduce(h['attachmentPoint'], function(memo, ap) {
+ return memo + ap.switchDPID + "-" + ap.port + " "}, "");
+ //console.log(h.swport);
+ h.lastSeen = new Date(h.lastSeen).toLocaleString();
+ self.add(h, {silent: true});
+ }
+ });
+ // old_ids now holds hosts that no longer exist; remove them
+ //console.log("old_ids" + old_ids);
+ _.each(old_ids, function(h) {
+ console.log("---removing host " + h);
+ self.remove({id:h});
+ });
+ self.trigger('add'); // batch redraws
+ }
+ });
+
+ },
+
+ /*
+ * findByName:function (key) { // TODO: Modify service to include firstName
+ * in search var url = (key == '') ? '/host/' : "/host/search/" + key;
+ * console.log('findByName: ' + key); var self = this; $.ajax({ url:url,
+ * dataType:"json", success:function (data) { console.log("search success: " +
+ * data.length); self.reset(data); } }); }
+ */
+
+});
diff --git a/web/js/models/portmodel.js b/web/js/models/portmodel.js
new file mode 100644
index 0000000..563f334
--- /dev/null
+++ b/web/js/models/portmodel.js
@@ -0,0 +1,42 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Port = Backbone.Model.extend({
+
+ defaults: {
+ name: '',
+ receiveBytes: 0,
+ receivePackets: 0,
+ transmitBytes: 0,
+ transmitPackets: 0,
+ dropped: 0,
+ errors: 0,
+ },
+
+ initialize:function () {
+ // TODO hook up associated hosts
+ }
+
+});
+
+window.PortCollection = Backbone.Collection.extend({
+
+ model:Port,
+
+ // instead of the collection loading its children, the switch will load them
+ initialize:function () {}
+
+});
\ No newline at end of file
diff --git a/web/js/models/statusmodel.js b/web/js/models/statusmodel.js
new file mode 100644
index 0000000..b7cdebd
--- /dev/null
+++ b/web/js/models/statusmodel.js
@@ -0,0 +1,73 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Status = Backbone.Model.extend({
+ defaults: {
+ host: 'localhost',
+ ofport: 6633,
+ uptime: 'unknown',
+ free: 0,
+ total: 0,
+ healthy: 'unknown',
+ modules: [],
+ moduleText: ''
+ },
+
+ initialize:function () {
+ var self = this;
+ console.log("fetching controller status");
+ $.ajax({
+ url:hackBase + "/wm/core/health/json",
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched controller status: health");
+ self.set(data);
+ // console.log(self.toJSON());
+ }
+ });
+ $.ajax({
+ url:hackBase + "/wm/core/system/uptime/json",
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched controller status: uptime");
+ self.set({uptime:(Math.round(data.systemUptimeMsec / 1000) + ' s')});
+ // console.log(self.toJSON());
+ }
+ });
+ $.ajax({
+ url:hackBase + "/wm/core/memory/json",
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched controller status: memory");
+ self.set(data);
+ // console.log(self.toJSON());
+ }
+ });
+ $.ajax({
+ url:hackBase + "/wm/core/module/loaded/json",
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched controller status: modules loaded");
+ // console.log(data);
+ self.set({modules:_.keys(data)});
+ self.set({moduleText:_.reduce(_.keys(data), function(s, m)
+ {return s+m.replace("net.floodlightcontroller", "n.f")+", "}, '')});
+ }
+ });
+
+ }
+
+});
\ No newline at end of file
diff --git a/web/js/models/switchmodel.js b/web/js/models/switchmodel.js
new file mode 100644
index 0000000..4104dd0
--- /dev/null
+++ b/web/js/models/switchmodel.js
@@ -0,0 +1,295 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Switch = Backbone.Model.extend({
+
+ urlRoot:"/wm/core/switch/",
+
+ defaults: {
+ datapathDescription: '',
+ hardwareDescription: '',
+ manufacturerDescription: '',
+ serialNumber: '',
+ softwareDescription: '',
+ flowCount: ' ',
+ packetCount: ' ',
+ byteCount: ' ',
+ },
+
+ initialize:function () {
+ var self = this;
+
+ //console.log("fetching switch " + this.id + " desc")
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/desc/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " desc");
+ //console.log(data[self.id][0]);
+ self.set(data[self.id][0]);
+ }
+ });
+
+ //console.log("fetching switch " + this.id + " aggregate")
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/aggregate/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " aggregate");
+ //console.log(data[self.id][0]);
+ self.set(data[self.id][0]);
+ }
+ });
+ self.trigger('add');
+ this.ports = new PortCollection();
+ this.flows = new FlowCollection();
+ //this.loadPorts();
+ //this.loadFlows();
+ },
+
+ fetch:function () {
+ this.initialize()
+ },
+
+ loadPorts:function () {
+ var self = this;
+ //console.log("fetching switch " + this.id + " ports")
+ //console.log("fetching switch " + this.id + " features")
+ $.when($.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/port/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " ports");
+ //console.log(data[self.id]);
+ var old_ids = self.ports.pluck('id');
+ //console.log("old_ids" + old_ids);
+
+ // create port models
+ _.each(data[self.id], function(p) {
+ // workaround for REST serialization signed/unsigned bug
+ if(p.portNumber < 0) {p.portNumber = 65536 + p.portNumber};
+
+ p.id = self.id+'-'+p.portNumber;
+ old_ids = _.without(old_ids, p.id);
+ p.dropped = p.receiveDropped + p.transmitDropped;
+ p.errors = p.receiveCRCErrors + p.receiveErrors + p.receiveOverrunErrors +
+ p.receiveFrameErrors + p.transmitErrors;
+ // this is a knda kludgy way to merge models
+ var m = self.ports.get(p.id);
+ if(m) {
+ m.set(p, {silent: true});
+ } else {
+ self.ports.add(p, {silent: true});
+ }
+ //console.log(p);
+ });
+
+ // old_ids now holds ports that no longer exist; remove them
+ //console.log("old_ids" + old_ids);
+ _.each(old_ids, function(p) {
+ console.log("removing port " + p);
+ self.remove({id:p});
+ });
+ }
+ }),
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/features/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " features");
+ //console.log(data[self.id]);
+ // update port models
+ _.each(data[self.id].ports, function(p) {
+ p.id = self.id+'-'+p.portNumber;
+ if(p.name != p.portNumber) {
+ p.name = p.portNumber + ' (' + p.name + ')';
+ }
+ p.status = '';
+ p.status += (p.state & 1) ? 'DOWN' : 'UP';
+ switch(p.currentFeatures & 0x7f) {
+ case 1:
+ p.status += ' 10 Mbps';
+ break;
+ case 2:
+ p.status += ' 10 Mbps FDX';
+ break;
+ case 4:
+ p.status += ' 100 Mbps';
+ break;
+ case 8:
+ p.status += ' 100 Mbps FDX';
+ break;
+ case 16:
+ p.status += ' 1 Gbps'; // RLY?
+ break;
+ case 32:
+ p.status += ' 1 Gbps FDX';
+ break;
+ case 64:
+ p.status += ' 10 Gbps FDX';
+ break;
+ }
+ // TODO parse copper/fiber, autoneg, pause
+
+ // this is a knda kludgy way to merge models
+ var m = self.ports.get(p.id);
+ if(m) {
+ m.set(p, {silent: true});
+ } else {
+ self.ports.add(p, {silent: true});
+ }
+ //console.log(p);
+ });
+ }
+ })).done(function() {
+ self.ports.trigger('add'); // batch redraws
+ });
+ },
+
+ loadFlows:function () {
+ var self = this;
+ //console.log("fetching switch " + this.id + " flows")
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/flow/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " flows");
+ var flows = data[self.id];
+ //console.log(flows);
+
+ // create flow models
+ var i = 0;
+ _.each(flows, function(f) {
+ f.id = self.id + '-' + i++;
+
+ // build human-readable match
+ f.matchHTML = '';
+ if(!(f.match.wildcards & (1<<0))) { // input port
+ f.matchHTML += "port=" + f.match.inputPort + ", ";
+ }
+ if(!(f.match.wildcards & (1<<1))) { // VLAN ID
+ f.matchHTML += "VLAN=" + f.match.dataLayerVirtualLan + ", ";
+ }
+ if(!(f.match.wildcards & (1<<20))) { // VLAN prio
+ f.matchHTML += "prio=" + f.match.dataLayerVirtualLanPriorityCodePoint + ", ";
+ }
+ if(!(f.match.wildcards & (1<<2))) { // src MAC
+ f.matchHTML += "src=<a href='/host/" + f.match.dataLayerSource + "'>" +
+ f.match.dataLayerSource + "</a>, ";
+ }
+ if(!(f.match.wildcards & (1<<3))) { // dest MAC
+ f.matchHTML += "dest=<a href='/host/" + f.match.dataLayerDestination + "'>" +
+ f.match.dataLayerDestination + "</a>, ";
+ }
+ if(!(f.match.wildcards & (1<<4))) { // Ethertype
+ // TODO print a human-readable name instead of hex
+ f.matchHTML += "ethertype=" + f.match.dataLayerType + ", ";
+ }
+ if(!(f.match.wildcards & (1<<5))) { // IP protocol
+ // TODO print a human-readable name
+ f.matchHTML += "proto=" + f.match.networkProtocol + ", ";
+ }
+ if(!(f.match.wildcards & (1<<6))) { // TCP/UDP source port
+ f.matchHTML += "IP src port=" + f.match.transportSource + ", ";
+ }
+ if(!(f.match.wildcards & (1<<7))) { // TCP/UDP dest port
+ f.matchHTML += "IP dest port=" + f.match.transportDestination + ", ";
+ }
+ if(!(f.match.wildcards & (32<<8))) { // src IP
+ f.matchHTML += "src=" + f.match.networkSource + ", ";
+ }
+ if(!(f.match.wildcards & (32<<14))) { // dest IP
+ f.matchHTML += "dest=" + f.match.networkDestination + ", ";
+ }
+ if(!(f.match.wildcards & (1<<21))) { // IP TOS
+ f.matchHTML += "TOS=" + f.match.networkTypeOfService + ", ";
+ }
+ // remove trailing ", "
+ f.matchHTML = f.matchHTML.substr(0, f.matchHTML.length - 2);
+
+ // build human-readable action list
+ f.actionText = _.reduce(f.actions, function (memo, a) {
+ switch (a.type) {
+ case "OUTPUT":
+ return memo + "output " + a.port + ', ';
+ case "OPAQUE_ENQUEUE":
+ return memo + "enqueue " + a.port + ':' + a.queueId + ', ';
+ case "STRIP_VLAN":
+ return memo + "strip VLAN, ";
+ case "SET_VLAN_ID":
+ return memo + "VLAN=" + a.virtualLanIdentifier + ', ';
+ case "SET_VLAN_PCP":
+ return memo + "prio=" + a.virtualLanPriorityCodePoint + ', ';
+ case "SET_DL_SRC":
+ return memo + "src=" + a.dataLayerAddress + ', ';
+ case "SET_DL_DST":
+ return memo + "dest=" + a.dataLayerAddress + ', ';
+ case "SET_NW_TOS":
+ return memo + "TOS=" + a.networkTypeOfService + ', ';
+ case "SET_NW_SRC":
+ return memo + "src=" + a.networkAddress + ', ';
+ case "SET_NW_DST":
+ return memo + "dest=" + a.networkAddress + ', ';
+ case "SET_TP_SRC":
+ return memo + "src port=" + a.transportPort + ', ';
+ case "SET_TP_DST":
+ return memo + "dest port=" + a.transportPort + ', ';
+ }
+ }, "");
+ // remove trailing ", "
+ f.actionText = f.actionText.substr(0, f.actionText.length - 2);
+
+ //console.log(f);
+ self.flows.add(f, {silent: true});
+ });
+ self.flows.trigger('add');
+ }
+ });
+ },
+});
+
+window.SwitchCollection = Backbone.Collection.extend({
+
+ model:Switch,
+
+ fetch:function () {
+ var self = this;
+ //console.log("fetching switch list")
+ $.ajax({
+ url:hackBase + "/wm/core/controller/switches/json",
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch list: " + data.length);
+ //console.log(data);
+ var old_ids = self.pluck('id');
+ //console.log("old_ids" + old_ids);
+
+ _.each(data, function(sw) {
+ old_ids = _.without(old_ids, sw['dpid']);
+ self.add({id: sw['dpid'], inetAddress: sw.inetAddress,
+ connectedSince: new Date(sw.connectedSince).toLocaleString()})});
+
+ // old_ids now holds switches that no longer exist; remove them
+ //console.log("old_ids" + old_ids);
+ _.each(old_ids, function(sw) {
+ console.log("removing switch " + sw);
+ self.remove({id:sw});
+ });
+ },
+ });
+ },
+
+});
diff --git a/web/js/models/topologymodel.js b/web/js/models/topologymodel.js
new file mode 100644
index 0000000..c5d8f9b
--- /dev/null
+++ b/web/js/models/topologymodel.js
@@ -0,0 +1,65 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Topology = Backbone.Model.extend({
+
+ url:"/wm/topology/links/json",
+
+ defaults:{
+ nodes: [],
+ links: [],
+ },
+
+ initialize:function () {
+ var self = this;
+ console.log("fetching topology")
+ $.ajax({
+ url:hackBase + self.url,
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched topology: " + data.length);
+ // console.log(data);
+ self.nodes = {};
+ self.links = [];
+
+ // step 1: build unique array of switch IDs
+ /* this doesn't work if there's only one switch,
+ because there are no switch-switch links
+ _.each(data, function (l) {
+ self.nodes[l['src-switch']] = true;
+ self.nodes[l['dst-switch']] = true;
+ });
+ // console.log(self.nodes);
+ var nl = _.keys(self.nodes);
+ */
+ var nl = swl.pluck('id');
+ self.nodes = _.map(nl, function (n) {return {name:n}});
+
+ // step 2: build array of links in format D3 expects
+ _.each(data, function (l) {
+ self.links.push({source:nl.indexOf(l['src-switch']),
+ target:nl.indexOf(l['dst-switch']),
+ value:10});
+ });
+ // console.log(self.nodes);
+ // console.log(self.links);
+ self.trigger('change');
+ //self.set(data);
+ }
+ });
+ }
+
+});
\ No newline at end of file
diff --git a/web/js/utils.js b/web/js/utils.js
new file mode 100644
index 0000000..c086e29
--- /dev/null
+++ b/web/js/utils.js
@@ -0,0 +1,36 @@
+// template loader from Christophe Coenraets
+tpl = {
+
+ // Hash of preloaded templates for the app
+ templates:{},
+
+ // Recursively pre-load all the templates for the app.
+ // This implementation should be changed in a production environment. All the template files should be
+ // concatenated in a single file.
+ loadTemplates:function (names, callback) {
+
+ var that = this;
+
+ var loadTemplate = function (index) {
+ var name = names[index];
+ console.log('Loading template: ' + name);
+ $.get('tpl/' + name + '.html', function (data) {
+ that.templates[name] = data;
+ index++;
+ if (index < names.length) {
+ loadTemplate(index);
+ } else {
+ callback();
+ }
+ });
+ }
+
+ loadTemplate(0);
+ },
+
+ // Get template by name from hash of preloaded templates
+ get:function (name) {
+ return this.templates[name];
+ }
+
+};
\ No newline at end of file
diff --git a/web/js/views/flow.js b/web/js/views/flow.js
new file mode 100644
index 0000000..65e0b71
--- /dev/null
+++ b/web/js/views/flow.js
@@ -0,0 +1,69 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+// not used for now
+window.FlowView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('flow'));
+ this.model.bind("change", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.FlowListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('flow-list-item'));
+ this.model.bind("change", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+// TODO throughput (bps) and pps sparklines would be nice here
+// TODO hovering over a MAC address could show a compact view of that host
+window.FlowListView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('flow-list'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ },
+
+ render:function (eventName) {
+ // console.log("rendering flow list view: " + this.model.models.length);
+ $(this.el).html(this.template({nflows:this.model.length}));
+ _.each(this.model.models, function (f) {
+ $(this.el).find('table.flow-table > tbody')
+ .append(new FlowListItemView({model:f}).render().el);
+ }, this);
+ return this;
+ },
+
+});
+
diff --git a/web/js/views/header.js b/web/js/views/header.js
new file mode 100644
index 0000000..65e49dd
--- /dev/null
+++ b/web/js/views/header.js
@@ -0,0 +1,49 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.HeaderView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('header'));
+ // this.searchResults = new HostCollection();
+ // this.searchresultsView = new SearchListView({model:this.searchResults, className:'dropdown-menu'});
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template());
+ $('#live-updates', this.el).change(function () {
+ updating = $(this).is(':checked');
+ })
+ // $('.navbar-search', this.el).append(this.searchresultsView.render().el);
+ return this;
+ },
+
+ events:{
+ "keyup .search-query":"search"
+ },
+
+ search:function (event) {
+// var key = event.target.value;
+ var key = $('#searchText').val();
+ console.log('search ' + key);
+ // TODO search the host and switch lists
+ this.searchResults.findByName(key);
+ setTimeout(function () {
+ $('#searchForm').addClass('open');
+ });
+ }
+
+});
\ No newline at end of file
diff --git a/web/js/views/home.js b/web/js/views/home.js
new file mode 100644
index 0000000..0283002
--- /dev/null
+++ b/web/js/views/home.js
@@ -0,0 +1,41 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.HomeView = Backbone.View.extend({
+
+ initialize:function () {
+ // console.log('Initializing Home View');
+ this.template = _.template(tpl.get('home'));
+ },
+
+ events:{
+ "click #showMeBtn":"showMeBtnClick"
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template());
+ var stats = new Status();
+ $(this.el).find('#controller-status').html(new StatusView({model:stats}).render().el);
+ $(this.el).find('#switch-list').html(new SwitchListView({model:swl}).render().el);
+ $(this.el).find('#host-list').html(new HostListView({model:hl}).render().el);
+ return this;
+ },
+
+ showMeBtnClick:function () {
+ app.headerView.search();
+ }
+
+});
\ No newline at end of file
diff --git a/web/js/views/host.js b/web/js/views/host.js
new file mode 100644
index 0000000..705703a
--- /dev/null
+++ b/web/js/views/host.js
@@ -0,0 +1,67 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.HostView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('host'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.HostListView = Backbone.View.extend({
+
+ initialize:function () {
+ var self = this;
+ this.template = _.template(tpl.get('host-list'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ this.model.bind("remove", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template({nhosts:hl.length}));
+ _.each(this.model.models, function (h) {
+ $(this.el).find('table.host-table > tbody')
+ .append(new HostListItemView({model:h}).render().el);
+ }, this);
+ return this;
+ }
+});
+
+window.HostListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('host-list-item'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
\ No newline at end of file
diff --git a/web/js/views/port.js b/web/js/views/port.js
new file mode 100644
index 0000000..e9aadb9
--- /dev/null
+++ b/web/js/views/port.js
@@ -0,0 +1,70 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+// not used for now
+window.PortView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('port'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.PortListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('port-list-item'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+// TODO throughput sparklines would be nice here
+window.PortListView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('port-list'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ },
+
+ render:function (eventName) {
+ // console.log("rendering port list view");
+ $(this.el).html(this.template({nports:this.model.length}));
+ _.each(this.model.models, function (p) {
+ $(this.el).find('table.port-table > tbody')
+ .append(new PortListItemView({model:p}).render().el);
+ }, this);
+ return this;
+ },
+
+});
+
diff --git a/web/js/views/status.js b/web/js/views/status.js
new file mode 100644
index 0000000..52c6c1c
--- /dev/null
+++ b/web/js/views/status.js
@@ -0,0 +1,30 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.StatusView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('status'));
+ this.model.bind("change", this.render, this);
+ },
+
+ render:function (eventName) {
+ // console.log("rendering status");
+ $(this.el).html(this.template(this.model.toJSON()));
+ //$(this.el).html(this.template());
+ return this;
+ }
+});
\ No newline at end of file
diff --git a/web/js/views/switch.js b/web/js/views/switch.js
new file mode 100644
index 0000000..d457633
--- /dev/null
+++ b/web/js/views/switch.js
@@ -0,0 +1,73 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.SwitchView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('switch'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+
+ // some parts of the model are large and are only needed in detail view
+ this.model.loadPorts();
+ this.model.loadFlows();
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ $(this.el).find('#port-list').html(new PortListView({model:this.model.ports}).render().el);
+ $(this.el).find('#flow-list').html(new FlowListView({model:this.model.flows}).render().el);
+ return this;
+ }
+
+});
+
+window.SwitchListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('switch-list-item'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.SwitchListView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('switch-list'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("remove", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template({nswitches:swl.length}));
+ _.each(this.model.models, function (sw) {
+ $(this.el).find('table.switch-table > tbody')
+ .append(new SwitchListItemView({model:sw}).render().el);
+ }, this);
+ return this;
+ },
+
+});
+
diff --git a/web/js/views/topology.js b/web/js/views/topology.js
new file mode 100644
index 0000000..77129aa
--- /dev/null
+++ b/web/js/views/topology.js
@@ -0,0 +1,111 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.TopologyView = Backbone.View.extend({
+ initialize:function () {
+ this.template = _.template(tpl.get('topology'));
+ this.model.bind("change", this.render, this);
+ this.hosts = this.options.hosts.models;
+ this.host_links = [];
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template());
+ var width = 900,
+ height = 600;
+ var color = d3.scale.category20();
+ var force = d3.layout.force()
+ .charge(-500)
+ .linkDistance(200)
+ .size([width, height]);
+ var svg = d3.select("#topology-graph").append("svg")
+ .attr("width", width)
+ .attr("height", height);
+ if(this.model.nodes) {
+ for (var i = 0; i < this.model.nodes.length; i++) {
+ this.model.nodes[i].group = 1;
+ this.model.nodes[i].id = this.model.nodes[i].name;
+ }
+
+ for (var i = 0; i < this.hosts.length; i++) {
+ host = this.hosts[i];
+ if (host.attributes['ipv4'].length > 0) {
+ host.name = host.attributes['ipv4'][0] + "\n" + host.id;
+ } else {
+ host.name = host.id;
+ }
+ host.group = 2;
+ //console.log(host);
+ }
+
+ var all_nodes = this.model.nodes.concat(this.hosts);
+
+ var all_nodes_map = [];
+
+ _.each(all_nodes, function(n) {
+ all_nodes_map[n.id] = n;
+ });
+
+ for (var i = 0; i < this.hosts.length; i++) {
+ host = this.hosts[i];
+ //for (var j = 0; j < host.attributes['attachmentPoint'].length; j++) {
+ for (var j = 0; j < 1; j++) { // FIXME hack to ignore multiple APs
+ var link = {source:all_nodes_map[host.id],
+ target:all_nodes_map[host.attributes['attachmentPoint'][j]['switchDPID']],
+ value:10};
+ //console.log(link);
+ if ( link.source && link.target) {
+ this.host_links.push(link);
+ } else {
+ console.log("Error: skipping link with undefined stuff!")
+ }
+ }
+ }
+
+ var all_links = this.model.links.concat(this.host_links);
+
+ force.nodes(all_nodes).links(all_links).start();
+ var link = svg.selectAll("line.link").data(all_links).enter()
+ .append("line").attr("class", "link")
+ .style("stroke", function (d) { return "black"; });
+ var node = svg.selectAll(".node").data(all_nodes)
+ .enter().append("g")
+ .attr("class", "node")
+ .call(force.drag);
+
+ node.append("image")
+ .attr("xlink:href", function (d) {return d.group==1 ? "/ui/img/switch.png" : "/ui/img/server.png"})
+ .attr("x", -16).attr("y", -16)
+ .attr("width", 32).attr("height", 32);
+ node.append("text").attr("dx", 20).attr("dy", ".35em")
+ .text(function(d) { return d.name });
+ node.on("click", function (d) {
+ // TODO we could add some functionality here
+ console.log('clicked '+d.name);
+ });
+ node.append("title").text(function(d) { return d.name; });
+ force.on("tick", function() {
+ link.attr("x1", function(d) { return d.source.x; })
+ .attr("y1", function(d) { return d.source.y; })
+ .attr("x2", function(d) { return d.target.x; })
+ .attr("y2", function(d) { return d.target.y; });
+ node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
+
+ });
+ }
+ return this;
+ }
+});