Add ROADM application

Change-Id: I50fa93cf3a69122f6434b46e831b254771159294
diff --git a/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.css b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.css
new file mode 100644
index 0000000..511b2fc
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.css
@@ -0,0 +1,69 @@
+/* css for ROADM port table view */
+
+#ov-roadm-port h2 {
+    display: inline-block;
+}
+
+/* Panel Styling */
+#ov-roadm-port-item-details-panel.floatpanel {
+    position: absolute;
+    top: 115px;
+}
+
+.light #ov-roadm-port-item-details-panel.floatpanel {
+    background-color: rgb(229, 234, 237);
+}
+.dark #ov-roadm-port-item-details-panel.floatpanel {
+    background-color: #3A4042;
+}
+
+#ov-roadm-port-item-details-panel h3 {
+    margin: 0;
+    font-size: large;
+}
+
+#ov-roadm-port-item-details-panel h4 {
+    margin: 0;
+}
+
+#ov-roadm-port-item-details-panel td {
+    padding: 5px;
+}
+
+#ov-roadm-port-item-details-panel td.label {
+    font-style: italic;
+    opacity: 0.8;
+}
+
+#ov-roadm-port .table-header span.units {
+    font-variant: normal;
+    text-transform: none;
+}
+
+/* Editable Target Power field */
+#ov-roadm-port .editable span {
+    width: 100%;
+    display: inline-block;
+}
+
+#ov-roadm-port .editable span.target-power:hover {
+    color: #009fdb
+}
+
+#ov-roadm-port .editable input {
+    padding: 0;
+}
+#ov-roadm-port .editable button {
+    margin: 0;
+    padding: 0px 5px 0px 5px;
+}
+
+#ov-roadm-port .editable input {
+    width: 80px;
+}
+
+#ov-roadm-port .editable .input-error {
+    color: red;
+    font-size: 10px;
+    width: 180px;
+}
diff --git a/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.html b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.html
new file mode 100644
index 0000000..1e2affd
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.html
@@ -0,0 +1,73 @@
+<!-- partial HTML -->
+<div id="ov-roadm-port" class="less-gap">
+
+    <div class="tabular-header">
+        <h2>Ports for Optical Device {{devId}} ({{tableData.length}} total)</h2>
+        <div class="ctrl-btns">
+            <div class="refresh" ng-class="{active: autoRefresh}"
+                 icon icon-id="refresh" icon-size="42"
+                 tooltip tt-msg="autoRefreshTip"
+                 ng-click="toggleRefresh()"></div>
+            <div class="separator"></div>
+
+            <div class="active"
+                 icon icon-id="deviceTable" icon-size="42"
+                 tooltip tt-msg="deviceTip"
+                 ng-click="nav('roadmDevice')"></div>
+
+            <div class="active"
+                 icon icon-id="flowTable" icon-size="42"
+                 tooltip tt-msg="flowTip"
+                 ng-click="nav('roadmFlow')"></div>
+
+            <div class="current-view"
+                 icon icon-id="portTable" icon-size="42"
+                 tooltip tt-msg="portTip"></div>
+        </div>
+    </div>
+
+    <div class="summary-list" onos-table-resize>
+
+        <div class="table-header" onos-sortable-header>
+            <table>
+                <tr>
+                    <td colId="id" sortable>Port Number </td>
+                    <td colId="name" sortable>Name </td>
+                    <td colId="type" sortable>Type </td>
+                    <td colId="enabled" sortable>Enabled </td>
+                    <td colId="minFreq" sortable>Min Freq <span class="units">(THz)</span> </td>
+                    <td colId="maxFreq" sortable>Max Freq <span class="units">(THz)</span> </td>
+                    <td colId="grid" sortable>Grid <span class="units">(GHz)</span> </td>
+                    <td colId="portMac" sortable>Input Power Range </td>
+                    <td colId="currentPower">Current Power <span class="units">(0.01dBm)</span> </td>
+                    <td colId="targetPower" col-width="200px">Target Power <span class="units">(0.01dBm)</span> </td>
+                </tr>
+            </table>
+        </div>
+
+        <div class="table-body">
+            <table>
+                <tr ng-if="!tableData.length" class="no-data">
+                    <td colspan="10">
+                        {{annots.no_rows_msg}}
+                    </td>
+                </tr>
+
+                <tr ng-repeat="item in tableData track by $index"
+                    ng-class="{selected: item.id === selId}">
+                    <td>{{item.id}}</td>
+                    <td>{{item.name}}</td>
+                    <td>{{item.type}}</td>
+                    <td>{{item.enabled}}</td>
+                    <td>{{item.minFreq}}</td>
+                    <td>{{item.maxFreq}}</td>
+                    <td>{{item.grid}}</td>
+                    <td>{{item.inputPowerRange}}</td>
+                    <td>{{item.currentPower}}</td>
+                    <td class="editable" roadm-power="item" roadm-set-power="setPortPower(port, targetVal, cb)"></td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+</div>
diff --git a/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js
new file mode 100644
index 0000000..447ac7d
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js
@@ -0,0 +1,168 @@
+// js for roadm port table view
+(function () {
+    'use strict';
+
+    var SET_TARGET_POWER_REQ = "roadmSetTargetPowerRequest";
+    var SET_TARGET_POWER_RESP = "roadmSetTargetPowerResponse";
+
+    // injected references
+    var $log, $scope, $location, fs, tbs, wss, ns;
+
+    var portCbTable = {};
+
+    function setPortPower(port, targetVal, cb) {
+        var id = port.id;
+        portCbTable[id] = cb;
+        wss.sendEvent("roadmSetTargetPowerRequest",
+            {
+                devId: $scope.devId,
+                id: port.id,
+                targetPower: targetVal
+            });
+    }
+
+    function portPowerCb(data) {
+        portCbTable[data.id](data.valid, data.message);
+    }
+
+    // check if value is an integer
+    function isInteger(val) {
+        var INTEGER_REGEXP = /^\-?\d+$/;
+        if (INTEGER_REGEXP.test(val)) {
+            return true;
+        }
+        return false;
+    }
+
+    angular.module('ovRoadmPort', [])
+    .controller('OvRoadmPortCtrl',
+        ['$log', '$scope', '$location',
+            'FnService', 'TableBuilderService', 'WebSocketService', 'NavService',
+
+        function (_$log_, _$scope_, _$location_, _fs_, _tbs_, _wss_, _ns_) {
+            var params;
+            $log = _$log_;
+            $scope = _$scope_;
+            $location = _$location_;
+            fs = _fs_;
+            tbs = _tbs_;
+            wss = _wss_;
+            ns = _ns_;
+
+            $scope.deviceTip = 'Show device table';
+            $scope.flowTip = 'Show flow view for this device';
+            $scope.groupTip = 'Show group view for this device';
+            $scope.meterTip = 'Show meter view for selected device';
+
+            var handlers = {};
+            handlers[SET_TARGET_POWER_RESP] = portPowerCb;
+            wss.bindHandlers(handlers);
+
+            params = $location.search();
+            if (params.hasOwnProperty('devId')) {
+                $scope.devId = params['devId'];
+            }
+
+            tbs.buildTable({
+                scope: $scope,
+                tag: 'roadmPort',
+                query: params
+            });
+
+            $scope.setPortPower = setPortPower;
+
+            $scope.setTargetPower = function (port, targetVal) {
+                wss.sendEvent("roadmSetTargetPowerRequest",
+                    {
+                        devId: $scope.devId,
+                        id: port.id,
+                        targetPower: targetVal
+                    });
+                $log.debug('Got a click on:', port);
+            }
+
+            $scope.nav = function (path) {
+                if ($scope.devId) {
+                    ns.navTo(path, { devId: $scope.devId });
+                }
+            };
+
+            $scope.$on('$destroy', function () {
+                wss.unbindHandlers(handlers);
+            });
+
+            $log.log('OvRoadmPortCtrl has been created');
+        }])
+
+    .directive('roadmPower', ['WebSocketService', function() {
+
+        var retTemplate =
+            '<span class="target-power" ng-show="!editMode" ng-click="enableEdit()">{{currItem.targetPower}}</span>' +
+            '<form ng-show="editMode" name="form" novalidate>' +
+                '<input type="number" name="formVal" ng-model="formVal">' +
+                '<button type="submit" ng-click="send()">Set</button>' +
+                '<button type="button" ng-click="cancel()">Cancel</button>' +
+                '<span class="input-error" ng-show="showError">{{errorMessage}}</span>' +
+            '</form>';
+
+        return {
+            restrict: 'A',
+            scope: {
+                currItem: '=roadmPower',
+                roadmSetPower: '&'
+            },
+            template: retTemplate,
+            link: function ($scope, $element) {
+                $scope.editMode = false;
+                $scope.showError = false;
+                $scope.errorMessage = "Invalid target power";
+            },
+            controller: function($scope, $timeout) {
+                $scope.enableEdit = function() {
+                    if ($scope.currItem.hasTargetPower === "true" && $scope.editMode === false) {
+                        // Ensure that the entry being edited remains the same even
+                        // if the table entries are shifted around.
+                        $scope.targetItem = $scope.currItem;
+                        // Ensure the value seen in the field remains the same
+                        $scope.formVal = parseInt($scope.currItem.targetPower);
+                        $scope.editMode = true;
+                        $timeout(function () {
+                            $scope.$apply()
+                        });
+                    }
+                };
+                // Callback for server-side validation. Displays the error message
+                // if the input is invalid.
+                $scope.sendCb = function(valid, message) {
+                    if (valid) {
+                        // check if it's still pointing to the same item
+                        // reordering the entries may change the binding
+                        if ($scope.currItem.id === $scope.targetItem.id) {
+                            // update the ui to display the new attenuation value
+                            $scope.currItem.targetPower = $scope.formVal;
+                        }
+                        $scope.cancel();
+                    } else {
+                        $scope.errorMessage = message;
+                        $scope.showError = true;
+                    }
+                    $timeout(function () {
+                        $scope.$apply()
+                    });
+                }
+                $scope.send = function() {
+                    // check input is an integer
+                    if (!isInteger($scope.formVal)) {
+                        $scope.sendCb(false, "Target power must be an integer");
+                        return;
+                    }
+                    $scope.roadmSetPower({port: $scope.targetItem, targetVal: $scope.formVal, cb: $scope.sendCb});
+                };
+                $scope.cancel = function() {
+                    $scope.editMode = false;
+                    $scope.showError = false;
+                }
+            }
+        };
+    }]);
+}());