1
0
Fork 0
mirror of https://github.com/mmumshad/ansible-playable.git synced 2025-03-09 23:38:54 +00:00

Initial Commit

This commit is contained in:
Mumshad Mannambeth 2017-06-07 13:36:44 -04:00
commit c92f737237
273 changed files with 16964 additions and 0 deletions

View file

@ -0,0 +1,235 @@
'use strict';
const angular = require('angular');
const uiRouter = require('angular-ui-router');
import routes from './custom_modules.routes';
export class CustomModulesComponent {
/*@ngInject*/
constructor($scope,customModules,$sce,ansi2html,Projects,$uibModal,YAML) {
'ngInject';
$scope.custom_modules = [];
$scope.selectedModule = {module:{},module_code:"",module_unchanged_code:""};
$scope.selected_module_code = "something";
$scope.showNewModuleForm = {value:false};
$scope.getProjects = function(){
$scope.projects = Projects.resource.query(function(){
if($scope.projects.length){
$scope.selectedProjectID = localStorage.selectedProjectID || $scope.projects[0]._id;
$scope.projectSelected($scope.selectedProjectID)
}
})
};
$scope.projectSelected = function(projectID){
localStorage.selectedProjectID = projectID;
$scope.selectedProject = Projects.resource.get({id: projectID},function(){
Projects.selectedProject = $scope.selectedProject;
$scope.getCustomModules();
})
};
$scope.getProjects();
$scope.getCustomModules = function(){
customModules.get(function(response){
console.log(response.data);
var lines = response.data.split("\n");
if(lines.length)
lines = lines
.filter(function(line){return line.indexOf(".py") > -1})
.map(function(item){return {name:item}});
$scope.custom_modules = lines;
if($scope.selectedModule.module.name){
$scope.selectedModule.module = $scope.custom_modules.filter(function(item){
return (item.name == $scope.selectedModule.module.name)
})[0]
}
});
};
$scope.loadingModuleCode = false
$scope.showModuleCode = function(module_name){
$scope.loadingModuleCode = true;
if(!module_name){
$scope.selectedModule.module_code = "Select a module";
return;
}
customModules.show(module_name,function(response) {
$scope.loadingModuleCode = false;
$scope.selectedModule.module_code = response.data.split("Stream :: close")[0];
$scope.selectedModule.module_unchanged_code = angular.copy($scope.selectedModule.module_code);
});
};
$scope.$watch('selectedModule.module',function(newValue,oldValue){
if(newValue.name && newValue.name !== oldValue.name){
$scope.selectedModule.module_code = "Loading Module Code...";
$scope.showModuleCode(newValue.name)
}
});
$scope.code_has_changed = false;
$scope.codeChanged = function(){
console.log("Code Changed");
if($scope.selectedModule.module_unchanged_code !== $scope.selectedModule.module_code){
$scope.code_has_changed = true
}else{
$scope.code_has_changed = false
}
};
$scope.discardCodeChanges = function(){
$scope.selectedModule.module_code = angular.copy($scope.selectedModule.module_unchanged_code);
};
$scope.saveModule = function(){
$scope.saving = true;
customModules.save($scope.selectedModule.module.name,$scope.selectedModule.module_code,function(response){
$scope.saving = false;
$scope.code_has_changed = false;
$scope.selectedModule.module_unchanged_code = angular.copy($scope.selectedModule.module_code);
console.log("Success")
},function(response){
$scope.saving = false;
console.error(response.data)
})
};
$scope.testModule = function(){
var re = /([^]+DOCUMENTATION = '''\s+)([^]+?)(\s+'''[^]+)/;
var module_string = $scope.selectedModule.module_code.replace(re,'$2');
$scope.selectedModuleObject = YAML.parse(module_string);
//var options_copy = angular.copy($scope.selectedModuleObject.options);
var options_copy = {};
/*options_copy = options_copy.map(function(item){
var temp_obj = {};
temp_obj[item.name] = "";
return temp_obj
});*/
var module_name = $scope.selectedModule.module.name;
var module_cached_args = null;
try{
module_cached_args = JSON.parse(localStorage['test_args_'+module_name]);
}catch (e){
console.log("Error getting cached arguments.");
module_cached_args = null;
}
angular.forEach($scope.selectedModuleObject.options,function(value,key){
//var temp_obj = {};
//temp_obj[key] = "";
options_copy[key] = "";
if(module_cached_args && key in module_cached_args){
options_copy[key] = module_cached_args[key];
}
});
var variable = {name:'',complexValue:options_copy};
$scope.showComplexVariable(variable);
};
$scope.newModule = function(){
$scope.showNewModuleForm.value = true;
$scope.$broadcast ('newModule');
};
$scope.editModule = function(){
$scope.showNewModuleForm.value = true;
$scope.$broadcast ('editModule');
};
$scope.showComplexVariable = function(variable){
$scope.result = "";
var modalInstance = $uibModal.open({
animation: true,
/*templateUrl: 'createTaskContent.html',*/
templateUrl: 'app/modals/complex_var_modal/complexVariable.html',
controller: 'ComplexVarModalController',
size: 'sm',
backdrop: 'static',
keyboard: false,
closeByEscape: false,
closeByDocument: false,
resolve: {
path: function () {
return variable.name
},
hostvars: function(){
return null
},
members: function(){
return variable.complexValue
}
}
});
modalInstance.result.then(function (module_args) {
var module_name = $scope.selectedModule.module.name;
/*var args = "";
angular.forEach(selectedItem,function(value,key){
if(value){
args += " " + key + "=" + value
}
});
if(args){
module_name += " -a " + args
}*/
localStorage['test_args_'+module_name] = JSON.stringify(module_args);
$scope.testing = true;
customModules.test(module_name,module_args,function(response) {
$scope.testing = false;
$scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data.split("Stream :: close")[0]).replace(/\n/g, "<br>"));
},
function(response) {
$scope.testing = false;
$scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data.split("Stream :: close")[0]).replace(/\n/g, "<br>"));
});
}, function () {
});
}
}
}
export default angular.module('webAppApp.custom_modules', [uiRouter])
.config(routes)
.component('customModules', {
template: require('./custom_modules.html'),
controller: CustomModulesComponent,
controllerAs: 'customModulesCtrl'
})
.name;

