Move code under lib/ansible to js/shared

This commit is contained in:
Joe Fiorini
2015-02-25 11:34:29 -05:00
parent f28543ecae
commit 36a596b68f
17 changed files with 52 additions and 18 deletions

View File

@@ -0,0 +1,159 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:AuthService
* @description AuthService.js
*
* User authentication functions
*
*/
import Utilities from './Utilities';
export default
angular.module('AuthService', ['ngCookies', Utilities.name])
.factory('Authorization', ['$http', '$rootScope', '$location', '$cookieStore', 'GetBasePath', 'Store',
function ($http, $rootScope, $location, $cookieStore, GetBasePath, Store) {
return {
setToken: function (token, expires) {
// set the session cookie
$cookieStore.remove('token');
$cookieStore.remove('token_expires');
$cookieStore.remove('userLoggedIn');
$cookieStore.put('token', token);
$cookieStore.put('token_expires', expires);
$cookieStore.put('userLoggedIn', true);
$cookieStore.put('sessionExpired', false);
$rootScope.token = token;
$rootScope.userLoggedIn = true;
$rootScope.token_expires = expires;
$rootScope.sessionExpired = false;
},
isUserLoggedIn: function () {
if ($rootScope.userLoggedIn === undefined) {
// Browser refresh may have occurred
$rootScope.userLoggedIn = $cookieStore.get('userLoggedIn');
$rootScope.sessionExpired = $cookieStore.get('sessionExpired');
}
return $rootScope.userLoggedIn;
},
getToken: function () {
return ($rootScope.token) ? $rootScope.token : $cookieStore.get('token');
},
retrieveToken: function (username, password) {
return $http({
method: 'POST',
url: GetBasePath('authtoken'),
data: {
"username": username,
"password": password
}
});
},
logout: function () {
// the following puts our primary scope up for garbage collection, which
// should prevent content flash from the prior user.
var scope = angular.element(document.getElementById('main-view')).scope();
scope.$destroy();
//$rootScope.$destroy();
$cookieStore.remove('token_expires');
$cookieStore.remove('current_user');
if($cookieStore.get('lastPath')==='/portal'){
$cookieStore.put( 'lastPath', '/portal');
$rootScope.lastPath = '/portal';
}
else {
$cookieStore.remove('lastPath');
$rootScope.lastPath = '/home';
}
$cookieStore.remove('token');
$cookieStore.put('userLoggedIn', false);
$cookieStore.put('sessionExpired', false);
$cookieStore.put('current_user', {});
$rootScope.current_user = {};
$rootScope.license_tested = undefined;
$rootScope.userLoggedIn = false;
$rootScope.sessionExpired = false;
$rootScope.token = null;
$rootScope.token_expires = null;
$rootScope.login_username = null;
$rootScope.login_password = null;
},
getLicense: function () {
return $http({
method: 'GET',
url: GetBasePath('config'),
headers: {
'Authorization': 'Token ' + this.getToken()
}
});
},
setLicense: function (data) {
var license = data.license_info;
license.version = data.version;
license.tested = false;
Store('license', license);
},
licenseTested: function () {
var license, result;
if ($rootScope.license_tested !== undefined) {
result = $rootScope.license_tested;
} else {
// User may have hit browser refresh
license = Store('license');
$rootScope.version = license.version;
if (license && license.tested !== undefined) {
result = license.tested;
} else {
result = false;
}
}
return result;
},
getUser: function () {
return $http({
method: 'GET',
url: '/api/v1/me/',
headers: {
'Authorization': 'Token ' + this.getToken()
}
});
},
setUserInfo: function (response) {
// store the response values in $rootScope so we can get to them later
$rootScope.current_user = response.results[0];
$cookieStore.put('current_user', response.results[0]); //keep in session cookie in the event of browser refresh
$rootScope.$emit('OpenSocket');
},
restoreUserInfo: function () {
$rootScope.current_user = $cookieStore.get('current_user');
},
getUserInfo: function (key) {
// Access values returned from the Me API call
var cu;
if ($rootScope.current_user) {
return $rootScope.current_user[key];
}
this.restoreUserInfo();
cu = $cookieStore.get('current_user');
return cu[key];
}
};
}
]);

View File

@@ -0,0 +1,606 @@
/************************************
*
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:inventoryTree
* @description
* InventoryTree.js
*
* Build data for the tree selector table used on inventory detail page.
*
*/
export default
angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'PromptDialog'])
.factory('SortNodes', [
function () {
return function (data) {
//Sort nodes by name
var i, j, names = [], newData = [];
for (i = 0; i < data.length; i++) {
names.push(data[i].name);
}
names.sort();
for (j = 0; j < names.length; j++) {
for (i = 0; i < data.length; i++) {
if (data[i].name === names[j]) {
newData.push(data[i]);
}
}
}
return newData;
};
}
])
.factory('BuildTree', ['$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'SortNodes', 'Wait', 'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Store',
function ($location, Rest, GetBasePath, ProcessErrors, SortNodes, Wait, GetSyncStatusMsg, GetHostsStatusMsg, Store) {
return function (params) {
var inventory_id = params.inventory_id,
scope = params.scope,
refresh = params.refresh,
emit = params.emit,
new_group_id = params.new_group_id,
groups = [],
id = 1,
local_child_store,
path = $location.path();
function buildAllHosts(tree_data) {
// Start our tree object with All Hosts
var children = [],
sorted = SortNodes(tree_data),
j, all_hosts;
for (j = 0; j < sorted.length; j++) {
children.push(sorted[j].id);
}
all_hosts = {
name: 'All Hosts',
id: 1,
group_id: null,
parent: 0,
description: '',
show: true,
ngicon: null,
has_children: false,
related: {},
selected_class: '',
show_failures: false,
isDraggable: false,
isDroppable: true,
children: children
};
groups.push(all_hosts);
}
function getExpandState(key) {
var result = true;
local_child_store.every(function(child) {
if (child.key === key) {
result = child.expand;
return false;
}
return true;
});
return result;
}
function getShowState(key) {
var result = null;
local_child_store.every(function(child) {
if (child.key === key) {
result = (child.show !== undefined) ? child.show : true;
return false;
}
return true;
});
return result;
}
function buildGroups(tree_data, parent, level) {
var children, stat, hosts_status, group,
sorted = SortNodes(tree_data),
expand, show;
sorted.forEach( function(row, i) {
id++;
stat = GetSyncStatusMsg({
status: sorted[i].summary_fields.inventory_source.status,
has_inventory_sources: sorted[i].has_inventory_sources,
source: ( (sorted[i].summary_fields.inventory_source) ? sorted[i].summary_fields.inventory_source.source : null )
}); // from helpers/Groups.js
hosts_status = GetHostsStatusMsg({
active_failures: sorted[i].hosts_with_active_failures,
total_hosts: sorted[i].total_hosts,
inventory_id: inventory_id,
group_id: sorted[i].id
}); // from helpers/Groups.js
children = [];
sorted[i].children.forEach( function(child, j) {
children.push(sorted[i].children[j].id);
});
expand = (sorted[i].children.length > 0) ? getExpandState(sorted[i].id) : false;
show = getShowState(sorted[i].id);
if (show === null) {
// this is a node we haven't seen before, so check the parent expand/collapse state
// If parent is not expanded, then child should be hidden.
show = true;
if (parent > 0) {
groups.every(function(g) {
if (g.id === parent) {
show = getExpandState(g.key);
return false;
}
return true;
});
}
}
group = {
name: sorted[i].name,
has_active_failures: sorted[i].has_active_failures,
total_hosts: sorted[i].total_hosts,
hosts_with_active_failures: sorted[i].hosts_with_active_failures,
total_groups: sorted[i].total_groups,
groups_with_active_failures: sorted[i].groups_with_active_failures,
parent: parent,
has_children: (sorted[i].children.length > 0) ? true : false,
has_inventory_sources: sorted[i].has_inventory_sources,
id: id,
source: sorted[i].summary_fields.inventory_source.source,
key: sorted[i].id,
group_id: sorted[i].id,
event_level: level,
children: children,
show: show,
related: sorted[i].related,
status: sorted[i].summary_fields.inventory_source.status,
status_class: stat['class'],
status_tooltip: stat.tooltip,
launch_tooltip: stat.launch_tip,
launch_class: stat.launch_class,
hosts_status_tip: hosts_status.tooltip,
show_failures: hosts_status.failures,
hosts_status_class: hosts_status['class'],
inventory_id: inventory_id,
selected_class: '',
isDraggable: true,
isDroppable: true
};
if (sorted[i].children.length > 0) {
if (expand) {
group.ngicon = 'fa fa-minus-square-o node-toggle';
}
else {
group.ngicon = 'fa fa-plus-square-o node-toggle';
}
}
else {
group.ngicon = 'fa fa-square-o node-no-toggle';
}
if (new_group_id && group.group_id === new_group_id) {
scope.selected_tree_id = id;
scope.selected_group_id = group.group_id;
}
groups.push(group);
if (sorted[i].children.length > 0) {
buildGroups(sorted[i].children, id, level + 1);
}
});
}
// Build the HTML for our tree
if (scope.buildAllGroupsRemove) {
scope.buildAllGroupsRemove();
}
scope.buildAllGroupsRemove = scope.$on('buildAllGroups', function (e, inventory_name, inventory_tree) {
Rest.setUrl(inventory_tree);
Rest.get()
.success(function (data) {
buildAllHosts(data);
buildGroups(data, 0, 0);
scope.autoShowGroupHelp = (data.length === 0) ? true : false;
if (refresh) {
scope.groups = groups;
scope.$emit('GroupTreeRefreshed', inventory_name, groups, emit);
} else {
scope.$emit('GroupTreeLoaded', inventory_name, groups, emit);
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory tree for: ' + inventory_id + '. GET returned: ' + status
});
});
});
function loadTreeData() {
// Load the inventory root node
Wait('start');
Rest.setUrl(GetBasePath('inventory') + inventory_id + '/');
Rest.get()
.success(function (data) {
scope.$emit('buildAllGroups', data.name, data.related.tree, data.related.groups);
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status
});
});
}
local_child_store = Store(path + '_children');
if (!local_child_store) {
local_child_store = [];
}
loadTreeData();
};
}
])
// Update a group with a set of properties
.factory('UpdateGroup', ['ApplyEllipsis', 'GetSyncStatusMsg', 'Empty',
function (ApplyEllipsis, GetSyncStatusMsg, Empty) {
return function (params) {
var scope = params.scope,
group_id = params.group_id,
properties = params.properties,
i, p, grp, old_name, stat;
for (i = 0; i < scope.groups.length; i++) {
if (scope.groups[i].id === group_id) {
grp = scope.groups[i];
for (p in properties) {
if (p === 'name') {
old_name = scope.groups[i].name;
}
if (p === 'source') {
if (properties[p] !== scope.groups[i][p]) {
// User changed source
if (!Empty(properties[p]) && (scope.groups[i].status === 'none' || Empty(scope.groups[i].status))) {
// We have a source but no status, seed the status with 'never' to enable sync button
scope.groups[i].status = 'never updated';
} else if (!properties[p]) {
// User removed source
scope.groups[i].status = 'none';
}
// Update date sync status links/icons
stat = GetSyncStatusMsg({
status: scope.groups[i].status,
has_inventory_sources: properties.has_inventory_sources,
source: properties.source
});
scope.groups[i].status_class = stat['class'];
scope.groups[i].status_tooltip = stat.tooltip;
scope.groups[i].launch_tooltip = stat.launch_tip;
scope.groups[i].launch_class = stat.launch_class;
}
}
scope.groups[i][p] = properties[p];
}
}
/*if (scope.groups[i].id === scope.selected_tree_id) {
//Make sure potential group name change gets reflected throughout the page
scope.selected_group_name = scope.groups[i].name;
scope.search_place_holder = 'Search ' + scope.groups[i].name;
scope.hostSearchPlaceholder = 'Search ' + scope.groups[i].name;
}*/
}
// Update any titles attributes created by ApplyEllipsis
if (old_name) {
setTimeout(function () {
$('#groups_table .group-name a[title="' + old_name + '"]').attr('title', properties.name);
ApplyEllipsis('#groups_table .group-name a');
}, 2500);
}
};
}
])
// Set node name and description after an update to Group properties.
.factory('SetNodeName', [
function () {
return function (params) {
var name = params.name,
descr = params.description,
group_id = (params.group_id !== undefined) ? params.group_id : null,
inventory_id = (params.inventory_id !== undefined) ? params.inventory_id : null;
if (group_id !== null) {
$('#inventory-tree').find('li [data-group-id="' + group_id + '"]').each(function () {
$(this).attr('data-name', name);
$(this).attr('data-description', descr);
$(this).find('.activate').first().text(name);
});
}
if (inventory_id !== null) {
$('#inventory-root-node').attr('data-name', name).attr('data-description', descr).find('.activate').first().text(name);
}
};
}
])
// Copy or Move a group on the tree after drag-n-drop
.factory('CopyMoveGroup', ['$compile', 'Alert', 'ProcessErrors', 'Find', 'Wait', 'Rest', 'Empty', 'GetBasePath',
function ($compile, Alert, ProcessErrors, Find, Wait, Rest, Empty, GetBasePath) {
return function (params) {
var scope = params.scope,
target = Find({ list: scope.groups, key: 'id', val: params.target_tree_id }),
inbound = Find({ list: scope.groups, key: 'id', val: params.inbound_tree_id }),
e, html = '';
// Build the html for our prompt dialog
html += "<div id=\"copy-prompt-modal\" class=\"modal fade\">\n";
html += "<div class=\"modal-dialog\">\n";
html += "<div class=\"modal-content\">\n";
html += "<div class=\"modal-header\">\n";
html += "<button type=\"button\" class=\"close\" data-target=\"#copy-prompt-modal\" " +
"data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n";
if (target.id === 1 || inbound.parent === 0) {
// We're moving the group to the top level, or we're moving a top level group down
html += "<h3>Move Group</h3>\n";
} else {
html += "<h3>Copy or Move?</h3>\n";
}
html += "</div>\n";
html += "<div class=\"modal-body\">\n";
if (target.id === 1) {
html += "<div class=\"alert alert-info\">Are you sure you want to move group " + inbound.name + " to the top level?</div>";
} else if (inbound.parent === 0) {
html += "<div class=\"alert alert-info\">Are you sure you want to move group " + inbound.name + " from the top level and make it a child of " +
target.name + "?</div>";
} else {
html += "<div class=\"text-center\">\n";
html += "<p>Would you like to copy or move group <em>" + inbound.name + "</em> to group <em>" + target.name + "</em>?</p>\n";
html += "<div style=\"margin-top: 30px;\">\n";
html += "<a href=\"\" ng-click=\"moveGroup()\" class=\"btn btn-primary\" style=\"margin-right: 15px;\"><i class=\"fa fa-cut\"></i> Move</a>\n";
html += "<a href=\"\" ng-click=\"copyGroup()\" class=\"btn btn-primary\"><i class=\"fa fa-copy\"></i> Copy</a>\n";
html += "</div>\n";
html += "</div>\n";
}
html += "</div>\n";
html += "<div class=\"modal-footer\">\n";
html += "<a href=\"#\" data-target=\"#prompt-modal\" data-dismiss=\"modal\" class=\"btn btn-default\">Cancel</a>\n";
if (target.id === 1 || inbound.parent === 0) {
// We're moving the group to the top level, or we're moving a top level group down
html += "<a href=\"\" data-target=\"#prompt-modal\" ng-click=\"moveGroup()\" class=\"btn btn-primary\">Yes</a>\n";
}
html += "</div>\n";
html += "</div><!-- modal-content -->\n";
html += "</div><!-- modal-dialog -->\n";
html += "</div><!-- modal -->\n";
// Inject our custom dialog
e= angular.element(document.getElementById('inventory-modal-container'));
e.empty().append(html);
$compile(e)(scope);
// Display it
$('#copy-prompt-modal').modal({
backdrop: 'static',
keyboard: true,
show: true
});
// Respond to move
scope.moveGroup = function () {
var url, group, parent;
$('#copy-prompt-modal').modal('hide');
Wait('start');
// disassociate the group from the original parent
if (scope.removeGroupRemove) {
scope.removeGroupRemove();
}
scope.removeGroupRemove = scope.$on('removeGroup', function () {
if (inbound.parent > 0) {
// Only remove a group from a parent when the parent is a group and not the inventory root
parent = Find({ list: scope.groups, key: 'id', val: inbound.parent });
url = GetBasePath('base') + 'groups/' + parent.group_id + '/children/';
Rest.setUrl(url);
Rest.post({ id: inbound.group_id, disassociate: 1 })
.success(function () {
//Triggers refresh of group list in inventory controller
scope.$emit('GroupDeleteCompleted');
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to remove ' + inbound.name +
' from ' + parent.name + '. POST returned status: ' + status
});
});
} else {
//Triggers refresh of group list in inventory controller
scope.$emit('GroupDeleteCompleted');
}
});
// add the new group to the target parent
url = (!Empty(target.group_id)) ?
GetBasePath('base') + 'groups/' + target.group_id + '/children/' :
GetBasePath('inventory') + scope.inventory_id + '/groups/';
group = {
id: inbound.group_id,
name: inbound.name,
description: inbound.description,
inventory: scope.inventory_id
};
Rest.setUrl(url);
Rest.post(group)
.success(function () {
scope.$emit('removeGroup');
})
.error(function (data, status) {
var target_name = (Empty(target.group_id)) ? 'inventory' : target.name;
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + inbound.name + ' to ' + target_name + '. POST returned status: ' + status });
});
};
scope.copyGroup = function () {
$('#copy-prompt-modal').modal('hide');
Wait('start');
// add the new group to the target parent
var url = (!Empty(target.group_id)) ?
GetBasePath('base') + 'groups/' + target.group_id + '/children/' :
GetBasePath('inventory') + scope.inventory_id + '/groups/',
group = {
id: inbound.group_id,
name: inbound.name,
description: inbound.description,
inventory: scope.inventory_id
};
Rest.setUrl(url);
Rest.post(group)
.success(function () {
//Triggers refresh of group list in inventory controller
scope.$emit('GroupDeleteCompleted');
})
.error(function (data, status) {
var target_name = (Empty(target.group_id)) ? 'inventory' : target.name;
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + inbound.name + ' to ' + target_name + '. POST returned status: ' + status
});
});
};
};
}
])
// Copy a host after drag-n-drop
.factory('CopyMoveHost', ['$compile', 'Alert', 'ProcessErrors', 'Find', 'Wait', 'Rest', 'Empty', 'GetBasePath',
function ($compile, Alert, ProcessErrors, Find, Wait, Rest, Empty, GetBasePath) {
return function (params) {
var scope = params.scope,
target = Find({ list: scope.groups, key: 'id', val: params.target_tree_id }),
host = Find({ list: scope.hosts, key: 'id', val: params.host_id }),
found = false, e, i, html = '';
if (host.summary_fields.all_groups) {
for (i = 0; i < host.summary_fields.all_groups.length; i++) {
if (host.summary_fields.all_groups[i].id === target.group_id) {
found = true;
break;
}
}
}
if (found) {
html += "<div id=\"copy-alert-modal\" class=\"modal fade\">\n";
html += "<div class=\"modal-dialog\">\n";
html += "<div class=\"modal-content\">\n";
html += "<div class=\"modal-header\">\n";
html += "<button type=\"button\" class=\"close\" ng-hide=\"disableButtons\" data-target=\"#copy-alert-modal\"\n";
html += "data-dismiss=\"modal\" class=\"modal\" aria-hidden=\"true\">&times;</button>\n";
html += "<h3>Already in Group</h3>\n";
html += "</div>\n";
html += "<div class=\"modal-body\">\n";
html += "<div class=\"alert alert-info\"><p>Host " + host.name + " is already in group " + target.name + ".</p></div>\n";
html += "</div>\n";
html += "<div class=\"modal-footer\">\n";
html += "<a href=\"#\" data-target=\"#copy-alert-modal\" data-dismiss=\"modal\" class=\"btn btn-primary\">OK</a>\n";
html += "</div>\n";
html += "</div>\n";
html += "</div>\n";
html += "</div>\n";
// Inject our custom dialog
e = angular.element(document.getElementById('inventory-modal-container'));
e.empty().append(html);
$compile(e)(scope);
// Display it
$('#copy-alert-modal').modal({
backdrop: 'static',
keyboard: true,
show: true
});
} else {
// Build the html for our prompt dialog
html = '';
html += "<div id=\"copy-prompt-modal\" class=\"modal fade\">\n";
html += "<div class=\"modal-dialog\">\n";
html += "<div class=\"modal-content\">\n";
html += "<div class=\"modal-header\">\n";
html += "<button type=\"button\" class=\"close\" data-target=\"#copy-prompt-modal\" " +
"data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n";
html += "<h3>Copy Host</h3>\n";
html += "</div>\n";
html += "<div class=\"modal-body\">\n";
html += "<div class=\"alert alert-info\">Are you sure you want to copy host " + host.name + ' to group ' + target.name + '?</div>';
html += "</div>\n";
html += "<div class=\"modal-footer\">\n";
html += "<a href=\"#\" data-target=\"#prompt-modal\" data-dismiss=\"modal\" class=\"btn btn-default\">No</a>\n";
html += "<a href=\"\" data-target=\"#prompt-modal\" ng-click=\"copyHost()\" class=\"btn btn-primary\">Yes</a>\n";
html += "</div>\n";
html += "</div><!-- modal-content -->\n";
html += "</div><!-- modal-dialog -->\n";
html += "</div><!-- modal -->\n";
// Inject our custom dialog
e = angular.element(document.getElementById('inventory-modal-container'));
e.empty().append(html);
$compile(e)(scope);
// Display it
$('#copy-prompt-modal').modal({
backdrop: 'static',
keyboard: true,
show: true
});
scope.copyHost = function () {
$('#copy-prompt-modal').modal('hide');
Wait('start');
Rest.setUrl(GetBasePath('groups') + target.group_id + '/hosts/');
Rest.post(host)
.success(function () {
// Signal the controller to refresh the hosts view
scope.$emit('GroupTreeRefreshed');
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to add ' + host.name + ' to ' +
target.name + '. POST returned status: ' + status });
});
};
}
};
}
]);

