Added webservices forwarding script for UI
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});
+                });
+            },
+        });
+    },
+
+});