View file

@ -0,0 +1,17 @@
'use strict';
describe('Component: CustomModulesComponent', function() {
// load the controller's module
beforeEach(module('webAppApp.custom_modules'));
var CustomModulesComponent;
// Initialize the controller and a mock scope
beforeEach(inject(function($componentController) {
CustomModulesComponent = $componentController('custom_modules', {});
}));
it('should ...', function() {
expect(1).to.equal(1);
});
});

View file

@ -0,0 +1,55 @@
<div class="row" style="margin:20px;">
<div class="col-md-7">
<div ng-show="!showNewModuleForm.value">
<div style="display: inline-block"><select class="form-control" ng-model="selectedProjectID" ng-change="projectSelected(selectedProjectID)" ng-options="project._id as project.name for project in projects">
</select></div>
<button class="btn btn-default" ng-click="newModule()"> New Module <span class="fa fa-plus"></span> </button>
<button class="btn btn-default" ng-disabled="!selectedModule.module.name || loadingModuleCode" ng-click="editModule()"> Edit Module <span class="fa fa-edit"></span> </button>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Select</th>
<th>Name</th>
<!--<th>Actions</th>-->
</tr>
</thead>
<tbody>
<tr ng-repeat="module in custom_modules">
<td><input name="moduleGroup" type="radio" ng-model="selectedModule.module" ng-value="module">
</td>
</td>
<td>{{module.name}}</td>
<!--<td><div class="btn-group">
<label class="btn btn-default" ng-click="showTaskModal($index)" ><span class="fa fa-edit"></span></label>
<label class="btn btn-danger" ng-click="deleteTask($index)" confirm="Are you sure you want to delete?"><span class="fa fa-trash"></span></label>
<div style="display: inline-block" tooltip-enable="!task.tags" uib-tooltip="Tag must be assigned to play individually"><label class="btn btn-success" ng-disabled="!task.tags" ng-click="executeAnsiblePlayBook(task.tags,'Task',task.name, selectedPlay)" ><span class="fa fa-play"></span></label></div>
<label class="btn btn-primary" ng-disabled="$first" ng-click="moveUp(selectedPlay.play.tasks,$index,'saveTaskListLoading')" ><span class="fa fa-arrow-up"></span></label>
<label class="btn btn-primary" ng-disabled="$last" ng-click="moveDown(selectedPlay.play.tasks,$index,'saveTaskListLoading')" ><span class="fa fa-arrow-down"></span></label>
</div></td>-->
</tr>
</tbody>
</table>
</div>
<!--<button class="btn btn-primary" ng-disabled="!selectedModule.module.name || !code_has_changed" ng-click="saveModule()"> Save <span ng-if="!saving" class="fa fa-save"></span> <span ng-if="saving" class="fa fa-spin fa-spinner"></span> </button>
<button class="btn btn-warning" ng-disabled="!selectedModule.module.name || !code_has_changed" confirm="Are you sure you want to discard code changes?" ng-click="discardCodeChanges()"> Discard <span class="fa fa-rotate-left"></span> </button>-->
<button class="btn btn-default" ng-disabled="!selectedModule.module.name || loadingModuleCode" ng-click="testModule()"> Test <span ng-if="!testing" class="fa fa-check-circle-o"></span> <span ng-if="testing" class="fa fa-spin fa-spinner"></span> </button>
<div style="background:black;color:lightgrey;width:100%;padding:20px;word-wrap: break-word;" ng-if="result">
<p class="logconsole" ng-bind-html="result"></p>
</div>
</div>
<div class="slide-animate" ng-show="showNewModuleForm.value" ng-include="'app/custom_modules/new_module/new_module.html'"></div>
</div>
<div class="col-md-5">
<div ng-readonly="!showNewModuleForm.value" ui-ace="{theme:'twilight',document:'Python',mode:'python',onChange:codeChanged}" ng-model="selectedModule.module_code">
</div>
</div>
</div>

