diff options
author | Christopher Lott (cl778h) <clott@research.att.com> | 2017-08-17 14:52:44 -0400 |
---|---|---|
committer | Christopher Lott (cl778h) <clott@research.att.com> | 2017-08-17 14:53:24 -0400 |
commit | 9015d0d86d23a83e578ded1bd95485d467515208 (patch) | |
tree | 577556c635e60bbd8446416c2cb6492f4b583a5f /ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify | |
parent | 3a32363f46a29cfed9ed1c28053424852f54382d (diff) |
Publish seed code for the OOM Dashboard
First open-source release of the ONAP Operations Manager Dashboard web application.
Issue: CCSDK-61
Change-Id: I902f789692d76ee583aa967682e39f03b6578fe9
Signed-off-by: Christopher Lott (cl778h) <clott@research.att.com>
Diffstat (limited to 'ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify')
13 files changed, 2119 insertions, 0 deletions
diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint-controllers.js b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint-controllers.js new file mode 100644 index 0000000..2845782 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint-controllers.js @@ -0,0 +1,467 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('blueprintTableController', function( + $rootScope, $scope, $log, $modal, modalService, BlueprintService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + + // this object holds all app data and functions + $scope.ecdapp = {}; + // models for controls on screen + $scope.ecdapp.tableData = []; + $scope.ecdapp.currentPageNum = 1; + $scope.ecdapp.viewPerPage = 10; + // other + $scope.ecdapp.errMsg = null; + $scope.ecdapp.isDataLoading = true; + $scope.ecdapp.isRequestFailed = false; + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.loadTable = function() { + $scope.ecdapp.isDataLoading = true; + BlueprintService.getBlueprints($scope.ecdapp.currentPageNum, $scope.ecdapp.viewPerPage) + .then(function(jsonObj) { + if (jsonObj.error) { + $log.error("blueprintController.loadTable failed: " + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + if (debug) + $log.debug("bluePrintController.loadTable succeeded, size " + jsonObj.data.length); + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.totalPages = jsonObj.totalPages; + $scope.ecdapp.tableData = jsonObj.items; + } + $scope.ecdapp.isDataLoading = false; + }, function(error) { + $log.error("blueprintController.loadTable failed: " + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + + /** + * Invoked at first page load AND when + * user clicks on the B2B pagination control. + */ + $scope.pageChangeHandler = function(page) { + // console.log('pageChangeHandler: current is ' + $scope.ecdapp.currentPageNum + ' new is ' + page); + $scope.ecdapp.currentPageNum = page; + $scope.ecdapp.loadTable(); + } + + /** + * Shows a modal pop-up with blueprint content. + * Passes data in via an object named "message". + */ + $scope.ecdapp.viewBlueprintModalPopup = function(blueprint) { + $scope.ecdapp.editBlueprint = null; + var modalInstance = $modal.open({ + templateUrl : 'blueprint_view_popup.html', + controller : 'blueprintViewCtrl', + windowClass: 'modal-docked', + sizeClass: 'modal-medium', + resolve : { + message : function() { + var dataForPopup = { + blueprint : blueprint + }; + return dataForPopup; + } + } + }); + modalInstance.result.then(function(response) { + // No response. + }); + }; + + /** + * Shows a modal pop-up to upload a blueprint. + * Passes data in via an object named "message". + * On success, updates the table. + */ + $scope.ecdapp.uploadBlueprintModalPopup = function() { + $scope.ecdapp.editBlueprint = null; + var modalInstance = $modal.open({ + templateUrl : 'blueprint_upload_popup.html', + controller : 'blueprintUploadCtrl', + windowClass: 'modal-docked', + sizeClass: 'modal-small', + resolve : { + message : function() { + var dataForPopup = { + blueprint : $scope.ecdapp.editBlueprint, + blueprintList : $scope.ecdapp.tableData, + }; + return dataForPopup; + } + } + }); + modalInstance.result.then(function(response) { + if (debug) + $log.debug('uploadBlueprintModalPopup: response: ' + JSON.stringify(response)); + if (response == null) { + // $log.debug('user closed dialog'); + } + else { + if (response.error != null) { + $log.error('uploadBlueprintModalPopup failed: ' + response.error); + alert('Failed to upload blueprint:\n' + response.error); + } + else { + // success, get the updated list. + $scope.ecdapp.loadTable() + } + } + }); + }; + + /** + * Shows a modal pop-up to create a deployment from a blueprint. + * Passes data in via an object named "message". + */ + $scope.ecdapp.deployBlueprintModalPopup = function(blueprint) { + var modalInstance = $modal.open({ + templateUrl : 'blueprint_deploy_popup.html', + controller : 'blueprintDeployCtrl', + windowClass: 'modal-docked', + sizeClass: 'modal-medium', + resolve : { + message : function() { + var dataForPopup = { + blueprint : blueprint, + }; + return dataForPopup; + } + } + }); + modalInstance.result.then(function(response) { + if (debug) + $log.debug('deployBlueprintModalPopup: response: ' + JSON.stringify(response)); + if (response == null) { + if (debug) + $log.debug('user closed dialog'); + } + else { + if (response.error != null) { + $log.error('deployBlueprintModalPopup failed: ' + response.error); + alert('Failed to deploy blueprint:\n' + response.error); + // No need to update THIS table. + // Must switch to deployments page to see result? Awkward. + } + } + }); + }; + + /** + * Shows a modal pop-up to confirm deletion. + * On successful completion, updates the table. + */ + $scope.ecdapp.deleteBlueprintModalPopup = function(blueprint) { + modalService.popupConfirmWin("Confirm", "Delete blueprint with ID '" + + blueprint.id + "'?", function() { + BlueprintService.deleteBlueprint(blueprint.id).then( + function(response) { + if (debug) + $log.debug('deleteBlueprintModalPopup: response: ' + JSON.stringify(response)); + if (response && response.error) { + // $log.error('deleteBlueprint failed: ' + response.error); + alert('Failed to delete blueprint:\n' + response.error); + } + else { + // No response body on success. + $scope.ecdapp.loadTable(); + } + }, + function(error) { + $log.error('BlueprintService.deleteBlueprint failed: ' + error); + alert('Service failed to delete blueprint:\n' + error); + }); + }) + }; + + // Populate the table on load. Note that the b2b selector code + // sets the page-number value, and the change event calls load table. + // Do not call this here to avoid double load: + // $scope.ecdapp.loadTable(); + +}); + +/*************************************************************************/ + +appDS2.controller('blueprintUploadCtrl', function( + $scope, $log, $modalInstance, message, BlueprintService) { + + 'use strict'; + + // this object holds all app data and functions + $scope.ecdapp = {}; + + $scope.ecdapp.label = 'Upload Blueprint'; + $scope.ecdapp.uploadRequest = + { + blueprint_id : '', + blueprint_filename : '', + zip_url : '' + }; + + /** + * Validates content of user-editable fields. + * Uses the list in message.feedList + * Returns null if all is well, + * a descriptive error message otherwise. + */ + $scope.ecdapp.validateRequest = function(uploadRequest) { + if (uploadRequest == null) + return "No data found.\nPlease enter some values."; + if (uploadRequest.blueprint_id == null || uploadRequest.blueprint_id.trim() == '') + return "ID is required.\nPlease enter a value."; + if (uploadRequest.blueprint_filename == null || uploadRequest.blueprint_filename.trim() == '') + return "File name is required.\nPlease enter a value."; + if (uploadRequest.blueprint_filename.toLowerCase().substr(-4) != 'yaml') + return "File name must end with YAML.\nPlease use that suffix."; + if (uploadRequest.zip_url == null || uploadRequest.zip_url.trim() == '') + return "Zip file URL is required.\nPlease enter a value."; + return null; + } + + $scope.ecdapp.uploadBlueprint = function(uploadRequest) { + var validateMsg = $scope.ecdapp.validateRequest(uploadRequest); + if (validateMsg != null) { + alert('Invalid upload request:\n' + validateMsg); + return; + } + BlueprintService.uploadBlueprint(uploadRequest) + .then(function(response) { + // $log.debug('blueprintPopupCtrl: response: ' + response); + if (response.error) + alert('Failed to upload blueprint:\n' + response.error); + else + $modalInstance.close(response); + }, + function (error) { + $log.error('blueprintUploadCtrl: error while uploading: ' + error); + alert('Server rejected upload request:\n' + error); + } + ); + + }; + +}); + +/*************************************************************************/ + +appDS2.controller('blueprintViewCtrl', function( + $scope, $log, message, BlueprintService) { + + 'use strict'; + + var debug = false; + + if (debug) + $log.debug("blueprintViewCtrl.message: " + JSON.stringify(message)); + + // this object holds all app data and functions + $scope.ecdapp = {}; + $scope.ecdapp.blueprintId = message.blueprint.id; + + $scope.ecdapp.label = 'View Blueprint ' + message.blueprint.id; + + // Fetch the blueprint + $scope.ecdapp.isDataLoading = true; + BlueprintService.viewBlueprint(message.blueprint.id).then(function(jsonObj) { + if (debug) + $log.debug("blueprintViewCtrl.viewBlueprint response: " + JSON.stringify(jsonObj)); + if (jsonObj.error) { + $scope.ecdapp.errMsg = 'Request Failed'; + } + else { + $scope.ecdapp.blueprint = jsonObj.content; + } + $scope.ecdapp.isDataLoading = false; + }, function(error) { + $scope.ecdapp.isDataLoading = false; + alert('Failed to get blueprint. Please retry.'); + $log.error("blueprintViewCtrl failed: " + error); + }); + +}); + + +/*************************************************************************/ + +appDS2.controller('blueprintDeployCtrl', function( + $scope, $log, $modalInstance, message, DeploymentService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + + // this object holds all app data and functions + $scope.ecdapp = {}; + $scope.ecdapp.label = 'Deploy Blueprint'; + + // Cache the input parameter names for validation + if (debug) + $log.debug('blueprintDeployCtrl: inputs: ' + JSON.stringify(message.blueprint.plan.inputs)); + $scope.ecdapp.inputsDict = message.blueprint.plan.inputs; + + // Copy the input parameter names and default values + let inputsAndDefaults = {}; + for (var pkey in message.blueprint.plan.inputs) { + if (debug) + $log.debug('blueprintDeployCtrl: checking key ' + pkey); + let dval = message.blueprint.plan.inputs[pkey].default; + if (! dval) + dval = ''; + inputsAndDefaults[pkey] = dval; + } + if (debug) + $log.debug('blueprintDeployCtrl: inputsAndDefaults: ' + JSON.stringify(inputsAndDefaults)); + + // Create an object for edit + $scope.ecdapp.editRequest = { + deployment_id : '', + blueprint_id : message.blueprint.id, + fileModel : null, + parmFileDict : inputsAndDefaults + }; + + /** + * Handler for file-read event reads file, parses YAML, validates content. + */ + var fileReader = new FileReader(); + fileReader.onload = function(event) { + let yamlString = fileReader.result; + if (debug) + $log.debug('fileReader.onload: read: ' + yamlString); + let ydict = {}; + try { + ydict = YAML.parse(yamlString); + } + catch (ex) { + alert('Failed to parse file as YAML:\n' + ex); + } + // Process the file + for (var ykey in ydict) { + let yval = ydict[ykey]; + if (debug) + $log.debug('fileReader.onload: typeof ' + ykey + ' is ' + typeof ykey); + // Allow only expected keys with scalar values + if (! (ykey in $scope.ecdapp.editRequest.parmFileDict)) + alert('Unexpected file content:\nKey not defined by blueprint:\n' + ykey); + else if (typeof yval !== 'string' && typeof yval !== 'number') + alert('Unexpected file content:\nNot a simple key-value pair:\n' + ykey); + else + $scope.ecdapp.editRequest.parmFileDict[ykey] = yval; + } + if (debug) + $log.debug('fileReader.onload: parmFileDict: ' + JSON.stringify($scope.ecdapp.editRequest.parmFileDict)); + + // Update table in all cases + $scope.$apply(); + } + + // Handler for file-select event + $scope.handleFileSelect = function() { + if (debug) + $log.debug('handleFileSelect: $scope.ecdapp.fileModel.name is ' + $scope.ecdapp.editRequest.fileModel.name); + fileReader.readAsText($scope.ecdapp.editRequest.fileModel); + }; + + /** + * Validates content of user-editable fields. + * Returns null if all is well, + * a descriptive error message otherwise. + */ + $scope.ecdapp.validateRequest = function(editRequest) { + if (editRequest == null) + return 'No data found.\nPlease enter some values.'; + if (editRequest.deployment_id == null || editRequest.deployment_id.trim() == '') + return 'Deployment ID is required.\nPlease enter a value.'; + if (editRequest.blueprint_id == null || editRequest.blueprint_id.trim() == '') + return 'Blueprint ID is required.\nPlease enter a value.'; + // Check that every file parameter is defined by blueprint + for (var pkey in $scope.ecdapp.editRequest.parmFileDict) { + // Defined in blueprint? + if (! $scope.ecdapp.inputsDict[pkey]) + return 'Unexpected input parameter\n' + pkey; + // Populated? + let parmVal = $scope.ecdapp.editRequest.parmFileDict[pkey]; + if (parmVal == null || (typeof (parmVal) === 'string' && parmVal.trim().length == 0)) + return 'Missing value for parameter\n' + pkey; + } + // Check that a value is supplied for every expected input + for (var bkey in $scope.ecdapp.inputsDict) { + if (! $scope.ecdapp.editRequest.parmFileDict[bkey]) + return 'Missing input parameter\n' + bkey; + } + return null; + } + + $scope.ecdapp.deployBlueprint = function(editRequest) { + if (debug) + $log.debug('deployBlueprint: editRequest is ' + JSON.stringify($scope.ecdapp.editRequest)); + var validateMsg = $scope.ecdapp.validateRequest(editRequest); + if (validateMsg != null) { + alert('Invalid Request:\n' + validateMsg); + return; + } + // Create request with key:value parameters dictionary + let deployRequest = { + deployment_id : editRequest.deployment_id, + blueprint_id : editRequest.blueprint_id, + parameters : {} + }; + for (var pkey in $scope.ecdapp.editRequest.parmFileDict) + deployRequest.parameters[pkey] = $scope.ecdapp.editRequest.parmFileDict[pkey]; + if (debug) + $log.debug('deployBlueprint: deployRequest is ' + JSON.stringify(deployRequest)); + + DeploymentService.deployBlueprint(deployRequest) + .then(function(response) { + if (response.error) + alert('Failed to deploy blueprint:\n' + response.error); + else + $modalInstance.close(response); + }, + function (error) { + $log.error('blueprintDeployCtrl: error while deploying: ' + error); + alert('Server rejected deployment request:\n' + error); + } + ); + + }; + +}); diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint-service.js b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint-service.js new file mode 100644 index 0000000..422e53d --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint-service.js @@ -0,0 +1,114 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.factory('BlueprintService', function ($http, $q, $log) { + return { + /** + * Gets one page of blue prints objects. + * @param {Number} pageNum - page number; e.g., 1 + * @param {Number} viewPerPage - number of items per page; e.g., 25 + * @return {JSON} Response object from remote side + */ + getBlueprints: function(pageNum,viewPerPage) { + // cache control for IE + let cc = "&cc=" + new Date().getTime().toString(); + let url = 'blueprints?pageNum=' + pageNum + '&viewPerPage=' + viewPerPage + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('BlueprintService.getBlueprints: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('BlueprintService.getBlueprints failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + /** + * Gets blueprint content. + * @return {JSON} Response object from remote side + */ + viewBlueprint: function(id) { + // cache control for IE + let url = 'viewblueprints/' + id; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('BlueprintService.viewBlueprint: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('BlueprintService.viewBlueprint failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + uploadBlueprint: function(uploadRequest) { + let url = 'blueprints'; + return $http({ + method: 'POST', + url: url, + data: uploadRequest, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('BlueprintService.uploadBlueprint: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('BlueprintService.uploadBlueprint failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + deleteBlueprint: function(id) { + let url = 'blueprints/' + id; + return $http({ + method: 'DELETE', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + // This is called on response code 200..299. + // On success, response.data is null. + // On failure, response.data has an error message. + return response.data; + }, + function(error) { + $log.error('BlueprintService.deleteBlueprint failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + }; +}); diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint_popups.html b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint_popups.html new file mode 100644 index 0000000..ee92d84 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint_popups.html @@ -0,0 +1,187 @@ +<script type="text/ng-template" id="blueprint_upload_popup.html"> + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <div class="b2b-modal-body ng-scope ng-isolate-scope" tabindex="0" + role="region" aria-label="Modal body content"> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="blueprintId">*Blueprint ID</label> + <div class="field-group"> + <!--autofocus is HTML5 attribute; doesn't work in Firefox--> + <input id="blueprintId" class="span12" type="text" data-ng-model="ecdapp.editBlueprint.blueprint_id" autofocus> + </div> + </div> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="blueprintFileName">*File Name</label> + <div class="field-group"> + <input id="blueprintFileName" class="span12" type="text" data-ng-model="ecdapp.editBlueprint.blueprint_filename"> + </div> + </div> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="zipFileUrl">*Zip File URL</label> + <div class="field-group"> + <input id="zipFileUrl" class="span12" type="text" data-ng-model="ecdapp.editBlueprint.zip_url"> + </div> + </div> + </div> + </div> + + </div> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="ecdapp.uploadBlueprint(ecdapp.editBlueprint);"> + Save + </button> + <button class="btn btn-small" type="button" + ng-click="$dismiss('cancel')"> + Cancel + </button> + </div> + </div> + +</script> + +<script type="text/ng-template" id="blueprint_view_popup.html"> + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <div class="b2b-modal-body ng-scope ng-isolate-scope" tabindex="0" + role="region" aria-label="Modal body content"> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom:20px;"> + <i class="icon-primary-spinner small" role="img" aria-label="Please wait while the content loads"></i> + Please wait while the content loads. + </div> + </div> + + <div ng-show="ecdapp.errMsg"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.errMsg"> + <pre>{{ecdapp.blueprint}}</pre> + </div> + + </div> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="$dismiss('cancel');"> + Close + </button> + </div> + </div> + +</script> + +<script type="text/ng-template" id="blueprint_deploy_popup.html"> + + <style> + .ecd-parameter-table + { + border: 1px; + overflow: auto; + } + .ecd-parameter-table th + { + font-size: 1.4rem; + } + </style> + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <div class="b2b-modal-body ng-scope ng-isolate-scope" tabindex="0" + role="region" aria-label="Modal body content"> + + <div class="row-nowrap"> + <div class="span12"> + <label for="blueprintFileName">*Deployment ID</label> + <!--autofocus is HTML5 attribute; doesn't work in Firefox--> + <input id="blueprintFileName" class="span12" type="text" data-ng-model="ecdapp.editRequest.deployment_id" autofocus/> + </div> + <div class="span12"> + <label for="blueprintId">Blueprint ID</label> + <!--not editable--> + <input id="blueprintId" class="span12" type="text" disabled="disabled" data-ng-model="ecdapp.editRequest.blueprint_id"/> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="parameters">*Parameters</label> + <div b2b-file-drop file-model="ecdapp.editRequest.fileModel" on-drop="handleFileSelect()" align="center"> + <span b2b-file-link file-model="ecdapp.editRequest.fileModel" on-file-select="handleFileSelect()" > + Drag & drop a parameters YAML file here, or click to browse. + </span> + </div> + </div> + <div class="ecd-parameter-table"> + <table id="parameters"> + <tr id="ecd-table-header"> + <th width="40%">Name</th> + <th width="60%">Value</th> + </tr> + <tbody ng-repeat="(pkey, pval) in ecdapp.editRequest.parmFileDict"> + <tr id="tr-rowData"> + <td ng-bind="pkey"/> + <td ng-bind="pval"/> + </tr> + </tbody> + </table> + </div> + </div> + </div> + + </div> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="ecdapp.deployBlueprint(ecdapp.editRequest);"> + Save + </button> + <button class="btn btn-small" type="button" + ng-click="$dismiss('cancel')"> + Cancel + </button> + </div> + </div> + +</script> diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint_table.html b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint_table.html new file mode 100644 index 0000000..3ce60b9 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/blueprint_table.html @@ -0,0 +1,98 @@ +<div id="page-content"> + + <h1 class="heading-page" id="blueprints-page">Blueprints</h1> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom:20px;"> + <i class="icon-primary-spinner small" role="img" aria-label="Please wait while the content loads"></i> + Please wait while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div id="button-search-row"> + <button class="btn btn-alt btn-small" + type="submit" + ng-click="ecdapp.uploadBlueprintModalPopup();"> + Upload Blueprint... + </button> + <div style="float:right;"> + <div class="form-field form-field__small"> + <input + type="text" + placeholder="Search Blueprints" + ng-model="ecdapp.searchString"/> + </div> + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div + b2b-table + id="blueprints-table" + class="b2b-table-div" + table-data="ecdapp.tableData" + search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + + <table> + + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="id">ID</th> + <th b2b-table-header key="main_file_name">File Name</th> + <th b2b-table-header key="description">Description</th> + <th b2b-table-header key="created_at">Created Date</th> + <th b2b-table-header key="updated_at">Updated Date</th> + <th b2b-table-header sortable="false"><i class="icon-controls-gear ecd-icon-display"></i></th> + </tr> + </thead> + + <tbody b2b-table-row type="body" row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body + ng-bind="rowData.id"/> + <td b2b-table-body + ng-bind="rowData.main_file_name"/> + <td b2b-table-body + ng-bind="rowData.description"/> + <td b2b-table-body + ng-bind="rowData.created_at"/> + <td b2b-table-body + ng-bind="rowData.updated_at"/> + <td b2b-table-body> + <div ng-click="ecdapp.viewBlueprintModalPopup(rowData);"> + <a href="" title="View blueprint" class="icon-people-preview ecd-icon-action"></a> + </div> + <div ng-click="ecdapp.deployBlueprintModalPopup(rowData);"> + <a href="" title="Deploy blueprint" class="icon-datanetwork-cloudupload ecd-icon-action"></a> + </div> + <div ng-click="ecdapp.deleteBlueprintModalPopup(rowData);"> + <a href="" title="Delete blueprint" class="icon-misc-trash ecd-icon-action"></a> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div b2b-pagination="" total-pages="ecdapp.totalPages" + current-page="ecdapp.currentPageNum" click-handler="pageChangeHandler" + role="navigation"> + </div> + + <div style="height: 10px;"> + <!-- space between page number and black footer --> + </div> + + </div><!-- loading --> + +</div><!-- page content --> diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment-controllers.js b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment-controllers.js new file mode 100644 index 0000000..b46d242 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment-controllers.js @@ -0,0 +1,371 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('deploymentTableController', function( + $rootScope, $scope, $log, $modal, DeploymentService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + + // this object holds all app data and functions + $scope.ecdapp = {}; + // models for controls on screen + $scope.ecdapp.tableData = []; + $scope.ecdapp.currentPageNum = 1; + $scope.ecdapp.viewPerPage = 10; + // other + $scope.ecdapp.errMsg = null; + $scope.ecdapp.isDataLoading = true; + $scope.ecdapp.isRequestFailed = false; + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either a list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.loadTable = function() { + $scope.ecdapp.isDataLoading = true; + DeploymentService.getDeployments($scope.ecdapp.currentPageNum, + $scope.ecdapp.viewPerPage).then( + function(jsonObj) { + if (jsonObj.error) { + $log.error("deploymentController.loadTable failed: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.totalPages = jsonObj.totalPages; + $scope.ecdapp.tableData = jsonObj.items; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("deploymentController.loadTable failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + + /** + * Invoked at first page load AND when + * user clicks on the B2B pagination control. + */ + $scope.pageChangeHandler = function(page) { + // console.log('pageChangeHandler: current is ' + $scope.ecdapp.currentPageNum + ' new is ' + page); + $scope.ecdapp.currentPageNum = page; + $scope.ecdapp.loadTable(); + } + + /** + * Shows a modal pop-up to create an execution from a deployment. + * Passes data in via an object named "message". + * On success, updates the table. + */ + $scope.ecdapp.createExecutionModalPopup = function(deployment) { + var modalInstance = $modal.open({ + templateUrl : 'deployment_execute_popup.html', + controller : 'deploymentExecuteCtrl', + windowClass: 'modal-docked', + sizeClass: 'modal-large', + resolve : { + message : function() { + var dataForPopup = { + deployment : deployment, + }; + return dataForPopup; + } + } + }); + modalInstance.result.then(function(response) { + if (debug) + $log.debug('createExecutionModalPopup: response: ' + JSON.stringify(response)); + if (response == null) { + // $log.debug('user closed dialog'); + } + else { + if (response.error != null) { + $log.error('createExecutionModalPopup: failed to delete: ' + error); + alert('Failed to create execution:\n' + response.error); + // No need to update THIS table. + // Must switch to executions page to see result? Awkward. + } + } + }); + }; + + /** + * Shows a modal pop-up to confirm deletion. + * On successful completion, updates the table. + */ + $scope.ecdapp.deleteDeploymentModalPopup = function(deployment) { + var modalInstance = $modal.open({ + templateUrl : 'deployment_delete_popup.html', + controller : 'deploymentDeleteCtrl', + sizeClass: 'modal-small', + resolve : { + message : function() { + var dataForPopup = { + deployment : deployment, + }; + return dataForPopup; + } + } + }); + modalInstance.result.then(function(response) { + if (debug) + $log.debug('deleteDeploymentPopup: response: ' + JSON.stringify(response)); + if (response == null) { + // $log.debug('user closed dialog'); + } + else { + if (response.error != null) { + $log.error('deleteDeploymentModalPopup failed: ' + response.error); + alert('Failed to delete deployment:\n' + response.error); + } + else { + // success, get the updated list. + $scope.ecdapp.loadTable() + } + } + }); + }; + +}); + +/*************************************************************************/ + +appDS2.controller('deploymentDeleteCtrl', function( + $scope, $log, $modalInstance, message, DeploymentService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + + // this object holds all app data and functions + $scope.ecdapp = {}; + $scope.ecdapp.label = 'Delete Deployment'; + $scope.ecdapp.deploymentId = message.deployment.id; + $scope.ecdapp.ignoreLiveNodes = false; + + $scope.ecdapp.deleteDeploymentById = function(){ + DeploymentService.deleteDeployment($scope.ecdapp.deploymentId, $scope.ecdapp.ignoreLiveNodes).then( + function(response) { + if (debug) + $log.debug('deploymentDeleteCtrl.deleteDeployment: ' + JSON.stringify(response)); + if (response && response.error) { + $log.error('DeploymentService.deleteDeployment failed: ' + response.error); + alert('Failed to delete deployment:\n' + response.error); + } + else { + // Delete service returns null on success. + $modalInstance.close("success"); + } + }, + function(error) { + $log.error('DeploymentService.deleteDeployment failed: ' + error); + alert('Service failed to delete deployment:\n' + error); + }); + } + +}); + +/*************************************************************************/ + +appDS2.controller('deploymentExecuteCtrl', function( + $scope, $log, $modalInstance, message, ExecutionService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + + // this object holds all app data and functions + $scope.ecdapp = {}; + $scope.ecdapp.label = 'Execute Deployment'; + + // Cache the input parameter names for validation + $scope.ecdapp.inputsDict = {}; + if (debug) + $log.debug('DeploymentXeqCtrl: inputsDict is ' + JSON.stringify($scope.ecdapp.inputsDict)); + + // Copy the input names and values + // (different structure than blueprint) + /*for (var pkey in message.deployment.inputs) { + let dval = message.deployment.inputs[pkey]; + inputsAndValues[pkey] = dval; + }*/ + + // Gather workflow names as a simple list + var workflowList = []; + for (var i = 0; i < message.deployment.workflows.length; i++) + workflowList.push(message.deployment.workflows[i].name); + if (debug) + $log.debug('DeploymentXeqCtrl: workflowList is ' + JSON.stringify(workflowList)); + + // Create an object for edit. + $scope.ecdapp.editRequest = { + deployment_id : message.deployment.id, + allow_custom_parameter : false, + force : false, + // Has title and value objects + workflow_name: { value : '' }, + workflow_list : workflowList, + fileModel : null, + parmFileDict : {} + }; + + $scope.selectWorkflowName = function(){ + var workflows = message.deployment.workflows; + let inputsAndValues = {}; + var res = workflows.filter(function(d){ + if(d.name == $scope.ecdapp.editRequest.workflow_name.value){ + return d + } + }); + for(var key in res[0].parameters){ + inputsAndValues[key] = typeof (res[0].parameters[key].default) === 'string' ? res[0].parameters[key].default : null; + } + $scope.ecdapp.inputsDict = inputsAndValues; + $scope.ecdapp.editRequest.parmFileDict = inputsAndValues; + }; + + if (debug) + $log.debug('DeploymentXeqCtrl: editRequest is ' + JSON.stringify($scope.ecdapp.editRequest)); + + /** + * Handler for file-read event reads file, parses YAML, validates content. + */ + var fileReader = new FileReader(); + fileReader.onload = function(event) { + let yamlString = fileReader.result; + if (debug) + $log.debug('fileReader.onload: read: ' + yamlString); + let ydict = {}; + try { + ydict = YAML.parse(yamlString); + } + catch (ex) { + alert('Failed to parse file as YAML:\n' + ex); + } + for (var ykey in ydict) { + let yval = ydict[ykey]; + if (debug) + $log.debug('fileReader.onload: typeof ' + ykey + ' is ' + typeof ykey); + // Only accept valid, expected key-value pairs + if (typeof yval !== 'string' && typeof yval!=='number') + alert('Unexpected file content:\nNot a simple key-value pair:\n' + ykey); + else if (! (ykey in $scope.ecdapp.editRequest.parmFileDict)) + alert('Unexpected file content:\nKey not defined by deployment:\n' + ykey); + else + $scope.ecdapp.editRequest.parmFileDict[ykey] = yval; + } + if (debug) + $log.debug('fileReader.onload: parmFileDict: ' + JSON.stringify($scope.ecdapp.editRequest.parmFileDict)); + + // Update table in all cases + $scope.$apply(); + } + + // Handler for file-select event + $scope.handleFileSelect = function() { + if (debug) + $log.debug('handleFileSelect: $scope.ecdapp.fileModel.name is ' + $scope.ecdapp.editRequest.fileModel.name); + fileReader.readAsText($scope.ecdapp.editRequest.fileModel); + }; + + /** + * Validates content of user-editable fields. + * Returns null if all is well, + * a descriptive error message otherwise. + */ + var validateRequest = function(editRequest) { + if (debug) + $log.debug('validateRequest: editRequest is ' + JSON.stringify(editRequest)); + if (editRequest == null) + return "No data found.\nPlease enter some values."; + if (editRequest.deployment_id == null || editRequest.deployment_id.trim() == '') + return "Deployment ID is required.\nPlease enter a value."; + if (editRequest.workflow_name.value == null || editRequest.workflow_name.value.trim() == '') + return "Workflow name is required.\nPlease select a workflow."; + // Check that every file parameter is defined by blueprint + for (var pkey in $scope.ecdapp.editRequest.parmFileDict) { + // Defined in blueprint? + if (! $scope.ecdapp.inputsDict[pkey]) + return 'Unexpected input parameter\n' + pkey; + // Populated? + let parmVal = $scope.ecdapp.editRequest.parmFileDict[pkey]; + if (parmVal.trim().length == 0) + return 'Missing value for parameter ' + pkey; + } + // Check that a value is supplied for every expected input + for (var bkey in $scope.ecdapp.inputsDict) { + if (! $scope.ecdapp.editRequest.parmFileDict[bkey]) + return 'Missing input parameter\n' + bkey; + } + return null; + } + + $scope.ecdapp.executeDeployment = function(editRequest) { + var validateMsg = validateRequest(editRequest); + if (validateMsg != null) { + alert('Invalid execution request:\n' + validateMsg); + return; + } + + // Create request with key:value parameters dictionary + let executeRequest = { + deployment_id : editRequest.deployment_id, + workflow_name : editRequest.workflow_name.value, + allow_custom_parameter : editRequest.allow_custom_parameter, + force : editRequest.force, + parameters : {} + }; + for (var pkey in $scope.ecdapp.editRequest.parmFileDict) + executeRequest.parameters[pkey] = $scope.ecdapp.editRequest.parmFileDict[pkey]; + if (debug) + $log.debug('executeDeployment: executeRequest is ' + JSON.stringify(executeRequest)); + + ExecutionService.executeDeployment(executeRequest) + .then(function(response) { + if (response.error) + alert('Failed to create execution:\n' + response.error); + else + $modalInstance.close(response); + }, + function (error) { + $log.error('deploymentExecuteCtrl: error while creating execution: ' + error); + alert('Server rejected execute request:\n' + error); + } + ); + + }; + +}); + diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment-service.js b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment-service.js new file mode 100644 index 0000000..886cba1 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment-service.js @@ -0,0 +1,89 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.factory('DeploymentService', function ($http, $q, $log) { + return { + /** + * Gets one page of objects. + * @param {Number} pageNum - page number; e.g., 1 + * @param {Number} viewPerPage - number of items per page; e.g., 25 + * @return {JSON} Response object from remote side + */ + getDeployments: function(pageNum,viewPerPage) { + // cache control for IE + let cc = "&cc=" + new Date().getTime().toString(); + let url = 'deployments?pageNum=' + pageNum + '&viewPerPage=' + viewPerPage + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('DeploymentService.getDeployments: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('DeploymentService.getDeployments failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + deployBlueprint: function(deployRequest) { + let url = 'deployments'; + return $http({ + method: 'POST', + url: url, + data: deployRequest, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('DeploymentService.deployBlueprint: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('DeploymentService.deployBlueprint failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + deleteDeployment: function(id, forceDelete) { + let url = 'deployments/' + id + '?ignore_live_nodes=' + forceDelete; + return $http({ + method: 'DELETE', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + // This is called on response code 200..299. + // On success, response.data is null. + // On failure, response.data has an error message. + return response.data; + }, + function(error) { + $log.error('DeploymentService.deleteDeployment failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + }; +}); diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment_popups.html b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment_popups.html new file mode 100644 index 0000000..f4a4cdd --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment_popups.html @@ -0,0 +1,170 @@ +<script type="text/ng-template" id="deployment_execute_popup.html"> + + <style> + .ecd-parameter-table + { + border: 0px; + overflow: auto; + } + .ecd-parameter-table th + { + font-size: 1.4rem; + } + </style> + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <div class="b2b-modal-body ng-scope ng-isolate-scope" tabindex="0" + role="region" aria-label="Modal body content"> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="blueprintId">Deployment ID</label> + <div class="field-group"> + <!--autofocus is HTML5 attribute; doesn't work in Firefox--> + <input id="blueprintId" class="span12" type="text" data-ng-model="ecdapp.editRequest.deployment_id" autofocus/> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="allowCustom"> </label> + <div class="field-group"> + <label for="allowCustomParameters" class="checkbox"> + <input id="allowCustomParameters" type="checkbox" ng-model="ecdapp.editRequest.allow_custom_parameter" /> + <i class="skin"></i><span>Allow Custom Parameters</span> + </label> + </div> + </div> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="workflowName">Workflow Name</label> + <div class="field-group"> + <select b2b-dropdown id="workflowName" name="workflowName" ng-model="ecdapp.editRequest.workflow_name.value" ng-change="selectWorkflowName()"> + <option b2b-dropdown-list option-repeat="w in ecdapp.editRequest.workflow_list" value="{{w}}"> + {{w}} + </option> + </select> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="force"> </label> + <div class="field-group"> + <label for="force" class="checkbox"> + <input id="force" type="checkbox" ng-model="ecdapp.editRequest.force" /> + <i class="skin"></i><span>Force</span> + </label> + </div> + </div> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="parameters">*Parameters</label> + <div b2b-file-drop file-model="ecdapp.editRequest.fileModel" on-drop="handleFileSelect()" align="center"> + <span b2b-file-link file-model="ecdapp.editRequest.fileModel" on-file-select="handleFileSelect()" > + Drag & drop a parameters YAML file here, or click to browse. + </span> + </div> + </div> + <div class="ecd-parameter-table"> + <table id="parameters"> + <tr id="ecd-table-header"> + <th width="40%">Name</th> + <th width="60%">Value</th> + </tr> + <tbody ng-repeat="(pkey, pval) in ecdapp.editRequest.parmFileDict"> + <tr id="tr-rowData"> + <td ng-bind="pkey"/> + <td ng-bind="pval"/> + </tr> + </tbody> + </table> + </div> + </div> + </div> + + </div> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="ecdapp.executeDeployment(ecdapp.editRequest);"> + Save + </button> + <button class="btn btn-small" type="button" + ng-click="$dismiss('cancel')"> + Cancel + </button> + </div> + </div> + +</script> + +<script type="text/ng-template" id="deployment_delete_popup.html"> + + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <div class="b2b-modal-body ng-scope ng-isolate-scope" tabindex="0" + role="region" aria-label="Modal body content"> + + <div class="span12"> + <div class="form-row"> + <div class="field-group"> + <label> + Delete deployment with ID '{{ecdapp.deploymentId}}'? + </label> + </div> + </div> + </div> + + <div class="span12"> + <div class="form-row"> + <label for="ignoreLiveNodesCheck"> </label> + <div class="field-group"> + <label for="ignoreLiveNodes" class="checkbox"> + <input id="ignoreLiveNodes" type="checkbox" ng-model="ecdapp.ignoreLiveNodes" /> + <i class="skin"></i><span>Ignore Live Nodes</span> + </label> + </div> + </div> + </div> + + </div> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="ecdapp.deleteDeploymentById(deployment);"> + Delete + </button> + <button class="btn btn-small" type="button" + ng-click="$dismiss('cancel')"> + Cancel + </button> + </div> + </div> + +</script>
\ No newline at end of file diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment_table.html b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment_table.html new file mode 100644 index 0000000..c414484 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/deployment_table.html @@ -0,0 +1,93 @@ +<div id="page-content"> + + <h1 class="heading-page" id="deployments-page">Deployments</h1> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom:20px;"> + <i class="icon-primary-spinner small" role="img" aria-label="Please wait while the content loads"></i> + Please wait while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div id="button-search-row"> + <div style="float:right;"> + <div class="form-field form-field__small"> + <input + type="text" + placeholder="Search Deployments" + ng-model="ecdapp.searchString"/> + </div> + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div + b2b-table + id="deployments-table" + class="b2b-table-div" + table-data="ecdapp.tableData" + search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + + <table> + + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="deployment_id">ID</th> + <th b2b-table-header key="blueprint_id">Blueprint ID</th> + <th b2b-table-header key="description">Description</th> + <th b2b-table-header key="created_at">Created Date</th> + <th b2b-table-header key="updated_at">Updated Date</th> + <th b2b-table-header sortable="false"><i class="icon-controls-gear ecd-icon-display"></i></th> + </tr> + </thead> + + <tbody b2b-table-row type="body" row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body + ng-bind="rowData.id"/> + <td b2b-table-body + ng-bind="rowData.blueprint_id"/> + <td b2b-table-body + ng-bind="rowData.description"/> + <td b2b-table-body + ng-bind="rowData.created_at"/> + <td b2b-table-body + ng-bind="rowData.updated_at"/> + <td b2b-table-body> + <div ng-click="ecdapp.createExecutionModalPopup(rowData);"> + <a href="" title="Create execution" class="icon-controls-playalt ecd-icon-action"></a> + </div> + <div ng-click="ecdapp.deleteDeploymentModalPopup(rowData);"> + <a href="" title="Delete deployment" class="icon-misc-trash ecd-icon-action"></a> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div b2b-pagination="" total-pages="ecdapp.totalPages" + current-page="ecdapp.currentPageNum" click-handler="pageChangeHandler" + role="navigation"> + </div> + + </div> + </div> + + <div style="height: 10px;"> + <!-- space between page number and black footer --> + </div> + + </div><!-- loading --> + +</div><!-- page content --> diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution-service.js b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution-service.js new file mode 100644 index 0000000..f1a5698 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution-service.js @@ -0,0 +1,110 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.factory('ExecutionService', function ($http, $q, $log) { + return { + /** + * Gets one page of objects. + * @param {Number} pageNum - page number; e.g., 1 + * @param {Number} viewPerPage - number of items per page; e.g., 25 + * @return {JSON} Response object from remote side + */ + getExecutions: function(pageNum,viewPerPage) { + // cache control for IE + let cc = "&cc=" + new Date().getTime().toString(); + let url = 'executions?pageNum=' + pageNum + '&viewPerPage=' + viewPerPage + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('ExecutionService.getExecutions: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('ExecutionService.getExecutions failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + getExecutionsByStatus: function(status) { + // cache control for IE + let cc = "&cc=" + new Date().getTime().toString(); + let url = 'executions?status=' + status + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('ExecutionService.getExecutions: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('ExecutionService.getExecutions failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + executeDeployment: function(executeRequest) { + let url = 'executions'; + return $http({ + method: 'POST', + url: url, + data: executeRequest, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('ExecutionService.executeDeployment: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('ExecutionService.executeDeployment failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + cancelExecution: function(id, deploymentId, action) { + let url = 'executions/' + id + '?deployment_id=' + deploymentId + '&action=' + action; + return $http({ + method: 'DELETE', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + // This is called on response code 200..299. + // On success, response.data is null. + // On failure, response.data has an error message. + return response.data; + }, + function(error) { + $log.error('ExecutionService.cancelExecution failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + }; +}); diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution-table-controller.js b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution-table-controller.js new file mode 100644 index 0000000..a9c8574 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution-table-controller.js @@ -0,0 +1,126 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('executionTableController', function( + $rootScope, $scope, $log, $modal, modalService, ExecutionService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + + // this object holds all app data and functions + $scope.ecdapp = {}; + // models for controls on screen + $scope.ecdapp.tableData = []; + $scope.ecdapp.currentPageNum = 1; + $scope.ecdapp.viewPerPage = 10; + // other + $scope.ecdapp.errMsg = null; + $scope.ecdapp.isDataLoading = true; + $scope.ecdapp.isRequestFailed = false; + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either a list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.loadTable = function() { + $scope.ecdapp.isDataLoading = true; + ExecutionService.getExecutions($scope.ecdapp.currentPageNum, + $scope.ecdapp.viewPerPage).then( + function(jsonObj) { + if (jsonObj.error) { + $log.error("executionController.loadTable failed: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.totalPages = jsonObj.totalPages; + $scope.ecdapp.tableData = jsonObj.items; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("executionController.loadTable failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + + /** + * Invoked at first page load AND when + * user clicks on the B2B pagination control. + */ + $scope.pageChangeHandler = function(page) { + if (debug) + console.log('pageChangeHandler: current is ' + $scope.ecdapp.currentPageNum + ' new is ' + page); + $scope.ecdapp.currentPageNum = page; + $scope.ecdapp.loadTable(); + } + + /** + * Shows a modal pop-up with the error. + */ + $scope.ecdapp.viewErrorModalPopup = function(row) { + modalService.showFailure('Error Details', row.error, function() { } ); + }; + + /** + * Shows a modal pop-up to confirm deletion. + * On successful completion, updates the table. + */ + $scope.ecdapp.cancelExecutionModalPopup = function(execution) { + modalService.popupConfirmWin("Confirm", "Cancel execution with ID '" + + execution.id + "'?", function() { + // TODO: gather action from user + ExecutionService.cancelExecution(execution.id, execution.deploymentId, "cancel").then( + function(response) { + if (debug) + $log.debug("xeqTblController.cancelExecutionModalPopup: " + JSON.stringify(response)); + if (response && response.error) { + // $log.error('cancelExectuion failed: ' + response.error); + alert('Failed to cancel execution:\n' + response.error); + } + else { + // No response body on success. + $scope.ecdapp.loadTable(); + } + }, + function(error) { + $log.error('ExecutionService.cancelExecution failed: ' + error); + alert('Service failed to cancel execution:\n' + error); + }); + }) + }; + + // Populate the table on load. Note that the b2b selector code + // sets the page-number value, and the change event calls load table. + // Do not call this here to avoid double load: + // $scope.ecdapp.loadTable(); + +}); diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution_table.html b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution_table.html new file mode 100644 index 0000000..f29e90d --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/execution_table.html @@ -0,0 +1,101 @@ +<div id="page-content"> + + <h1 class="heading-page" id="executions-page">Executions</h1> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom:20px;"> + <i class="icon-primary-spinner small" role="img" aria-label="Please wait while the content loads"></i> + Please wait while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div id="button-search-row"> + <div style="float:right;"> + <div class="form-field form-field__small"> + <input + type="text" + placeholder="Search Executions" + ng-model="ecdapp.searchString"/> + </div> + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div + b2b-table + id="executions-table" + class="b2b-table-div" + table-data="ecdapp.tableData" + search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + + <table> + + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="id">ID</th> + <th b2b-table-header key="deployment_id">Deployment ID</th> + <th b2b-table-header key="blueprint_id">Blueprint ID</th> + <th b2b-table-header key="workflow_id">Workflow ID</th> + <th b2b-table-header key="description">Description</th> + <th b2b-table-header key="created_at">Created Date</th> + <th b2b-table-header key="status">Status</th> + <th b2b-table-header key="error">Error</th> + <th b2b-table-header sortable="false"><i class="icon-controls-gear ecd-icon-display"></i></th> + </tr> + </thead> + + <tbody b2b-table-row type="body" row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body + ng-bind="rowData.id"/> + <td b2b-table-body + ng-bind="rowData.deployment_id"/> + <td b2b-table-body + ng-bind="rowData.blueprint_id"/> + <td b2b-table-body + ng-bind="rowData.workflow_id"/> + <td b2b-table-body + ng-bind="rowData.description"/> + <td b2b-table-body + ng-bind="rowData.created_at"/> + <td b2b-table-body + ng-bind="rowData.status"/> + <td b2b-table-body> + <div ng-show="rowData.error" ng-click="ecdapp.viewErrorModalPopup(rowData);"> + <a href="" title="View error details" class="icon-people-preview ecd-icon-action"></a> + </div> + </td> + <td b2b-table-body> + <div ng-click="ecdapp.cancelExecutionModalPopup(rowData);"> + <a href="" title="Cancel execution" class="icon-controls-stop ecd-icon-action"></a> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div b2b-pagination="" total-pages="ecdapp.totalPages" + current-page="ecdapp.currentPageNum" click-handler="pageChangeHandler" + role="navigation"> + </div> + + </div> + + <div style="height: 10px;"> + <!-- space between page number and black footer --> + </div> + + </div><!-- loading --> + +</div><!-- page content --> diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/tosca-table-controller.js b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/tosca-table-controller.js new file mode 100644 index 0000000..cafc517 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/tosca-table-controller.js @@ -0,0 +1,106 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('toscaTableController', function( + $rootScope, $scope, $log, $modal, modalService, ExecutionService) { + + 'use strict'; + + // this object holds all app data and functions + $scope.ecdapp = {}; + // models for controls on screen + $scope.ecdapp.tableData = []; + $scope.ecdapp.currentPageNum = 1; + $scope.ecdapp.viewPerPage = 10; + // other + $scope.ecdapp.errMsg = null; + $scope.ecdapp.isDataLoading = true; + $scope.ecdapp.isRequestFailed = false; + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either a list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.loadTable = function() { + var toBeDefined = true; + if (toBeDefined) { + $scope.ecdapp.isDataLoading = false; + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = 'TOSCA Models not available (yet).'; + } + else { + $scope.ecdapp.isDataLoading = true; + ExecutionService.getExecutions($scope.ecdapp.currentPageNum, + $scope.ecdapp.viewPerPage).then( + function(jsonObj) { + if (jsonObj.error) { + $log.error("executionController.loadTable failed: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.totalPages = jsonObj.totalPages; + $scope.ecdapp.tableData = jsonObj.items; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("executionController.loadTable failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + } + }; + + /** + * Invoked at first page load AND when + * user clicks on the B2B pagination control. + */ + $scope.pageChangeHandler = function(page) { + // console.log('pageChangeHandler: current is ' + $scope.ecdapp.currentPageNum + ' new is ' + page); + $scope.ecdapp.currentPageNum = page; + $scope.ecdapp.loadTable(); + } + + /** + * Shows a modal pop-up to confirm deletion. + * On successful completion, updates the table. + */ + $scope.ecdapp.deleteToscaModalPopup = function(execution) { + modalService.popupConfirmWin("Confirm", "Delete TOSCA model with ID '" + + execution.id + "'?", function() { + // TODO: gather action from user + }) + }; + + // Populate the table on load. Note that the b2b selector code + // sets the page-number value, and the change event calls load table. + // Do not call this here to avoid double load: + // $scope.ecdapp.loadTable(); + +}); diff --git a/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/tosca_table.html b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/tosca_table.html new file mode 100644 index 0000000..affec91 --- /dev/null +++ b/ecd-app-overlay/src/main/webapp/app/ecdapp/cloudify/tosca_table.html @@ -0,0 +1,87 @@ +<div id="page-content"> + + <h1 class="heading-page" id="blueprints-page">TOSCA Models</h1> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom:20px;"> + <i class="icon-primary-spinner small" role="img" aria-label="Please wait while the content loads"></i> + Please wait while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div id="button-search-row"> + <div style="float:right;"> + <div class="form-field form-field__small"> + <input + type="text" + placeholder="Search TOSCA Models" + ng-model="ecdapp.searchString"/> + </div> + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div + b2b-table + id="tosca-table" + class="b2b-table-div" + table-data="ecdapp.tableData" + search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + + <table> + + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="id">ID</th> + <th b2b-table-header key="name">Name</th> + <th b2b-table-header key="description">Description</th> + <th b2b-table-header key="created_at">Created Date</th> + <th b2b-table-header key="updated_at">Updated Date</th> + <th b2b-table-header sortable="false"><i class="icon-controls-gear ecd-icon-display"></i></th> + </tr> + </thead> + + <tbody b2b-table-row type="body" row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body + ng-bind="rowData.id"/> + <td b2b-table-body + ng-bind="rowData.main_file_name"/> + <td b2b-table-body + ng-bind="rowData.description"/> + <td b2b-table-body + ng-bind="rowData.created_at"/> + <td b2b-table-body + ng-bind="rowData.updated_at"/> + <td b2b-table-body> + <div ng-click="ecdapp.deleteToscaModalPopup(rowData);"> + <a href="" title="Delete TOSCA model" class="icon-misc-trash ecd-icon-action"></a> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div b2b-pagination="" total-pages="ecdapp.totalPages" + current-page="ecdapp.currentPageNum" click-handler="pageChangeHandler" + role="navigation"> + </div> + + <div style="height: 10px;"> + <!-- space between page number and black footer --> + </div> + + </div><!-- loading --> + +</div><!-- page content --> |