aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Saunders <steve@llantz9470.cablelabs.com>2015-08-12 08:48:07 -0600
committerSteven Saunders <steve@khiatt8460b.cablelabs.com>2015-08-12 18:21:36 -0600
commite8a4652f70284c414ceafc086669dfd4b935573d (patch)
treeedfa339167fee1863505f7a39b5b3cab431b3eee
parented73dbf357aff41edcbab401a94e5fbc266d9391 (diff)
Add demo-ui folder. This folder contains HTML/CSS/Javascript code for the VCPE Demo UI. The Demo UI allows you to CRUD CoS instances, and EPL instances, and will also display information about EVCs and UNIs that were created as well
Change-Id: I32eea1121c21e0b451efc057a0c0f30fe5601cd5 Signed-off-by: Steven Saunders <steve@llantz9470.cablelabs.com>
-rw-r--r--demo-ui/README.md149
-rw-r--r--demo-ui/app/config.json32
-rw-r--r--demo-ui/app/controllers/CosController.js381
-rw-r--r--demo-ui/app/controllers/EplController.js528
-rw-r--r--demo-ui/app/controllers/MainController.js31
-rw-r--r--demo-ui/app/controllers/MefController.js117
-rw-r--r--demo-ui/app/services/cosServices.js64
-rw-r--r--demo-ui/app/services/dbg.js54
-rw-r--r--demo-ui/app/services/eplServices.js64
-rw-r--r--demo-ui/app/services/mefServices.js71
-rw-r--r--demo-ui/app/services/model.js61
-rw-r--r--demo-ui/app/views/cos-panel.html171
-rw-r--r--demo-ui/app/views/css/vcpe.css340
-rw-r--r--demo-ui/app/views/epl-panel.html187
-rw-r--r--demo-ui/app/views/less/vcpe.less365
-rw-r--r--demo-ui/app/views/mef-panel.html370
-rw-r--r--demo-ui/app/views/vcpe-portal.html71
-rw-r--r--demo-ui/bower.json11
-rw-r--r--demo-ui/gruntfile.js25
-rw-r--r--demo-ui/package.json18
-rw-r--r--demo-ui/vcpeUiServer.js19
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] }}&nbsp
+ ( {{ 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] }}&nbsp
+ ( {{ 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">
+ &nbsp<span class="warning" ng-show="eplNameConflict()">[Please Enter Unique Name]&nbsp&nbsp&nbsp</span>
+ <span class="warning" ng-show="uniConflict()">[UNI-1 and UNI-2 must be different]&nbsp&nbsp&nbsp</span>
+ <span class="warning" ng-show="cosConflict()">[Must create SL before creating EPL]&nbsp&nbsp&nbsp</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