View file

@ -0,0 +1,10 @@
'use strict';
export default function($stateProvider) {
'ngInject';
$stateProvider
.state('custom_modules', {
url: '/custom_modules',
template: '<custom-modules></custom-modules>'
});
}

View file

@ -0,0 +1,30 @@
'use strict';
const angular = require('angular');
/*@ngInject*/
export function customModulesService($http,Projects) {
// AngularJS will instantiate a singleton by calling "new" on this function
var uri = '/api/custom_modules';
this.get = function(successCallback,errorCallback){
$http.post(uri + '/query',{ansibleEngine:Projects.selectedProject.ansibleEngine}).then(successCallback,errorCallback)
};
this.show = function(customModule,successCallback,errorCallback){
$http.post(uri+ '/' + customModule+'/get',{ansibleEngine:Projects.selectedProject.ansibleEngine}).then(successCallback,errorCallback)
};
this.test = function(customModule,module_args,successCallback,errorCallback){
$http.post(uri + '/' + customModule + '/test',{ansibleEngine:Projects.selectedProject.ansibleEngine,moduleArgs:module_args}).then(successCallback,errorCallback)
};
this.save = function(customModule,customModuleCode,successCallback,errorCallback){
$http.post(uri + '/' + customModule,{ansibleEngine:Projects.selectedProject.ansibleEngine,custom_module_code:customModuleCode}).then(successCallback,errorCallback)
}
}
export default angular.module('webAppApp.custom_modules_service', [])
.service('customModules', customModulesService)
.name;

View file

@ -0,0 +1,16 @@
'use strict';
describe('Service: customModules', function() {
// load the service's module
beforeEach(module('webAppApp.custom_modules'));
// instantiate service
var customModules;
beforeEach(inject(function(_customModules_) {
customModules = _customModules_;
}));
it('should do something', function() {
expect(!!customModules).to.be.true;
});
});

View file

