ONOS-6327: Implemented details panel for host view.
- WIP: still have to render the host details in the panel.
- YakShaving:
   * augmented ConnectPoint to implement Comparable
   * cleaned up ConnectPointTest

Change-Id: I5c901099319820e08af812807ff65bf5dac1fb23
diff --git a/core/api/src/main/java/org/onosproject/net/ConnectPoint.java b/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
index 13471ec..f12bf1b 100644
--- a/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
+++ b/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
@@ -24,7 +24,7 @@
  * Abstraction of a network connection point expressed as a pair of the
  * network element identifier and port number.
  */
-public class ConnectPoint {
+public class ConnectPoint implements Comparable<ConnectPoint> {
 
     private final ElementId elementId;
     private final PortNumber portNumber;
@@ -167,4 +167,13 @@
         return elementId + "/" + portNumber;
     }
 
+    @Override
+    public int compareTo(ConnectPoint o) {
+        int result = deviceId().toString().compareTo(o.deviceId().toString());
+        if (result == 0) {
+            long delta = port().toLong() - o.port().toLong();
+            result = delta == 0 ? 0 : (delta < 0 ? -1 : +1);
+        }
+        return result;
+    }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java b/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
index ee55723..74b162e 100644
--- a/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
+++ b/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
@@ -21,6 +21,9 @@
 import static junit.framework.TestCase.fail;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.ConnectPointTest.Relate.AFTER;
+import static org.onosproject.net.ConnectPointTest.Relate.BEFORE;
+import static org.onosproject.net.ConnectPointTest.Relate.SAME_AS;
 import static org.onosproject.net.DeviceId.deviceId;
 import static org.onosproject.net.PortNumber.portNumber;
 
@@ -34,9 +37,13 @@
     private static final PortNumber P1 = portNumber(1);
     private static final PortNumber P2 = portNumber(2);
 
+    private ConnectPoint cp(DeviceId d, PortNumber p) {
+        return new ConnectPoint(d, p);
+    }
+
     @Test
     public void basics() {
-        ConnectPoint p = new ConnectPoint(DID1, P2);
+        ConnectPoint p = cp(DID1, P2);
         assertEquals("incorrect element id", DID1, p.deviceId());
         assertEquals("incorrect element id", P2, p.port());
     }
@@ -44,9 +51,9 @@
     @Test
     public void testEquality() {
         new EqualsTester()
-                .addEqualityGroup(new ConnectPoint(DID1, P1), new ConnectPoint(DID1, P1))
-                .addEqualityGroup(new ConnectPoint(DID1, P2), new ConnectPoint(DID1, P2))
-                .addEqualityGroup(new ConnectPoint(DID2, P1), new ConnectPoint(DID2, P1))
+                .addEqualityGroup(cp(DID1, P1), cp(DID1, P1))
+                .addEqualityGroup(cp(DID1, P2), cp(DID1, P2))
+                .addEqualityGroup(cp(DID2, P1), cp(DID2, P1))
                 .testEquals();
     }
 
@@ -107,4 +114,34 @@
             assertTrue(true);
         }
     }
+
+    enum Relate { BEFORE, SAME_AS, AFTER }
+
+    private void checkComparison(ConnectPoint cpA, Relate r, ConnectPoint cpB) {
+        switch (r) {
+            case BEFORE:
+                assertTrue("Bad before", cpA.compareTo(cpB) < 0);
+                break;
+            case SAME_AS:
+                assertTrue("Bad same_as", cpA.compareTo(cpB) == 0);
+                break;
+            case AFTER:
+                assertTrue("Bad after", cpA.compareTo(cpB) > 0);
+                break;
+            default:
+                fail("Bad relation");
+        }
+    }
+
+    @Test
+    public void comparator() {
+        checkComparison(cp(DID1, P1), SAME_AS, cp(DID1, P1));
+        checkComparison(cp(DID1, P1), BEFORE, cp(DID1, P2));
+        checkComparison(cp(DID1, P2), AFTER, cp(DID1, P1));
+
+        checkComparison(cp(DID1, P1), BEFORE, cp(DID2, P1));
+        checkComparison(cp(DID1, P2), BEFORE, cp(DID2, P1));
+        checkComparison(cp(DID1, P1), BEFORE, cp(DID2, P2));
+        checkComparison(cp(DID1, P2), BEFORE, cp(DID2, P2));
+    }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
index 6e821f4..169b89f 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
@@ -15,11 +15,14 @@
  */
 package org.onosproject.ui.impl;
 
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import org.onlab.packet.IpAddress;
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
 import org.onosproject.net.host.HostService;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiMessageHandler;
