diff options
-rw-r--r-- | demo-ui/README.md | 149 | ||||
-rw-r--r-- | demo-ui/app/config.json | 32 | ||||
-rw-r--r-- | demo-ui/app/controllers/CosController.js | 381 | ||||
-rw-r--r-- | demo-ui/app/controllers/EplController.js | 528 | ||||
-rw-r--r-- | demo-ui/app/controllers/MainController.js | 31 | ||||
-rw-r--r-- | demo-ui/app/controllers/MefController.js | 117 | ||||
-rw-r--r-- | demo-ui/app/services/cosServices.js | 64 | ||||
-rw-r--r-- | demo-ui/app/services/dbg.js | 54 | ||||
-rw-r--r-- | demo-ui/app/services/eplServices.js | 64 | ||||
-rw-r--r-- | demo-ui/app/services/mefServices.js | 71 | ||||
-rw-r--r-- | demo-ui/app/services/model.js | 61 | ||||
-rw-r--r-- | demo-ui/app/views/cos-panel.html | 171 | ||||
-rw-r--r-- | demo-ui/app/views/css/vcpe.css | 340 | ||||
-rw-r--r-- | demo-ui/app/views/epl-panel.html | 187 | ||||
-rw-r--r-- | demo-ui/app/views/less/vcpe.less | 365 | ||||
-rw-r--r-- | demo-ui/app/views/mef-panel.html | 370 | ||||
-rw-r--r-- | demo-ui/app/views/vcpe-portal.html | 71 | ||||
-rw-r--r-- | demo-ui/bower.json | 11 | ||||
-rw-r--r-- | demo-ui/gruntfile.js | 25 | ||||
-rw-r--r-- | demo-ui/package.json | 18 | ||||
-rw-r--r-- | demo-ui/vcpeUiServer.js | 19 |
21 files changed, 3129 insertions, 0 deletions
diff --git a/demo-ui/README.md b/demo-ui/README.md new file mode 100644 index 0000000..5382eef --- /dev/null +++ b/demo-ui/README.md @@ -0,0 +1,149 @@ +=================================== +Virtual Business CPE Demo UI Portal +=================================== +* This portal communicates with the vcpe service manager defined in vcpe-services repo (cosmgr, evcmgr, svcmgr) +* It enbles the creation of Service Levels (Cos) and Eth Private Lines (EPL) +* As part of the EPL creation, EVCs and UNIs are also created +* Currently the code is housed in private VCPE gerrit repo: + http://euca-10-5-5-106.cirrus.cloud.cablelabs.com:8080/#/admin/projects/vcpe-ui +* All development and testing have been on OSX +* The portal currently must be run within Chrome + +Environment Requirements: +========================= +* node.js / npm +* bower +* grunt +* less + +If you need help with environement set-up instrucitons, see "Environment Set Up" notes at the bottom of this document + +Building the runtime environement: +================================== +* Once your environemnt set up (per Environment Set Up instructions at the bottom of this document) + + $ git clone http://yourusername@euca-10-5-5-106.cirrus.cloud.cablelabs.com:8080/vcpe-ui + $ cd vcpe-ui + $ npm install + - reads package.json to install dependencies for the vcpeUiServer.js node app + - dependencies are installed in ./node_modules directory + $ bower install + - reads bower.json to install dependencies for the webapp (angular,etc) + - dependencies are installed in ./bower_components directory + $ grunt + - reads gruntfile.js and carries out specified "build" steps + - in our case, we compile the .less file to .css + +Starting the web app +=================== +* Start the vcpe server managers (so the app has someting to talk to) + - see README.md in vcpe-services repo +* The webapp can be served by any web server. vcpeUiServer.js is a nodejs app that can be used as an easy way to serve the webapp. +* Start the server (within the vcpe-ui directory) + + $ node vcpeUiServer.js [portnum] & + + NOTES: + - portnum is optional, and if not provided the app will be served on port 4000 + - An issue that you may run into when you start the server is a port conflict if the server is already running from a previous session (Error: listen EADDRINUSE). To solve the issue run 'pkill node', and then start the server. + +* Bring up the web app + - To render properly, Chrome must be used (safari has an html flexbox issue, to be fixed in the future) + - In the chrome address bar enter the following + + http://localhost:4000/app/views/vcpe-portal.html + +Using the web app +=================== +* Config the app via the vcpe-ui/app/config.json file. This allows configuration of: + - Communications with the service managers (cosmgr, evcmgr, eplmgr) + - UNI's to be used for Eth Private Line creation +* You will need to start the VCPE services (cosmgr, evcmgr, eplmgr) in order for the web app to run. Please refer to vcpe-services/README.md for instrutcioons on buiding and running these services +* In order for the web app to work, either the vcpe ODL plugin, or the ODL emulator must be running, to handle ODL rest call sent by the UI. + - To run the ODL Uni Manager Emulator, please see the section "Running the ODL Uni Manager Emulator" section the vcpe-services/README.md + - To run the ODL plugin please see the vcpe-odl repsoitory +* Start the app: http://localhost:4000/app/views/vcpe-portal.html +* Two panels will be visible + - Service Levels (CoS in MEF Speak) + - Ethernet Private Lines +* w/in each panel you can CRUD Service Levels (CoS) and Eth Private Lines (EPL) + - creation (+), modification (/) and delete (-) +* Notes + - Service levels must be created before creating Eth Private Lines + - Eth private lines present drop down boxes with info as configured in config.json and created service levels + +Debugging the Web App +===================== +* The webapp provides farily extensive logging of events and issues +* To see the logging and reported errors + - within the chrome menu: view/developer/developer tools + +To Look Behind the Scenes +========================= +* The web portal sends REST messages to CoS MGR, evc MGR, and epl MGR +* Using postman (or similar tool), you can easily see a list of MEF entities that have been created w/in each of the service layers: + - NOTES: + header: Accept application/json + body: no body required + - To get list of created MEF entities (no request body required) + cos: GET http://localhost:9090/cosmgr/webapi/cos/list + evc: GET http://localhost:9090/evcmgr/webapi/evc/list + epl: GET http://localhost:9090/svcmgr/webapi/svc/epl/list + +================== +Environment Set Up +================== + +Install node/npm +================ +**OSX:** +* Download and execute the Nodejs Mac OS X Installer (.pkg) from + https://nodejs.org/download/ + +**Windows/Cygwin:** +TBD + +**Linux:** +TBD + +Install Bower +============= +**OSX:** + * Bower is a web front end dependency mangament tool + * Once you have installed nodejs/npm, install bower as follows + + $ sudo npm install -g bower + +**Windows/Cygwin:** +TBD + +**Linux:** +TBD + +Install Grunt Client +==================== +**OSX:** + * Grunt is a web front end build utility + * Once you have installed nodejs/npm, install grunt client as follows + + $ sudo npm install -g grunt-cli + +**Windows/Cygwin:** +TBD + +**Linux:** +TBD + +Install Less +==================== +**OSX:** + * Less is a css enhancment environement + * Once you have installed nodejs/npm, install less as follows + + $ sudo npm install -g less + +**Windows/Cygwin:** +TBD + +**Linux:** +TBD diff --git a/demo-ui/app/config.json b/demo-ui/app/config.json new file mode 100644 index 0000000..56c74c6 --- /dev/null +++ b/demo-ui/app/config.json @@ -0,0 +1,32 @@ +{ + "cosMgr" : { "ip": "localhost", "port": "9090" }, + "evcMgr" : { "ip": "localhost", "port": "9090" }, + "eplMgr" : { "ip": "localhost", "port": "9090" }, + "evcMgr" : { "ip": "localhost", "port": "9090" }, + "uniMgr" : { "ip": "localhost", "port": "8181" }, + "evcPathMgr": { "ip": "localhost", "port": "8181" }, + + + "uniList" : [ + { + "ip" : "10.1.1.11", + "mac" : "00:11:11:11:11:11", + "address" : "Denver" + }, + { + "ip" : "10.1.1.12", + "mac" : "00:22:22:22:22:22", + "address" : "Paris" + }, + { + "ip" : "10.1.1.13", + "mac" : "00:33:33:33:33:33", + "address" : "Tokyo" + }, + { + "ip" : "10.1.1.14", + "mac" : "00:44:44:44:44:44", + "address" : "Quebec" + } + ] +}
\ No newline at end of file diff --git a/demo-ui/app/controllers/CosController.js b/demo-ui/app/controllers/CosController.js new file mode 100644 index 0000000..523f3c1 --- /dev/null +++ b/demo-ui/app/controllers/CosController.js @@ -0,0 +1,381 @@ +(function() { + +var CosController = function($scope, $http, $interval, $log, cosServices, model, dbg ) { + + // + // Controller Set Up + // + + dbg.p("---> in CosController()"); + + actionState = { + IDLE : 1, ADD : 2, UPDATE : 3, DEL : 4 + } + + // $scope variables + $scope.cosList = null; + $scope.selectedCosIdx = -1; + $scope.cosToEdit = null; + $scope.cosActionButtonText = "unset"; + + $scope.availableBWs = [ + { + name : "10M", + cir : 10000 + }, + { + name : "100M", + cir : 100000 + }, + { + name : "1G", + cir : 1000000 + }, + { + name : "10G", + cir : 10000000 + }, + ]; + + dbg.p("bandwidths avaialble:") + dbg.pj($scope.availableBWs); + + // congroller variables + var cosActionState = actionState.IDLE; + var newCos = null; + var uniqueId = true; + var nameNum = 1; + + + // cos constructor + var Cos = function(){ + if ( uniqueId ) + this.id = "new"+ nameNum; + else + this.id = "new"; + this.commitedInfoRate = 50; + this.availbility = 95.5; + this.frameDelay = 2.2; + this.jitter = 3.3; + this.frameLoss = 4.4 ; + } + + // + // utility fxns + // + + var validCosIdx = function(idx) { + return ( idx >= 0 && idx < $scope.cosList.length ) + } + + var cosIdxReset = function() { + return ( $scope.cosList.length > 0 ? 0 : -1); + } + + var cosIdxAfter = function( action ) { + switch ( action ) { + case actionState.DEL: + if ( validCosIdx($scope.selectedCosIdx) ) + return $scope.selectedCosIdx; + else if ( validCosIdx($scope.selectedCosIdx-1) ) + return $scope.selectedCosIdx-1; + else + return cosIdxReset(); + break; + case actionState.ADD: + return ( $scope.cosList.length >= 0 ? + $scope.cosList.length-1 : cosIdxReset() ); + break; + case actionState.UPDATE: + return ( validCosIdx($scope.selectedCosIdx) ? + $scope.selectedCosIdx : cosIdxReset() ); + break; + default: + dbg.e("invalid action state"); + break; + } + } + + $scope.cosExists = function(cosId) { + var nameCheck = function (cosElm) { return cosElm.id == cosId; } + return ( $scope.cosList && + $scope.cosList.filter(nameCheck).length > 0 ); + } + + $scope.bwText = function(cos) { + if (cos) { + switch ( cos.commitedInfoRate ) { + case 10000 : return "10M"; + case 100000 : return "100M"; + case 1000000 : return "1G"; + case 10000000 : return "10G"; + } + return "invalid BW"; + } + else { + return "null cos"; + } + } + + // return index of avilable BW based cir (-1 if not found) + var availableBwIdx = function (cir) { + for ( var i = 0; i < $scope.availableBWs.length; i++ ) { + if ($scope.availableBWs[i].cir == cir) { + return i; + } + } + return -1; + } + + // return index of cos in current list (-1 if not found) + var cosNameToIdx = function ( cosName ) { + if ( !cosName || !$scope.cosList ) + return -1; + + for ( var i = 0; i < $scope.cosList.length; i++ ) { + if ($scope.cosList[i].id == cosName ) { + return i; + } + } + return -1; + } + + // + // HTTP Repsponse Handlers + // + + var onRequestError = function(reason){ + dbg.e("HTTP Request Problem\n" + JSON.stringify(reason)); + }; + var onCosListResp = function(data) { + dbg.p("in onCosListResp()"); + $scope.cosList = data; + + if ( $scope.cosList && $scope.cosList.length > 0 ) + $scope.selectedCosIdx = 0; + else + $scope.selectedCosIdx = -1; + + dbg.p("selectedCosIdx = " + $scope.selectedCosIdx,1); + }; + var onCosCreateResp = function(data) { + dbg.p("in onCosCreateResp()"); + dbg.p("following cos returned from create cmd", 1); + dbg.pj(data); + $scope.cosList.push(newCos); + $scope.selectedCosIdx = cosIdxAfter(actionState.ADD); + nameNum++; + newCos = null; + $scope.cosToEdit = null; + dbg.p("selectedCosIdx = " + $scope.selectedCosIdx,1); + }; + var onCosUpdateResp = function(data) { + dbg.p("in onCosUpdateResp()"); + dbg.p("following cos returned after update", 1); + dbg.pj(data); + $scope.selectedCosIdx = cosIdxAfter(actionState.UPDATE); + $scope.cosToEdit = null; + dbg.p("selectedCosIdx = " + $scope.selectedCosIdx,1); + }; + var onCosDeleteResp = function(data) { + dbg.p("in onCosDeleteResp()"); + dbg.p("following returned after delete", 1); + dbg.pj(data); + if ( validCosIdx($scope.selectedCosIdx) ) + $scope.cosList.splice($scope.selectedCosIdx,1); + $scope.selectedCosIdx = cosIdxAfter(actionState.DEL); + dbg.p("selectedCosIdx = " + $scope.selectedCosIdx,1); + }; + + // + // CoS Event Handlers & Utils + // + + $scope.onCosClick = function (i) { + dbg.p("in onCosClick(): clicked ("+i+")"); + $scope.selectedCosIdx = i; + cosActionState = actionState.IDLE; + dbg.p("selectedCosIdx = "+ $scope.selectedCosIdx,1); + } + $scope.onAddCos = function () { + dbg.p("in onAddCos()"); + cosActionState = actionState.ADD; + $scope.cosActionButtonText = "Create"; + newCos = new Cos(); + $scope.cosToEdit = newCos; + + if ($scope.availableBWs.length >=3 ) + $scope.cosToEdit.bwSelected = $scope.availableBWs[2]; + else + $scope.cosToEdit.bwSelected = $scope.availableBWs[0]; + + dbg.pj(newCos); + } + $scope.onUpdateCos = function () { + dbg.p("in onUpdateCos()"); + if ( $scope.cosList.length <= 0 ) + { + dbg.p("nos CoS's exist, no action being taken"); + return; + } + cosActionState = actionState.UPDATE; + $scope.cosActionButtonText = "Update"; + $scope.cosToEdit = $scope.cosList[$scope.selectedCosIdx]; + + dbg.p("looking for dd list bw: " + $scope.cosToEdit.commitedInfoRate); + var availBwIdx = availableBwIdx($scope.cosToEdit.commitedInfoRate); + if ( availBwIdx < 0 || availBwIdx > $scope.availableBWs.length-1 ) { + dbg.e("invalid selected BW idx: "+availBwIdx+"(setting to 0)"); + $scope.cosToEdit.bwSelected = $scope.availableBWs[0]; + } + else { + $scope.cosToEdit.bwSelected = $scope.availableBWs[availBwIdx]; + } + + dbg.p("about to edit cos:"); + dbg.pj($scope.cosToEdit); + + + } + $scope.onDelCos = function () { + dbg.p("in onDelCos()"); + if ( $scope.cosList.length <= 0 ) + { + dbg.p("nos CoS's exist, no action being taken"); + return; + } + cosActionState = actionState.DEL; + $scope.cosActionButtonText = "Delete"; + } + + $scope.onCosInputSubmit = function () { + dbg.p("in onCosInputSubmit()"); + + var cosToOperateOn = null; + if ( cosActionState === actionState.ADD) + cosToOperateOn = newCos; + else + cosToOperateOn = $scope.cosList[$scope.selectedCosIdx]; + + switch(cosActionState) { + + case actionState.DEL: + dbg.p("about to delete " + cosToOperateOn.id,1); + cosServices.deleteCos(cosToOperateOn) + .then(onCosDeleteResp, onRequestError); + break; + + case actionState.ADD: + cosToOperateOn.commitedInfoRate = cosToOperateOn.bwSelected.cir; + delete cosToOperateOn.bwSelected; + dbg.p("about to add " + cosToOperateOn.id, 1); + cosServices.createCos(cosToOperateOn) + .then(onCosCreateResp, onRequestError); + break; + + case actionState.UPDATE: + cosToOperateOn.commitedInfoRate = cosToOperateOn.bwSelected.cir; + delete cosToOperateOn.bwSelected; + dbg.p("about to update " + cosToOperateOn.id, 1); + cosServices.updateCos(cosToOperateOn) + .then(onCosUpdateResp, onRequestError); + break; + + default: + dbg.e("invalid action state"); + break; + } + + cosActionState = actionState.IDLE; + } + + // + // State query fxns + // + + $scope.cosActionInProgress = function () { + return ( cosActionState != actionState.IDLE ) + } + $scope.cosEditInProgress = function () { + return ( cosActionState === actionState.ADD || + cosActionState === actionState.UPDATE ); + } + $scope.cosUpdateInProgress = function () { + return ( cosActionState === actionState.UPDATE ); + } + $scope.cosAddInProgress = function () { + return ( cosActionState === actionState.ADD ); + } + $scope.cosDelInProgress = function () { + return ( cosActionState === actionState.DEL ); + } + $scope.cosNameConflict = function () { + return ( $scope.cosAddInProgress() && + $scope.cosExists($scope.cosToEdit.id)); + } + $scope.showCosInputs = function () { + return $scope.cosEditInProgress(); + } + $scope.showCosValues = function () { + return (!$scope.showCosInputs() && + validCosIdx($scope.selectedCosIdx)); + } + $scope.showCosNameInput = function () { + return $scope.cosAddInProgress(); + } + $scope.showCosName = function () { + return (! $scope.showCosNameInput()); + } + $scope.showCosActionButton = function () { + return ( $scope.cosAddInProgress() || + ($scope.cosActionInProgress() && + $scope.cosList.length > 0 )); + } + $scope.activateCosActionButton = function () { + return ( $scope.cosActionInProgress() && + !$scope.cosNameConflict() ) + } + + // + // Watchers + // + + // we can't get our initial CoS list until + // the URL has been set by config file read in + cosUrlWatcher = function () { return cosServices.getCosUrl(); } + onCosUrlChange = function (newCosUrl, oldCosUrl) { + if (newCosUrl !== oldCosUrl) { + dbg.p("detected cos url change, getting cos list"); + cosServices.getCosList() + .then(onCosListResp, onRequestError); + } + } + $scope.$watch( cosUrlWatcher, onCosUrlChange ); + + // Watch for change in selected EPL so tht + // we can set the appropraite CoS as selected + eplWatcher = function () { return model.getCurrentEpl(); } + onEplChange = function (newEpl, oldEpl) { + dbg.p("in CosController:onEplChange"); + //dbg.p("oldEpl:"); dbg.pj(oldEpl); + //dbg.p("newEpl:"); dbg.pj(newEpl); + + // only make a chance if we have an + if ( newEpl ) { + var cosIdx = cosNameToIdx(newEpl.cos); + dbg.p("changing selected CoS to: " + newEpl.cos + + " [idx="+cosIdx+"]", 1); + if ( validCosIdx(cosIdx) ) { + cosActionState = actionState.IDLE + $scope.selectedCosIdx = cosIdx; + } + } + } + $scope.$watch( eplWatcher, onEplChange ); + +}; + +// register controller in the module +app.controller("CosController", CosController); + +}());
\ No newline at end of file diff --git a/demo-ui/app/controllers/EplController.js b/demo-ui/app/controllers/EplController.js new file mode 100644 index 0000000..8dbdc90 --- /dev/null +++ b/demo-ui/app/controllers/EplController.js @@ -0,0 +1,528 @@ +(function() { + +var EplController = function($scope, $http, $interval, $log, + eplServices, cosServices, model, dbg ) { + + // + // Controller Set Up + // + // + + dbg.p("---> in EplController()"); + + actionState = { + IDLE : 1, ADD : 2, UPDATE : 3, DEL : 4 + }; + + // $scope variables + $scope.eplList = []; + $scope.selectedEplIdx = -1; + $scope.eplToEdit = null; + $scope.eplActionButtonText = "unset"; + $scope.availableUnis = []; + $scope.availableCosIds = []; + + // for trouble shooting + $scope.availableCosIds.push("unset cos 0"); + $scope.availableCosIds.push("unset cos 1"); + $scope.availableCosIds.push("unset cos 2"); + + // controller variables + var _uniqueId = true; + var _nameNum = 1; + var _eplActionState = actionState.IDLE; + var _newEpl = null; + + // start out with null until we do list query) + model.setCurrentEpl(null); + + // + // utility fxns + // + + var validEplIdx = function(idx) { + return ( idx >= 0 && idx < $scope.eplList.length ) + } + var eplIdxReset = function() { + return ( $scope.eplList.length > 0 ? 0 : -1); + } + var cosIdxReset = function() { + return ( $scope.eplList.length > 0 ? 0 : -1); + } + var eplIdxAfter = function( action ) { + switch ( action ) { + case actionState.DEL: + if ( validEplIdx($scope.selectedEplIdx) ) + return $scope.selectedEplIdx; + else if ( validEplIdx($scope.selectedEplIdx-1) ) + return $scope.selectedEplIdx-1; + else + return eplIdxReset(); + break; + case actionState.ADD: + return ( $scope.eplList.length >= 0 ? + $scope.eplList.length-1 : eplIdxReset() ); + break; + case actionState.UPDATE: + return ( validEplIdx($scope.selectedEplIdx) ? + $scope.selectedEplIdx : eplIdxReset() ); + break; + default: + dbg.e("invalid action state"); + return -1; + break; + } + } + $scope.eplExists = function(eplId) { + var nameCheck = function (eplElm) { return eplElm.id == eplId; } + return ( $scope.eplList && + $scope.eplList.filter(nameCheck).length > 0 ); + } + var setEplUniInfo = function(epl, uniIdx1, uniIdx2) { + + if ( $scope.availableUnis.length < 2 ) { + dbg.e("Can't set unis in constructor, avaulable uni list too short") + return; + } + + epl.numCustLocations = 2; + + if ( uniIdx1 < 0 || uniIdx1 > 1) { + epl.uniHostIpList.push($scope.availableUnis[0].ip); + epl.uniHostMacList.push($scope.availableUnis[0].mac); + epl.custAddressList.push($scope.availableUnis[0].address); + } + else { + epl.uniHostIpList.push($scope.availableUnis[uniIdx1].ip); + epl.uniHostMacList.push($scope.availableUnis[uniIdx1].mac); + epl.custAddressList.push($scope.availableUnis[uniIdx1].address); + } + + if ( uniIdx2 < 0 || uniIdx2 > 1) { + epl.uniHostIpList.push($scope.availableUnis[0].ip); + epl.uniHostMacList.push($scope.availableUnis[0].mac); + epl.custAddressList.push($scope.availableUnis[0].address); + } + else { + epl.uniHostIpList.push($scope.availableUnis[uniIdx2].ip); + epl.uniHostMacList.push($scope.availableUnis[uniIdx2].mac); + epl.custAddressList.push($scope.availableUnis[uniIdx2].address); + } + } + + // return index of avilable Uni based on IP (-1 if not found) + var availableUniIdx = function (uniIp) { + for ( var i = 0; i < $scope.availableUnis.length; i++ ) { + if ($scope.availableUnis[i].ip == uniIp) { + return i; + } + } + return -1; + } + + var updateEplWithSelectedUnis = function(epl) { + + if ( epl == null || $scope.eplToEdit == null ) { + dbg.e("can't update EPL with UNIS, missing object"); + return + } + + epl.uniHostIpList = []; + epl.uniHostIpList.push($scope.eplToEdit.uni1Selected.ip); + epl.uniHostIpList.push($scope.eplToEdit.uni2Selected.ip); + + epl.uniHostMacList = []; + epl.uniHostMacList.push($scope.eplToEdit.uni1Selected.mac); + epl.uniHostMacList.push($scope.eplToEdit.uni2Selected.mac); + + epl.custAddressList = []; + epl.custAddressList.push($scope.eplToEdit.uni1Selected.address); + epl.custAddressList.push($scope.eplToEdit.uni2Selected.address); + } + + // + // epl constructor + // + + var Epl = function(){ + dbg.pj("constructing EPL"); + if ( _uniqueId ) + this.id = "new"+ _nameNum; + else + this.id = "new"; + + // Don't set COS here, we have to wait until we get the latest cos list + this.cos = "unset"; + + // EVC ID set by lower layers + // TODO : how does evc ID cat capture at EPL layer?? + this.evcId = "unset"; + + this.uniHostIpList = []; + this.uniHostMacList = []; + this.custAddressList = []; + + if ( $scope.availableUnis.length >= 2 ) + setEplUniInfo(this, 0, 1); + else { + dbg.e("can't construct EPL, not enough uni's in the available uni list"); + return; + } + + dbg.pj(this); + } + + var copyReturnedEplParams = function(srcEpl, destEpl ) { + destEpl.evcId = srcEpl.evcId; + } + + var setModalActiveEpl =function () { + if ( $scope.selectedEplIdx >= 0 && + $scope.selectedEplIdx < $scope.eplList.length ) + { + model.setCurrentEpl($scope.eplList[$scope.selectedEplIdx]); + } + else + { + model.setCurrentEpl(null); + } + } + + // + // EPL HTTP Response Handlers + // + + var onRequestError = function(reason){ + dbg.e("HTTP Request Problem\n" + JSON.stringify(reason)); + }; + + var onEplListResp = function(data) { + dbg.p("in onEplListResp()"); + $scope.eplList = data; + + if ( $scope.eplList && $scope.eplList.length > 0 ) { + $scope.selectedEplIdx = 0; + model.setCurrentEpl($scope.eplList[$scope.selectedEplIdx]); + dbg.p("selectedEplIdx/Id = "+ $scope.selectedEplIdx + "/" + + $scope.eplList[$scope.selectedEplIdx].id, 1); + } + else { + $scope.selectedEplIdx = -1; + model.setCurrentEpl(null); + dbg.p("selectedEplIdx = "+ $scope.selectedEplIdx, 1); + } + _eplActionState = actionState.IDLE; + }; + var onEplCreateResp = function(returnedEpl) { + dbg.p("in onEplCreateResp()"); + dbg.p("following epl returned from create cmd", 1); + dbg.pj(returnedEpl); + + // grab entire returned Epl, since we don't know what + // the svcmgr may have changed on creation + $scope.eplList.push(returnedEpl); + + // a safer alternative? + //copyReturnedEplParams(returnedEpl, _newEpl ); + //$scope.eplList.push(_newEpl); + + $scope.selectedEplIdx = eplIdxAfter(actionState.ADD); + setModalActiveEpl(); + + _nameNum++; + _newEpl = null; + $scope.eplToEdit = null; + dbg.p("selectedEplIdx = " + $scope.selectedEplIdx,1); + }; + var onEplUpdateResp = function(returnedEpl) { + dbg.p("in onEplUpdateResp()"); + dbg.p("following epl returned after update", 1); + dbg.pj(returnedEpl); + + // splice in entire returned Epl, since we don't know what + // the svcmgr may have changed on update (esp evc) + $scope.eplList[$scope.selectedEplIdx] = returnedEpl; + + // a safer alternative? + // copyReturnedEplParams(returnedEpl, eplToEdit ); + + + + $scope.selectedEplIdx = eplIdxAfter(actionState.UPDATE); + + // To force a refresh + model.setCurrentEpl(null); + setModalActiveEpl(); + + $scope.eplToEdit = null; + dbg.p("selectedEplIdx = " + $scope.selectedEplIdx,1); + }; + var onEplDeleteResp = function(data) { + dbg.p("in onEplDeleteResp()"); + dbg.p("following returned after delete", 1); + dbg.pj(data); + if ( validEplIdx($scope.selectedEplIdx) ) + $scope.eplList.splice($scope.selectedEplIdx,1); + $scope.selectedEplIdx = eplIdxAfter(actionState.DEL); + setModalActiveEpl(); + dbg.p("selectedEplIdx = " + $scope.selectedEplIdx,1); + }; + + // + // Epl Event Handlers + // + + $scope.onEplClick = function (i) { + dbg.p("in onEplClick(): clicked ("+i+")"); + $scope.selectedEplIdx = i; + _eplActionState = actionState.IDLE; + eplToOperateOn = $scope.eplList[$scope.selectedEplIdx] + model.setCurrentEpl(eplToOperateOn); + dbg.p("selectedEplIdx/Id = "+ $scope.selectedEplIdx + "/" + + eplToOperateOn.id, 1); + } + $scope.onAddEpl = function () { + dbg.p("in onAddEpl()"); + _eplActionState = actionState.ADD; + $scope.eplActionButtonText = "Create"; + + var updateEplToAddOnCosListResponse = function(data) { + dbg.p("in updateNewEplCosOnCosListResponse()", 1); + var cosList = data; + + $scope.availableCosIds = []; + for ( var i = 0; i < cosList.length; i++ ) + $scope.availableCosIds.push(cosList[i].id); + + dbg.p("Avilable CoS List"); + dbg.pj($scope.availableCosIds); + + // defaults for drop down lists + if ( $scope.availableCosIds.length > 0 ) + $scope.eplToEdit.cos = $scope.availableCosIds[0]; + + $scope.eplToEdit.uni1Selected = $scope.availableUnis[0]; + $scope.eplToEdit.uni2Selected = $scope.availableUnis[1]; + + dbg.p("Newly created EPL to add after CoS list retrieved"); + dbg.pj($scope.eplToEdit); + }; + + _newEpl = new Epl(); + $scope.eplToEdit = _newEpl; + + // need to get the current cos list before adding new EPL + cosServices.getCosList() + .then(updateEplToAddOnCosListResponse, + onRequestError); + } + $scope.onUpdateEpl = function () { + dbg.p("in onUpdateEpl()"); + if ( $scope.eplList.length <= 0 ) + { + dbg.p("nos Epl's exist, no action being taken"); + return; + } + _eplActionState = actionState.UPDATE; + $scope.eplActionButtonText = "Update"; + + var updateEplToEditOnCosListResponse = function(data) { + dbg.p("in onCosListResp()"); + var cosList = data; + $scope.availableCosIds = []; + for ( var i = 0; i < cosList.length; i++ ) + $scope.availableCosIds.push(cosList[i].id); + dbg.p("Avilable CoS List"); + dbg.pj($scope.availableCosIds); + + $scope.eplToEdit = $scope.eplList[$scope.selectedEplIdx]; + + // TODO (if time): put below stuff in fxn since duplicated twice + dbg.p("looking for dd list ip: " + $scope.eplToEdit.uniHostIpList[0] ); + var availUniIdx = availableUniIdx($scope.eplToEdit.uniHostIpList[0]); + if ( availUniIdx < 0 || availUniIdx > $scope.availableUnis.length-1 ) { + dbg.e("invalid selected uni-1 idx: "+availUniIdx+"(setting to 0)"); + $scope.eplToEdit.uni1Selected = $scope.availableUnis[0]; + } + else { + $scope.eplToEdit.uni1Selected = $scope.availableUnis[availUniIdx]; + } + + dbg.p("looking for dd list ip: " + $scope.eplToEdit.uniHostIpList[1] ); + availUniIdx = availableUniIdx($scope.eplToEdit.uniHostIpList[1]); + if ( availUniIdx < 0 || availUniIdx > $scope.availableUnis.length-1 ) { + dbg.e("invalid selected uni-2 idx: "+availUniIdx+"(setting to 0)"); + $scope.eplToEdit.uni2Selected = $scope.availableUnis[0] + } + else { + $scope.eplToEdit.uni2Selected = $scope.availableUnis[availUniIdx]; + } + + dbg.p("about to edit epl:"); + dbg.pj($scope.eplToEdit); + }; + + // need to get the current cos list before editing + cosServices.getCosList() + .then(updateEplToEditOnCosListResponse, + onRequestError); + } + + $scope.onDelEpl = function () { + dbg.p("in onDelEpl()"); + if ( $scope.eplList.length <= 0 ) + { + dbg.p("nos Epl's exist, no action being taken"); + return; + } + _eplActionState = actionState.DEL; + $scope.eplActionButtonText = "Delete"; + } + + $scope.onEplInputSubmit = function () { + dbg.p("in onEplInputSubmit()"); + + var eplToOperateOn = null; + if ( _eplActionState === actionState.ADD) + eplToOperateOn = _newEpl; + else + eplToOperateOn = $scope.eplList[$scope.selectedEplIdx]; + + switch(_eplActionState) { + + case actionState.DEL: + + dbg.p("about to delete " + eplToOperateOn.id,1); + eplServices.deleteEpl(eplToOperateOn) + .then(onEplDeleteResp, onRequestError); + break; + + case actionState.ADD: + + updateEplWithSelectedUnis(eplToOperateOn); + + delete $scope.eplToEdit.uni1Selected; + delete $scope.eplToEdit.uni2Selected; + + dbg.p("about to add " + eplToOperateOn.id, 1); + dbg.pj(eplToOperateOn); + eplServices.createEpl(eplToOperateOn) + .then(onEplCreateResp, onRequestError); + break; + + case actionState.UPDATE: + + updateEplWithSelectedUnis(eplToOperateOn); + + delete $scope.eplToEdit.uni1Selected; + delete $scope.eplToEdit.uni2Selected; + + dbg.p("about to update " + eplToOperateOn.id, 1); + eplServices.updateEpl(eplToOperateOn) + .then(onEplUpdateResp, onRequestError); + break; + + default: + dbg.e("invalid action state"); + break; + } + + _eplActionState = actionState.IDLE; + } + + // + // State query fxns + // + + $scope.eplActionInProgress = function () { + return ( _eplActionState != actionState.IDLE ) + } + $scope.eplEditInProgress = function () { + return ( _eplActionState === actionState.ADD || + _eplActionState === actionState.UPDATE ); + } + $scope.eplUpdateInProgress = function () { + return ( _eplActionState === actionState.UPDATE ); + } + $scope.eplAddInProgress = function () { + return ( _eplActionState === actionState.ADD ); + } + $scope.eplDelInProgress = function () { + return ( _eplActionState === actionState.DEL ); + } + $scope.cosExists = function () { + return ( $scope.availableCosIds && + $scope.availableCosIds.length > 0 ); + } + $scope.cosConflict = function () { + return ( $scope.eplAddInProgress() && + !$scope.cosExists() ); + } + $scope.eplNameConflict = function () { + return ( $scope.eplAddInProgress() && + $scope.eplExists($scope.eplToEdit.id)); + } + $scope.uniConflict = function () { + return ( $scope.eplToEdit && + $scope.eplToEdit.uni1Selected && + $scope.eplToEdit.uni2Selected && + $scope.eplEditInProgress() && + ( $scope.eplToEdit.uni1Selected.ip == + $scope.eplToEdit.uni2Selected.ip )); + } + $scope.showEplInputs = function () { + return $scope.eplEditInProgress(); + } + $scope.showEplValues = function () { + return (!$scope.showEplInputs() && + validEplIdx($scope.selectedEplIdx)); + } + $scope.showEplNameInput = function () { + return $scope.eplAddInProgress(); + } + $scope.showEplName = function () { + return (! $scope.showEplNameInput()); + } + $scope.showEplActionButton = function () { + return ( $scope.eplAddInProgress() || + ($scope.eplActionInProgress() && + $scope.eplList.length > 0 )); + } + $scope.activateEplActionButton = function () { + return ( $scope.eplActionInProgress() && + !$scope.eplNameConflict() && + !$scope.uniConflict() && + !$scope.cosConflict() ) + } + + // + // Initial Page Set Up + // + + eplUrlWatcher = function () { return eplServices.getEplUrl(); } + onEplUrlChange = function (newEplUrl, oldEplUrl) { + if (newEplUrl !== oldEplUrl) { + dbg.p("detected epl url change, getting epl list"); + eplServices.getEplList() + .then(onEplListResp, onRequestError); + } + } + $scope.$watch( eplUrlWatcher, onEplUrlChange ); + + + uniListWatcher = function () { return model.getAvailableUnis(); } + onUniListChange = function (newUniList, oldUniList) { + if (newUniList !== oldUniList) { + dbg.p("detected uni list change") + dbg.p("new avilable uni list:") + $scope.availableUnis = newUniList; + dbg.pj($scope.availableUnis); + } + } + $scope.$watch( uniListWatcher, onUniListChange ); +}; + +// register controller in the module +app.controller("EplController", EplController); + +}());
\ No newline at end of file diff --git a/demo-ui/app/controllers/MainController.js b/demo-ui/app/controllers/MainController.js new file mode 100644 index 0000000..199048b --- /dev/null +++ b/demo-ui/app/controllers/MainController.js @@ -0,0 +1,31 @@ +// Create our module +var app = angular.module("vcpe", []); + +(function() { +var MainController = function($http, $log, cosServices, eplServices, mefServices, model, dbg ) { + + dbg.p("---> in MainController()"); + + // config our app + var cfgFile = "../config.json"; + $http.get(cfgFile). + success(function(data) { + dbg.p("in cfgFile read callback"); + dbg.p("incoming data"); + dbg.pj(data); + cosServices.setCosUrl(data.cosMgr); + eplServices.setEplUrl(data.eplMgr); + mefServices.setEvcUrl(data.evcMgr); + mefServices.setUniUrl(data.uniMgr); + mefServices.setEvcPathUrl(data.evcPathMgr); + model.setAvailableUnis(data.uniList); + }). + error(function() { + dbg.e("Could not read " + cfgFile); + }); +}; + +// register controller in the module +app.controller("MainController", MainController); + +}());
\ No newline at end of file diff --git a/demo-ui/app/controllers/MefController.js b/demo-ui/app/controllers/MefController.js new file mode 100644 index 0000000..9531b49 --- /dev/null +++ b/demo-ui/app/controllers/MefController.js @@ -0,0 +1,117 @@ +(function() { + +var MefController = function($scope, $log, mefServices, model, dbg ) { + + // + // Controller Set Up + // + + dbg.p("---> in MefController()"); + $scope.currentEpl = null; + $scope.currentEplEvc = null; + $scope.currentEvcUnis = []; + + // + // Utils + // + + $scope.curEplEvcToJson = function () { + return angular.toJson($scope.currentEplEvc, true); + }; + + + $scope.uniToSpeedString = function(uni) { + if (uni) { + speed = uni.uni[0].speed; + return Object.getOwnPropertyNames(speed)[0];s + } + else + return ""; + } + // + // HTTP Response Handlers + // + + var onRequestError = function(reason){ + dbg.e("HTTP Request Problem\n" + JSON.stringify(reason)); + }; + + + var onUniGetResp = function(data) { + dbg.p("in onUniGetResp()"); + dbg.pj(data); + + // dbg.p("current EVC UNI list before adding"); + //oSpeedString($scop dbg.pj($scope.currentEvcUnis); + + $scope.currentEvcUnis.push(data); + + // dbg.p("current EVC UNI list after adding"); + // dbg.pj($scope.currentEvcUnis); + } + + var onEvcGetResp = function(data) { + dbg.p("in onEvcGetResp()"); + dbg.pj(data); + $scope.currentEplEvc = data; + + // out with the old unis + $scope.currentEvcUnis = []; + + // in with the new unis + mefServices.getUni($scope.currentEplEvc.uniIdList[0]) + .then(onUniGetResp, onRequestError); + mefServices.getUni($scope.currentEplEvc.uniIdList[1]) + .then(onUniGetResp, onRequestError); + }; + + // + // State query fxns + // + + $scope.showEvcValues = function () { + return ( $scope.currentEplEvc != null ); + } + + $scope.showUniValues = function () { + return ( $scope.currentEvcUnis != null ); + } + + // + // Watch for change in selected EPL so tht we can + // update the corresponding EVC info + // + + eplWatcher = function () { return model.getCurrentEpl(); } + onEplChange = function (newEpl, oldEpl) { + dbg.p("in MefController:onEplChange"); + // dbg.p("oldEpl:"); dbg.pj(oldEpl); + // dbg.p("newEpl:"); dbg.pj(newEpl); + + // only make a chance if we have a new EPL object + if ( newEpl ) { + var newEplId = newEpl.id; + var oldEplId = "null"; + if (oldEpl) oldId = oldEpl.id + + dbg.p("detected selected EPL change from " + + oldEplId + " to " + newEplId, 1); + $scope.currentEpl = newEpl; + + // get the EVC info + mefServices.getEvc($scope.currentEpl.evcId) + .then(onEvcGetResp, onRequestError); + } + else { + $scope.currentEplEvc = null; + $scope.currentEvcUnis = null; + } + + } + $scope.$watch( eplWatcher, onEplChange ); +}; + +// register controller in the module +app.controller("MefController", MefController); + +}());
\ No newline at end of file diff --git a/demo-ui/app/services/cosServices.js b/demo-ui/app/services/cosServices.js new file mode 100644 index 0000000..c650b6d --- /dev/null +++ b/demo-ui/app/services/cosServices.js @@ -0,0 +1,64 @@ +(function(){ + + var cosServices = function ($http, dbg) { + + var _cosBasePath = "/cosmgr/webapi/cos" + var _cosUrl = "unset"; + + var setCosUrl = function (cosMgrCfg) { + dbg.p("in setCosUrl"); + _cosUrl = "http://"+cosMgrCfg.ip+":"+cosMgrCfg.port+_cosBasePath; + dbg.p(_cosUrl); + } + + var getCosUrl = function () { + dbg.p("in getCosUrl"); + return _cosUrl; + } + + var getCosList = function() { + dbg.p("in cosServices.getCosList()",2); + var url = _cosUrl+ "/list;" + dbg.p("GET: " + url , 2); + return $http.get(url) + .then(function(response){ return response.data; }); + }; + var createCos = function(cos) { + dbg.p("in cosServices.createCos()",2) + var url = _cosUrl; + dbg.p("POST: " + url, 2); + dbg.pj(cos); + return $http.post(url, cos) + .then(function(response){ return response.data; }); + }; + var updateCos = function(cos) { + dbg.p("in cosServices.updateCos()",2) + var url = _cosUrl + "/" + cos.id; + dbg.p("PUT: " + url, 2); + dbg.pj(cos); + return $http.put(url, cos) + .then(function(response){ return response.data; }); + }; + var deleteCos = function(cos) { + dbg.p("in cosServices.deleteCos()",2) + var url = _cosUrl + "/" + cos.id; + dbg.p("DELELE: " + url, 2); + dbg.pj(cos); + return $http.delete(url) + .then(function(response){ return response.data; }); + }; + + return { // public API + setCosUrl : setCosUrl, + getCosUrl : getCosUrl, + createCos : createCos, + getCosList : getCosList, + deleteCos : deleteCos, + updateCos : updateCos + }; + }; + + var module = angular.module("vcpe"); + module.factory("cosServices", cosServices); + +}());
\ No newline at end of file diff --git a/demo-ui/app/services/dbg.js b/demo-ui/app/services/dbg.js new file mode 100644 index 0000000..1e12523 --- /dev/null +++ b/demo-ui/app/services/dbg.js @@ -0,0 +1,54 @@ +(function(){ + + var dbg = function ($log) { + + var dbgFlag = true; + + var on = function() { dbgFlag = true; }; + var off = function() { dbgFlag = false; }; + + var p = function(str, tab, emphasize) { + if ( dbgFlag ) + { + var tabStr = ""; + if (tab) { + for (var i = 0; i < tab; i++ ) + tabStr += " "; + } + var preStr = "... " + tabStr; + var postStr = ""; + if ( emphasize ) { + preStr = tabStr + "####################### "; + postStr = " ####################### "; + } + $log.info(preStr + str + postStr); + } + }; + + var pj = function(obj) { + if ( dbgFlag ) + $log.info(JSON.stringify(obj,null,3)) + }; + + var e = function(eMsg) { + $log.error("!! ERROR: " + eMsg); + } + + var w = function(eMsg) { + $log.warn("!! WARNIING: " + eMsg); + } + + return { // Public API + on : on, // squelch DBG printing + off : off, // enable DBG printing + p : p, // print a debug string + pj : pj, // print JSON version of an object + w : w, // print an warning msg + e : e // print an error msg + }; + }; + + var module = angular.module("vcpe"); + module.factory("dbg", dbg); + +}());
\ No newline at end of file diff --git a/demo-ui/app/services/eplServices.js b/demo-ui/app/services/eplServices.js new file mode 100644 index 0000000..20fb7af --- /dev/null +++ b/demo-ui/app/services/eplServices.js @@ -0,0 +1,64 @@ +(function(){ + + var eplServices = function ($http, dbg) { + + var _eplBasePath = "/svcmgr/webapi/svc/epl" + var _eplUrl = "unset"; + + var setEplUrl = function (eplMgrCfg) { + dbg.p("in setEplUrl"); + _eplUrl = "http://"+eplMgrCfg.ip+":"+eplMgrCfg.port+_eplBasePath; + dbg.p(_eplUrl); + } + + var getEplUrl = function () { + dbg.p("in setEplUrl"); + return _eplUrl; + } + + var getEplList = function() { + dbg.p("in eplServices.getEplList()",2); + var url = _eplUrl+ "/list;" + dbg.p("GET: " + url , 2); + return $http.get(url) + .then(function(response){ return response.data; }); + }; + var createEpl = function(epl) { + dbg.p("in eplServices.createEpl()",2) + var url = _eplUrl; + dbg.p("POST: " + url, 2); + dbg.pj(epl); + return $http.post(url, epl) + .then(function(response){ return response.data; }); + }; + var updateEpl = function(epl) { + dbg.p("in eplServices.updateEpl()",2) + var url = _eplUrl + "/" + epl.id; + dbg.p("PUT: " + url, 2); + dbg.pj(epl); + return $http.put(url, epl) + .then(function(response){ return response.data; }); + }; + var deleteEpl = function(epl) { + dbg.p("in eplServices.deleteEpl()",2) + var url = _eplUrl + "/" + epl.id; + dbg.p("DELELE: " + url, 2); + dbg.pj(epl); + return $http.delete(url) + .then(function(response){ return response.data; }); + }; + + return { // public API + setEplUrl : setEplUrl, + getEplUrl : getEplUrl, + createEpl : createEpl, + getEplList : getEplList, + deleteEpl : deleteEpl, + updateEpl : updateEpl + }; + }; + + var module = angular.module("vcpe"); + module.factory("eplServices", eplServices); + +}());
\ No newline at end of file diff --git a/demo-ui/app/services/mefServices.js b/demo-ui/app/services/mefServices.js new file mode 100644 index 0000000..798ceb3 --- /dev/null +++ b/demo-ui/app/services/mefServices.js @@ -0,0 +1,71 @@ +(function(){ + + var mefServices = function ($http, dbg) { + + var _evcBasePath = "/evcmgr/webapi/evc" + var _evcUrl = "unset"; + + var _uniBasePath = "/restconf/operational/cl-vcpe-mef:unis/uni" + var _uniUrl = "unset"; + + var _evcPathBasePath = "/restconf/operational/cl-vcpe-mef:evcs" + var _evcPathUrl = "unset"; + + // + // Configuration services + // + + var setEvcUrl = function (evcMgrCfg) { + dbg.p("in setEvcUrl"); + _evcUrl = "http://"+evcMgrCfg.ip+":"+evcMgrCfg.port+_evcBasePath; + dbg.p(_evcUrl); + } + + var setUniUrl = function (uniMgrCfg) { + dbg.p("in setUniUrl"); + _uniUrl = "http://"+uniMgrCfg.ip+":"+uniMgrCfg.port+_uniBasePath; + dbg.p(_uniUrl); + } + + var setEvcPathUrl = function (evcPathMgrCfg) { + dbg.p("in setEvcPathUrl"); + _evcPathUrl = "http://"+evcPathMgrCfg.ip+":"+evcPathMgrCfg.port+_evcPathBasePath; + dbg.p(_evcPathUrl); + } + + // + // REST services + // + + var getEvc = function(evcId) { + dbg.p("in mefServices.getEvc()",2) + var url = _evcUrl + "/" + evcId; + dbg.p("GET: " + url, 2); + return $http.get(url) + .then(function(response){ return response.data; }); + }; + + var getUni = function(uniId) { + dbg.p("in mefServices.getUni()",2) + var url = _uniUrl + "/" + uniId; + dbg.p("GET: " + url, 2); + // return $http.get(url) + // .then(function(response){ return response.data; }); + return $http({method: 'GET', url: url, + headers: {'Authorization': 'Basic YWRtaW46YWRtaW4='}}) + .then(function(response){ return response.data; }); + }; + + return { // public API + setEvcUrl : setEvcUrl, + setUniUrl : setUniUrl, + setEvcPathUrl : setEvcPathUrl, + getEvc : getEvc, + getUni : getUni + }; + }; + + var module = angular.module("vcpe"); + module.factory("mefServices", mefServices); + +}());
\ No newline at end of file diff --git a/demo-ui/app/services/model.js b/demo-ui/app/services/model.js new file mode 100644 index 0000000..c41794b --- /dev/null +++ b/demo-ui/app/services/model.js @@ -0,0 +1,61 @@ +// +// For data that must be shared accross controllers +// + +(function(){ +var model = function ($log, dbg) { + + var _shared = { + availableUnis : [], + currentEpl : null + }; + + var getAvailableUnis = function () { + return _shared.availableUnis; + } + + var setAvailableUnis = function (availableUnis) { + dbg.p("in model:setAvailableUnis"); + _shared.availableUnis = availableUnis; + dbg.pj(_shared.availableUnis); + } + + // var getCurrentEplId = function () { + // // dbg.p("in model:getCurrentEplId, returning: "+_shared.currentEplId); + // return _shared.currentEplId; + // } + + // var setCurrentEplId = function (currentEplId) { + // // dbg.p("in model:setCurrentEplId, setting to: "+currentEplId); + // _shared.currentEplId = currentEplId; + // } + + var getCurrentEpl = function () { + dbg.p("in model:getCurrentEpl:"); + //dbg.pj(_shared.currentEpl); + return _shared.currentEpl; + } + + var setCurrentEpl = function (currentEpl) { + // dbg.p("in model:setCurrentEplId, setting to: "+currentEplId); + dbg.p("in model:setCurrentEpl"); + //dbg.pj(currentEpl); + _shared.currentEpl = currentEpl; + } + + + var dumpShareDdata = function() { dbg.pj(_shared); }; + + return { // Public API + getAvailableUnis : getAvailableUnis, + setAvailableUnis : setAvailableUnis, + getCurrentEpl : getCurrentEpl, + setCurrentEpl : setCurrentEpl, + dumpShareDdata : dumpShareDdata + }; + }; + + var module = angular.module("vcpe"); + module.factory("model", model); + +}());
\ No newline at end of file diff --git a/demo-ui/app/views/cos-panel.html b/demo-ui/app/views/cos-panel.html new file mode 100644 index 0000000..682f154 --- /dev/null +++ b/demo-ui/app/views/cos-panel.html @@ -0,0 +1,171 @@ +<!-- + +To Do + Change Bandwidth to drop down, with selected levels (10M, 100M, 1G, etc) + --> + +<div class="primary-lable ">Service Levels</div> +<div class="cos-list"> + <div class="list-item choosable" + ng-repeat="cos in cosList track by $index" + + ng-class="{'selected-item-idle' : $index === selectedCosIdx && + !cosActionInProgress(), + 'selected-item-update' : $index === selectedCosIdx && + cosUpdateInProgress(), + 'selected-item-delete' : $index === selectedCosIdx && + cosDelInProgress() }" + ng-click="onCosClick($index)"> + {{ cos.id }} + </div> +</div> + +<div class = "action-icon-container"> + + <span class="action-icon glyphicon glyphicon-plus" + ng-click="onAddCos()" + ng-class="{'action' : cosAddInProgress()}"> + </span> + + <span class="action-icon glyphicon glyphicon-pencil" + ng-click="onUpdateCos()" + ng-class="{'action' : cosUpdateInProgress()}"> + </span> + + <span class="action-icon glyphicon glyphicon-minus" + ng-click="onDelCos()" + ng-class="{'action' : cosDelInProgress()}"> + </span> +</div> + +<div class="cos-info-contaier"> + <form role="form" ng-submit="onCosInputSubmit()"> + + <!-- Name --> + + <div class="data-row"> + <div class="label-col">Name:</div> + <div class="data-col" ng-show="showCosName()"> + <span class="data-item"> + {{ cosList[selectedCosIdx].id }} + </span> + </div> + <div class="data-col" ng-show="showCosNameInput()"> + <input class="data-input" name="id" + type="text" ng-required="showCosInputs()" + ng-model="cosToEdit.id"> + </div> + </div> + + <div class="data-row"> + <div class="label-col">Bandwidth:</div> + <div class="data-col" ng-show="showCosValues()"> + + <span class="data-item"> + {{ bwText(cosList[selectedCosIdx]) }} + </span> + </div> + <div class="data-col" ng-show="showCosInputs()"> + <select class="cos-dd-input" + name="bandwidth" + ng-required="showCosInputs()" + ng-model="cosToEdit.bwSelected" + ng-options="bw.name for bw in availableBWs" + > + </option> + </select> + </div> + </div> + + <!-- Availability --> + + <div class="data-row"> + <div class="label-col">Availability:</div> + <div class="data-col" ng-show="showCosValues()"> + <span class="data-item"> + {{ cosList[selectedCosIdx].availbility }} + </span> + <span class="data-unit">%</span> + </div> + <div class="data-col" ng-show="showCosInputs()"> + <input class="data-input" name="availbility" + type="number" step="any" ng-required="showCosInputs()" + min="1" max="100" + ng-model="cosToEdit.availbility"> + <span class="data-unit">%</span> + </div> + </div> + + <!-- Frame Delay --> + + <div class="data-row"> + <div class="label-col">Frame Delay:</div> + <div class="data-col" ng-show="showCosValues()"> + <span class="data-item"> + {{ cosList[selectedCosIdx].frameDelay }} + </span> + <span class="data-unit">ms</span> + </div> + <div class="data-col" ng-show="showCosInputs()"> + <input class="data-input" name="frameDelay" + type="number" step="any" ng-required="showCosInputs()" + min="0" + ng-model="cosToEdit.frameDelay"> + <span class="data-unit">ms</span> + </div> + </div> + + <!-- Jitter --> + + <div class="data-row"> + <div class="label-col">Jitter:</div> + <div class="data-col" ng-show="showCosValues()"> + <span class="data-item"> + {{ cosList[selectedCosIdx].jitter }} + </span> + <span class="data-unit">ms</span> + </div> + <div class="data-col" ng-show="showCosInputs()"> + <input class="data-input" name="jitter" + type="number" step="any" ng-required="showCosInputs()" + min="0" + ng-model="cosToEdit.jitter"> + <span class="data-unit">ms</span> + </div> + </div> + + <!-- Frame Loss --> + + <div class="data-row"> + <div class="label-col">Frame Loss:</div> + <div class="data-col" ng-show="showCosValues()"> + <span class="data-item"> + {{ cosList[selectedCosIdx].frameLoss }} + </span> + <span class="data-unit">%</span> + </div> + <div class="data-col" name="frameLoss"ng-show="showCosInputs()"> + <input class="data-input" name="frameLoss" + type="number" step="any" ng-required="showCosInputs()" + min="0" max="100" + ng-model="cosToEdit.frameLoss"> + <span class="data-unit">%</span> + </div> + </div> + + + <div class="warning-container"> + <div class="warning" ng-show="cosNameConflict()">Please Enter Unique Name</div> + </div> + <div class="button-container"> + <button type="submit" + class="btn btn-sm my-btn-addon" + <div ng-class="{'btn-success' : cosAddInProgress(), + 'btn-warning' : cosUpdateInProgress(), + 'btn-danger' : cosDelInProgress()}" + ng-disabled="!activateCosActionButton()" + ng-show="showCosActionButton()")> + {{ cosActionButtonText }}</button> + </div> + </form> +</div> diff --git a/demo-ui/app/views/css/vcpe.css b/demo-ui/app/views/css/vcpe.css new file mode 100644 index 0000000..0d08734 --- /dev/null +++ b/demo-ui/app/views/css/vcpe.css @@ -0,0 +1,340 @@ +input[type='number'] { + font-size: 12px; +} +.choosable { + cursor: pointer; +} +.gradient { + background: -webkit-linear-gradient(rgba(255, 255, 255, 0) 0%, #ffffff 100%); + background: linear-gradient(to bottom, #ffffff 0%, rgba(255, 255, 255, 0) 100%); +} +.my-btn-addon { + height: 30px; + width: 100px; +} +.primary-lable { + color: darkred; + font-size: large; + font-weight: bold; + text-align: center; +} +.list-item { + color: black; + font-size: small; + font-weight: normal; + text-align: left; + background-color: none; +} +.selected-item-idle { + background-color: #e3e3e3; +} +.selected-item-delete { + background-color: #FFE4E1; +} +.selected-item-update { + background-color: #FFEFD5; +} +.primary-container { + border: 3px solid black; + background-color: #e3e3e3; + border-radius: 15px; + padding: 10px; + height: 400px; +} +.secondary-container { + border: 1px solid darkgray; + background-color: white; + padding: 5px 10px; + margin: 10px 10px -1px 10px; + height: 125px; + overflow-y: scroll; +} +.action-icon-container { + border: 1px solid darkgray; + background: -webkit-linear-gradient(rgba(255, 255, 255, 0) 0%, #ffffff 100%); + background: linear-gradient(to bottom, #ffffff 0%, rgba(255, 255, 255, 0) 100%); + margin: 0px 10px; + padding: 2px 10px; +} +.action-icon { + cursor: pointer; + padding: 0px 1px; + font-size: small; + color: black; + padding: 2px; +} +.action { + color: blue !important; +} +.warning { + color: red !important; + font-size: small !important; +} +.data-row { + display: flex; + flex-flow: row nowrap; + height: 20px; + width: 300; +} +.label-col { + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + flex: 0 0 auto; + order: 1; + width: 85px; +} +.data-col { + flex: 0 0 auto; + order: 2; + color: black; + font-size: small; + font-weight: normal; + text-align: left; +} +.data-label { + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + vertical-align: middle; +} +.data-item { + color: black; + font-size: small; + font-weight: bold; + text-align: left; +} +.data-input { + height: 20px; + width: 70px; + border-radius: 5px; +} +.cos-dd-input { + height: 20px; + width: 70px; +} +.epl-dd-input { + height: 20px; + width: 150px; +} +.data-unit { + color: black; + font-size: small; + font-weight: normal; + text-align: left; + margin-left: 5px; + vertical-align: middle; +} +.header-container { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; +} +.header-container .hdr { + flex: 1 1 auto; + order: 1; +} +.header-container .img { + flex: 1 1 auto; + order: 2; + align-self: flex-end; + margin-bottom: 12px; +} +.frame { + display: flex; + flex-flow: row nowrap; + border: 1px solid lightgray; + margin: 20px; + padding: 20px 0px; +} +.frame .left { + flex: 1 6 5%; + order: 1; +} +.frame .right { + flex: 1 6 5%; + order: 3; +} +.frame .content { + border: none; + flex: 1 6 90%; + order: 2; +} +.frame .content .action-container { + border: none; + display: flex; + flex-flow: row nowrap; +} +.frame .content .action-container .cos-container { + border: 3px solid black; + background-color: #e3e3e3; + border-radius: 15px; + padding: 10px; + height: 400px; + flex: 1 1 50%; + order: 1; + margin-right: 20px; +} +.frame .content .action-container .cos-container .cos-list { + border: 1px solid darkgray; + background-color: white; + padding: 5px 10px; + margin: 10px 10px -1px 10px; + height: 125px; + overflow-y: scroll; +} +.frame .content .action-container .cos-container .cos-info-contaier { + display: flex; + flex-flow: column nowrap; + margin: 10px 20px; +} +.frame .content .action-container .cos-container .cos-info-contaier .labelContainer { + flex: 1 1 33%; + order: 1; + min-width: 80px; + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + vertical-align: middle; +} +.frame .content .action-container .cos-container .warning-container { + height: 15px; + margin-top: 10px; +} +.frame .content .action-container .epl-container { + border: 3px solid black; + background-color: #e3e3e3; + border-radius: 15px; + padding: 10px; + height: 400px; + flex: 1 1 50%; + order: 2; + margin-right: 20px; +} +.frame .content .action-container .epl-container .epl-list { + border: 1px solid darkgray; + background-color: white; + padding: 5px 10px; + margin: 10px 10px -1px 10px; + height: 125px; + overflow-y: scroll; +} +.frame .content .action-container .epl-container .epl-info-contaier { + display: flex; + flex-flow: column nowrap; + margin: 10px 20px; +} +.frame .content .action-container .epl-container .epl-info-contaier .labelContainer { + flex: 1 1 33%; + order: 1; + min-width: 80px; + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + vertical-align: middle; +} +.frame .content .action-container .epl-container .warning-container { + height: 15px; + margin-top: 10px; +} +.frame .content .monitor-container .mef-container { + border: 3px solid black; + background-color: #e3e3e3; + border-radius: 15px; + padding: 10px; + height: 400px; + height: 450px; + background-color: #B0C4DE; + margin: 20px 20px 20px 0px; +} +.frame .content .monitor-container .mef-container .primary-mef-lable { + color: SteelBlue; + font-size: large; + font-weight: bold; + text-align: center; + margin-bottom: 10px; +} +.frame .content .monitor-container .mef-container .mef-panel-hdr { + border-bottom: 1px solid SlateGray; +} +.frame .content .monitor-container .mef-container .secondary-mef-label { + color: SlateGray; + font-size: small; + font-weight: bold; + text-align: center; + background-color: #e3e3e3; +} +.frame .content .monitor-container .mef-container .mef-data-label { + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + vertical-align: middle; +} +.frame .content .monitor-container .mef-container .mef-label-col { + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + flex: 0 0 auto; + order: 1; + padding-left: 10px; +} +.frame .content .monitor-container .mef-container .evc-label-col { + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + flex: 0 0 auto; + order: 1; + padding-left: 10px; + width: 200px; +} +.frame .content .monitor-container .mef-container .uni-label-col { + color: purple; + font-size: small; + font-weight: normal; + text-align: left; + flex: 0 0 auto; + order: 1; + padding-left: 10px; + width: 100px; +} +.frame .content .monitor-container .mef-container .mef-info-container { + display: flex; + flex-flow: row nowrap; + justify-content: center; +} +.frame .content .monitor-container .mef-container .mef-info-container .mef-secondary-container { + border: 1px solid darkgray; + background-color: WhiteSmoke; + margin: 5px; +} +.frame .content .monitor-container .mef-container .mef-info-container .uni1-container { + border: 1px solid darkgray; + background-color: WhiteSmoke; + margin: 5px; + border: 2px solid SlateGray; + flex: 1 1 24%; + order: 1; +} +.frame .content .monitor-container .mef-container .mef-info-container .evc-container { + border: 1px solid darkgray; + background-color: WhiteSmoke; + margin: 5px; + border: 2px solid SlateGray; + flex: 1 1 38%; + order: 2; +} +.frame .content .monitor-container .mef-container .mef-info-container .uni2-container { + border: 1px solid darkgray; + background-color: WhiteSmoke; + margin: 5px; + border: 2px solid SlateGray; + flex: 1 1 24%; + order: 3; +} diff --git a/demo-ui/app/views/epl-panel.html b/demo-ui/app/views/epl-panel.html new file mode 100644 index 0000000..3de2523 --- /dev/null +++ b/demo-ui/app/views/epl-panel.html @@ -0,0 +1,187 @@ +<!-- + +To Do + construct default name based on UNIs and COS + display EVC params when clicked + changed currently hardcoded widths (150) of input fields/dd's to be set by a class + update config file based on watch (already have hit issue), rather than wait + update available cos list when new cos added (low priority) + + --> + +<!-- List of existing EPLs --> + +<div class="primary-lable ">Ethernet Private Line Services</div> +<div class="epl-list"> + <div class="list-item choosable" + ng-repeat="epl in eplList track by $index" + + ng-class="{'selected-item-idle' : $index === selectedEplIdx && + !eplActionInProgress(), + 'selected-item-update' : $index === selectedEplIdx && + eplUpdateInProgress(), + 'selected-item-delete' : $index === selectedEplIdx && + eplDelInProgress() }" + ng-click="onEplClick($index)"> + {{ epl.id }} + </div> +</div> + + +<!-- Action Icons (add/mod/del) --> + +<div class = "action-icon-container"> + + <span class="action-icon glyphicon glyphicon-plus" + ng-click="onAddEpl()" + ng-class="{'action' : eplAddInProgress()}"> + </span> + + <span class="action-icon glyphicon glyphicon-pencil" + ng-click="onUpdateEpl()" + ng-class="{'action' : eplUpdateInProgress()}"> + </span> + + <span class="action-icon glyphicon glyphicon-minus" + ng-click="onDelEpl()" + ng-class="{'action' : eplDelInProgress()}"> + </span> +</div> + +<!-- Data for EPL selected / being acted on --> + +<div class="epl-info-contaier"> + <form role="form" ng-submit="onEplInputSubmit()"> + + <!-- Name --> + + <div class="data-row"> + <div class="label-col">Name:</div> + <div class="data-col" ng-show="showEplName()"> + <span class="data-item"> + {{ eplList[selectedEplIdx].id }} + </span> + </div> + + <div class="data-col" ng-show="showEplNameInput()"> + <input class="data-input" name="id" + style="width:150" + type="text" ng-required="showEplInputs()" + ng-model="eplToEdit.id"> + </div> + </div> + + <!-- Class of servce --> + + <div class="data-row"> + <div class="label-col">Service level</div> + <div class="data-col" ng-show="showEplValues()"> + <span class="data-item"> + {{ eplList[selectedEplIdx].cos }}</span> + </div> + <div class="data-col" ng-show="showEplInputs()"> + <select class="dd-input" + style="width:150" + name="cos Id" + ng-required="showEplInputs()" + ng-model="eplToEdit.cos" + > + <option ng-repeat="cos in availableCosIds" + value="{{ cos }}" + ng-selected="{{cos == eplToEdit.cos}}" + > + {{ cos }} + </option> + </select> + </div> + </div> + + + <!-- UNI 1 --> + + <div class="data-row"> + <div class="label-col">UNI 1</div> + <div class="data-col" ng-show="showEplValues()"> + <span class="data-item"> + {{ eplList[selectedEplIdx].uniHostIpList[0] }}  + ( {{ eplList[selectedEplIdx].custAddressList[0] }} ) + </span> + </div> + + <div class="data-col" ng-show="showEplInputs()"> + <select class="dd-input" + style="width:150" + name="uni 1" + ng-required="showEplInputs()" + ng-model="eplToEdit.uni1Selected" + ng-options="uni.ip + ' ( ' + uni.address + ' )' for uni in availableUnis" + > + </option> + </select> + </div> + </div> + + + <!-- UNI 2 --> + + <div class="data-row"> + <div class="label-col">UNI 2</div> + <div class="data-col" ng-show="showEplValues()"> + <span class="data-item"> + {{ eplList[selectedEplIdx].uniHostIpList[1] }}  + ( {{ eplList[selectedEplIdx].custAddressList[1] }} ) + </span> + </div> + + <div class="data-col" ng-show="showEplInputs()"> + <select class="dd-input" + style="width:150" + name="uni 1" + ng-required="showEplInputs()" + ng-model="eplToEdit.uni2Selected" + ng-options="uni.ip + ' ( ' + uni.address + ' )' for uni in availableUnis" + > + </option> + </select> + </div> + </div> + + + <!-- Evc --> + + <div class="data-row" > + <div class="label-col" ng-hide="eplAddInProgress()">Assoc Evc:</div> + <div class="data-col"> + <span class="data-item" ng-hide="eplAddInProgress()"> + {{ eplList[selectedEplIdx].evcId }} + </span> + </div> + </div> + + <!-- Action Buttons --> + + <div class="warning-container"> +  <span class="warning" ng-show="eplNameConflict()">[Please Enter Unique Name]   </span> + <span class="warning" ng-show="uniConflict()">[UNI-1 and UNI-2 must be different]   </span> + <span class="warning" ng-show="cosConflict()">[Must create SL before creating EPL]   </span> + </div> + <div class="button-container"> + <button type="submit" + class="btn btn-sm my-btn-addon" + <div ng-class="{'btn-success' : eplAddInProgress(), + 'btn-warning' : eplUpdateInProgress(), + 'btn-danger' : eplDelInProgress()}" + ng-disabled="!activateEplActionButton()" + ng-show="showEplActionButton()")> + {{ eplActionButtonText }}</button> + </div> + + <!-- Debug --> + +<!-- + <div> + </div> + --> + + </form> +</div> diff --git a/demo-ui/app/views/less/vcpe.less b/demo-ui/app/views/less/vcpe.less new file mode 100644 index 0000000..5598160 --- /dev/null +++ b/demo-ui/app/views/less/vcpe.less @@ -0,0 +1,365 @@ +@dbg-flg: false; + +@side-panel-width: 5%; +@main-panel-width: 100%-(2*@side-panel-width); + +@backgroundGray: #E3E3E3; + +@cos-basis: 50%; +@epl-basis: 100%-(@cos-basis); + + +// +// Utilities +// + +.dbg-border( // show border if @dbg-flg is true + @dbg-bordersFlg, + @dbg-borderClr, + @dbg-borderStyle) + when (@dbg-bordersFlg=true) { + border: 3px @dbg-borderStyle @dbg-borderClr; + margin: 1px; padding: 1px; +} + +.set-text(@color, + @font-size, + @font-weight, + @text-align) { + color:@color; font-size:@font-size; + font-weight:@font-weight; text-align:@text-align; +} + +// +// Common Styles +// + +input[type='number'] { font-size: 12px; } + +.choosable { + cursor: pointer; +} + +.gradient { + background: -webkit-linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); + background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); +} + +.my-btn-addon { + height: 30px; + width: 100px; +} + +.primary-lable { + .set-text(darkred, large, bold, center); } + +.list-item { + .set-text(black, small, normal, left); + background-color:none; } + +.selected-item-idle { + background-color: @backgroundGray; } + +.selected-item-delete { + background-color: #FFE4E1; } + +.selected-item-update { + background-color: #FFEFD5; } + +.primary-container { + border: 3px solid black; + .dbg-border(@dbg-flg, blue, solid); + background-color: @backgroundGray; + border-radius: 15px; + padding: 10px; + height: 400px; +} + +.secondary-container { + border: 1px solid darkgray; + .dbg-border(@dbg-flg, green, solid); + background-color: white; + padding: 5px 10px; // TB RL + margin: 10px 10px -1px 10px; // T R B L + height: 125px; + overflow-y: scroll; +} + +.action-icon-container { + border: 1px solid darkgray; + .dbg-border(@dbg-flg, magenta, solid); + .gradient; + margin: 0px 10px; // TB RL + padding: 2px 10px; // TB RL +} + +.action-icon { + .choosable; + padding: 0px 1px; // TB RL + font-size: small; + color: black; + padding: 2px; +} + +.action { + color: blue !important; +} + +.warning { + color: red !important; + font-size: small !important; +} + +.data-row { + .dbg-border(@dbg-flg, red, solid); + display: flex; + flex-flow:row nowrap; + height: 20px; + width: 300; + } +.label-col { + .dbg-border(@dbg-flg, gray, solid); + .set-text(purple, small, normal, left); + flex: 0 0 auto; order: 1; + width: 85px; + } + + +.data-col { + .dbg-border(@dbg-flg, gray, solid); + flex: 0 0 auto; order: 2; + .set-text(black, small, normal, left); + + } + +.data-label { + .set-text(purple, small, normal, left); + vertical-align: middle; +} +.data-item { + .set-text(black, small, bold, left); +} +.data-input { + height: 20px; + width: 70px; + border-radius: 5px; +} +.cos-dd-input { + height: 20px; + width: 70px; +} + +.epl-dd-input { + height: 20px; + width: 150px; +} + + +.data-unit { + .set-text(black, small, normal, left); + margin-left: 5px; + vertical-align: middle; +} + +// +// Page starts here +// + +.header-container { + display: flex; + flex-flow:row nowrap; + justify-content: space-between; + .hdr { + flex: 1 1 auto; order: 1; + } + .img { + flex: 1 1 auto; order: 2; + align-self: flex-end; + margin-bottom: 12px; + } +} + + +.frame{ + .dbg-border(@dbg-flg, lightgray, solid); + display: flex; + flex-flow:row nowrap; + border: 1px solid lightgray; + + margin: 20px; // TBRL + padding: 20px 0px; // TB RL + + .left { .dbg-border(@dbg-flg, lightgray, solid); + flex: 1 6 @side-panel-width; order: 1; } + + .right{ .dbg-border(@dbg-flg, lightgray, solid); + flex: 1 6 @side-panel-width; order: 3; } + + .content { + border: none; + .dbg-border(@dbg-flg, lightgray, solid); + flex: 1 6 @main-panel-width; order: 2; + + .action-container { + border: none; + .dbg-border(@dbg-flg, yellow, solid); + + display: flex; + flex-flow:row nowrap; + + .cos-container { + .primary-container; + flex: 1 1 @cos-basis; order: 1; + margin-right: 20px; + + .cos-list { + .secondary-container; + } + .cos-info-contaier { + .dbg-border(@dbg-flg, green, solid); + display: flex; + flex-flow:column nowrap; + margin: 10px 20px; // TB RL + .labelContainer { + .dbg-border(@dbg-flg, yellow, solid); + flex: 1 1 33%; order: 1; + min-width: 80px; + .data-label; + } + } + .warning-container { + .dbg-border(@dbg-flg, magenta, solid); + height: 15px; + margin-top: 10px; + } + .button-container { + .dbg-border(@dbg-flg, magenta, solid); + } + } + .epl-container { + .primary-container; + flex: 1 1 @epl-basis; order: 2; + margin-right: 20px; + + .epl-list { + .secondary-container; + } + .epl-info-contaier { + .dbg-border(@dbg-flg, green, solid); + display: flex; + flex-flow:column nowrap; + margin: 10px 20px; // TB RL + .labelContainer { + .dbg-border(@dbg-flg, yellow, solid); + flex: 1 1 33%; order: 1; + min-width: 80px; + .data-label; + } + } + .warning-container { + .dbg-border(@dbg-flg, magenta, solid); + height: 15px; + margin-top: 10px; + } + .button-container { + .dbg-border(@dbg-flg, magenta, solid); + } + } + } + + .monitor-container { + .dbg-border(@dbg-flg, yellow, solid); + + + .mef-container { + .primary-container; + height: 450px; + + .dbg-border(@dbg-flg, blue, solid); + background-color: #B0C4DE; + margin: 20px 20px 20px 0px; // T R B L + + .primary-mef-lable { + .set-text(SteelBlue, large, bold, center); + margin-bottom: 10px; + } + + @mef-container-border-color: SlateGray; + + .mef-panel-hdr { + border-bottom: 1px solid @mef-container-border-color; + .dbg-border(@dbg-flg, blue, solid); + } + + .secondary-mef-label { + .set-text(SlateGray, small, bold, center); + background-color: @backgroundGray; + } + + .mef-data-label { + .set-text(purple, small, normal, left); + vertical-align: middle; + + } + .mef-label-col { + .dbg-border(@dbg-flg, gray, solid); + .set-text(purple, small, normal, left); + flex: 0 0 auto; order: 1; + padding-left: 10px; + } + + .evc-label-col { + .mef-label-col; + width: 200px; + + } + .uni-label-col { + .mef-label-col; + width: 100px; + } + + .mef-info-container { + display: flex; + flex-flow:row nowrap; + justify-content: center; + + .mef-secondary-container { + border: 1px solid darkgray; + .dbg-border(@dbg-flg, green, solid); + background-color: WhiteSmoke; + //padding: 5px 30px; // TB RL + margin: 5px; + } + + @evc-basis: 38%; + @uni-basis: 100%-(2*@evc-basis); + + .uni1-container { + .mef-secondary-container; + border: 2px solid @mef-container-border-color; + .dbg-border(@dbg-flg, darkred, solid); + flex: 1 1 @uni-basis; order: 1; + + } + .evc-container { + .mef-secondary-container; + border: 2px solid @mef-container-border-color; + .dbg-border(@dbg-flg, darkred, solid); + flex: 1 1 @evc-basis; order: 2; + + } + .uni2-container { + .mef-secondary-container; + border: 2px solid @mef-container-border-color; + .dbg-border(@dbg-flg, darkred, solid); + flex: 1 1 @uni-basis; order: 3; + + } + } + } + } + } +} + + + diff --git a/demo-ui/app/views/mef-panel.html b/demo-ui/app/views/mef-panel.html new file mode 100644 index 0000000..df9f60b --- /dev/null +++ b/demo-ui/app/views/mef-panel.html @@ -0,0 +1,370 @@ +<!-- + +To Do + Use cases to handle properly RE EVC/UNI displaying info + - EPL exist at start up + - no EPL at start up + - delete an EPL + - delete last EPL + --> + +<div class="primary-mef-lable">MEF EPL Data</div> +<div class="mef-info-container"> + + + <!-- UNI 1 --> + + <div class="uni1-container"> + <div class="mef-panel-hdr"> + <div class="secondary-mef-label">UNI-1 MEF Attributes</div> + <div class="secondary-mef-label" + style="font-size: xx-small; font-weight: normal; color: #b3b3b3;"> + (Source = ODL UNI Mgr DB) + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Uni ID:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["id"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">IP Address:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["ip-address"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">MAC Address:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["mac-address"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Speed:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ uniToSpeedString(currentEvcUnis[0]) }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Mac Layer:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["mac-layer"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Phys Medium:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["physical-medium"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">MTU Size:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["mtu-size"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Mode:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["mode"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Type:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["type"] }} + </span> + </div> + </div> + + </div> + + <!-- EVC --> + + <div class="evc-container"> + + <div class="mef-panel-hdr"> + <div class="secondary-mef-label">EVC MEF Attributes</div> + <div class="secondary-mef-label" + style="font-size: xx-small; font-weight: normal; color: #b3b3b3;"> + (Source = EVC Mgr DB)</div> + </div> + <div class="data-row"> + <div class="evc-label-col">Evc ID:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.id }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">CoS ID:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.cosId }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">Uni-1/2 IDs:</div> + <div class="data-col" ng-show="showEvcValues()"> + + <span class="data-item"> + {{ "[" + currentEplEvc.uniIdList[0] + "] | [" + currentEplEvc.uniIdList[1] + "]"}} + </span> + + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">Uni-1/2 IPs:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.uniIpList[0] }} + </span> + </div> + </div> + <div class="data-row"> + <div class="evc-label-col"></div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.uniIpList[1] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">Uni-1/2 MACs:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.uniMacList[0] }} + </span> + </div> + </div> + <div class="data-row"> + <div class="evc-label-col"></div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.uniMacList[1] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">1-Way Availability:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.oneWayAvailability }} + </span> + <span class="data-unit">%</span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">1-Way Frame Delay:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.oneWayFrameDelay }} + </span> + <span class="data-unit">ms</span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">1-Way Frame Loss Ratio:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.oneWayFrameLossRatio }} + </span> + <span class="data-unit">%</span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">EVC Type:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.evcType }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">Unicast Frame Delivery:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.unicastFrameDelivery }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">Broadcast Frame Delivery:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.broadcastFrameDelivery }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">Multicast Frame Delivery:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.multicastFrameDelivery }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">CE VLAN ID Preservation:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.ceVLanIdPreservation }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">CE VLAN CoS Preservation:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.ceVlanCosPreservation }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="evc-label-col">Max Service Frame Size:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEplEvc.evcMaxSvcFrameSize }} + </span> + <span class="data-unit">bytes</span> + </div> + </div> + </div> + + <!-- UNI 2 --> + + <div class="uni2-container"> + <div class="mef-panel-hdr"> + <div class="secondary-mef-label">UNI-2 MEF Attributes</div> + <div class="secondary-mef-label" + style="font-size: xx-small; font-weight: normal; color: #b3b3b3;"> + (Source = ODL UNI Mgr DB) + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Uni ID:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[1].uni[0]["id"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">IP Address:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[1].uni[0]["ip-address"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">MAC Address:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[1].uni[0]["mac-address"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Speed:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ uniToSpeedString(currentEvcUnis[1]) }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Mac Layer:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[1].uni[0]["mac-layer"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Phys Medium:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[1].uni[0]["physical-medium"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">MTU Size:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[0].uni[0]["mtu-size"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Mode:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[1].uni[0]["mode"] }} + </span> + </div> + </div> + + <div class="data-row"> + <div class="uni-label-col">Type:</div> + <div class="data-col" ng-show="showEvcValues()"> + <span class="data-item"> + {{ currentEvcUnis[1].uni[0]["type"] }} + </span> + </div> + </div> + + </div> +</div>
\ No newline at end of file diff --git a/demo-ui/app/views/vcpe-portal.html b/demo-ui/app/views/vcpe-portal.html new file mode 100644 index 0000000..437485f --- /dev/null +++ b/demo-ui/app/views/vcpe-portal.html @@ -0,0 +1,71 @@ +<!-- + +TODO + - Use watchers to trigger changes aftger config file reads as opposed + to the current "sleep for a bit" approach + + --> + +<html ng-app="vcpe"> +<head ng-controller="MainController"> + + <script src="../../bower_components/angular/angular.min.js"></script> + + <link rel="stylesheet" href="../../bower_components/bootstrap/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="../../bower_components/bootstrap/dist/css/bootstrap-theme.min.css"> + + <link rel="stylesheet" href="css/vcpe.css" /> + <script src="../controllers/MainController.js"></script> + <script src="../controllers/CosController.js"></script> + <script src="../controllers/EplController.js"></script> + <script src="../controllers/MefController.js"></script> + <script src="../services/cosServices.js"></script> + <script src="../services/eplServices.js"></script> + <script src="../services/mefServices.js"></script> + <script src="../services/model.js"></script> + <script src="../services/dbg.js"></script> + +</head> + +<body> +<div class="frame"> +<div class="left"></div> + +<div class="content"> + + <div class="header-container"> + <div class="hdr"> + <h2>Virtual Business CPE Demo</h2> + </div> + </div> + + <div class="action-container"> + + <div class="cos-container" + ng-controller="CosController" + ng-include="'cos-panel.html'"> + </div> + + <div class="epl-container" + ng-controller="EplController" + ng-include="'epl-panel.html'"> + </div> + + </div> + + <div class="monitor-container"> + + <div class="mef-container" + ng-controller="MefController" + ng-include="'mef-panel.html'"> + </div> + + + </div> + +</div> + +<div class="right"></div> +</div> +</body> +</html> diff --git a/demo-ui/bower.json b/demo-ui/bower.json new file mode 100644 index 0000000..aaac185 --- /dev/null +++ b/demo-ui/bower.json @@ -0,0 +1,11 @@ +{ + "name": "dhd", + "version": "0.0.1", + "dependencies": { + "angular": "1.4.1", + "bootstrap": "3.3.5" + }, + "resolutions": { + "angular": "1.4.1" + } +} diff --git a/demo-ui/gruntfile.js b/demo-ui/gruntfile.js new file mode 100644 index 0000000..7e8bc68 --- /dev/null +++ b/demo-ui/gruntfile.js @@ -0,0 +1,25 @@ +module.exports = function (grunt) { + + grunt.initConfig({ + less: { + app: { + files: { + 'app/views/css/vcpe.css': 'app/views/less/vcpe.less' + } + } + }, + + watch: { + less: { + files: 'app/views/less/*.less', + tasks: ['css'] + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-less'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.registerTask('css', ['less']); + grunt.registerTask('default', ['css']); + +}; diff --git a/demo-ui/package.json b/demo-ui/package.json new file mode 100644 index 0000000..086cadb --- /dev/null +++ b/demo-ui/package.json @@ -0,0 +1,18 @@ +{ + "name": "vcpe-demo-portal", + "version": "0.0.1", + "description": "Portal that drives VCPE MEF demo", + "main": "src/portal.js", + "author": "steve@sentosatech.com", + "dependencies": { + "less": "latest", + "express": "latest", + "body-parser": "*" + }, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-contrib-copy": "^0.8.0", + "grunt-contrib-less": "^1.0.1", + "grunt-contrib-watch": "^0.6.1" + } +} diff --git a/demo-ui/vcpeUiServer.js b/demo-ui/vcpeUiServer.js new file mode 100644 index 0000000..94c5b9c --- /dev/null +++ b/demo-ui/vcpeUiServer.js @@ -0,0 +1,19 @@ +// Create express HTTP server app +var express = require('express'); +var app = express(); + +var bodyParser = require('body-parser'); +app.use(bodyParser.json()); +app.use(express.static(".")); + +// defaults +var PORT = 4000; + +// grap the command line arguments +var args = process.argv.slice(2); +if ( args[0] ) { + PORT = args[0]; +} + +app.listen(PORT); +console.log('Running on http://localhost:' + PORT);
\ No newline at end of file |