@ -0,0 +1,280 @@
'use strict';
const angular = require('angular');
/*@ngInject*/
export function newModuleController($scope,$filter,customModules,ansible,YAML) {
$scope.optionTypes = ['str','list','dict','bool','int','float','path','raw','jsonarg','json','bytes','bits'];
var defaultModule = {
module:null,
short_description:"",
description:"",
version_added:"",
author:"",
notes: "",
requirements: "",
options:[
{
name:"parameter1",
description: 'Description of parameter 1',
required: true,
default: null,
choices: '"choice1", "choice2"',
aliases: '"option1", "argument1"',
type: ""
}
]
};
$scope.loadDefaultTemplate = function(){
$scope.newModule = angular.copy(defaultModule);
$scope.selectedModule.module_code = "Loading Template..";
customModules.show('template.py',function(response) {
$scope.selectedModule.module_code = response.data.split("Stream :: close")[0];
});
};
$scope.$watch('newModule',function(newValue,oldValue){
updateDocumentation(newValue);
updateParameters(newValue);
updateExamples(newValue);
},true);
var updateParameters = function(newValue){
newValue = angular.copy(newValue);
var parameters_definition_lines = [];
var parameters_retreive_lines = [];
angular.forEach(newValue.options,function(option){
if(option.name) {
var line = option.name + "=dict(";
var line_arguments = [];
if (option.required)line_arguments.push("required=True");
if (!option.required && option.default)line_arguments.push("default='" + option.default + "'");
if (option.type)line_arguments.push("type='" + option.type + "'");
if (option.choices)line_arguments.push("choices=[" + option.choices + "]");
if (option.aliases)line_arguments.push("aliases=[" + option.aliases + "]");
line += line_arguments.join(",");
line += ")";
parameters_definition_lines.push(line);
parameters_retreive_lines.push(option.name + ' = module.params[\'' + option.name + '\']')
}
});
var parameters_definition_string = parameters_definition_lines.join(",\n ");
var parameters_retreive_string = parameters_retreive_lines.join("\n ");
var re = /(# <--Begin Parameter Definition -->\s+ )([^]+)(\s+ # <--END Parameter Definition -->)/;
$scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re,"$1" + parameters_definition_string + "$3");
var supports_check_mode_string = '\n';
if(newValue.supports_check_mode){
supports_check_mode_string = '\n supports_check_mode=True\n'
}
var re2 = /(# <--Begin Supports Check Mode -->)([^]+)( # <--End Supports Check Mode -->)/;
$scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re2,"$1" + supports_check_mode_string + "$3");
var re3 = /(# <--Begin Retreiving Parameters -->\s+ )([^]+)(\s+ # <--End Retreiving Parameters -->)/;
$scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re3,"$1" + parameters_retreive_string + "$3");
};
var updateDocumentation = function(newValue){
newValue = angular.copy(newValue);
newValue.options = convertOptionsToObject(newValue.options);
delete newValue['supports_check_mode'];
if(newValue.description)
newValue.description = newValue.description.split(";");
if(newValue.notes)
newValue.notes = newValue.notes.split(";");
if(newValue.requirements)
newValue.requirements = newValue.requirements.split(";");
$scope.documentation_yaml = '---\n' + $filter('json2yaml')(angular.toJson(newValue)).toString().replace(/__dot__/g,".");
//var re = /(.*DOCUMENTATION = '''\n)([^]+?)(\n'''.*)/;
var re = /([^]+DOCUMENTATION = '''\s+)([^]+?)(\s+'''[^]+)/;
$scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re,'$1' + $scope.documentation_yaml + '$3');
};
var updateExamples = function(newValue){
newValue = angular.copy(newValue);
var moduleCopy = {
};
moduleCopy[newValue.module] = convertOptionsToExampleObject(newValue.options);
$scope.example_yaml = YAML.stringify(moduleCopy,4);
//var re = /(.*DOCUMENTATION = '''\n)([^]+?)(\n'''.*)/;
var re = /([^]+EXAMPLES = '''[^]+# <-- -->\s+)([^]+?)(\s+# <-- \/ -->\s+'''[^]+)/;
$scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re,'$1' + $scope.example_yaml + '$3');
};
var convertOptionsToObject = function(options){
var result = {};
angular.forEach(options,function(option){
if(option.name){
result[option.name] = {
description: option.description
};
if(option.required)
result[option.name]['required'] = "True";
else
delete result[option.name]['required'];
if(!option.required && option.default)
result[option.name]['default'] = option.default;
if(option.choices){
result[option.name]['choices'] = "[" + option.choices + "]"
}
if(option.aliases){
result[option.name]['aliases'] = "[" + option.aliases + "]"
}
}
});
return result
};
var convertOptionsToExampleObject = function(options){
var result = {};
angular.forEach(options,function(option){
if(option.name){
result[option.name] = "value";
}
});
return result
};
var convertOptionsToArrays = function(options){
var result = [];
angular.forEach(options,function(value,key){
var option = {
name: key,
description: value.description,
required: value.required,
default:value.default
};
if(value.choices && value.choices.length)
option['choices'] = value.choices.map(function(item){return ('"' + item + '"')}).join(",")
if(value.aliases && value.aliases.length)
option['aliases'] = value.aliases.map(function(item){return ('"' + item + '"')}).join(",")
result.push(option)
});
return result
};
$scope.saveNewModule = function(){
$scope.saving = true;
customModules.save($scope.newModule.module + '.py',$scope.selectedModule.module_code,function(response){
$scope.saving = false;
$scope.getCustomModules();
ansible.getAnsibleModules(function(response){
}, function(response){
},null,true);
$scope.cancelNewModule();
},function(response){
$scope.saving = false;
console.error(response.data)
})
};
$scope.cancelNewModule = function(){
$scope.showNewModuleForm.value = false;
$scope.$parent.showModuleCode($scope.selectedModule.module.name)
};
var getPropertiesFromCode = function(module_code){
//var re = /([^]+DOCUMENTATION = '''\n)([^]+?)(\n'''[^]+)/;
var re = /([^]+DOCUMENTATION = '''\s+)([^]+?)(\s+'''[^]+)/;
var module_string = $scope.selectedModule.module_code.replace(re,'$2');
$scope.newModule = YAML.parse(module_string);
$scope.newModule.options = convertOptionsToArrays($scope.newModule.options);
if($scope.newModule.description && $scope.newModule.description.length)
$scope.newModule.description = $scope.newModule.description.join(";");
if($scope.newModule.notes && $scope.newModule.notes.length)
$scope.newModule.notes = $scope.newModule.notes.join(";");
if($scope.newModule.requirements && $scope.newModule.requirements.length)
$scope.newModule.requirements = $scope.newModule.requirements.join(";");
re = /([^]+# <--Begin Parameter Definition -->\s+ )([^]+)(\s+ # <--END Parameter Definition -->[^]+)/;
var parameter_string = $scope.selectedModule.module_code.replace(re,"$2");
// Read property type form parameter definition
re = /\s+(.*?)=.*type=(.*?)[,\)].*/g;
var m;
while ((m = re.exec(parameter_string)) !== null) {
if (m.index === re.lastIndex) {
re.lastIndex++;
}
// View your result using the m-variable.
// eg m[0] etc.
if(m[1]){
angular.forEach($scope.newModule.options,function(option){
if(option.name === m[1]){
option.type = m[2].replace(/'/g,'')
}
})
}
}
};
$scope.$on('editModule', function(e) {
getPropertiesFromCode($scope.selected_module_code)
});
$scope.$on('newModule', function(e) {
$scope.loadDefaultTemplate();
});
}
export default angular.module('webAppApp.new_module', [])
.controller('NewModuleController', newModuleController)
.name;