View File

@@ -0,0 +1,240 @@
/************************************
*
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:Modal
* @description
* Modal.js
*
* Create a draggable, resizable modal dialog using jQueryUI.
*
*
*/
export default
angular.module('ModalDialog', ['Utilities', 'ParseHelper'])
/**
* @ngdoc method
* @name lib.ansible.function:Modal#CreateDialog
* @methodOf lib.ansible.function:Modal
* @description
* CreateDialog({
* id: - id attribute value of the target DOM element
* scope: - Required, $scope associated with the #id DOM element
* buttons: - Required, Array of button objects. See example below.
* width: - Desired width of modal dialog on open. Defaults to 500.
* height: - Desired height of modal on open. Defaults to 600.
* minWidth: - Minimum width that must be maintained regardless of reize attempts. Defaults to 400.
* title: - Modal window title, optional
* onResizeStop: - Function to call when user stops resizing the dialog, optional
* onClose: - Function to call after window closes, optional
* onOpen: - Function to call after window opens, optional
* beforeDestroy: - Function to call during onClose and prior to destroying the window
* callback: - String to pass to scope.$emit() after dialog is created, optional
* })
*
* Note that the dialog will be created but not opened. It's up to the caller to open it. Use callback
* option to respond to dialog created event.
*/
.factory('CreateDialog', ['Empty', function(Empty) {
return function(params) {
var scope = params.scope,
buttonSet = params.buttons,
width = params.width || 500,
height = params.height || 600,
minWidth = params.minWidth || 300,
title = params.title || '',
onResizeStop = params.onResizeStop,
onClose = params.onClose,
onOpen = params.onOpen,
callback = params.callback,
beforeDestroy = params.beforeDestroy,
closeOnEscape = (params.closeOnEscape === undefined) ? false : params.closeOnEscape,
resizable = (params.resizable === undefined) ? true : params.resizable,
forms = _.chain([params.form]).flatten().compact().value(),
buttons,
id = params.id,
x, y, wh, ww;
function updateButtonStatus(isValid) {
$('.ui-dialog[aria-describedby="' + id + '"]').find('.btn-primary').prop('disabled', !isValid);
}
if (Empty(buttonSet)) {
// Default button object
buttonSet = [{
label: "OK",
onClick: function() {
scope.modalOK();
},
icon: "",
"class": "btn btn-primary",
"id": "dialog-ok-button"
}];
}
buttons = {};
buttonSet.forEach( function(btn) {
buttons[btn.label] = btn.onClick;
});
// Set modal dimensions based on viewport width
ww = $(document).width();
wh = $('body').height();
x = (width > ww) ? ww - 10 : width;
y = (height > wh) ? wh - 10 : height;
// Create the modal
$('#' + id).dialog({
buttons: buttons,
modal: true,
width: x,
height: y,
autoOpen: false,
minWidth: minWidth,
title: title,
closeOnEscape: closeOnEscape,
resizable: resizable,
create: function () {
// Fix the close button
$('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).text('x');
setTimeout(function() {
// Make buttons bootstrapy
$('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-buttonset button').each(function () {
var txt = $(this).text(), self = $(this);
buttonSet.forEach(function(btn) {
if (txt === btn.label) {
self.attr({ "class": btn['class'], "id": btn.id });
if (btn.icon) {
self.empty().html('<i class="fa ' + btn.icon + '"></i> ' + btn.label);
}
}
});
});
}, 300);
if (forms.length > 0) {
forms.map(function(form_ctrl) {
scope.$watch(form_ctrl.$name + '.$valid', updateButtonStatus);
});
}
setTimeout(function() {
scope.$apply(function() {
scope.$emit(callback);
});
}, 300);
},
resizeStop: function () {
// for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100%
var dialog = $('.ui-dialog[aria-describedby="' + id + '"]'),
titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(),
buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(),
content = dialog.find('#' + id);
content.width(dialog.width() - 28);
content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) });
if (onResizeStop) {
onResizeStop();
}
},
close: function () {
// Destroy on close
$('.tooltip').each(function () {
// Remove any lingering tooltip <div> elements
$(this).remove();
});
$('.popover').each(function () {
// remove lingering popover <div> elements
$(this).remove();
});
if (beforeDestroy) {
beforeDestroy();
}
$('#' + id).dialog('destroy');
$('#' + id).hide();
if (onClose) {
onClose();
}
},
open: function () {
$('.tooltip').each(function () {
// Remove any lingering tooltip <div> elements
$(this).remove();
});
$('.popover').each(function () {
// remove lingering popover <div> elements
$(this).remove();
});
if (onOpen) {
onOpen();
}
}
});
};
}])
/**
* TextareaResize({
* scope: - $scope associated with the textarea element
* textareaId: - id attribute value of the textarea
* modalId: - id attribute of the <div> element used to create the modal
* formId: - id attribute of the textarea's parent form
* parse: - if true, call ParseTypeChange and replace textarea with codemirror editor
* fld: - optional, form field name
* bottom_margin: - optional, integer value for additional margin to leave below the textarea
* onChange; - optional, function to call when the textarea value changes
* })
*
* Use to resize a textarea field contained on a modal. Has only been tested where the
* form contains 1 textarea and the the textarea is at the bottom of the form/modal.
*
**/
.factory('TextareaResize', ['ParseTypeChange', 'Wait', function(ParseTypeChange, Wait){
return function(params) {
var scope = params.scope,
textareaId = params.textareaId,
modalId = params.modalId,
formId = params.formId,
fld = params.fld,
parse = (params.parse === undefined) ? true : params.parse,
bottom_margin = (params.bottom_margin) ? params.bottom_margin : 0,
onChange = params.onChange,
textarea,
formHeight, model, windowHeight, offset, rows;
function waitStop() {
Wait('stop');
}
// Attempt to create the largest textarea field that will fit on the window. Minimum
// height is 6 rows, so on short windows you will see vertical scrolling
textarea = $('#' + textareaId);
if (scope.codeMirror) {
model = textarea.attr('ng-model');
scope[model] = scope.codeMirror.getValue();
scope.codeMirror.destroy();
}
textarea.attr('rows', 1);
formHeight = $('#' + formId).height();
windowHeight = $('#' + modalId).height() - 20; //leave a margin of 20px
offset = Math.floor(windowHeight - formHeight - bottom_margin);
rows = Math.floor(offset / 20);
rows = (rows < 6) ? 6 : rows;
textarea.attr('rows', rows);
while(rows > 6 && ($('#' + formId).height() > $('#' + modalId).height() + bottom_margin)) {
rows--;
textarea.attr('rows', rows);
}
if (parse) {
ParseTypeChange({ scope: scope, field_id: textareaId, onReady: waitStop, variable: fld, onChange: onChange });
}
};
}]);

View File

@@ -0,0 +1,271 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:RestServices
* @description
*
* A wrapper for angular's $http service. Post user authentication API requests should go through Rest rather than directly using $http. The goal is to decouple
* the application from $http, allowing Rest or anything that provides the same methods to handle setting authentication headers and performing tasks such as checking
* for an expired authentication token. Having this buffer between the application and $http will prove useful should the authentication scheme change.
*
* #.setUrl(<url>)
*
* Before calling an action methods (i.e. get, put, post, destroy, options) to send the request, call setUrl. Pass a string containing the URL endpoint and any parameters.
* Note that $http will automaticall encode the URL, replacing spaces and special characters with appropriate %hex codes. Example URL values might include:
*
* ```
* /api/v1/inventories/9/
* /api/v1/credentials/?name=SSH Key&kind=ssh
* ```
*
* When constructing the URL be sure to use the GetBasePath() method found in lib/ansible/Utilities.js. GetBasePath uses the response objects from /api and
* /api/<version>/to construct the base portion of the path. This way the API version number and base endpoints are not hard-coded within the application.
*
* #Action methods: .get(), put(<JSON data object>), .post(<JSON data object>), .destroy(<JSON data object>), options()
*
* Use the method matching the REST action to be performed. In the case of put, post and destroy a JSON object can be passed as a parameter. If included,
* it will be sent as part of the request.
*
* In every case a call to an action method returns a promise object, allowing the caller to pass reponse functions to the promise methods. The functions can inspect
* the respoinse details and initiate action. For example:
*
* ```
* var url = GetBasePath('inventories') + $routeParams.id + '/';
* Rest.setUrl(url);
* Rest.get()
* .success(function(data) {
* // review the data object and take action
* })
* .error(function(status, data) {
* // handle the error - typically a call to ProcessErrors() found in lib/ansible/Utitlties.js
* });
* ```
*
* ##Options Reqeusts
*
* options() requests are used by the GetChoices() method found in lib/ansible/Utilities.js. Sending an Options request to an API endpoint returns an object that includes
* possible values for fields that are typically presented in the UI as dropdowns or &lt;select&gt; elements. GetChoices will inspect the response object for the request
* field and return an array of { label: 'Choice Label', value: 'choice 1' } objects.
*
*/
import AuthService from './AuthService';
export default
angular.module('RestServices', ['ngCookies', AuthService.name])
.factory('Rest', ['$http', '$rootScope', '$cookieStore', '$q', 'Authorization',
function ($http, $rootScope, $cookieStore, $q, Authorization) {
return {
headers: {},
setUrl: function (url) {
this.url = url;
},
checkExpired: function () {
return ($rootScope.sessionTimer) ? $rootScope.sessionTimer.isExpired() : false;
},
pReplace: function () {
//in our url, replace :xx params with a value, assuming
//we can find it in user supplied params.
var key, rgx;
for (key in this.params) {
rgx = new RegExp("\\:" + key, 'gm');
if (rgx.test(this.url)) {
this.url = this.url.replace(rgx, this.params[key]);
delete this.params[key];
}
}
},
createResponse: function (data, status) {
// Simulate an http response when a token error occurs
// http://stackoverflow.com/questions/18243286/angularjs-promises-simulate-http-promises
var promise = $q.reject({
data: data,
status: status
});
promise.success = function (fn) {
promise.then(function (response) {
fn(response.data, response.status);
}, null);
return promise;
};
promise.error = function (fn) {
promise.then(null, function (response) {
fn(response.data, response.status);
});
return promise;
};
return promise;
},
setHeader: function (hdr) {
// Pass in { key: value } pairs to be added to the header
for (var h in hdr) {
this.headers[h] = hdr[h];
}
},
get: function (args) {
args = (args) ? args : {};
this.params = (args.params) ? args.params : null;
this.pReplace();
var expired = this.checkExpired(),
token = Authorization.getToken();
if (expired) {
return this.createResponse({
detail: 'Token is expired'
}, 401);
} else if (token) {
this.setHeader({
Authorization: 'Token ' + token
});
this.setHeader({
"X-Auth-Token": 'Token ' + token
});
return $http({
method: 'GET',
url: this.url,
headers: this.headers,
params: this.params
});
} else {
return this.createResponse({
detail: 'Invalid token'
}, 401);
}
},
post: function (data) {
var token = Authorization.getToken(),
expired = this.checkExpired();
if (expired) {
return this.createResponse({
detail: 'Token is expired'
}, 401);
} else if (token) {
this.setHeader({
Authorization: 'Token ' + token
});
this.setHeader({
"X-Auth-Token": 'Token ' + token
});
return $http({
method: 'POST',
url: this.url,
headers: this.headers,
data: data
});
} else {
return this.createResponse({
detail: 'Invalid token'
}, 401);
}
},
put: function (data) {
var token = Authorization.getToken(),
expired = this.checkExpired();
if (expired) {
return this.createResponse({
detail: 'Token is expired'
}, 401);
} else if (token) {
this.setHeader({
Authorization: 'Token ' + token
});
this.setHeader({
"X-Auth-Token": 'Token ' + token
});
return $http({
method: 'PUT',
url: this.url,
headers: this.headers,
data: data
});
} else {
return this.createResponse({
detail: 'Invalid token'
}, 401);
}
},
patch: function (data) {
var token = Authorization.getToken(),
expired = this.checkExpired();
if (expired) {
return this.createResponse({
detail: 'Token is expired'
}, 401);
} else if (token) {
this.setHeader({
Authorization: 'Token ' + token
});
this.setHeader({
"X-Auth-Token": 'Token ' + token
});
return $http({
method: 'PATCH',
url: this.url,
headers: this.headers,
data: data
});
} else {
return this.createResponse({
detail: 'Invalid token'
}, 401);
}
},
destroy: function (data) {
var token = Authorization.getToken(),
expired = this.checkExpired();
if (expired) {
return this.createResponse({
detail: 'Token is expired'
}, 401);
} else if (token) {
this.setHeader({
Authorization: 'Token ' + token
});
this.setHeader({
"X-Auth-Token": 'Token ' + token
});
return $http({
method: 'DELETE',
url: this.url,
headers: this.headers,
data: data
});
} else {
return this.createResponse({
detail: 'Invalid token'
}, 401);
}
},
options: function () {
var token = Authorization.getToken(),
expired = this.checkExpired();
if (expired) {
return this.createResponse({
detail: 'Token is expired'
}, 401);
} else if (token) {
this.setHeader({
Authorization: 'Token ' + token
});
this.setHeader({
"X-Auth-Token": 'Token ' + token
});
return $http({
method: 'OPTIONS',
url: this.url,
headers: this.headers
});
} else {
return this.createResponse({
detail: 'Invalid token'
}, 401);
}
}
};
}
]);

