Add ROADM application
Change-Id: I50fa93cf3a69122f6434b46e831b254771159294
diff --git a/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.css b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.css
new file mode 100644
index 0000000..80f975f
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.css
@@ -0,0 +1,39 @@
+/* css for ROADM device table view */
+
+.less-gap {
+ margin-top: -20px;
+}
+
+#ov-roadm-device h2 {
+ display: inline-block;
+}
+
+/* Panel Styling */
+#ov-roadm-device-item-details-panel.floatpanel {
+ position: absolute;
+ top: 115px;
+}
+
+.light #ov-roadm-device-item-details-panel.floatpanel {
+ background-color: rgb(229, 234, 237);
+}
+.dark #ov-roadm-device-item-details-panel.floatpanel {
+ background-color: #3A4042;
+}
+
+#ov-roadm-device-item-details-panel h3 {
+ margin: 0;
+ font-size: large;
+}
+
+#ov-roadm-device-item-details-panel h4 {
+ margin: 0;
+}
+
+#ov-roadm-device-item-details-panel td {
+ padding: 5px;
+}
+#ov-roadm-device-item-details-panel td.label {
+ font-style: italic;
+ opacity: 0.8;
+}
diff --git a/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.html b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.html
new file mode 100644
index 0000000..a9a70e8
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.html
@@ -0,0 +1,70 @@
+<!-- partial HTML -->
+<div id="ov-roadm-device" class="less-gap">
+
+ <div class="tabular-header">
+ <h2>Optical Devices ({{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 ng-class="{'current-view': !!selId}"
+ icon icon-id="deviceTable" icon-size="42"></div>
+
+ <div ng-class="{active: !!selId}"
+ icon icon-id="flowTable" icon-size="42"
+ tooltip tt-msg="flowTip"
+ ng-click="nav('roadmFlow')"></div>
+
+
+ <div ng-class="{active: !!selId}"
+ icon icon-id="portTable" icon-size="42"
+ tooltip tt-msg="portTip"
+ ng-click="nav('roadmPort')"></div>
+ </div>
+ </div>
+
+ <div class="summary-list" onos-table-resize>
+
+ <div class="table-header" onos-sortable-header>
+ <table>
+ <tr>
+ <td colId="name"sortable>Friendly Name</td>
+ <td colId="id" sortable>Device ID </td>
+ <td colId="master" sortable col-width="120px">Master </td>
+ <td colId="ports" sortable col-width="70px">Ports </td>
+ <td colId="vendor" sortable>Vendor </td>
+ <td colId="hwVersion" sortable>H/W Version </td>
+ <td colId="swVersion" sortable>S/W Version </td>
+ <td colId="protocol" sortable col-width="100px">Protocol </td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="table-body">
+ <table>
+ <tr ng-if="!tableData.length" class="no-data">
+ <td colspan="3">
+ {{annots.no_rows_msg}}
+ </td>
+ </tr>
+
+ <tr ng-repeat="item in tableData track by $index"
+ ng-class="{selected: item.id === selId}"
+ ng-click="selectCallback($event, item)">
+ <td>{{item.name}}</td>
+ <td>{{item.id}}</td>
+ <td>{{item.master}}</td>
+ <td>{{item.ports}}</td>
+ <td>{{item.vendor}}</td>
+ <td>{{item.hwVersion}}</td>
+ <td>{{item.swVersion}}</td>
+ <td>{{item.protocol}}</td>
+ </tr>
+ </table>
+ </div>
+
+ </div>
+</div>
diff --git a/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.js b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.js
new file mode 100644
index 0000000..2537ed8
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.js
@@ -0,0 +1,49 @@
+// js for roadm device table view
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, $scope, $loc, wss, ns;
+
+ // constants
+ var detailsReq = 'roadmDeviceDetailsRequest';
+
+ angular.module('ovRoadmDevice', [])
+ .controller('OvRoadmDeviceCtrl',
+ ['$log', '$scope', '$location', 'TableBuilderService', 'WebSocketService',
+ 'NavService',
+
+ function (_$log_, _$scope_, _$loc_, tbs, _wss_, _ns_) {
+ $log = _$log_;
+ $scope = _$scope_;
+ $loc = _$loc_;
+ wss = _wss_;
+ ns = _ns_;
+
+ // query for if a certain device needs to be highlighted
+ var params = $loc.search();
+ if (params.hasOwnProperty('devId')) {
+ $scope.selId = params['devId'];
+ }
+
+ // TableBuilderService creating a table for us
+ tbs.buildTable({
+ scope: $scope,
+ tag: 'roadmDevice'
+ });
+
+ $scope.nav = function (path) {
+ if ($scope.selId) {
+ ns.navTo(path, { devId: $scope.selId });
+ }
+ };
+
+ // cleanup
+ $scope.$on('$destroy', function () {
+ //wss.unbindHandlers(handlers);
+ $log.log('OvRoadmDeviceCtrl has been destroyed');
+ });
+
+ $log.log('OvRoadmDeviceCtrl has been created');
+ }]);
+}());
diff --git a/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css
new file mode 100644
index 0000000..81e5c16
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css
@@ -0,0 +1,132 @@
+/* css for ROADM flow table view */
+
+#ov-roadm-flow h2 {
+ display: inline-block;
+}
+
+/* Panel Styling */
+#ov-roadm-flow-item-details-panel.floatpanel {
+ position: absolute;
+ top: 115px;
+}
+
+.light #ov-roadm-flow-item-details-panel.floatpanel {
+ background-color: rgb(229, 234, 237);
+}
+
+.dark #ov-roadm-flow-item-details-panel.floatpanel {
+ background-color: #3A4042;
+}
+
+#ov-roadm-flow-item-details-panel h3 {
+ margin: 0;
+ font-size: large;
+}
+
+#ov-roadm-flow-item-details-panel h4 {
+ margin: 0;
+}
+
+#ov-roadm-flow-item-details-panel td {
+ padding: 5px;
+}
+#ov-roadm-flow-item-details-panel td.label {
+ font-style: italic;
+ opacity: 0.8;
+}
+
+#ov-roadm-flow .table-header span.units {
+ font-variant: normal;
+ text-transform: none;
+}
+
+/* editable attenuation */
+#ov-roadm-flow .editable span {
+ width: 100%;
+ display: inline-block;
+}
+
+#ov-roadm-flow .editable span.attenuation:hover {
+ color: #009fdb
+}
+
+#ov-roadm-flow .editable input {
+ padding: 0;
+}
+#ov-roadm-flow .editable button {
+ margin: 0;
+ padding: 0px 5px 0px 5px;
+}
+
+#ov-roadm-flow .editable input {
+ width: 80px;
+}
+
+#ov-roadm-flow .editable .input-error {
+ color: red;
+ font-size: 10px;
+ width: 180px;
+}
+
+/* delete flow button */
+#ov-roadm-flow .table-body .delete-icon {
+ font-size: 24px;
+ line-height: 0px;
+ text-align: center;
+}
+
+#ov-roadm-flow .table-body .delete-icon:hover {
+ color: red;
+}
+
+/* Create connection form */
+#ov-roadm-flow div.flow-form {
+ background-color: #ffffff;
+ border: 1px solid #888888;
+ width: 720px;
+ height: 270px;
+ padding: 20px;
+ position: absolute;
+ right: 15px;
+ bottom: 15px;
+}
+
+#ov-roadm-flow .flow-form div.delete-icon {
+ cursor: pointer;
+ cursor: hand;
+ font-size: 36px;
+ position: absolute;
+ right: 20px;
+ top: 5px;
+}
+
+#ov-roadm-flow .flow-form label {
+ width: 150px;
+ display: inline-block;
+}
+
+#ov-roadm-flow .flow-form input {
+ width: 150px;
+ display: inline-block
+}
+
+#ov-roadm-flow .flow-form select {
+ width: 150px;
+}
+
+#ov-roadm-flow .flow-form .form-error {
+ margin-left: 15px;
+ color: red;
+}
+
+#ov-roadm-flow .flow-form form {
+ font-size: 14px;
+ color: #444444;
+ line-height: 26px;
+ margin-bottom: 15px;
+}
+
+#ov-roadm-flow .flow-form button.submit {
+ margin-left: 150px;
+ width: 150px;
+}
diff --git a/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html
new file mode 100644
index 0000000..323eec5
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html
@@ -0,0 +1,118 @@
+<!-- partial HTML -->
+<div id="ov-roadm-flow" class="less-gap">
+
+ <div class="tabular-header">
+ <h2>Connections for Optical Device {{devId}} ({{tableData.length}} total)</h2>
+ <div class="ctrl-btns">
+ <div class="active"
+ icon icon-id="plus" icon-size="42"
+ tooltip tt-msg="addFlowTip"
+ ng-click="displayFlowForm()"></div>
+
+ <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="current-view"
+ icon icon-id="flowTable" icon-size="42"
+ tooltip tt-msg="flowTip"></div>
+
+ <div class="active"
+ icon icon-id="portTable" icon-size="42"
+ tooltip tt-msg="portTip"
+ ng-click="nav('roadmPort')"></div>
+ </div>
+ </div>
+
+ <div class="summary-list" onos-table-resize>
+
+ <div class="table-header" onos-sortable-header>
+ <table>
+ <tr>
+ <td col-width="30px"></td>
+ <td colId="flowId" sortable col-width="180px">Flow ID </td>
+ <td colId="appId" sortable>App ID </td>
+ <td colId="priority" sortable>Priority </td>
+ <td colId="timeout" sortable>Timeout </td>
+ <td colId="permanent" sortable>Permanent </td>
+ <td colId="state" sortable>State </td>
+ <td colId="inPort" sortable>In Port </td>
+ <td colId="outPort" sortable>Out Port </td>
+ <td colId="multiplier" sortable>Channel </td>
+ <td colId="spacing">Spacing <span class="units">(GHz)</span> </td>
+ <td colId="currentPower" col-width="180px">Current Power <span class="units">(0.01dBm)</span></td>
+ <td colId="attenuation" col-width="200px">Attenuation <span class="units">(0.01dB)</span></td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="table-body">
+ <table>
+ <tr ng-if="!tableData.length" class="no-data">
+ <td colspan="13">
+ {{annots.no_rows_msg}}
+ </td>
+ </tr>
+
+ <tr ng-repeat="flow in tableData track by $index"
+ ng-class="{selected: flow.id === selId}">
+ <td class="delete-icon" ng-click="deleteFlow($event, flow)">×</td>
+ <td>{{flow.flowId}}</td>
+ <td>{{flow.appId}}</td>
+ <td>{{flow.priority}}</td>
+ <td>{{flow.timeout}}</td>
+ <td>{{flow.permanent}}</td>
+ <td>{{flow.state}}</td>
+ <td>{{flow.inPort}}</td>
+ <td>{{flow.outPort}}</td>
+ <td>{{flow.multiplier}} ({{flow.multiplier * 0.05 + 193.1 | number:2}} THz)</td>
+ <td>{{flow.spacing}}</td>
+ <td>{{flow.currentPower}}</td>
+ <td class="editable" roadm-att="flow" roadm-set-att="setAttenuation(flow, targetVal, cb)"></td>
+ </tr>
+ </table>
+ </div>
+
+ </div>
+
+ <div class="flow-form" ng-controller="FlowFormController as form" ng-show="showFlowForm">
+ <div class="delete-icon" ng-click="hideFlowForm()">×</div>
+ <form>
+ <label>Priority</label><input type="number" ng-model="form.flow.priority" />
+ <span class="form-error" ng-show="form.priorityError" >{{form.priorityMessage}}</span><br />
+
+ <label>Is Permanent</label><input type="checkbox" ng-model="form.flow.permanent" /><br />
+
+ <label>Timeout</label><input type="number" ng-model="form.flow.timeout" ng-disabled="form.flow.permanent"/>
+ <span class="form-error" ng-show="form.timeoutError" >{{form.timeoutMessage}}</span><br />
+
+ <label>In Port</label><input type="number" ng-model="form.flow.inPort" />
+ <span class="form-error" ng-show="form.inPortError">{{form.inPortMessage}}</span><br />
+
+ <label>Out Port</label><input type="number" ng-model="form.flow.outPort" />
+ <span class="form-error" ng-show="form.outPortError">{{form.outPortMessage}}</span>
+ <span class="form-error" ng-show="form.connectionError">{{form.connectionMessage}}</span><br />
+
+ <label>Channel Spacing</label><select ng-model="form.flow.spacing" ng-options="x.freq for x in form.spacings"></select>
+ <span class="form-error" ng-show="form.spacingError">{{form.spacingMessage}}</span><br />
+
+ <label>Spacing Multiplier</label><input type="number" ng-model="form.flow.multiplier" />
+ <span class="form-error" ng-show="form.multiplierError">{{form.multiplierMessage}}</span>
+ <span class="form-error" ng-show="form.channelError">{{form.channelMessage}}</span><br />
+
+ <label>Include Attenuation</label><input type="checkbox" ng-model="form.flow.includeAttenuation" /><br />
+
+ <label>Attenuation</label><input type="number" ng-model="form.flow.attenuation" ng-disabled="!form.flow.includeAttenuation"/>
+ <span class="form-error" ng-show="form.attenuationError">{{form.attenuationMessage}}</span><br />
+ </form>
+ <button type="submit" class="submit" ng-click="form.createFlow(form.flow)">Create Connection</button>
+ </div>
+
+</div>
diff --git a/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js
new file mode 100644
index 0000000..ad9bf76e
--- /dev/null
+++ b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js
@@ -0,0 +1,324 @@
+// js for roadm flow table view
+(function () {
+ 'use strict';
+
+ var SET_ATT_REQ = "roadmSetAttenuationRequest";
+ var SET_ATT_RESP = "roadmSetAttenuationResponse";
+ var DELETE_FLOW_REQ = "roadmDeleteFlowRequest";
+ var CREATE_FLOW_REQ = "roadmCreateFlowRequest";
+ var CREATE_FLOW_RESP = "roadmCreateFlowResponse";
+
+ // injected references
+ var $log, $scope, $location, fs, tbs, wss, ns;
+
+ // used to map id to a request call function
+ var flowCbTable = {};
+
+ function setAttenuation(flow, targetVal, cb) {
+ flowCbTable[flow.id] = cb;
+ wss.sendEvent(SET_ATT_REQ,
+ {
+ devId: $scope.devId,
+ flowId: flow.id,
+ attenuation: targetVal
+ });
+ }
+
+ function attenuationCb(data) {
+ flowCbTable[data.flowId](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('ovRoadmFlow', [])
+ .controller('OvRoadmFlowCtrl',
+ ['$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.addFlowTip = 'Create a flow';
+ $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';
+
+ $scope.showFlowForm = false;
+
+ var handlers = {};
+ handlers[SET_ATT_RESP] = attenuationCb;
+ wss.bindHandlers(handlers);
+
+ params = $location.search();
+ if (params.hasOwnProperty('devId')) {
+ $scope.devId = params['devId'];
+ }
+
+ tbs.buildTable({
+ scope: $scope,
+ tag: 'roadmFlow',
+ query: params
+ });
+
+ $scope.displayFlowForm = function () {
+ $scope.showFlowForm = true;
+ }
+
+ $scope.hideFlowForm = function () {
+ $scope.showFlowForm = false;
+ }
+
+ $scope.setAttenuation = setAttenuation;
+
+ $scope.deleteFlow = function ($event, row) {
+ wss.sendEvent(DELETE_FLOW_REQ,
+ {
+ devId: $scope.devId,
+ id: row.id
+ });
+ }
+
+ $scope.createFlow = function(flow) {
+ wss.sendEvent(CREATE_FLOW_REQ,
+ {
+ devId: $scope.devId,
+ flow: flow
+ });
+ }
+
+ $scope.fakeCurrentPower = function(flow) {
+ if (!isNaN(flow.currentPower)) {
+ var val = parseInt(flow.attenuation);
+ return val + (val % 5 - 2);
+ } else {
+ return flow.currentPower;
+ }
+ }
+
+ $scope.nav = function (path) {
+ if ($scope.devId) {
+ ns.navTo(path, { devId: $scope.devId });
+ }
+ };
+
+ $scope.$on('$destroy', function () {
+ wss.unbindHandlers(handlers);
+ });
+
+ $log.log('OvRoadmFlowCtrl has been created');
+ }])
+
+ .directive('roadmAtt', ['WebSocketService', function() {
+
+ var retTemplate =
+ '<span class="attenuation" ng-show="!editMode" ng-click="enableEdit()">{{currItem.attenuation}}</span>' +
+ '<form ng-show="editMode" name="form" novalidate>' +
+ '<input type="number" name="formVal" ng-model="formVal">' +
+ '<button type="submit" class="submit" ng-click="send()">Set</button>' +
+ '<button type="button" class="cancel" ng-click="cancel()">Cancel</button>' +
+ '<span class="input-error" ng-show="showError">{{errorMessage}}</span>' +
+ '</form>';
+
+ return {
+ restrict: 'A',
+ scope: {
+ currItem: '=roadmAtt',
+ roadmSetAtt: '&'
+ },
+ template: retTemplate,
+ link: function ($scope, $element) {
+ $scope.editMode = false;
+ $scope.showError = false;
+ $scope.errorMessage = "Invalid attenuation"
+ },
+ controller: function($scope, $timeout) {
+ $scope.enableEdit = function() {
+ // connection must support attenuation to be editable
+ if ($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.attenuation);
+ $scope.editMode = true;
+ $timeout(function () {
+ $scope.$apply()
+ });
+ }
+ };
+ $scope.send = function() {
+ // check input is an integer
+ if (!isInteger($scope.formVal)) {
+ $scope.sendCb(false, "Attenuation must be an integer");
+ return;
+ }
+ $scope.roadmSetAtt({flow: $scope.targetItem, targetVal: $scope.formVal, cb: $scope.sendCb});
+ };
+ // 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.attenuation = $scope.formVal;
+ }
+ $scope.cancel();
+ } else {
+ $scope.errorMessage = message;
+ $scope.showError = true;
+ }
+ $timeout(function () {
+ $scope.$apply()
+ });
+ }
+ $scope.cancel = function() {
+ $scope.editMode = false;
+ $scope.showError = false;
+ }
+ }
+ };
+ }])
+
+ .controller('FlowFormController', function($timeout) {
+ var notIntegerError = "Must be an integer.";
+
+ this.clearErrors = function() {
+ this.priorityError = false;
+ this.timeoutError = false;
+ this.isPermanentError = false;
+ this.inPortError = false;
+ this.outPortError = false;
+ this.spacingError = false;
+ this.multiplierError = false;
+ this.attenuationError = false;
+ this.connectionError = false;
+ this.channelError = false;
+ }
+ this.clearErrors();
+
+ this.spacings = [
+ {index: 0, freq: "100 GHz"},
+ {index: 1, freq: "50 GHz"},
+ {index: 2, freq: "25 GHz"},
+ {index: 3, freq: "12.5 GHz"}
+ ];
+
+ this.flow = {};
+ //this.flow.priority = 88;
+ this.flow.permanent = true;
+ this.flow.timeout = 0;
+ //this.flow.inPort = 2;
+ //this.flow.outPort = 2;
+ this.flow.spacing = this.spacings[1];
+ //this.flow.multiplier = 0;
+ this.flow.includeAttenuation = true;
+ this.flow.attenuation = 0;
+
+ var parent = this;
+
+ function createFlowCb(data) {
+ if (!data.inPort.valid) {
+ parent.inPortMessage = data.inPort.message;
+ parent.inPortError = true;
+ }
+ if (!data.outPort.valid) {
+ parent.outPortMessage = data.outPort.message;
+ parent.outPortError = true;
+ }
+ if (!data.connection.valid) {
+ parent.connectionMessage = data.connection.message;
+ parent.connectionError = true;
+ }
+ if (!data.spacing.valid) {
+ parent.spacingMessage = data.spacing.message;
+ parent.spacingError = true;
+ }
+ if (!data.multiplier.valid) {
+ parent.multiplierMessage = data.multiplier.message;
+ parent.multiplierError = true;
+ }
+ if (!data.channelAvailable.valid) {
+ parent.channelMessage = data.channelAvailable.message;
+ parent.channelError = true;
+ }
+ if (data.includeAttenuation && !data.attenuation.valid) {
+ parent.attenuationMessage = data.attenuation.message;
+ parent.attenuationError = true;
+ }
+ $timeout(function () {
+ $scope.$apply()
+ });
+ }
+
+ var handlers = {}
+ handlers[CREATE_FLOW_RESP] = createFlowCb;
+ wss.bindHandlers(handlers);
+
+ this.createFlow = function(connection) {
+ this.clearErrors();
+
+ var error = false;
+ if (!isInteger(connection.priority)) {
+ this.priorityMessage = notIntegerError;
+ this.priorityError = true;
+ error = true;
+ }
+ if (!connection.permanent && !isInteger(connection.timeout)) {
+ this.timeoutMessage = notIntegerError;
+ this.timeoutError = true;
+ error = true;
+ }
+ if (!isInteger(connection.inPort)) {
+ this.inPortMessage = notIntegerError;
+ this.inPortError = true;
+ error = true;
+ }
+ if (!isInteger(connection.outPort)) {
+ this.outPortMessage = notIntegerError;
+ this.outPortError = true;
+ error = true;
+ }
+ if (!isInteger(connection.multiplier)) {
+ this.multiplierMessage = notIntegerError;
+ this.multiplierError = true;
+ error = true;
+ }
+ if (connection.includeAttenuation && !isInteger(connection.attenuation)) {
+ this.attenuationMessage = notIntegerError;
+ this.attenuationError = true;
+ error = true;
+ }
+
+ if (!error) {
+ wss.sendEvent(CREATE_FLOW_REQ,
+ {
+ devId: $scope.devId,
+ formData: connection
+ });
+ $log.log('Request to create connection has been sent');
+ }
+ }
+
+ $scope.$on('$destroy', function () {
+ wss.unbindHandlers(handlers);
+ });
+ });
+
+}());
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;
+ }
+ }
+ };
+ }]);
+}());
diff --git a/apps/roadm/src/main/resources/webgui/css.html b/apps/roadm/src/main/resources/webgui/css.html
new file mode 100644
index 0000000..4db2eeb
--- /dev/null
+++ b/apps/roadm/src/main/resources/webgui/css.html
@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="app/view/roadmDevice/roadmDevice.css">
+<link rel="stylesheet" href="app/view/roadmPort/roadmPort.css">
+<link rel="stylesheet" href="app/view/roadmFlow/roadmFlow.css">
diff --git a/apps/roadm/src/main/resources/webgui/js.html b/apps/roadm/src/main/resources/webgui/js.html
new file mode 100644
index 0000000..b3e37df
--- /dev/null
+++ b/apps/roadm/src/main/resources/webgui/js.html
@@ -0,0 +1,3 @@
+<script src="app/view/roadmDevice/roadmDevice.js"></script>
+<script src="app/view/roadmPort/roadmPort.js"></script>
+<script src="app/view/roadmFlow/roadmFlow.js"></script>