View file

@ -0,0 +1,17 @@
'use strict';
describe('Controller: NewModuleCtrl', function() {
// load the controller's module
beforeEach(module('webAppApp.new_module'));
var NewModuleCtrl;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller) {
NewModuleCtrl = $controller('NewModuleCtrl', {});
}));
it('should ...', function() {
expect(1).to.equal(1);
});
});

View file

@ -0,0 +1,144 @@
<div class="row" ng-controller="NewModuleController">
<div class="panel">
<div class="panel panel-primary">
<div class="panel-heading">New Module</div>
<div class="panel-body">
<div class="row">
<div class="col-md-4">
<p class="form-group">
<label>Module Name</label>
<input type="text" ng-model="newModule.module" class="form-control" ng-required="true" placeholder="modulename">
</p>
</div>
<div class="col-md-2">
<p class="form-group">
<label>Version Added</label>
<input type="text" ng-model="newModule.version_added" class="form-control" ng-required="true" placeholder="X.Y">
</p>
</div>
<div class="col-md-2">
<p class="form-group">
<label>Check Mode</label>
<label class="checkbox-inline"><input ng-model="newModule.supports_check_mode" type="checkbox"></label>
</p>
</div>
<div class="col-md-4">
<p class="form-group">
<label>Author</label>
<input type="text" ng-model="newModule.author" class="form-control" ng-required="true" placeholder="Your AWESOME name, @awesome-github-id">
</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
<p class="form-group">
<label>Short Description</label>
<input type="text" ng-model="newModule.short_description" class="form-control" ng-required="true" placeholder="This is a sentence describing the module">
</p>
</div>
<div class="col-md-8">
<p class="form-group">
<label>Description</label>
<input type="text" ng-model="newModule.description" class="form-control" ng-required="true" placeholder="Longer description of the module; You might include instructions">
</p>
</div>
</div>
<p class="form-group">
<label>Notes</label>
<input type="text" ng-model="newModule.notes" class="form-control" ng-required="true" placeholder="Other things consumers of your module should know">
</p>
<p class="form-group">
<label>Requirements</label>
<input type="text" ng-model="newModule.requirements" class="form-control" ng-required="true" placeholder="List of required things separated by semicolon; like the factor package; or a specific platform">
</p>
<div class="panel">
<div class="panel panel-default">
<div class="panel-heading">Options</div>
<div class="panel-body">
<div class="row">
<div class="col-md-2" style="text-align: center">
<label>Name</label>
</div>
<div class="col-md-2" style="text-align: center">
<label>Description</label>
</div>
<div class="col-md-1" style="text-align: center">
<label>Type</label>
</div>
<div class="col-md-1" style="text-align: center">
<label>Required</label>
</div>
<div class="col-md-1" style="text-align: center">
<label>Default</label>
</div>
<div class="col-md-2" style="text-align: center">
<label>Aliases</label>
</div>
<div class="col-md-2" style="text-align: center">
<label>Choices</label>
</div>
<div class="col-md-1" style="text-align: center">
<label>Actions</label>
</div>
</div>
<div ng-repeat="option in newModule.options">
<div class="row">
<div class="col-md-2" style="padding:2px;">
<input type="text" ng-model="option.name" class="form-control">
</div>
<div class="col-md-2" style="padding:2px;">
<!--<input type="text" ng-model="member.value">-->
<input type="text" ng-model="option.description" class="form-control">
</div>
<div class="col-md-1" style="padding:2px;">
<select class="form-control" ng-model="option.type" ng-options="optionType for optionType in optionTypes">
</select>
</div>
<div class="col-md-1" style="padding:2px;text-align: center">
<label class="checkbox-inline"><input ng-model="option.required" type="checkbox" value=""></label>
</div>
<div class="col-md-1" style="padding:2px;">
<input type="text" ng-disabled="option.required" ng-model="option.default" class="form-control">
</div>
<div class="col-md-2" style="padding:2px;">
<input type="text" ng-model="option.aliases" class="form-control">
</div>
<div class="col-md-2" style="padding:2px;">
<input type="text" ng-model="option.choices" class="form-control">
</div>
<div class="col-md-1" style="padding:2px;text-align: center;">
<div class="btn-group">
<label class="btn btn-default" uib-tooltip="Insert new row after" ng-click="newModule.options.splice($index+1,0,{})" style="padding-top: 3px;padding-bottom: 3px;padding-left:5px;padding-right:5px;"><span class="fa fa-plus"></span></label>
<label class="btn btn-danger" uib-tooltip="Delete row" ng-click="newModule.options.splice($index,1)" style="padding-top: 3px;padding-bottom: 3px; padding-left:5px; padding-right:5px;"><span class="fa fa-minus"></span></label>
</div>
</div>
</div>
<!--<input type="text" ng-model="member.value" uib-typeahead="('&quot;\{\{' + var.name + '\}\}&quot;') as (var.name + ' = ' + var.value) for var in hostvars | filter: $viewValue" class="form-control">-->
</div>
<!--<button class="btn btn-danger" ng-click="deleteProject()" confirm="Are you sure you want to delete?">Delete <span ng-if="!deleteProjectLoading" class="fa fa-trash"></span> <span ng-if="deleteProjectLoading" class="fa fa-spinner fa-spin"></span></button>-->
</div>
</div>
</div>
</div>
</div>
<button class="btn btn-primary" ng-disabled="!newModule.module" ng-click="saveNewModule()"> Save <span ng-if="!saving" class="fa fa-save"></span> <span ng-if="saving" class="fa fa-spin fa-spinner"></span> </button>
<button class="btn btn-default" confirm="Are you sure you want to discard the changes?" ng-click="cancelNewModule()"> Cancel <span class="fa fa-times"></span> </button>
</div>
</div>