@@ -27,11 +30,17 @@
 import org.onosproject.ui.table.TableModel;
 import org.onosproject.ui.table.TableRequestHandler;
 import org.onosproject.ui.table.cell.HostLocationFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onosproject.net.HostId.hostId;
 
 /**
  * Message handler for host view related messages.
@@ -42,22 +51,51 @@
     private static final String HOST_DATA_RESP = "hostDataResponse";
     private static final String HOSTS = "hosts";
 
+    private static final String HOST_DETAILS_REQ = "hostDetailsRequest";
+    private static final String HOST_DETAILS_RESP = "hostDetailsResponse";
+    private static final String DETAILS = "details";
+
+    private static final String HOST_NAME_CHANGE_REQ = "hostNameChangeRequest";
+    private static final String HOST_NAME_CHANGE_RESP = "hostNameChangeResponse";
+
     private static final String TYPE_IID = "_iconid_type";
+    private static final String NAME = "name";
     private static final String ID = "id";
     private static final String MAC = "mac";
     private static final String VLAN = "vlan";
     private static final String IPS = "ips";
     private static final String LOCATION = "location";
+    private static final String LOCATIONS = "locations";
+    private static final String CONFIGURED = "configured";
+
 
     private static final String HOST_ICON_PREFIX = "hostIcon_";
 
     private static final String[] COL_IDS = {
-            TYPE_IID, ID, MAC, VLAN, IPS, LOCATION
+            TYPE_IID, NAME, ID, MAC, VLAN, CONFIGURED, IPS, LOCATION
     };
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     @Override
     protected Collection<RequestHandler> createRequestHandlers() {
-        return ImmutableSet.of(new HostDataRequest());
+        return ImmutableSet.of(
+                new HostDataRequest(),
+                new DetailRequestHandler(),
+                new NameChangeHandler()
+        );
+    }
+
+    private String getTypeIconId(Host host) {
+        String hostType = host.annotations().value(AnnotationKeys.TYPE);
+        return HOST_ICON_PREFIX +
+                (isNullOrEmpty(hostType) ? "endstation" : hostType);
+    }
+
+    // returns the "friendly name" for the host
+    private String getHostName(Host host) {
+        // TODO: acutally use the name field (not just the ID)
+        return host.id().toString();
     }
 
     // handler for host table requests
@@ -96,17 +134,14 @@
 
         private void populateRow(TableModel.Row row, Host host) {
             row.cell(TYPE_IID, getTypeIconId(host))
+                    .cell(NAME, getHostName(host))
                     .cell(ID, host.id())
                     .cell(MAC, host.mac())
                     .cell(VLAN, host.vlan())
+                    .cell(CONFIGURED, host.configured())
                     .cell(IPS, host.ipAddresses())
                     .cell(LOCATION, host.location());
-        }
-
-        private String getTypeIconId(Host host) {
-            String hostType = host.annotations().value(AnnotationKeys.TYPE);
-            return HOST_ICON_PREFIX +
-                    (isNullOrEmpty(hostType) ? "endstation" : hostType);
+            // Note: leave complete list of all LOCATIONS to the details panel
         }
 
         private final class IpSetFormatter implements CellFormatter {
@@ -123,8 +158,7 @@
                     sb.append(ip.toString())
                             .append(COMMA);
                 }
-                removeTrailingComma(sb);
-                return sb.toString();
+                return removeTrailingComma(sb).toString();
             }
 
             private StringBuilder removeTrailingComma(StringBuilder sb) {
@@ -134,4 +168,65 @@
             }
         }
     }
+
+
+    private final class DetailRequestHandler extends RequestHandler {
+        private DetailRequestHandler() {
+            super(HOST_DETAILS_REQ);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            String id = string(payload, ID, "");
+
+            HostId hostId = hostId(id);
+            HostService service = get(HostService.class);
+            Host host = service.getHost(hostId);
+            ObjectNode data = objectNode();
+
+            data.put(TYPE_IID, getTypeIconId(host))
+                    .put(NAME, getHostName(host))
+                    .put(ID, hostId.toString())
+                    .put(MAC, host.mac().toString())
+                    .put(VLAN, host.vlan().toString())
+                    .put(CONFIGURED, host.configured())
+                    .put(LOCATION, host.location().toString());
+
+            List<IpAddress> sortedIps = new ArrayList<>(host.ipAddresses());
+            Collections.sort(sortedIps);
+            ArrayNode ips = arrayNode();
+            for (IpAddress ip : sortedIps) {
+                ips.add(ip.toString());
+            }
+            data.set(IPS, ips);
+
+            List<HostLocation> sortedLocs = new ArrayList<>(host.locations());
+            Collections.sort(sortedLocs);
+            ArrayNode locs = arrayNode();
+            for (HostLocation hl : sortedLocs) {
+                locs.add(hl.toString());
+            }
+            data.set(LOCATIONS, locs);
+
+            ObjectNode root = objectNode();
+            root.set(DETAILS, data);
+
+            sendMessage(HOST_DETAILS_RESP, root);
+        }
+    }
+
+    private final class NameChangeHandler extends RequestHandler {
+        public NameChangeHandler() {
+            super(HOST_NAME_CHANGE_REQ);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            // TODO:
+
+            ObjectNode root = objectNode();
+
+            sendMessage(HOST_NAME_CHANGE_RESP, root);
+        }
+    }
 }
diff --git a/web/gui/src/main/webapp/app/view/host/host.html b/web/gui/src/main/webapp/app/view/host/host.html
index e88efa3..85b8b5e 100644
--- a/web/gui/src/main/webapp/app/view/host/host.html
+++ b/web/gui/src/main/webapp/app/view/host/host.html
@@ -15,9 +15,11 @@
             <table>
                 <tr>
                     <td colId="type" class="table-icon"></td>
+                    <td colId="name" sortable>Friendly Name </td>
                     <td colId="id" sortable>Host ID </td>
                     <td colId="mac" sortable>MAC Address </td>
                     <td colId="vlan" sortable>VLAN ID </td>
+                    <td colId="configured" sortable>Configured </td>
                     <td colId="ips" sortable>IP Addresses </td>
                     <td colId="location" sortable>Location </td>
                 </tr>
@@ -27,7 +29,7 @@
         <div class="table-body">
             <table onos-flash-changes id-prop="id">
                 <tr ng-if="!tableData.length" class="no-data">
-                    <td colspan="6">
+                    <td colspan="8">
                         {{annots.no_rows_msg}}
                     </td>
                 </tr>
@@ -39,9 +41,11 @@
                     <td class="table-icon">
                         <div icon icon-id="{{host._iconid_type}}"></div>
                     </td>
+                    <td>{{host.name}}</td>
                     <td>{{host.id}}</td>
                     <td>{{host.mac}}</td>
                     <td>{{host.vlan}}</td>
+                    <td>{{host.configured}}</td>
                     <td>{{host.ips}}</td>
                     <td>{{host.location}}</td>
                 </tr>
diff --git a/web/gui/src/main/webapp/app/view/host/host.js b/web/gui/src/main/webapp/app/view/host/host.js
index 537bc13..ee23f00 100644
--- a/web/gui/src/main/webapp/app/view/host/host.js
+++ b/web/gui/src/main/webapp/app/view/host/host.js
@@ -148,6 +148,8 @@
         setUpPanel();
         populateTop(details);
         detailsPanel.height(pHeight);
+        // configure width based on content.. for now hardcoded
+        detailsPanel.width(600);
     }
 
     function respDetailsCb(data) {