View File

@@ -0,0 +1,195 @@
/**************************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:Socket
* @description
* Socket.js
*
* Wrapper for lib/socket.io-client/dist/socket.io.js.
*/
/* global io */
/**
* @ngdoc method
* @name lib.ansible.function:Socket#SocketIO
* @methodOf lib.ansible.function:Socket
* @description
*/
export default
angular.module('SocketIO', ['AuthService', 'Utilities'])
.factory('Socket', ['$rootScope', '$location', '$log', 'Authorization', 'Store', function ($rootScope, $location, $log, Authorization, Store) {
return function(params) {
var scope = params.scope,
host = $location.host(),
endpoint = params.endpoint,
protocol = $location.protocol(),
config, socketPort,
url;
// Since some pages are opened in a new tab, we might get here before AnsibleConfig is available.
// In that case, load from local storage.
if ($AnsibleConfig) {
socketPort = $AnsibleConfig.websocket_port;
}
else {
$log.debug('getting web socket port from local storage');
config = Store('AnsibleConfig');
socketPort = config.websocket_port;
}
url = protocol + '://' + host + ':' + socketPort + '/socket.io/' + endpoint;
$log.debug('opening socket connection to: ' + url);
function getSocketTip(status) {
var result = '';
switch(status) {
case 'error':
result = "Live events: error connecting to the Tower server. Click for troubleshooting help.";
break;
case 'connecting':
result = "Live events: attempting to connect to the Tower server. Click for troubleshooting help.";
break;
case "ok":
result = "Live events: connected. Pages containing job status information will automatically update in real-time.";
}
return result;
}
return {
scope: scope,
url: url,
socket: null,
init: function() {
var self = this,
token = Authorization.getToken();
if (!$rootScope.sessionTimer || ($rootScope.sessionTimer && !$rootScope.sessionTimer.isExpired())) {
// We have a valid session token, so attempt socket connection
$log.debug('Socket connecting to: ' + url);
self.scope.socket_url = url;
self.socket = io.connect(url, {
query: "Token="+token,
headers:
{
'Authorization': 'Token ' + token, // i don't think these are actually inserted into the header--jt
'X-Auth-Token': 'Token ' + token
},
'connect timeout': 3000,
'try multiple transports': false,
'max reconnection attempts': 3,
'reconnection limit': 3000
});
self.socket.on('connection', function() {
$log.debug('Socket connecting...');
self.scope.$apply(function () {
self.scope.socketStatus = 'connecting';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
self.socket.on('connect', function() {
$log.debug('Socket connection established');
self.scope.$apply(function () {
self.scope.socketStatus = 'ok';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
self.socket.on('connect_failed', function(reason) {
var r = reason || 'connection refused by host';
$log.error('Socket connection failed: ' + r);
self.scope.$apply(function () {
self.scope.socketStatus = 'error';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
self.socket.on('diconnect', function() {
$log.debug('Socket disconnected');
self.scope.$apply(function() {
self.socketStatus = 'error';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
self.socket.on('error', function(reason) {
var r = reason || 'connection refused by host';
console.error(reason)
$log.debug('Socket error: ' + r);
$log.error('Socket error: ' + r);
self.scope.$apply(function() {
self.scope.socketStatus = 'error';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
self.socket.on('reconnecting', function() {
$log.debug('Socket attempting reconnect...');
self.scope.$apply(function() {
self.scope.socketStatus = 'connecting';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
self.socket.on('reconnect', function() {
$log.debug('Socket reconnected');
self.scope.$apply(function() {
self.scope.socketStatus = 'ok';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
self.socket.on('reconnect_failed', function(reason) {
$log.error('Socket reconnect failed: ' + reason);
self.scope.$apply(function() {
self.scope.socketStatus = 'error';
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
});
});
}
else {
// encountered expired token, redirect to login page
$rootScope.sessionTimer.expireSession();
$location.url('/login');
}
},
checkStatus: function() {
// Check connection status
var self = this;
if (self.socket.socket.connected) {
self.scope.socketStatus = 'ok';
}
else if (self.socket.socket.connecting || self.socket.reconnecting) {
self.scope.socketStatus = 'connecting';
}
else {
self.scope.socketStatus = 'error';
}
self.scope.socketTip = getSocketTip(self.scope.socketStatus);
return self.scope.socketStatus;
},
on: function (eventName, callback) {
var self = this;
self.socket.on(eventName, function () {
var args = arguments;
self.scope.$apply(function () {
callback.apply(self.socket, args);
});
});
},
emit: function (eventName, data, callback) {
var self = this;
self.socket.emit(eventName, data, function () {
var args = arguments;
self.scope.$apply(function () {
if (callback) {
callback.apply(self.socket, args);
}
});
});
},
getUrl: function() {
return url;
}
};
};
}]);

View File

@@ -0,0 +1,69 @@
/**************************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:Timer
* @description
* Timer.js
*
* Use to track user idle time and expire session. Timeout
* duration set in config.js
*
*/
/**
* @ngdoc method
* @name lib.ansible.function:Timer#TimerService
* @methodOf lib.ansible.function:Timer
* @description
*/
export default
angular.module('TimerService', ['ngCookies', 'Utilities'])
.factory('Timer', ['$rootScope', '$cookieStore', '$location', 'GetBasePath', 'Empty',
function ($rootScope, $cookieStore) {
return {
sessionTime: null,
timeout: null,
getSessionTime: function () {
return (this.sessionTime) ? this.sessionTime : $cookieStore.get('sessionTime');
},
isExpired: function () {
var stime = this.getSessionTime(),
now = new Date().getTime();
if ((stime - now) <= 0) {
//expired
return true;
} else {
// not expired. move timer forward.
this.moveForward();
return false;
}
},
expireSession: function () {
this.sessionTime = 0;
$rootScope.sessionExpired = true;
$cookieStore.put('sessionExpired', true);
},
moveForward: function () {
var tm, t;
tm = ($AnsibleConfig) ? $AnsibleConfig.session_timeout : 1800;
t = new Date().getTime() + (tm * 1000);
this.sessionTime = t;
$cookieStore.put('sessionTime', t);
$rootScope.sessionExpired = false;
$cookieStore.put('sessionExpired', false);
},
init: function () {
this.moveForward();
return this;
}
};
}
]);

View File

@@ -0,0 +1,865 @@
/************************************
*
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:Utilities
* @description
* Utility functions
*
*/
/* jshint devel:true */
export default
angular.module('Utilities', ['RestServices', 'Utilities'])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#ClearScope
* @methodOf lib.ansible.function:Utilities
* @description
* Place to remove things that might be lingering from a prior tab or view.
* This used to destroy the scope, but that causes issues in angular 1.2.x
*/
.factory('ClearScope', [ '$rootScope', function ($rootScope) {
return function () {
$rootScope.flashMessage = null;
$('#form-modal .modal-body').empty();
$('#form-modal2 .modal-body').empty();
$('.tooltip').each(function () {
$(this).remove();
});
$('.popover').each(function () {
$(this).remove();
});
$('.ui-dialog-content').each(function(){
$(this).dialog('close');
});
try {
$('#help-modal').dialog('close');
} catch (e) {
// ignore
}
};
}])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#Empty
* @methodOf lib.ansible.function:Utilities
* @description Empty()
*
* Test if a value is 'empty'. Returns true if val is null | '' | undefined.
* Only works on non-Ojbect types.
*
*/
.factory('Empty', [
function () {
return function (val) {
return (val === null || val === undefined || val === '') ? true : false;
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#ToggleClass
* @methodOf lib.ansible.function:Utilities
* @description
*/
.factory('ToggleClass', function () {
return function (selector, cssClass) {
// Toggles the existance of a css class on a given element
if ($(selector) && $(selector).hasClass(cssClass)) {
$(selector).removeClass(cssClass);
} else if ($(selector)) {
$(selector).addClass(cssClass);
}
};
})
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#Alert
* @methodOf lib.ansible.function:Utilities
* @description Pass in the header and message you want displayed on TB modal dialog found in index.html.
* Assumes an #id of 'alert-modal'. Pass in an optional TB alert class (i.e. alert-danger, alert-success,
* alert-info...). Pass an optional function(){}, if you want a specific action to occur when user
* clicks 'OK' button. Set secondAlert to true, when a second dialog is needed.
*/
.factory('Alert', ['$rootScope', function ($rootScope) {
return function (hdr, msg, cls, action, secondAlert, disableButtons, backdrop) {
var scope = $rootScope.$new(), alertClass, local_backdrop;
if (secondAlert) {
$('#alertHeader2').html(hdr);
$('#alert2-modal-msg').html(msg);
alertClass = (cls) ? cls : 'alert-danger'; //default alert class is alert-danger
local_backdrop = (backdrop === undefined) ? "static" : backdrop;
$('#alert2-modal-msg').attr({ "class": "alert " + alertClass });
$('#alert-modal2').modal({
show: true,
keyboard: true,
backdrop: local_backdrop
});
scope.disableButtons2 = (disableButtons) ? true : false;
$('#alert-modal2').on('hidden.bs.modal', function () {
if (action) {
action();
}
});
$('#alert-modal2').on('shown.bs.modal', function () {
$('#alert2_ok_btn').focus();
});
$(document).bind('keydown', function (e) {
if (e.keyCode === 27 || e.keyCode === 13) {
$('#alert-modal2').modal('hide');
}
});
} else {
$('#alertHeader').html(hdr);
$('#alert-modal-msg').html(msg);
alertClass = (cls) ? cls : 'alert-danger'; //default alert class is alert-danger
local_backdrop = (backdrop === undefined) ? "static" : backdrop;
$('#alert-modal-msg').attr({ "class": "alert " + alertClass });
$('#alert-modal').modal({
show: true,
keyboard: true,
backdrop: local_backdrop
});
$('#alert-modal').on('hidden.bs.modal', function () {
if (action) {
action();
}
});
$('#alert-modal').on('shown.bs.modal', function () {
$('#alert_ok_btn').focus();
});
$(document).bind('keydown', function (e) {
if (e.keyCode === 27 || e.keyCode === 13) {
$('#alert-modal').modal('hide');
}
});
scope.disableButtons = (disableButtons) ? true : false;
}
};
}])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#ProcessErrors
* @methodOf lib.ansible.function:Utilities
* @description For handling errors that are returned from the API
*/
.factory('ProcessErrors', ['$rootScope', '$cookieStore', '$log', '$location', 'Alert', 'Wait',
function ($rootScope, $cookieStore, $log, $location, Alert, Wait) {
return function (scope, data, status, form, defaultMsg) {
var field, fieldErrors, msg, keys;
Wait('stop');
$log.debug('Debug status: ' + status);
$log.debug('Debug data: ');
$log.debug(data);
if (defaultMsg.msg) {
$log.debug('Debug: ' + defaultMsg.msg);
}
if (status === 403) {
msg = 'The API responded with a 403 Access Denied error. ';
if (data.detail) {
msg += 'Detail: ' + data.detail;
} else {
msg += 'Please contact your system administrator.';
}
Alert(defaultMsg.hdr, msg);
} else if (status === 410) {
Alert('Deleted Object', 'The requested object was previously deleted and can no longer be accessed.');
} else if ((status === 'Token is expired') || (status === 401 && data.detail && data.detail === 'Token is expired') ||
(status === 401 && data.detail && data.detail === 'Invalid token')) {
if ($rootScope.sessionTimer) {
$rootScope.sessionTimer.expireSession();
}
$location.url('/login');
} else if (data.non_field_errors) {
Alert('Error!', data.non_field_errors);
} else if (data.detail) {
Alert(defaultMsg.hdr, defaultMsg.msg + ' ' + data.detail);
} else if (data.__all__) {
if (typeof data.__all__ === 'object' && Array.isArray(data.__all__)) {
Alert('Error!', data.__all__[0]);
}
else {
Alert('Error!', data.__all__);
}
} else if (form) { //if no error code is detected it begins to loop through to see where the api threw an error
fieldErrors = false;
for (field in form.fields) {
if (data[field] && form.fields[field].tab) {
// If the form is part of a tab group, activate the tab
$('#' + form.name + "_tabs a[href=\"#" + form.fields[field].tab + '"]').tab('show');
}
if (form.fields[field].realName) {
if (data[form.fields[field].realName]) {
scope[field + '_api_error'] = data[form.fields[field]][0];
//scope[form.name + '_form'][form.fields[field].realName].$setValidity('apiError', false);
$('[name="' + form.fields[field].realName + '"]').addClass('ng-invalid');
$('[name="' + form.fields[field].realName + '"]').ScrollTo({ "onlyIfOutside": true, "offsetTop": 100 });
fieldErrors = true;
}
}
if (form.fields[field].sourceModel) {
if (data[field]) {
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '_api_error'] =
data[field][0];
//scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField].$setValidity('apiError', false);
$('[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').addClass('ng-invalid');
$('[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').ScrollTo({ "onlyIfOutside": true, "offsetTop": 100 });
fieldErrors = true;
}
} else {
if (data[field]) {
scope[field + '_api_error'] = data[field][0];
//scope[form.name + '_form'][field].$setValidity('apiError', false);
$('[name="' + field + '"]').addClass('ng-invalid');
$('[name="' + field + '"]').ScrollTo({ "onlyIfOutside": true, "offsetTop": 100 });
fieldErrors = true;
}
}
}
if ((!fieldErrors) && defaultMsg) {
Alert(defaultMsg.hdr, defaultMsg.msg);
}
} else if (typeof data === 'object' && Object.keys(data).length > 0) {
keys = Object.keys(data);
if (Array.isArray(data[keys[0]])) {
msg = data[keys[0]][0];
}
else {
msg = data[keys[0]];
}
Alert(defaultMsg.hdr, msg);
} else {
Alert(defaultMsg.hdr, defaultMsg.msg);
}
};
}
])
.factory('LoadBreadCrumbs', ['$rootScope', '$routeParams', '$location', 'Empty',
function ($rootScope, $routeParams, $location, Empty) {
return function (crumb) {
var title, found, j, i, paths, ppath, parent, child;
function toUppercase(a) {
return a.toUpperCase();
}
function singular(a) {
return (a === 'ies') ? 'y' : '';
}
//Keep a list of path/title mappings. When we see /organizations/XX in the path, for example,
//we'll know the actual organization name it maps to.
if (!Empty(crumb)) {
found = false;
//crumb.title = crumb.title.charAt(0).toUpperCase() + crumb.title.slice(1);
for (i = 0; i < $rootScope.crumbCache.length; i++) {
if ($rootScope.crumbCache[i].path === crumb.path) {
found = true;
$rootScope.crumbCache[i] = crumb;
break;
}
}
if (!found) {
$rootScope.crumbCache.push(crumb);
}
}
paths = $location.path().replace(/^\//, '').split('/');
ppath = '';
$rootScope.breadcrumbs = [];
if (paths.length > 1) {
for (i = 0; i < paths.length - 1; i++) {
if (i > 0 && paths[i].match(/\d+/)) {
parent = paths[i - 1];
child = parent.replace(/(ies$|s$)/, singular);
child = child.charAt(0).toUpperCase() + child.slice(1);
// find the correct title
found = false;
if ($rootScope.crumbCache) {
for (j = 0; j < $rootScope.crumbCache.length; j++) {
if ($rootScope.crumbCache[j].path === '/' + parent + '/' + paths[i]) {
child = $rootScope.crumbCache[j].title;
found = true;
break;
}
}
if (found && $rootScope.crumbCache[j].altPath !== undefined) {
// Use altPath to override default path construction
$rootScope.breadcrumbs.push({
title: child,
path: $rootScope.crumbCache[j].altPath
});
} else {
$rootScope.breadcrumbs.push({
title: child,
path: ppath + '/' + paths[i]
});
}
}
} else {
//if (/_/.test(paths[i])) {
// replace '_' with space and uppercase each word
//}
//title = paths[i].charAt(0).toUpperCase() + paths[i].slice(1);
title = paths[i].replace(/(?:^|_)\S/g, toUppercase).replace(/_/g, ' ');
$rootScope.breadcrumbs.push({
title: title,
path: ppath + '/' + paths[i]
});
}
ppath += '/' + paths[i];
}
}
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#HelpDialog
* @methodOf lib.ansible.function:Utilities
* @description Display a help dialog
*
* HelpDialog({ defn: <HelpDefinition> })
* discuss difference b/t this and other modal windows/dialogs
*/
.factory('HelpDialog', ['$rootScope', '$compile', '$location', 'Store',
function ($rootScope, $compile, $location, Store) {
return function (params) {
var defn = params.defn,
current_step = params.step,
autoShow = params.autoShow || false,
scope = (params.scope) ? params.scope : $rootScope.$new();
function setButtonMargin() {
var width = ($('.ui-dialog[aria-describedby="help-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#help-next-button').outerWidth() - 93;
$('#help-next-button').css({'margin-right': width + 'px'});
}
function showHelp(step) {
var e, btns, ww, width, height, isOpen = false;
current_step = step;
function buildHtml(step) {
var html = '';
html += "<h4>" + step.intro + "</h4>\n";
if (step.img) {
html += "<div class=\"img-container\">\n";
html += "<img src=\"" + $basePath + "img/help/" + step.img.src + "\" ";
html += (step.img.maxWidth) ? "style=\"max-width:" + step.img.maxWidth + "px\" " : "";
html += ">";
html += "</div>\n";
}
if (step.icon) {
html += "<div class=\"icon-container\"";
html += (step.icon.containerHeight) ? "style=\"height:" + step.icon.containerHeight + "px;\">\n" : "";
html += "<i class=\"" + step.icon['class'] + "\" ";
html += (step.icon.style) ? "style=\"" + step.icon.style + "\" " : "";
html += "></i>\n";
html += "</div>\n";
}
html += "<div class=\"help-box\">" + step.box + "</div>";
html += (autoShow && step.autoOffNotice) ? "<div class=\"help-auto-off\"><label><input type=\"checkbox\" " +
"name=\"auto-off-checkbox\" id=\"auto-off-checkbox\"> Do not show this message in the future</label></div>\n" : "";
return html;
}
width = (defn.story.width) ? defn.story.width : 510;
height = (defn.story.height) ? defn.story.height : 600;
// Limit modal width to width of viewport
ww = $(document).width();
width = (width > ww) ? ww : width;
try {
isOpen = $('#help-modal-dialog').dialog('isOpen');
} catch (err) {
// ignore
}
e = angular.element(document.getElementById('help-modal-dialog'));
e.empty().html(buildHtml(defn.story.steps[current_step]));
setTimeout(function() { scope.$apply(function() { $compile(e)(scope); }); });
if (!isOpen) {
// Define buttons based on story length
btns = [];
if (defn.story.steps.length > 1) {
btns.push({
text: "Prev",
click: function () {
if (current_step - 1 === 0) {
$('#help-prev-button').prop('disabled', true);
}
if (current_step - 1 < defn.story.steps.length - 1) {
$('#help-next-button').prop('disabled', false);
}
showHelp(current_step - 1);
},
disabled: true
});
btns.push({
text: "Next",
click: function() {
if (current_step + 1 > 0) {
$('#help-prev-button').prop('disabled', false);
}
if (current_step + 1 >= defn.story.steps.length - 1) {
$('#help-next-button').prop('disabled', true);
}
showHelp(current_step + 1);
}
});
}
btns.push({
text: "Close",
click: function () {
$('#help-modal-dialog').dialog('close');
}
});
$('.overlay').css({
width: $(document).width(),
height: $(document).height()
}).fadeIn();
// Show the dialog
$('#help-modal-dialog').dialog({
position: {
my: "center top",
at: "center top+150",
of: 'body'
},
title: defn.story.hdr,
width: width,
height: height,
buttons: btns,
closeOnEscape: true,
show: 500,
hide: 500,
resizable: false,
close: function () {
$('.overlay').hide();
$('#help-modal-dialog').empty();
}
});
// Make the buttons look like TB and add FA icons
$('.ui-dialog-buttonset button').each(function () {
var c, h, i, l;
l = $(this).text();
if (l === 'Close') {
h = "fa-times";
c = "btn btn-default";
i = "help-close-button";
$(this).attr({
'class': c,
'id': i
}).html("<i class=\"fa " + h + "\"></i> Close");
} else if (l === 'Prev') {
h = "fa-chevron-left";
c = "btn btn-primary";
i = "help-prev-button";
$(this).attr({
'class': c,
'id': i
}).html("<i class=\"fa " + h + "\"></i> Prev");
} else {
h = "fa-chevron-right";
c = "btn btn-primary";
i = "help-next-button";
$(this).attr({
'class': c,
'id': i
}).html("Next <i class=\"fa " + h + "\"></i>").css({
'margin-right': '20px'
});
}
});
$('.ui-dialog[aria-describedby="help-modal-dialog"]').find('.ui-dialog-titlebar button')
.empty().attr({
'class': 'close'
}).text('x');
// If user clicks the checkbox, update local storage
$('#auto-off-checkbox').click(function () {
if ($('input[name="auto-off-checkbox"]:checked').length) {
Store('inventoryAutoHelp', 'off');
} else {
Store('inventoryAutoHelp', 'on');
}
});
setButtonMargin();
}
}
showHelp(0);
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#ReturnToCaller
* @methodOf lib.ansible.function:Utilities
* @description
* Split the current path by '/' and use the array elements from 0 up to and
* including idx as the new path. If no idx value supplied, use 0 to length - 1.
*
*/
.factory('ReturnToCaller', ['$location', 'Empty',
function ($location, Empty) {
return function (idx) {
var paths = $location.path().replace(/^\//, '').split('/'),
newpath = '',
i;
idx = (Empty(idx)) ? paths.length - 1 : idx + 1;
for (i = 0; i < idx; i++) {
newpath += '/' + paths[i];
}
$location.path(newpath);
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#FormatDate
* @methodOf lib.ansible.function:Utilities
* @description
* Wrapper for data filter- an attempt to insure all dates display in
* the same format. Pass in date object or string. See: http://docs.angularjs.org/api/ng.filter:date
*/
.factory('FormatDate', ['$filter',
function ($filter) {
return function (dt) {
return $filter('date')(dt, 'MM/dd/yy HH:mm:ss');
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#Wait
* @methodOf lib.ansible.function:Utilities
* @description
* Display a spinning icon in the center of the screen to freeze the
* UI while waiting on async things to complete (i.e. API calls).
* Wait('start' | 'stop');
*
*/
.factory('Wait', ['$rootScope',
function ($rootScope) {
return function (directive) {
var docw, doch, spinnyw, spinnyh, x, y;
if (directive === 'start' && !$rootScope.waiting) {
$rootScope.waiting = true;
docw = $(window).width();
doch = $(window).height();
spinnyw = $('.spinny').width();
spinnyh = $('.spinny').height();
x = (docw - spinnyw) / 2;
y = (doch - spinnyh) / 2;
$('.overlay').css({
width: $(document).width(),
height: $(document).height()
}).fadeIn();
$('.spinny').css({
top: y,
left: x
}).fadeIn(400);
} else if (directive === 'stop' && $rootScope.waiting) {
$('.spinny, .overlay').fadeOut(400, function () {
$rootScope.waiting = false;
});
}
};
}
])
.factory('HideElement', [
function () {
return function (selector, action) {
// Fade-in a cloack or vail or a specific element
var target = $(selector),
width = target.css('width'),
height = target.css('height'),
position = target.position(),
parent = target.parent(),
borderRadius = target.css('border-radius'),
backgroundColor = target.css('background-color'),
margin = target.css('margin'),
padding = target.css('padding');
parent.append("<div id=\"curtain-div\" style=\"" +
"position: absolute;" +
"top: " + position.top + "px; " +
"left: " + position.left + "px; " +
"z-index: 1000; " +
"width: " + width + "; " +
"height: " + height + "; " +
"background-color: " + backgroundColor + "; " +
"margin: " + margin + "; " +
"padding: " + padding + "; " +
"border-radius: " + borderRadius + "; " +
"opacity: .75; " +
"display: none; " +
"\"></div>");
$('#curtain-div').show(0, action);
};
}
])
.factory('ShowElement', [
function () {
return function () {
// And Fade-out the cloack revealing the element
$('#curtain-div').fadeOut(500, function () {
$(this).remove();
});
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#GetChoices
* @methodOf lib.ansible.function:Utilities
* @description Make an Options call to the API and retrieve dropdown options
* GetChoices({
* scope: Parent $scope
* url: API resource to access
* field: API element in the response object that contains the option list.
* variable: Scope variable that will receive the list.
* callback: Optional. Will issue scope.$emit(callback) on completion.
* choice_name: Optional. Used when list is found in a variable other than 'choices'.
* })
*/
.factory('GetChoices', ['Rest', 'ProcessErrors',
function (Rest, ProcessErrors) {
return function (params) {
var scope = params.scope,
url = params.url,
field = params.field,
variable = params.variable,
callback = params.callback,
choice_name = params.choice_name;
if (scope[variable]) {
scope[variable].length = 0;
} else {
scope[variable] = [];
}
Rest.setUrl(url);
Rest.options()
.success(function (data) {
var choices;
choices = (choice_name) ? data.actions.GET[field][choice_name] : data.actions.GET[field].choices;
// including 'name' property so list can be used by search
choices.forEach(function(choice) {
scope[variable].push({
label: choice[1],
value: choice[0],
name: choice[1]
});
});
if (callback) {
scope.$emit(callback);
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get ' + url + '. GET status: ' + status });
});
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#Find
* @methodOf lib.ansible.function:Utilities
* @description
* Search an array of objects, returning the matchting object or null
*
* Find({ list: [], key: "key", val: <key value> });
*/
.factory('Find', [
function () {
return function (params) {
var list = params.list,
key = params.key,
val = params.val,
found = false,
i;
if (typeof list === 'object' && Array.isArray(list)) {
for (i = 0; i < params.list.length; i++) {
if (list[i][key] === val) {
found = true;
break;
}
}
return (found) ? list[i] : null;
}
return null;
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#DebugForm
* @methodOf lib.ansible.function:Utilities
* @description
* DebugForm({ form: <form object>, scope: <current scope object> });
*
* Use to log the $pristine and $valid properties of each form element. Helpful when form
* buttons fail to enable/disable properly.
*
*/
.factory('DebugForm', [
function () {
return function (params) {
var form = params.form,
scope = params.scope,
fld;
for (fld in form.fields) {
if (scope[form.name + '_form'][fld]) {
console.log(fld + ' valid: ' + scope[form.name + '_form'][fld].$valid);
}
if (form.fields[fld].sourceModel) {
if (scope[form.name + '_form'][form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]) {
console.log(form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField + ' valid: ' +
scope[form.name + '_form'][form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField].$valid);
}
}
}
console.log('form pristine: ' + scope[form.name + '_form'].$pristine);
console.log('form valid: ' + scope[form.name + '_form'].$valid);
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#Store
* @methodOf lib.ansible.function:Utilities
* @description Store
*
* Wrapper for local storage. All local storage requests flow through here so that we can
* stringify/unstringify objects and respond to future issues in one place. For example,
* we may at some point want to only use session storage rather than local storage. We might
* want to add a test for whether or not local/session storage exists for the browser, etc.
*
* store(key,value) will store the value using the key
*
* store(key) retrieves the value of the key
* discuss use case
*/
.factory('Store', ['Empty',
function (Empty) {
return function (key, value) {
if (!Empty(value)) {
// Store the value
localStorage[key] = JSON.stringify(value);
} else if (!Empty(key)) {
// Return the value
var val = localStorage[key];
return (!Empty(val)) ? JSON.parse(val) : null;
}
};
}
])
/**
* @ngdoc method
* @name lib.ansible.function:Utilities#ApplyEllipsis
* @methodOf lib.ansible.function:Utilities
* @description
* ApplyEllipsis()
* discuss significance
*/
.factory('ApplyEllipsis', [
function () {
return function (selector) {
// Add a hidden element to the DOM. We'll use this to calc the px length of
// our target text.
var tmp = $('#string-test');
if (!tmp.length) {
$('body').append('<div style="display:none;" id="string-test"></div>');
tmp = $('#string-test');
}
// Find and process the text.
$(selector).each(function () {
var setTitle = true,
txt, w, pw, cw, df;
if ($(this).attr('title')) {
txt = $(this).attr('title');
setTitle = false;
} else {
txt = $(this).text();
}
tmp.text(txt);
w = tmp.width(); //text width
pw = $(this).parent().width(); //parent width
if (w > pw) {
// text is wider than parent width
if (setTitle) {
// Save the original text in the title
$(this).attr('title', txt);
}
cw = w / txt.length; // px width per character
df = w - pw; // difference in px
txt = txt.substr(0, txt.length - (Math.ceil(df / cw) + 3));
$(this).text(txt + '...');
}
if (pw > w && !setTitle) {
// the parent has expanded and we previously set the title text
txt = $(this).attr('title');
$(this).text(txt);
}
});
};
}
]);

View File

@@ -0,0 +1,76 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
*
*/
/**
* @ngdoc overview
* @name lib.ansible
* @description lib files
*
*/
/**
* @ngdoc function
* @name lib.ansible.function:api-loader
* @description Read /api and /api/X to discover all the base paths needed
* to access the primary model objects.
*
*/
export default
angular.module('ApiLoader', ['Utilities'])
.factory('LoadBasePaths', ['$http', '$rootScope', 'Store', 'ProcessErrors',
function ($http, $rootScope, Store, ProcessErrors) {
return function () {
$http({ method: 'GET', url:'/api/', headers: { 'Authorization': "" } })
.success(function (data) {
var base = data.current_version;
$http({ method: 'GET', url:base, headers: { 'Authorization': "" } })
.success(function (data) {
data.base = base;
$rootScope.defaultUrls = data;
Store('api', data);
})
.error(function (data, status) {
$rootScope.defaultUrls = {
status: 'error'
};
ProcessErrors(null, data, status, null, {
hdr: 'Error',
msg: 'Failed to read ' + base + '. GET status: ' + status
});
});
})
.error(function (data, status) {
$rootScope.defaultUrls = {
status: 'error'
};
ProcessErrors(null, data, status, null, {
hdr: 'Error',
msg: 'Failed to read /api. GET status: ' + status
});
});
};
}
])
.factory('GetBasePath', ['$rootScope', 'Store', 'LoadBasePaths', 'Empty',
function ($rootScope, Store, LoadBasePaths, Empty) {
return function (set) {
// use /api/v1/ results to construct API URLs.
if (Empty($rootScope.defaultUrls)) {
// browser refresh must have occurred. load from local storage
if (Store('api')) {
$rootScope.defaultUrls = Store('api');
return $rootScope.defaultUrls[set];
}
return ''; //we should never get here
}
return $rootScope.defaultUrls[set];
};
}
]);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:filters
* @description
* Custom filters
*
*/
export default
angular.module('AWFilters', [])
//
// capitalize -capitalize the first letter of each word
//
.filter('capitalize', [ function () {
return function (input) {
var values, result, i;
if (input) {
values = input.replace(/\_/g, ' ').split(" ");
result = "";
for (i = 0; i < values.length; i++) {
result += values[i].charAt(0).toUpperCase() + values[i].substr(1) + ' ';
}
return result.trim();
}
};
}])
//
// Filter an object of objects by id using an array of id values
// Created specifically for Filter Events on job detail page.
//
.filter('FilterById', [ function() {
return function(input, list) {
var results = [];
if (input && list.length > 0) {
list.forEach(function(itm) {
input.forEach(function(row) {
if (row.id === itm) {
results.push(row);
}
});
});
return results;
}
return input;
};
}])
.filter('FilterByField', [ function() {
return function(input, list) {
var fld, key, search = {}, results = {};
for (fld in list) {
if (list[fld]) {
search[fld] = list[fld];
}
}
if (Object.keys(search).length > 0) {
for (fld in search) {
for (key in input) {
if (input[key][fld] === search[fld]) {
results[key] = input[key];
}
}
}
return results;
}
return input;
};
}])
.filter('FilterFailedEvents', [ function() {
return function(input, liveEventProcessing, searchAllStatus) {
var results = [];
if (liveEventProcessing) {
// while live events are happening, we don't want angular to filter out anything
return input;
}
else if (searchAllStatus === 'failed') {
// filter by failed
input.forEach(function(row) {
if (row.status === 'failed') {
results.push(row);
}
});
return results;
}
return input;
};
}]);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,872 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:generator-helpers
* @description
* GeneratorHelpers
*
* Functions shared between FormGenerator and ListGenerator
*
*/
export default
angular.module('GeneratorHelpers', [])
.factory('Attr', function () {
return function (obj, key, fld) {
var i, s, result,
value = (typeof obj[key] === "string") ? obj[key].replace(/[\'\"]/g, '&quot;') : obj[key];
if (/^ng/.test(key)) {
result = 'ng-' + key.replace(/^ng/, '').toLowerCase() + "=\"" + value + "\" ";
} else if (/^data|^aw/.test(key) && key !== 'awPopOver') {
s = '';
for (i = 0; i < key.length; i++) {
if (/[A-Z]/.test(key.charAt(i))) {
s += '-' + key.charAt(i).toLowerCase();
} else {
s += key.charAt(i);
}
}
result = s + "=\"" + value + "\" ";
} else {
switch (key) {
case 'trueValue':
result = "ng-true-value=\"" + value + "\" ";
break;
case 'falseValue':
result = "ng-false-value=\"" + value + "\" ";
break;
case 'awPopOver':
// construct the entire help link
result = "<a id=\"awp-" + fld + "\" href=\"\" aw-pop-over=\'" + value + "\' ";
result += (obj.dataPlacement) ? "data-placement=\"" + obj.dataPlacement + "\" " : "";
result += (obj.dataContainer) ? "data-container=\"" + obj.dataContainer + "\" " : "";
result += (obj.dataTitle) ? "data-title=\"" + obj.dataTitle + "\" " : "";
result += (obj.dataTrigger) ? "data-trigger=\"" + obj.dataTrigger + "\" " : "";
result += (obj.awPopOverWatch) ? "aw-pop-over-watch=\"" + obj.awPopOverWatch + "\" " : "";
result += "class=\"help-link\" ";
result += "><i class=\"fa fa-question-circle\"></i></a> ";
break;
case 'columnShow':
result = "ng-show=\"" + value + "\" ";
break;
case 'icon':
// new method of constructing <i> icon tag. Replaces Icon method.
result = "<i class=\"fa fa-" + value;
result += (obj.iconSize) ? " " + obj.iconSize : "";
result += "\"></i>";
break;
case 'autocomplete':
result = "autocomplete=\"";
result += (value) ? 'true' : 'false';
result += "\" ";
break;
default:
result = key + "=\"" + value + "\" ";
}
}
return result;
};
})
.factory('Icon', function () {
return function (icon) {
return "<i class=\"fa " + icon + "\"></i> ";
};
})
.factory('SelectIcon', ['Icon',
function (Icon) {
return function (params) {
// Common point for matching any type of action to the appropriate
// icon. The intention is to maintain consistent meaning and presentation
// for every icon used in the application.
var icon,
action = params.action,
size = params.size;
switch (action) {
case 'help':
icon = "fa-question-circle";
break;
case 'add':
case 'create':
icon = "fa-plus";
break;
case 'edit':
icon = "fa-pencil";
break;
case 'delete':
icon = "fa-trash-o";
break;
case 'group_update':
icon = 'fa-exchange';
break;
case 'scm_update':
icon = 'fa-cloud-download';
break;
case 'cancel':
icon = 'fa-minus-circle';
break;
case 'run':
case 'rerun':
case 'submit':
icon = 'fa-rocket';
break;
case 'stream':
icon = 'fa-clock-o';
break;
case 'socket':
icon = 'fa-power-off';
break;
case 'refresh':
icon = 'fa-refresh';
break;
case 'close':
icon = 'fa-arrow-left';
break;
case 'save':
case 'form_submit':
icon = 'fa-check-square-o';
break;
case 'properties':
icon = "fa-wrench";
break;
case 'reset':
icon = "fa-undo";
break;
case 'view':
icon = "fa-search-plus";
break;
case 'sync_status':
icon = "fa-cloud";
break;
case 'schedule':
icon = "fa-calendar";
break;
case 'stdout':
icon = "fa-external-link";
break;
case 'question_cancel':
icon = 'fa-times';
break;
case 'job_details':
icon = 'fa-list-ul';
break;
case 'copy':
icon = "fa-copy";
break;
}
icon += (size) ? " " + size : "";
return Icon(icon);
};
}
])
.factory('Button', ['Attr', 'SelectIcon',
function (Attr, SelectIcon) {
return function (params) {
// pass in button object, get back html
var btn = params.btn,
action = params.action, // label used to select the icon
toolbar = params.toolbar,
html = '';
if (toolbar) {
//if this is a toolbar button, set some defaults
btn.class = 'btn-xs btn-primary';
btn.iconSize = 'fa-lg';
delete btn.label;
}
html += "<button type=\"button\" ";
html += "class=\"btn";
if (btn['class']) {
html += ' ' + btn['class'];
} else {
html += " btn-sm";
}
html += (btn.awPopOver) ? " help-link-white" : "";
html += "\" ";
html += (btn.ngClick) ? Attr(btn, 'ngClick') : "";
if (btn.id) {
html += "id=\"" + btn.id + "\" ";
} else {
if (action) {
html += "id=\"" + action + "_btn\" ";
}
}
html += (btn.ngHide) ? Attr(btn, 'ngHide') : "";
html += (btn.awToolTip) ? Attr(btn, 'awToolTip') : "";
html += (btn.awToolTip && btn.dataPlacement === undefined) ? "data-placement=\"top\" " : "";
html += (btn.dataTipWatch) ? "data-tip-watch=\"" + btn.dataTipWatch + "\" " : "";
html += (btn.awPopOver) ? "aw-pop-over=\"" +
btn.awPopOver.replace(/[\'\"]/g, '&quot;') + "\" " : "";
html += (btn.dataPlacement) ? Attr(btn, 'dataPlacement') : "";
html += (btn.dataContainer) ? Attr(btn, 'dataContainer') : "";
html += (btn.dataTitle) ? Attr(btn, 'dataTitle') : "";
html += (btn.ngShow) ? Attr(btn, 'ngShow') : "";
html += (btn.ngHide) ? Attr(btn, 'ngHide') : "";
html += (btn.ngDisabled) ? Attr(btn, 'ngDisabled') : "";
html += (btn.ngClass) ? Attr(btn, 'ngClass') : "";
html += (btn.awTipPlacement) ? Attr(btn, 'awTipPlacement') : "";
html += " >";
html += (btn.img) ? "<img src=\"" + $basePath + "img/" + btn.img + "\" style=\"width: 12px; height: 12px;\" >" : "";
if (btn.iconClass) {
html += "<i class=\"" + btn.iconClass + "\"></i>";
}
else {
html += SelectIcon({
action: action,
size: btn.iconSize
});
}
html += (btn.label) ? " " + btn.label : "";
html += "</button> ";
return html;
};
}
])
.factory('NavigationLink', ['Attr', 'Icon',
function (Attr, Icon) {
return function (link) {
var html = "<a ";
html += (link.href) ? Attr(link, 'href') : '';
html += (link.ngClick) ? Attr(link, 'ngClick') : '';
html += (link.ngShow) ? Attr(link, 'ngShow') : '';
html += '>';
html += (link.icon) ? Icon(link.icon) : '';
html += (link.label) ? link.label : '';
html += "</a>\n";
return html;
};
}
])
.factory('DropDown', ['Attr', 'Icon',
function (Attr, Icon) {
return function (params) {
var list = params.list,
fld = params.fld,
i, html, field, name;
if (params.field) {
field = params.field;
} else {
if (params.type) {
field = list[params.type][fld];
} else {
field = list.fields[fld];
}
}
name = field.label.replace(/ /g, '_');
if (params.td === undefined || params.td !== false) {
html = "<td class=\"" + fld + "-column\">\n";
} else {
html = '';
}
html += "<div class=\"dropdown\">\n";
html += "<a href=\"\" class=\"toggle";
html += "\" ";
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += (field.ngShow) ? "ng-show=\"" + field.ngShow + "\" " : "";
html += "data-toggle=\"dropdown\" ";
html += ">";
html += (field.icon) ? Icon(field.icon) : "";
html += field.label;
html += " <span class=\"caret\"></span></a>\n";
html += "<ul class=\"dropdown-menu pull-right\" role=\"menu\" aria-labelledby=\"dropdownMenu1\">\n";
for (i = 0; i < field.options.length; i++) {
html += "<li role=\"presentation\"><a role=\"menuitem\" tabindex=\"-1\" ";
html += (field.options[i].ngClick) ? "ng-click=\"" + field.options[i].ngClick + "\" " : "";
html += (field.options[i].ngHref) ? "ng-href=\"" + field.options[i].ngHref + "\" " : "";
html += (field.options[i].ngShow) ? "ng-show=\"" + field.options[i].ngShow + "\" " : "";
html += (field.options[i].ngHide) ? "ng-hide=\"" + field.options[i].ngHide + "\" " : "";
html += "href=\"\">" + field.options[i].label + "</a></li>\n";
}
html += "</ul>\n";
html += "</div>\n";
html += (params.td === undefined || params.td !== false) ? "</td>\n" : "";
return html;
};
}
])
.factory('BadgeCount', [
function () {
return function (params) {
// Adds a badge count with optional tooltip
var list = params.list,
fld = params.fld,
field = list.fields[fld],
html;
html = "<td class=\"" + fld + "-column";
html += (field.columnClass) ? " " + field.columnClass : "";
html += "\">\n";
html += "<a ng-href=\"" + field.ngHref + "\" aw-tool-tip=\"" + field.awToolTip + "\"";
html += (field.dataPlacement) ? " data-placement=\"" + field.dataPlacement + "\"" : "";
html += ">";
html += "<span class=\"badge";
html += (field['class']) ? " " + field['class'] : "";
html += "\">";
html += "{{ " + list.iterator + '.' + fld + " }}";
html += "</span>";
html += (field.badgeLabel) ? " " + field.badgeLabel : "";
html += "</a>\n";
html += "</td>\n";
return html;
};
}
])
.factory('Badge', [
function () {
return function (field) {
// Adds an icon(s) with optional tooltip
var i, html = '';
if (field.badges) {
for (i = 0; i < field.badges.length; i++) {
if (field.badges[i].toolTip) {
html += "<a href=\"\" aw-tool-tip=\"" + field.badges[i].toolTip + "\"";
html += (field.badges[i].toolTipPlacement) ? " data-placement=\"" + field.badges[i].toolTipPlacement + "\"" : "";
html += (field.badges[i].badgeShow) ? " ng-show=\"" + field.badges[i].badgeShow + "\"" : "";
html += ">";
}
html += "<i ";
html += (field.badges[i].badgeShow) ? "ng-show=\"" + field.badges[i].badgeShow + "\" " : "";
html += " class=\"field-badge " + field.badges[i].icon;
html += (field.badges[i].badgeClass) ? " " + field.badges[i].badgeClass : "";
html += "\"></i>";
if (field.badges[i].toolTip) {
html += "</a>";
}
html += "\n";
}
} else {
if (field.badgeToolTip) {
html += "<a ";
html += (field.badgeNgHref) ? "ng-href=\"" + field.badgeNgHref + "\" " : "href=\"\"";
html += (field.ngClick) ? "ng-click=\"" + field.ngClick + "\" " : "";
html += " aw-tool-tip=\"" + field.badgeToolTip + "\"";
html += (field.badgeTipPlacement) ? " data-placement=\"" + field.badgeTipPlacement + "\"" : "";
html += (field.badgeTipWatch) ? " data-tip-watch=\"" + field.badgeTipWatch + "\"" : "";
html += (field.badgeShow) ? " ng-show=\"" + field.badgeShow + "\"" : "";
html += ">";
}
html += "<i ";
html += (field.badgeShow) ? "ng-show=\"" + field.badgeShow + "\" " : "";
html += " class=\"fa " + field.badgeIcon;
html += (field.badgeClass) ? " " + field.badgeClass : "";
html += "\"></i>";
if (field.badgeToolTip) {
html += "</a>";
}
html += "\n";
}
return html;
};
}
])
.factory('Breadcrumbs', ['$rootScope', 'Attr',
function ($rootScope, Attr) {
return function (params) {
// Generate breadcrumbs using the list-generator.js method.
var list = params.list,
mode = params.mode,
html = '', itm, navigation;
html += "<ul class=\"ansible-breadcrumb\" id=\"breadcrumb-list\">\n";
html += "<li ng-repeat=\"crumb in breadcrumbs\"><a href=\"{{ '#' + crumb.path }}\">{{ crumb.title }}</a></li>\n";
if (list.navigationLinks) {
navigation = list.navigationLinks;
if (navigation.ngHide) {
html += "<li class=\"active\" ng-show=\"" + navigation.ngHide + "\">";
html += list.editTitle;
html += "</li>\n";
html += "<li class=\"active\" ng-hide=\"" + navigation.ngHide + "\"> </li>\n";
} else {
html += "<li class=\"active\"> </li>\n";
html += "</ul>\n";
}
html += "<div class=\"dropdown\" ";
html += (navigation.ngHide) ? Attr(navigation, 'ngHide') : '';
html += ">\n";
for (itm in navigation) {
if (typeof navigation[itm] === 'object' && navigation[itm].active) {
html += "<a href=\"\" class=\"toggle\" ";
html += "data-toggle=\"dropdown\" ";
html += ">" + navigation[itm].label + " <i class=\"fa fa-chevron-circle-down crumb-icon\"></i></a>";
break;
}
}
html += "<ul class=\"dropdown-menu\" role=\"menu\">\n";
for (itm in navigation) {
if (typeof navigation[itm] === 'object') {
html += "<li role=\"presentation\"><a role=\"menuitem\" tabindex=\"-1\" href=\"" +
navigation[itm].href + "\" ";
// html += (navigation[itm].active) ? "class=\"active\" " : "";
html += ">";
html += "<i class=\"fa fa-check\" style=\"visibility: ";
html += (navigation[itm].active) ? "visible" : "hidden";
html += "\"></i> ";
html += navigation[itm].label;
html += "</a></li>\n";
}
}
html += "</ul>\n";
html += "</div><!-- dropdown -->\n";
} else {
html += "<li class=\"active\"><a href=\"\">";
if (mode === 'select') {
html += list.selectTitle;
} else {
html += list.editTitle;
}
html += "</a></li>\n</ul>\n";
}
return html;
};
}
])
// List field with multiple icons
.factory('BuildLink', ['Attr', 'Icon', function(Attr, Icon){
return function(params) {
var html = '',
field = params.field,
list = params.list,
base = params.base,
fld = params.fld;
if (field.linkTo) {
html += "<a href=\"" + field.linkTo + "\" ";
} else if (field.ngClick) {
html += "<a href=\"\" " + Attr(field, 'ngClick') + " ";
} else if (field.ngHref) {
html += "<a ng-href=\"" + field.ngHref + "\" ";
} else if (field.link || (field.key && (field.link === undefined || field.link))) {
html += "<a href=\"#/" + base + "/{{" + list.iterator + ".id }}\" ";
} else {
html += "<a href=\"\">";
}
if (field.awDroppable) {
html += Attr(field, 'awDroppable');
html += (field.dataAccept) ? Attr(field, 'dataAccept') : '';
}
if (field.awDraggable) {
html += Attr(field, 'awDraggable');
html += (field.dataContainment) ? Attr(field, 'dataContainment') : '';
html += (field.dataTreeId) ? Attr(field, 'dataTreeId') : '';
html += (field.dataGroupId) ? Attr(field, 'dataGroupId') : '';
html += (field.dataHostId) ? Attr(field, 'dataHostId') : '';
html += (field.dataType) ? Attr(field, 'dataType') : '';
}
if (field.awToolTip) {
html += Attr(field, 'awToolTip');
html += (field.dataPlacement && !field.awPopOver) ? Attr(field, 'dataPlacement') : "";
html += (field.dataTipWatch) ? Attr(field, 'dataTipWatch') : "";
html += (field.awTipPlacement) ? Attr(field, 'awTipPlacement') : "";
}
if (field.awToolTipEllipses) {
html += Attr(field, 'awToolTipEllipses');
html += (field.dataPlacement && !field.awPopOver) ? Attr(field, 'dataPlacement') : "";
html += (field.dataTipWatch) ? Attr(field, 'dataTipWatch') : "";
html += (field.awTipPlacement) ? Attr(field, 'awTipPlacement') : "";
}
if (field.awPopOver) {
html += "aw-pop-over=\"" + field.awPopOver + "\" ";
html += (field.dataPlacement) ? "data-placement=\"" + field.dataPlacement + "\" " : "";
html += (field.dataTitle) ? "data-title=\"" + field.dataTitle + "\" " : "";
}
html += (field.ngClass) ? Attr(field, 'ngClass') : '';
html += (field.ngEllipsis) ? "data-ng-bind=\"" + list.iterator + "." + fld + "\" data-ellipsis " : "";
html += ">";
// Add icon:
if (field.ngShowIcon) {
html += "<i ng-show=\"" + field.ngShowIcon + "\" class=\"" + field.icon + "\"></i> ";
} else if (field.icon) {
html += Icon(field.icon) + " ";
}
// Add data binds
if (!field.ngBindHtml && !field.iconOnly && !field.ngEllipsis && (field.showValue === undefined || field.showValue === true)) {
if (field.ngBind) {
html += "{{ " + field.ngBind;
} else {
html += "{{" + list.iterator + "." + fld;
}
if (field.filter) {
html += " | " + field.filter + " }}";
}
else {
html += " }}";
}
}
// Add additional text:
if (field.text) {
html += field.text;
}
html += "</a>";
return html;
};
}])
.factory('Column', ['Attr', 'Icon', 'DropDown', 'Badge', 'BadgeCount', 'BuildLink',
function (Attr, Icon, DropDown, Badge, BadgeCount, BuildLink) {
return function (params) {
var list = params.list,
fld = params.fld,
options = params.options,
base = params.base,
field = list.fields[fld],
html = '';
if (field.type !== undefined && field.type === 'DropDown') {
html = DropDown(params);
} else if (field.type === 'badgeCount') {
html = BadgeCount(params);
} else if (field.type === 'badgeOnly') {
html = Badge(field);
} else {
html += "<td class=\"" + fld + "-column";
html += (field['class']) ? " " + field['class'] : "";
if (options.mode === 'lookup' && field.modalColumnClass) {
html += " " + field.modalColumnClass;
}
else if (field.columnClass) {
html += " " + field.columnClass;
}
html += "\" ";
html += (field.ngClass) ? Attr(field, 'ngClass') : "";
html += (options.mode === 'lookup' || options.mode === 'select') ? " ng-click=\"toggle_" + list.iterator +
"(" + list.iterator + ".id)\"" : "";
html += (field.columnShow) ? Attr(field, 'columnShow') : "";
html += (field.ngBindHtml) ? "ng-bind-html=\"" + field.ngBindHtml + "\" " : "";
html += (field.columnClick) ? "ng-click=\"" + field.columnClick + "\" " : "";
html += (field.awEllipsis) ? "aw-ellipsis " : "";
html += ">\n";
// Add ngShow
html += (field.ngShow) ? "<span " + Attr(field, 'ngShow') + ">" : "";
//Add ngHide
//html += (field.ngHide) ? "<span " + Attr(field, 'ngHide') + ">" : "";
// Badge
if (options.mode !== 'lookup' && (field.badges || (field.badgeIcon && field.badgePlacement && field.badgePlacement === 'left'))) {
html += Badge(field);
}
// Add collapse/expand icon --used on job_events page
if (list.hasChildren && field.hasChildren) {
html += "<div class=\"level level-{{ " + list.iterator + ".event_level }}\"><a href=\"\" ng-click=\"toggle(" +
list.iterator + ".id)\"> " +
"<i class=\"{{ " + list.iterator + ".ngicon }}\"></i></a></div>";
}
if (list.name === 'groups') {
html += "<div class=\"group-name\">";
}
if (list.name === 'hosts') {
html += "<div class=\"host-name\">";
}
// Start the Link
if ((field.key || field.link || field.linkTo || field.ngClick || field.ngHref || field.awToolTip || field.awPopOver) &&
options.mode !== 'lookup' && options.mode !== 'select' && !field.noLink && !field.ngBindHtml) {
if(field.noLink === true){
// provide an override here in case we want key=true for sorting purposes but don't want links -- see: portal mode,
}
else if (field.icons) {
field.icons.forEach(function(icon, idx) {
var key, i = field.icons[idx];
for (key in i) {
field[key] = i[key];
}
html += BuildLink({
list: list,
field: field,
fld: fld,
base: base
}) + ' ';
});
}
else {
html += BuildLink({
list: list,
field: field,
fld: fld,
base: base
});
}
}
else {
// Add icon:
if (field.ngShowIcon) {
html += "<i ng-show=\"" + field.ngShowIcon + "\" class=\"" + field.icon + "\"></i> ";
} else if (field.icon) {
html += Icon(field.icon) + " ";
}
// Add data binds
if (!field.ngBindHtml && !field.iconOnly && (field.showValue === undefined || field.showValue === true)) {
if (field.ngBind) {
html += "{{ " + field.ngBind;
} else {
html += "{{ " + list.iterator + "." + fld;
}
if (field.filter) {
html += " | " + field.filter + " }}";
}
else {
html += " }}";
}
}
// Add additional text:
if (field.text) {
html += field.text;
}
}
if (list.name === 'hosts' || list.name === 'groups') {
html += "</div>";
}
// close ngShow
html += (field.ngShow) ? "</span>" : "";
//close ngHide
//html += (field.ngHide) ? "</span>" : "";
// Specific to Job Events page -showing event detail/results
html += (field.appendHTML) ? "<div ng-show=\"" + field.appendHTML + " !== null\" " +
"ng-bind-html=\"" + field.appendHTML + "\" " +
"class=\"level-{{ " + list.iterator + ".event_level }}-detail\" " +
"></div>\n" : "";
// Badge
if (options.mode !== 'lookup' && field.badgeIcon && field.badgePlacement && field.badgePlacement !== 'left') {
html += Badge(field);
}
}
return html += "</td>\n";
};
}
])
.factory('HelpCollapse', function () {
return function (params) {
var hdr = params.hdr,
content = params.content,
show = params.show,
idx = params.idx,
bind = params.bind,
html = '';
html += "<div class=\"panel-group collapsible-help\" ";
html += (show) ? "ng-show=\"" + show + "\"" : "";
html += ">\n";
html += "<div class=\"panel panel-default\">\n";
html += "<div class=\"panel-heading\" ng-click=\"accordionToggle('#accordion" + idx + "')\">\n";
html += "<h4 class=\"panel-title\">\n";
//html += "<i class=\"fa fa-question-circle help-collapse\"></i> " + hdr;
html += hdr;
html += "<i class=\"fa fa-minus pull-right collapse-help-icon\" id=\"accordion" + idx + "-icon\"></i>";
html += "</h4>\n";
html += "</div>\n";
html += "<div id=\"accordion" + idx + "\" class=\"panel-collapse collapse in\">\n";
html += "<div class=\"panel-body\" ";
html += (bind) ? "ng-bind-html=\"" + bind + "\" " : "";
html += ">\n";
html += (!bind) ? content : "";
html += "</div>\n";
html += "</div>\n";
html += "</div>\n";
html += "</div>\n";
return html;
};
})
.factory('SearchWidget', function () {
return function (params) {
//
// Generate search widget
//
var iterator = params.iterator,
form = params.template,
size = params.size,
includeSize = (params.includeSize === undefined) ? true : params.includeSize,
i, html = '',
modifier,
searchWidgets = (params.searchWidgets) ? params.searchWidgets : 1,
sortedKeys;
function addSearchFields(idx) {
var html = '';
sortedKeys = Object.keys(form.fields).sort();
sortedKeys.forEach(function(fld) {
if ((form.fields[fld].searchable === undefined || form.fields[fld].searchable === true) &&
(((form.fields[fld].searchWidget === undefined || form.fields[fld].searchWidget === 1) && idx === 1) ||
(form.fields[fld].searchWidget === idx))) {
html += "<li><a href=\"\" ng-click=\"setSearchField('" + iterator + "','";
html += fld + "','";
if (form.fields[fld].searchLabel) {
html += form.fields[fld].searchLabel + "', " + idx + ")\">" +
form.fields[fld].searchLabel + "</a></li>\n";
} else {
html += form.fields[fld].label.replace(/<br \/>/g, ' ') + "', " + idx + ")\">" +
form.fields[fld].label.replace(/<br \/>/g, ' ') + "</a></li>\n";
}
}
});
return html;
}
for (i = 1; i <= searchWidgets; i++) {
modifier = (i === 1) ? '' : i;
if (includeSize) {
html += "<div class=\"";
html += (size) ? size : "col-lg-4 col-md-6 col-sm-8 col-xs-9";
html += "\" id=\"search-widget-container" + modifier + "\">\n";
}
html += "<div class=\"input-group input-group-sm";
html += "\">\n";
html += "<div class=\"input-group-btn dropdown\">\n";
html += "<button type=\"button\" ";
html += "id=\"search_field_ddown\" ";
html += "class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\"";
html += ">\n";
html += "<span ng-bind=\"" + iterator + "SearchFieldLabel" + modifier + "\"></span>\n";
html += "<span class=\"caret\"></span>\n";
html += "</button>\n";
html += "<ul class=\"dropdown-menu\" id=\"" + iterator + "SearchDropdown" + modifier + "\">\n";
html += addSearchFields(i);
html += "</ul>\n";
html += "</div><!-- input-group-btn -->\n";
html += "<select id=\"search_value_select\" ng-show=\"" + iterator + "SelectShow" + modifier + "\" " +
"ng-model=\"" + iterator + "SearchSelectValue" + modifier + "\" ng-change=\"search('" + iterator + "')\" ";
html += "ng-options=\"c.name for c in " + iterator + "SearchSelectOpts track by c.value" + modifier + "\" class=\"form-control search-select";
html += "\"></select>\n";
html += "<input id=\"search_value_input\" type=\"text\" ng-hide=\"" + iterator + "SelectShow" + modifier + " || " +
iterator + "InputHide" + modifier + "\" " +
"class=\"form-control\" ng-model=\"" + iterator + "SearchValue" + modifier + "\" " +
"aw-placeholder=\"" + iterator + "SearchPlaceholder" + modifier + "\" type=\"text\" ng-disabled=\"" + iterator +
"InputDisable" + modifier + " || " + iterator + "HoldInput" + modifier + "\" ng-keypress=\"startSearch($event,'" +
iterator + "')\">\n";
// Reset button for drop-down
html += "<div class=\"input-group-btn\" ng-show=\"" + iterator + "SelectShow" + modifier + "\" >\n";
html += "<button type=\"button\" class=\"btn btn-default btn-small\" id=\"search-reset-button\" ng-click=\"resetSearch('" + iterator + "')\" " +
"aw-tool-tip=\"Clear the search\" data-placement=\"top\"><i class=\"fa fa-times\"></i></button>\n";
html += "</div><!-- input-group-btn -->\n";
html += "</div><!-- input-group -->\n";
html += "<a class=\"search-reset-start\" id=\"search-reset-button\" ng-click=\"resetSearch('" + iterator + "')\"" +
"ng-hide=\"" + iterator + "SelectShow" + modifier + " || " + iterator + "InputHide" + modifier + " || " +
iterator + "ShowStartBtn" + modifier + " || " +
iterator + "HideAllStartBtn" + modifier + "\"" +
"><i class=\"fa fa-times\"></i></a>\n";
html += "<a class=\"search-reset-start\" id=\"search-submit-button\" ng-click=\"search('" + iterator + "')\"" +
"ng-hide=\"" + iterator + "SelectShow" + modifier + " || " + iterator + "InputHide" + modifier + " || " +
"!" + iterator + "ShowStartBtn" + modifier + " || " +
iterator + "HideAllStartBtn" + modifier + "\"" +
"><i class=\"fa fa-search\"></i></a>\n";
html += "<div id=\"search-widget-spacer\" ng-show=\"" + iterator + "SelectShow" + modifier + "\"></div>\n";
if (includeSize) {
html += "</div>\n";
}
}
return html;
};
})
.factory('PaginateWidget', [
function () {
return function (params) {
var iterator = params.iterator,
set = params.set,
html = '';
html += "<!-- Paginate Widget -->\n";
html += "<div id=\"" + iterator + "-pagination\" class=\"row page-row\">\n";
html += "<div class=\"col-lg-8 col-md-8\">\n";
html += "<ul id=\"pagination-links\" class=\"pagination\" ng-hide=\"" + iterator + "HidePaginator || " + iterator + "_num_pages <= 1\">\n";
html += "<li ng-hide=\"" + iterator + "_page -5 <= 1 \"><a href id=\"first-page-set\" ng-click=\"getPage(1,'" + set + "','" + iterator + "')\">" +
"<i class=\"fa fa-angle-double-left\"></i></a></li>\n";
html += "<li ng-hide=\"" + iterator + "_page -1 <= 0\"><a href " +
"id=\"previous-page\" ng-click=\"getPage(" + iterator + "_page - 1,'" + set + "','" + iterator + "')\">" +
"<i class=\"fa fa-angle-left\"></i></a></li>\n";
// html += "<li ng-repeat=\"page in " + iterator + "_page_range\" ng-class=\"pageIsActive(page,'" + iterator + "')\">" +
// "<a href id=\"{{ 'link-to-page-' + page }}\" ng-click=\"getPage(page,'" + set + "','" + iterator + "')\">{{ page }}</a></li>\n";
html += "<li ng-repeat=\"page in " + iterator + "_page_range\" ng-class=\"pageIsActive(page,'" + iterator + "')\">" +
"<a href id=\"{{ 'page-' + page }}\" ng-click=\"getPage(page,'" + set + "','" + iterator + "')\">{{ page }}</a></li>\n";
html += "<li ng-hide=\"" + iterator + "_page + 1 > " + iterator + "_num_pages\"><a href id=\"next-page\" ng-click=\"" +
"getPage(" + iterator + "_page + 1,'" + set + "','" + iterator + "')\"><i class=\"fa fa-angle-right\"></i></a></li>\n";
html += "<li ng-hide=\"" + iterator + "_page +4 >= " + iterator + "_num_pages\"><a href id=\"last-page-set\" ng-click=\"" +
"getPage(" + iterator + "_num_pages,'" + set + "','" + iterator + "')\"><i class=\"fa fa-angle-double-right\"></i></a></li>\n";
html += "</ul>\n";
html += "</div>\n";
html += "<div class=\"col-lg-4 col-md-4\" ng-hide=\"" + iterator + "_mode == 'lookup'\">\n";
html += "<div id=\"pagination-labels\" class=\"page-label\">\n";
html += "Page <span id=\"current-page\">{{ " + iterator + "_page }}</span> of <span id=\"total-pages\">{{ " + iterator + "_num_pages }}</span> (<span id=\"total-items\">{{ " + iterator + "_total_rows | number:0 }}</span> items)";
html += "</div>\n";
html += "</div>\n";
html += "</div>\n";
return html;
};
}
]);

View File

@@ -0,0 +1,585 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:list-generator
* @description
* #ListGenerator
*
* Use GenerateList.inject(list_object, { key:value }) to generate HTML from a list object and inject it into the DOM. Returns the $scope of the new list.
*
* Pass in a list object and a JSON object of key:value parameters. List objects are found in lists/*.js. Possible parameters include:
*
* | Parameter | Required | Description |
* | --------- | -------- | ----------- |
* | activityStream | | Used in widgets/stream.js to create the list contained within the activity stream widget. |
* | breadCrumbs | | true or false. Set to false, if breadcrumbs should not be included in the generated HTML. |
* | hdr | | Deprecated. Was used when list generator created the lookup dialog. This was moved to helpers/Lookup.js. |
* | id | | DOM element ID attribute value. Use to inject the list into a custom DOM element. Otherwise, the HTML for a list will be injected into the DOM element with an ID attribute of 'htmlTemplate'. |
* | listSize | | Bootstrap size class to apply to the grid column containing the action buttons, which generally appears to the right of the search widget. Defaults to 'col-lg-8 col-md-6 col-sm-4 col-xs-3'. |
* | mode | Yes | One of 'edit', 'lookup', 'select', or 'summary'. Generally this will be 'edit'. helpers/Lookup.js uses 'lookup' to generate the lookup dialog. The 'select' option is used in certain controllers when multiple objects are being added to a parent object. For example, building a select list of Users that can be added to an Oranization. 'summary' is no longer used. |
* | scope | | If the HTML will be injected into the DOM by list generator, pass in an optional $scope to be used in conjuction with $compile. The list will be associated with the scope value. Otherwise, the scope of the DOM element will be fetched passed to $compile. |
* | showSearch | | true or false. Set to false, if the search widget should not be included in the generated HTML. |
* | searchSize | | Bootstrap size class (e.g. col-lg-3, col-md-4, col-sm-5, etc.) to apply to the search widget. Defaults to 'col-lg-4 col-md-6 col-sm-8 col-xs-9'. |
*
* #HTML only
*
* Use the buildHTML() method to get a string containing the generated HTML for a list object. buldHTML() expects the same parameters as the inject method. For example:
* ```
* var html = GenerateList.buildHTML({
* mode: 'edit',
* breadCrumbs: false,
* showSearch: false
* });
* ```
*
* #List Objects
*
* List objects are found in lists/*.js. Any API endpoint that returns a collection or array is represented with a list object. Examples inlcude Organizations, Credentials, Inventories, etc.
* A list can have the following attributes:
*
* | Attribute | Description |
* | --------- | ----------- |
* | hover | true or false. If true, 'table-hover' class will be added to the &lt;table&gt; element. |
* | index | true or false. If false, the index column, which adds a sequential number to each table row starting with 1, will not be added to the table. |
* | iterator | String containing a descriptive name of a single row in the collection - inventory, organization, credential, etc. Used to generate name and ID attributes in the list HTML. |
* | name | Name of the collection. Generally matches the endpoint name - inventories, organizations, etc. Will match the $scope variable containing the array of rows comprising the collection. |
* | selectTitle | Descriptive title used when mode is 'select'. |
* | selectInstructions | Text and HTML used to create popover for help button when mode is 'select'. |
* | editTitle | Descriptive title used when mode is 'edit'. |
*
* ##Fields
*
* A list contains a fields object. Each column in the list is defined as a separate object within the fields object. A field definition may contain the following attributes:
*
* | Attribute | Description |
* | --------- | ----------- |
* | columnClass | String of CSS class names to add to the &lt;td&gt; elemnts of the table column. |
* | columnClick | Adds an ng-click directive to the &lt;td&gt; element. |
* | excludeModal | true or false. If false, the field will not be included in the generated HTML when the mode is 'lookup' |
* | key | true or false. If set to true, helpers/search.js will use the field name as the default sort order when generating the API request. |
* | noLink | true or false. If set to true this will override any 'key', 'linkTo', 'ngClick', or other option that would cause the field to be a link. Used in portal mode and custom inv. script. |
* | label | Text string used as the column header text. |
* | linkTo | Wraps the field value with an &lt;a&gt; element. Set to the value of the href attribute. |
* | ngClick | Wraps the field value with an &lt;a&gt; and adds the ng-click directive. Set to the JS expression that ng-click will evaluate. |
* | nosort | true or false. Setting to false removes the ability to sort the table by the column. |
* | searchable | true or fasel. Set to false if the field should not be included as in option in the search widget. |
* | searchOnly | true or false. Set to true if the field should be included in the search widget but not included as a column in the generated HTML &lt;table&gt;. |
* | searchOptions | Array of { name: 'Descriptive Name', value: 'api_value' } objects used to generate &lt;options&gt; for the &lt;select&gt; when searchType is 'select'. |
* | searchType | One of the available search types defined in helpers/search.js. |
* | sourceField | Name of the attribute within summary_fields.<source_model> that the field maps to in the API response object. Used in conjunction with sourceModel. |
* | sourceModel | Name of the summary_fields object that the field maps to in the API response object. |
*
* ##Field Actions
*
* A list contains a fieldActions object. Each icon found in the Actions column is defined as an object within the feildActions object. fieldActions can have a columnClass attribute,
* which may contain a string of CSS class names to add to the action &lt;td&gt; element. It may also contain a label attribute, which can be set to false to suppress the Actions column header.
*
* Feld action items can have the following attributes:
*
* | Attribute | Description |
* | --------- | ----------- |
* | awToolTip | Adds the aw-tool-tip directive. Set to the value of the HTML or text to dislay in the tooltip. |
* | 'class' | Set to a string containing any CSS classes to add to the &lt;a&gt; element. |
* | dataPlacement | Set to the Bootstrip tooltip placement - right, left, top, bottom, etc. |
* | dataTipWatch | Set to the $scope variable that contains the text and HTML to display in the tooltip. A $scope.$watch will be added to the variable so that anytime its value changes the tooltip will change. |
* | iconClass | By default the CSS icon class is set by the SelectIcon() method in lib/ansible/generator-helpers.js. The icon is based on the action name. Use iconClass to override the default value. |
* | mode | One of 'all' or 'edit'. Will generally be 'all'. Note that field actions are not displayed when the list is in 'lookup' mode. |
* | ngClass | Adds the ng-class directive. Set to the JS expressino that ng-class will evaluate. |
* | ngShow | Adds the ng-show directive. Set to the JS expression that ng-show will evaluate. |
*
* ##Actions
*
* A list can contain an actions object. The actions object contains an object for each action button displayed in the top-right corner of the list container. An action can have the same
* attributes as an action defined in fieldAction. Both are actions. Clicking on an action evaluates the JS found in the ngClick attribute. In both cases icon is generated automatically by the SelectIcon() method in lib/ansible/generator-helpers.js.
* The real difference is that an &lt;a&gt element is used to generate fieldAction items while a &lt;button&gt; element is used for action items.
*/
export default
angular.module('ListGenerator', ['GeneratorHelpers'])
.factory('GenerateList', ['$location', '$compile', '$rootScope', 'SearchWidget', 'PaginateWidget', 'Attr', 'Icon',
'Column', 'DropDown', 'NavigationLink', 'Button', 'SelectIcon', 'Breadcrumbs',
function ($location, $compile, $rootScope, SearchWidget, PaginateWidget, Attr, Icon, Column, DropDown, NavigationLink,
Button, SelectIcon, Breadcrumbs) {
return {
setList: function (list) {
this.list = list;
},
setOptions: function(options) {
this.options = options;
},
attr: Attr,
icon: Icon,
has: function (key) {
return (this.form[key] && this.form[key] !== null && this.form[key] !== undefined) ? true : false;
},
hide: function () {
$('#lookup-modal').modal('hide');
},
button: Button,
buildHTML: function(list, options) {
this.setList(list);
return this.build(options);
},
inject: function (list, options) {
// options.mode = one of edit, select or lookup
//
// Modes edit and select will inject the list as html into element #htmlTemplate.
// 'lookup' mode injects the list html into #lookup-modal-body.
//
// For options.mode == 'lookup', include the following:
//
// hdr: <lookup dialog header>
//
// Inject into a custom element using options.id: <element id attribute value>
// Control breadcrumb creation with options.breadCrumbs: <true | false>
//
var element;
if (options.id) {
element = angular.element(document.getElementById(options.id));
} else {
element = angular.element(document.getElementById('htmlTemplate'));
}
this.setOptions(options);
this.setList(list);
element.html(this.build(options)); // Inject the html
if (options.prepend) {
element.prepend(options.prepend);
}
if (options.append) {
element.append(options.append);
}
if (options.scope) {
this.scope = options.scope;
} else {
this.scope = element.scope();
}
$compile(element)(this.scope);
// Reset the scope to prevent displaying old data from our last visit to this list
//this.scope[list.name] = null;
this.scope[list.iterator] = [];
// Remove any lingering tooltip and popover <div> elements
$('.tooltip').each(function() {
$(this).remove();
});
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
try {
$('#help-modal').empty().dialog('destroy');
} catch (e) {
//ignore any errors should the dialog not be initialized
}
/*if (options.mode === 'lookup') {
// options should include {hdr: <dialog header>, action: <function...> }
this.scope.formModalActionDisabled = false;
this.scope.lookupHeader = options.hdr;
$('#lookup-modal').modal({
backdrop: 'static',
keyboard: true
});
$('#lookup-modal').unbind('hidden.bs.modal');
$(document).bind('keydown', function (e) {
if (e.keyCode === 27) {
$('#lookup-modal').modal('hide');
}
});
}*/
return this.scope;
},
build: function (options) {
//
// Generate HTML. Do NOT call this function directly. Called by inject(). Returns an HTML
// string to be injected into the current view.
//
var html = '',
list = this.list,
base, size, action, btn, fld, cnt, field_action, fAction, itm;
if (options.activityStream) {
// Breadcrumbs for activity stream widget
// Make the links clickable using ng-click function so we can first remove the stream widget
// before navigation
html += "<div>\n";
html += "<ul class=\"ansible-breadcrumb\">\n";
html += "<li ng-repeat=\"crumb in breadcrumbs\"><a href=\"\" " + "ng-click=\"closeStream(crumb.path)\">" +
"{{ crumb.title }}</a></li>\n";
html += "<li class=\"active\"><a href=\"\">";
html += list.editTitle;
html += "</a></li>\n</ul>\n</div>\n";
}
//else if (options.mode !== 'lookup' && (options.breadCrumbs === undefined || options.breadCrumbs)) {
else if (options.breadCrumbs) {
html += Breadcrumbs({
list: list,
mode: options.mode
});
}
if (options.mode === 'edit' && list.editInstructions) {
html += "<div class=\"alert alert-info alert-block\">\n";
html += "<button type=\"button\" class=\"close\" data-dismiss=\"alert\">&times;</button>\n";
html += "<strong>Hint: </strong>" + list.editInstructions + "\n";
html += "</div>\n";
}
if (options.instructions) {
html += "<div class=\"instructions alert alert-info\">" + options.instructions + "</div>\n";
}
else if (list.instructions) {
html += "<div class=\"instructions alert alert-info\">" + list.instructions + "</div>\n";
}
if (options.mode !== 'lookup' && (list.well === undefined || list.well)) {
html += "<div class=\"list-well\">\n";
}
if (options.activityStream) {
// Add a title row
html += "<div class=\"row\">\n";
html += "<div class=\"col-lg-12\">\n";
html += "<h5>{{ streamTitle }}</h5>\n";
html += "</div>\n";
html += "</div>\n";
}
if (options.showSearch=== undefined || options.showSearch === true) {
html += "<div class=\"row\">\n";
if (options.searchSize) {
html += SearchWidget({
iterator: list.iterator,
template: list,
mini: true,
size: options.searchSize,
searchWidgets: list.searchWidgets
});
} else if (options.mode === 'summary') {
html += SearchWidget({
iterator: list.iterator,
template: list,
mini: true,
size: 'col-lg-6'
});
} else if (options.mode === 'lookup' || options.id !== undefined) {
html += SearchWidget({
iterator: list.iterator,
template: list,
mini: true,
size: 'col-lg-8'
});
} else {
html += SearchWidget({
iterator: list.iterator,
template: list,
mini: true
});
}
if (options.mode !== 'lookup') {
//actions
base = $location.path().replace(/^\//, '').split('/')[0];
html += "<div class=\"";
if (options.searchSize && !options.listSize) {
// User supplied searchSize, calc the remaining
size = parseInt(options.searchSize.replace(/([A-Z]|[a-z]|\-)/g, ''));
size = (list.searchWidgets) ? list.searchWidgets * size : size;
html += 'col-lg-' + (12 - size);
} else if (options.listSize) {
html += options.listSize;
} else if (options.mode === 'summary') {
html += 'col-lg-6';
} else if (options.id !== undefined) {
html += "col-lg-4";
} else {
html += "col-lg-8 col-md-6 col-sm-4 col-xs-3";
}
html += "\">\n";
html += "<div class=\"list-actions\">\n";
// Add toolbar buttons or 'actions'
for (action in list.actions) {
if (list.actions[action].mode === 'all' || list.actions[action].mode === options.mode) {
if ((list.actions[action].basePaths === undefined) ||
(list.actions[action].basePaths && list.actions[action].basePaths.indexOf(base) > -1)) {
html += this.button({
btn: list.actions[action],
action: action,
toolbar: true
});
}
}
}
//select instructions
if (options.mode === 'select' && list.selectInstructions) {
btn = {
awPopOver: list.selectInstructions,
dataPlacement: 'left',
dataContainer: 'body',
'class': 'btn-xs btn-help',
awToolTip: 'Click for help',
dataTitle: 'Help',
iconSize: 'fa-lg'
};
//html += this.button(btn, 'select');
html += this.button({
btn: btn,
action: 'help',
toolbar: true
});
}
html += "</div><!-- list-acitons -->\n";
html += "</div><!-- list-actions-column -->\n";
} else {
//lookup
html += "<div class=\"col-lg-7\"></div>\n";
}
html += "</div><!-- row -->\n";
}
// Add a title and optionally a close button (used on Inventory->Groups)
if (options.mode !== 'lookup' && list.showTitle) {
html += "<div class=\"form-title\">";
html += (options.mode === 'edit' || options.mode === 'summary') ? list.editTitle : list.addTitle;
html += "</div>\n";
}
// table header row
html += "<div class=\"list-table-container\"";
html += (list.awCustomScroll) ? " aw-custom-scroll " : "";
html += ">\n";
html += "<table id=\"" + list.name + "_table\" ";
html += "class=\"table table-condensed";
html += (list['class']) ? " " + list['class'] : "";
html += (options.mode !== 'summary' && options.mode !== 'edit' && (options.mode === 'lookup' || options.id)) ?
' table-hover-inverse' : '';
html += (list.hover) ? ' table-hover' : '';
html += (options.mode === 'summary') ? ' table-summary' : '';
html += "\" ";
html += ">\n";
if (!options.skipTableHead) {
html += this.buildHeader(options);
}
// table body
html += "<tbody>\n";
html += "<tr ng-class=\"" + list.iterator;
html += (options.mode === 'lookup' || options.mode === 'select') ? ".success_class" : ".active_class";
html += "\" ";
html += "id=\"{{ " + list.iterator + ".id }}\" ";
html += "class=\"" + list.iterator + "_class\" ";
html += "ng-repeat=\"" + list.iterator + " in " + list.name;
html += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : "";
html += (list.filterBy) ? " | filter: " + list.filterBy : "";
html += "\">\n";
if (list.index) {
html += "<td class=\"index-column hidden-xs\">{{ $index + ((" + list.iterator + "_page - 1) * " + list.iterator + "_page_size) + 1 }}.</td>\n";
}
cnt = 2;
base = (list.base) ? list.base : list.name;
base = base.replace(/^\//, '');
for (fld in list.fields) {
cnt++;
if ((list.fields[fld].searchOnly === undefined || list.fields[fld].searchOnly === false) &&
!(options.mode === 'lookup' && list.fields[fld].excludeModal === true)) {
html += Column({
list: list,
fld: fld,
options: options,
base: base
});
}
}
if (options.mode === 'select' || options.mode === 'lookup') {
if(options.input_type==="radio"){ //added by JT so that lookup forms can be either radio inputs or check box inputs
html += "<td><input type=\"radio\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-value=\"1\" " +
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
}
else { // its assumed that options.input_type = checkbox
html += "<td><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " +
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
}
} else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) {
// Row level actions
html += "<td class=\"actions\">";
for (field_action in list.fieldActions) {
if (field_action !== 'columnClass') {
if (list.fieldActions[field_action].type && list.fieldActions[field_action].type === 'DropDown') {
html += DropDown({
list: list,
fld: field_action,
options: options,
base: base,
type: 'fieldActions',
td: false
});
} else {
fAction = list.fieldActions[field_action];
html += "<a id=\"";
html += (fAction.id) ? fAction.id : field_action + "-action";
html += "\" ";
html += (fAction.href) ? "href=\"" + fAction.href + "\" " : "";
html += (fAction.ngHref) ? "ng-href=\"" + fAction.ngHref + "\" " : "";
html += (field_action === 'cancel') ? "class=\"cancel red-txt\" " : "";
html += (fAction.awPopOver) ? "aw-pop-over=\"" + fAction.awPopOver + "\" " : "";
html += (fAction.dataPlacement) ? Attr(fAction, 'dataPlacement') : "";
html += (fAction.dataTitle) ? Attr(fAction, 'dataTitle') : "";
for (itm in fAction) {
if (itm !== 'ngHref' && itm !== 'href' && itm !== 'label' && itm !== 'icon' && itm !== 'class' &&
itm !== 'iconClass' && itm !== "dataPlacement" && itm !== "awPopOver" &&
itm !== "dataTitle") {
html += Attr(fAction, itm);
}
}
html += ">";
if (fAction.iconClass) {
html += "<i class=\"" + fAction.iconClass + "\"></i>";
} else {
html += SelectIcon({
action: field_action
});
}
//html += (fAction.label) ? "<span class=\"list-action-label\"> " + list.fieldActions[field_action].label +
// "</span>" : "";
html += "</a>";
}
}
}
html += "</td>\n";
}
html += "</tr>\n";
// Message for when a collection is empty
html += "<tr class=\"loading-info\" ng-show=\"" + list.iterator + "Loading == false && " + list.name + ".length == 0\">\n";
html += "<td colspan=\"" + cnt + "\"><div class=\"loading-info\">No records matched your search.</div></td>\n";
html += "</tr>\n";
// Message for loading
html += "<tr class=\"loading-info\" ng-show=\"" + list.iterator + "Loading == true\">\n";
html += "<td colspan=\"" + cnt + "\"><div class=\"loading-info\">Loading...</div></td>\n";
html += "</tr>\n";
// End List
html += "</tbody>\n";
html += "</table>\n";
html += "</div><!-- table container -->\n";
if (options.mode === 'select' && (options.selectButton === undefined || options.selectButton)) {
html += "<div class=\"navigation-buttons\">\n";
html += " <button class=\"btn btn-sm btn-primary pull-right\" id=\"select_btn\" aw-tool-tip=\"Complete your selection\" " +
"ng-click=\"finishSelection()\" ng-disabled=\"disableSelectBtn\"><i class=\"fa fa-check\"></i> Select</button>\n";
html += "</div>\n";
}
if (options.mode !== 'lookup' && (list.well === undefined || list.well === true)) {
html += "</div>\n"; //well
}
if (options.mode === 'lookup' || (options.id && options.id === "form-modal-body")) {
html += PaginateWidget({
set: list.name,
iterator: list.iterator
});
} else {
html += PaginateWidget({
set: list.name,
iterator: list.iterator
});
}
return html;
},
buildHeader: function(options) {
var list = this.list,
fld, html;
if (options === undefined) {
options = this.options;
}
html = "<thead>\n";
html += "<tr>\n";
if (list.index) {
html += "<th class=\"col-lg-1 col-md-1 col-sm-2 hidden-xs\">#</th>\n";
}
for (fld in list.fields) {
if ((list.fields[fld].searchOnly === undefined || list.fields[fld].searchOnly === false) &&
!(options.mode === 'lookup' && list.fields[fld].excludeModal === true)) {
html += "<th class=\"list-header";
if (options.mode === 'lookup' && list.fields[fld].modalColumnClass) {
html += " " + list.fields[fld].modalColumnClass;
}
else if (list.fields[fld].columnClass) {
html += " " + list.fields[fld].columnClass;
}
html += "\" id=\"" + list.iterator + "-" + fld + "-header\"";
html += (list.fields[fld].columnShow) ? " ng-show=\"" + list.fields[fld].columnShow + "\" " : "";
html += (list.fields[fld].nosort === undefined || list.fields[fld].nosort !== true) ? " ng-click=\"sort('" + list.iterator + "','" + fld + "')\"" : "";
html += ">";
html += list.fields[fld].label;
if (list.fields[fld].nosort === undefined || list.fields[fld].nosort !== true) {
html += " <i class=\"fa ";
if (list.fields[fld].key) {
if (list.fields[fld].desc) {
html += "fa-sort-down";
} else {
html += "fa-sort-up";
}
} else {
html += "fa-sort";
}
html += "\"></i></a>";
}
html += "</th>\n";
}
}
if (options.mode === 'select' || options.mode === 'lookup') {
html += "<th class=\"col-lg-1 col-md-1 col-sm-2 col-xs-2\">Select</th>";
} else if (options.mode === 'edit' && list.fieldActions) {
html += "<th class=\"actions-column";
html += (list.fieldActions && list.fieldActions.columnClass) ? " " + list.fieldActions.columnClass : "";
html += "\">";
html += (list.fieldActions.label === undefined || list.fieldActions.label) ? "Actions" : "";
html += "</th>\n";
}
html += "</tr>\n";
html += "</thead>\n";
return html;
}
};
}]);

View File

@@ -0,0 +1,126 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*/
/**
* @ngdoc function
* @name lib.ansible.function:prompt-dialog
* @description
* PromptDialog
* Prompt the user with a Yes/No dialog to confirm an action such
* as Delete. Assumes a hidden dialog already exists in $scope.
* See example at bottom. If user responds with Yes, execute action
* parameter.
*
* params: { hdr: 'header msg',
* body: 'body text/html',
* class: 'btn-class for Yes button', --defaults to btn-danger
* action: function() {} --action to take, if use clicks Yes
* }
*/
/**
* @ngdoc method
* @name lib.ansible.function:prompt-dialog#PromptDialog
* @methodOf lib.ansible.function:prompt-dialog
* @description discuss difference b/t this and other modals
*/
export default
angular.module('PromptDialog', ['Utilities'])
.factory('Prompt', ['$sce',
function ($sce) {
return function (params) {
var dialog = angular.element(document.getElementById('prompt-modal')),
scope = dialog.scope(), cls, local_backdrop;
scope.promptHeader = params.hdr;
scope.promptBody = $sce.trustAsHtml(params.body);
scope.promptAction = params.action;
local_backdrop = (params.backdrop === undefined) ? "static" : params.backdrop;
cls = (params['class'] === null || params['class'] === undefined) ? 'btn-danger' : params['class'];
$('#prompt_action_btn').removeClass(cls).addClass(cls);
// bootstrap modal's have an open defect with disallowing tab index's of the background of the modal
// This will keep the tab indexing on the modal's focus. This is to fix an issue with tabbing working when
// the user is attempting to delete something. Might need to be checked for other occurances of the bootstrap
// modal other than deleting
function disableTabModalShown() {
$('.modal').on('shown.bs.modal', function() {
var modal = $(this),
focusableChildren = modal.find('a[href], a[data-dismiss], area[href], input, select, textarea, button, iframe, object, embed, *[tabindex], *[contenteditable]'),
numElements = focusableChildren.length,
currentIndex = 0,
focus,
focusPrevious,
focusNext;
$(document.activeElement).blur();
focus = function() {
var focusableElement = focusableChildren[currentIndex];
if (focusableElement) {
focusableElement.focus();
}
};
focusPrevious = function () {
currentIndex--;
if (currentIndex < 0) {
currentIndex = numElements - 1;
}
focus();
return false;
};
focusNext = function () {
currentIndex++;
if (currentIndex >= numElements) {
currentIndex = 0;
}
focus();
return false;
};
$(document).on('keydown', function (e) {
if (e.keyCode === 9 && e.shiftKey) {
e.preventDefault();
focusPrevious();
}
else if (e.keyCode === 9) {
e.preventDefault();
focusNext();
}
});
$(this).focus();
});
$('.modal').on('hidden.bs.modal', function() {
$(document).unbind('keydown');
});
}
$('#prompt-modal').off('hidden.bs.modal');
$('#prompt-modal').modal({
backdrop: 'local_backdrop',
keyboard: true,
show: true
});
disableTabModalShown();
};
}
]);

View File

@@ -0,0 +1,327 @@
/*
** Created by: Jeff Todnem (http://www.todnem.com/)
** Created on: 2007-08-14
** Modified: 2013-07-31 by James Cammarata
**
** License Information:
** -------------------------------------------------------------------------
** Copyright (C) 2007 Jeff Todnem
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 2 of the License, or (at your
** option) any later version.
**
** This program is distributed in the hope that it will be useful, but
** WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public License along
** with this program; if not, write to the Free Software Foundation, Inc.,
** 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
**
**/
/**
* @ngdoc function
* @name lib.ansible.function:pwdmeter
* @description
** CLH 09/05/13 - Set required strength in config.js
** 02/10/14 - Applied jsHint
*/
/*jshint eqeqeq:false, unused:false */
String.prototype.strReverse = function () {
var newstring = "", s;
for (s = 0; s < this.length; s++) {
newstring = this.charAt(s) + newstring;
}
return newstring;
};
var nScore = 0;
export function chkPass(pwd) {
// Simultaneous variable declaration and value assignment aren't supported in IE apparently
// so I'm forced to assign the same value individually per var to support a crappy browser *sigh*
var nLength = 0,
nAlphaUC = 0,
nAlphaLC = 0,
nNumber = 0,
nSymbol = 0,
nMidChar = 0,
nRequirements = 0,
nAlphasOnly = 0,
nNumbersOnly = 0,
nUnqChar = 0,
nRepChar = 0,
nRepInc = 0,
nConsecAlphaUC = 0,
nConsecAlphaLC = 0,
nConsecNumber = 0,
nConsecSymbol = 0,
nConsecCharType = 0,
nSeqAlpha = 0,
nSeqNumber = 0,
nSeqSymbol = 0,
nSeqChar = 0,
nReqChar = 0,
nMultConsecCharType = 0,
nMultRepChar = 1,
nMultConsecSymbol = 1,
nMultMidChar = 2,
nMultRequirements = 2,
nMultConsecAlphaUC = 2,
nMultConsecAlphaLC = 2,
nMultConsecNumber = 2,
nReqCharType = 3,
nMultAlphaUC = 3,
nMultAlphaLC = 3,
nMultSeqAlpha = 3,
nMultSeqNumber = 3,
nMultSeqSymbol = 3,
nMultLength = 4,
nMultNumber = 4,
nMultSymbol = 6,
nTmpAlphaUC = "",
nTmpAlphaLC = "",
nTmpNumber = "",
nTmpSymbol = "",
sAlphaUC = "0",
sAlphaLC = "0",
sNumber = "0",
sSymbol = "0",
sMidChar = "0",
sRequirements = "0",
sAlphasOnly = "0",
sNumbersOnly = "0",
sRepChar = "0",
sConsecAlphaUC = "0",
sConsecAlphaLC = "0",
sConsecNumber = "0",
sSeqAlpha = "0",
sSeqNumber = "0",
sSeqSymbol = "0",
sAlphas = "abcdefghijklmnopqrstuvwxyz",
sNumerics = "01234567890",
sSymbols = ")_!@#$%^&*()",
sComplexity = "Too Short",
sStandards = "Below",
nMinPwdLen = 8,
a, nd, arrPwd, arrPwdLen,bCharExists,b,s,sFwd,sRev,progbar,required_strength,warning_level;
if (document.all) {
nd = 0;
} else {
nd = 1;
}
if (pwd) {
nScore = parseInt(pwd.length * nMultLength);
nLength = pwd.length;
arrPwd = pwd.replace(/\s+/g, "").split(/\s*/);
arrPwdLen = arrPwd.length;
/* Loop through password to check for Symbol, Numeric, Lowercase and Uppercase pattern matches */
for (a = 0; a < arrPwdLen; a++) {
if (arrPwd[a].match(/[A-Z]/g)) {
if (nTmpAlphaUC !== "") {
if ((nTmpAlphaUC + 1) == a) {
nConsecAlphaUC++;
nConsecCharType++;
}
}
nTmpAlphaUC = a;
nAlphaUC++;
} else if (arrPwd[a].match(/[a-z]/g)) {
if (nTmpAlphaLC !== "") {
if ((nTmpAlphaLC + 1) == a) {
nConsecAlphaLC++;
nConsecCharType++;
}
}
nTmpAlphaLC = a;
nAlphaLC++;
} else if (arrPwd[a].match(/[0-9]/g)) {
if (a > 0 && a < (arrPwdLen - 1)) {
nMidChar++;
}
if (nTmpNumber !== "") {
if ((nTmpNumber + 1) == a) {
nConsecNumber++;
nConsecCharType++;
}
}
nTmpNumber = a;
nNumber++;
} else if (arrPwd[a].match(/[^a-zA-Z0-9_]/g)) {
if (a > 0 && a < (arrPwdLen - 1)) {
nMidChar++;
}
if (nTmpSymbol !== "") {
if ((nTmpSymbol + 1) == a) {
nConsecSymbol++;
nConsecCharType++;
}
}
nTmpSymbol = a;
nSymbol++;
}
/* Internal loop through password to check for repeat characters */
bCharExists = false;
for (b = 0; b < arrPwdLen; b++) {
if (arrPwd[a] == arrPwd[b] && a != b) { /* repeat character exists */
bCharExists = true;
/*
Calculate icrement deduction based on proximity to identical characters
Deduction is incremented each time a new match is discovered
Deduction amount is based on total password length divided by the
difference of distance between currently selected match
*/
nRepInc += Math.abs(arrPwdLen / (b - a));
}
}
if (bCharExists) {
nRepChar++;
nUnqChar = arrPwdLen - nRepChar;
nRepInc = (nUnqChar) ? Math.ceil(nRepInc / nUnqChar) : Math.ceil(nRepInc);
}
}
/* Check for sequential alpha string patterns (forward and reverse) */
for (s = 0; s < 23; s++) {
sFwd = sAlphas.substring(s, parseInt(s + 3));
sRev = sFwd.strReverse();
if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sRev) != -1) {
nSeqAlpha++;
nSeqChar++;
}
}
/* Check for sequential numeric string patterns (forward and reverse) */
for (s = 0; s < 8; s++) {
sFwd = sNumerics.substring(s, parseInt(s + 3));
sRev = sFwd.strReverse();
if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sRev) != -1) {
nSeqNumber++;
nSeqChar++;
}
}
/* Check for sequential symbol string patterns (forward and reverse) */
for (s = 0; s < 8; s++) {
sFwd = sSymbols.substring(s, parseInt(s + 3));
sRev = sFwd.strReverse();
if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sRev) != -1) {
nSeqSymbol++;
nSeqChar++;
}
}
/* Modify overall score value based on usage vs requirements */
/* General point assignment */
if (nAlphaUC > 0 && nAlphaUC < nLength) {
nScore = parseInt(nScore + ((nLength - nAlphaUC) * 2));
sAlphaUC = "+ " + parseInt((nLength - nAlphaUC) * 2);
}
if (nAlphaLC > 0 && nAlphaLC < nLength) {
nScore = parseInt(nScore + ((nLength - nAlphaLC) * 2));
sAlphaLC = "+ " + parseInt((nLength - nAlphaLC) * 2);
}
if (nNumber > 0 && nNumber < nLength) {
nScore = parseInt(nScore + (nNumber * nMultNumber));
sNumber = "+ " + parseInt(nNumber * nMultNumber);
}
if (nSymbol > 0) {
nScore = parseInt(nScore + (nSymbol * nMultSymbol));
sSymbol = "+ " + parseInt(nSymbol * nMultSymbol);
}
if (nMidChar > 0) {
nScore = parseInt(nScore + (nMidChar * nMultMidChar));
sMidChar = "+ " + parseInt(nMidChar * nMultMidChar);
}
/* Point deductions for poor practices */
if ((nAlphaLC > 0 || nAlphaUC > 0) && nSymbol === 0 && nNumber === 0) { // Only Letters
nScore = parseInt(nScore - nLength);
nAlphasOnly = nLength;
sAlphasOnly = "- " + nLength;
}
if (nAlphaLC === 0 && nAlphaUC === 0 && nSymbol === 0 && nNumber > 0) { // Only Numbers
nScore = parseInt(nScore - nLength);
nNumbersOnly = nLength;
sNumbersOnly = "- " + nLength;
}
if (nRepChar > 0) { // Same character exists more than once
nScore = parseInt(nScore - nRepInc);
sRepChar = "- " + nRepInc;
}
if (nConsecAlphaUC > 0) { // Consecutive Uppercase Letters exist
nScore = parseInt(nScore - (nConsecAlphaUC * nMultConsecAlphaUC));
sConsecAlphaUC = "- " + parseInt(nConsecAlphaUC * nMultConsecAlphaUC);
}
if (nConsecAlphaLC > 0) { // Consecutive Lowercase Letters exist
nScore = parseInt(nScore - (nConsecAlphaLC * nMultConsecAlphaLC));
sConsecAlphaLC = "- " + parseInt(nConsecAlphaLC * nMultConsecAlphaLC);
}
if (nConsecNumber > 0) { // Consecutive Numbers exist
nScore = parseInt(nScore - (nConsecNumber * nMultConsecNumber));
sConsecNumber = "- " + parseInt(nConsecNumber * nMultConsecNumber);
}
if (nSeqAlpha > 0) { // Sequential alpha strings exist (3 characters or more)
nScore = parseInt(nScore - (nSeqAlpha * nMultSeqAlpha));
sSeqAlpha = "- " + parseInt(nSeqAlpha * nMultSeqAlpha);
}
if (nSeqNumber > 0) { // Sequential numeric strings exist (3 characters or more)
nScore = parseInt(nScore - (nSeqNumber * nMultSeqNumber));
sSeqNumber = "- " + parseInt(nSeqNumber * nMultSeqNumber);
}
if (nSeqSymbol > 0) { // Sequential symbol strings exist (3 characters or more)
nScore = parseInt(nScore - (nSeqSymbol * nMultSeqSymbol));
sSeqSymbol = "- " + parseInt(nSeqSymbol * nMultSeqSymbol);
}
progbar = $("#progbar");
required_strength = $AnsibleConfig.password_strength;
warning_level = ($AnsibleConfig.password_strength - 15 < 0) ? 0 : $AnsibleConfig.password_strength - 15;
/* Enforce a minimum length of 8 */
if (nLength < 8) {
nScore = 0;
}
/* Determine complexity based on overall score */
if (nScore > 100) {
nScore = 100;
} else if (nScore < 0) {
nScore = 0;
} else if (nScore >= required_strength) {
nScore = 100; //a green progress bar should be at 100%
}
progbar.css("width", nScore + '%');
if (nScore >= 0 && nScore <= warning_level) {
sComplexity = 'Weak';
progbar.addClass('progress-bar-danger');
progbar.removeClass('progress-bar-success progress-bar-warning');
} else if (nScore > warning_level && nScore <= required_strength) {
sComplexity = 'Good';
progbar.addClass('progress-bar-warning');
progbar.removeClass('progress-bar-success progress-bar-danger');
} else if (nScore > required_strength) {
sComplexity = "Strong";
progbar.addClass('progress-bar-success');
progbar.removeClass('progress-bar-warning progress-bar-danger');
}
} else {
/* no password, so reset the displays */
progbar = $("#progbar");
progbar.css("width", '0%');
progbar.removeClass('progress-bar-success progress-bar-warning');
}
return nScore;
}