Merge branch 'bfederle-projects-ui'

This commit is contained in:
bfederle 2012-02-07 14:32:15 -08:00
commit 957d131cc6
6 changed files with 500 additions and 162 deletions

View File

@ -2769,6 +2769,22 @@ Dialogs*/
text-shadow: 0px 1px 1px #FFFFFF;
}
.ui-dialog span.message ul {
margin-left: 30px;
margin-top: 14px;
text-align: left;
list-style: disc;
}
.ui-dialog span.message ul li {
margin-top: 3px;
}
.ui-dialog span.message p {
text-align: left;
margin-top: 20px;
}
.ui-dialog-titlebar {
background: #4C5F70 url(../images/bg-dialog-header.png);
color: #FFFFFF;
@ -6184,7 +6200,8 @@ div.panel.ui-dialog div.list-view div.fixed-header {
.multi-edit label.error {
font-size: 10px;
margin: 0 0 0 10px;
margin: 3px 0 0;
float: left;
}
.multi-edit .data-table td span {
@ -6440,27 +6457,27 @@ div.panel.ui-dialog div.list-view div.fixed-header {
background: #F2F0F0;
}
/*Security Rules*/
.security-rules .add-by {
.ui-tabs-panel .add-by {
font-size: 12px;
color: #536474;
width: 94%;
margin: 13px 0 0 14px;
}
.security-rules .add-by .selection {
.ui-tabs-panel .add-by .selection {
width: 236px;
margin: 8px 0 0;
}
.security-rules .add-by .selection input {
.ui-tabs-panel .add-by .selection input {
margin: 0 6px 0 0;
}
.security-rules .add-by .selection label {
.ui-tabs-panel .add-by .selection label {
margin: 0 22px 0 0;
}
/*Security Rules*/
.security-rules .multi-edit input {
width: 69px;
margin: 0 0 0 9px;
@ -7515,6 +7532,15 @@ div.panel.ui-dialog div.list-view div.fixed-header {
clear: both;
}
.detail-view .project-dashboard .resources form {
width: 83%;
border-bottom: 1px solid #DBDBDB;
}
.detail-view .project-dashboard .resources form .field input {
margin-right: 105px;
}
/*** Dashboard*/
.project-dashboard .toolbar {
position: relative;
@ -7852,6 +7878,20 @@ div.panel.ui-dialog div.list-view div.fixed-header {
text-align: left;
}
.ui-dialog .new-project .add-by {
font-size: 12px;
color: #5E6D7D;
margin-left: 11px;
}
.ui-dialog .new-project .add-by input {
margin-right: 8px;
}
.ui-dialog .new-project .add-by label {
margin-right: 12px;
}
.new-project .title {
color: #3497E6;
font-size: 26px;
@ -7878,6 +7918,7 @@ div.panel.ui-dialog div.list-view div.fixed-header {
#new-project-review-tabs-resouces {
background: #D2D2D2;
height: 225px;
}
.new-project .resources .ui-widget-content {
@ -8050,10 +8091,10 @@ div.panel.ui-dialog div.list-view div.fixed-header {
.new-project .review .ui-tabs ul {
text-align: left;
/*+placement:shift -233px 1px;*/
/*+placement:shift 0px -2px;*/
position: relative;
left: -233px;
top: 1px;
left: 0px;
top: -2px;
}
.new-project .review .ui-tabs .list-view {

View File

@ -1,17 +1,34 @@
(function(cloudStack) {
cloudStack.projects = {
requireInvitation: function(args) {
return window.g_projectsInviteRequired;
return g_capabilities.projectinviterequired;
},
invitationCheck: function(args) {
$.ajax({
url: createURL('listProjectInvitations'),
data: { state: 'Pending' },
success: function(json) {
args.response.success({
data: json.listprojectinvitationsresponse.projectinvitation ?
json.listprojectinvitationsresponse.projectinvitation : []
});
}
});
},
resourceManagement: {
update: function(args) {
update: function(args, projectID) {
var totalResources = 5;
var updatedResources = 0;
projectID = projectID ? projectID : cloudStack.context.projects[0].id;
$.each(args.data, function(key, value) {
$.ajax({
url: createURL('updateResourceLimit'),
url: createURL('updateResourceLimit', { ignoreProject: true }),
data: {
projectid: projectID,
resourcetype: key,
max: args.data[key]
},
@ -25,9 +42,14 @@
});
},
dataProvider: function(args) {
dataProvider: function(args, projectID) {
projectID = projectID ? projectID : cloudStack.context.projects[0].id;
$.ajax({
url: createURL('listResourceLimits'),
url: createURL('listResourceLimits', { ignoreProject: true }),
data: {
projectid: projectID
},
success: function(json) {
args.response.success({
data: $.map(
@ -70,7 +92,6 @@
});
}
});
}
},
@ -254,14 +275,16 @@
},
inviteForm: {
noSelect: true,
noHeaderActionsColumn: true,
ignoreEmptyFields: true,
fields: {
'email': { edit: true, label: 'E-mail' },
'account': { edit: 'ignore', label: 'Account' },
'account': { edit: true, label: 'Account' },
'state': { edit: 'ignore', label: 'Status' },
'add-user': { addButton: true, label: '' }
},
add: {
label: 'E-mail invite',
label: 'Invite',
action: function(args) {
$.ajax({
url: createURL('addAccountToProject', { ignoreProject: true }),
@ -291,9 +314,9 @@
}
},
actionPreFilter: function(args) {
if (cloudStack.context.projects &&
cloudStack.context.projects[0] &&
!cloudStack.context.projects[0].isNew) {
if (args.context.projects &&
args.context.projects[0] &&
!args.context.projects[0].isNew) {
return args.context.actions;
}
@ -328,12 +351,15 @@
$.ajax({
url: createURL('listProjectInvitations', { ignoreProject: true }),
data: {
state: 'Pending',
listAll: true,
projectId: args.context.projects[0].id
},
dataType: 'json',
async: true,
success: function(data) {
var invites = data.listprojectinvitationsresponse.projectinvitation;
var invites = data.listprojectinvitationsresponse.projectinvitation ?
data.listprojectinvitationsresponse.projectinvitation : [];
args.response.success({
data: $.map(invites, function(elem) {
return {
@ -350,6 +376,9 @@
},
addUserForm: {
noSelect: true,
hideForm: function() {
return g_capabilities.projectinviterequired;
},
fields: {
'username': { edit: true, label: 'Account' },
'role': { edit: 'ignore', label: 'Role' },
@ -385,7 +414,7 @@
}
},
actionPreFilter: function(args) {
if (!cloudStack.context.projects &&
if (!args.context.projects &&
args.context.multiRule[0].role != 'Admin') { // This is for the new project wizard
return ['destroy'];
}
@ -432,7 +461,7 @@
$.ajax({
url: createURL('updateProject', { ignoreProject: true }),
data: {
id: cloudStack.context.projects[0].id,
id: args.context.projects[0].id,
account: args.context.multiRule[0].username
},
dataType: 'json',
@ -526,20 +555,20 @@
state: { label: 'Status', indicator: { 'Active': 'on', 'Destroyed': 'off', 'Disabled': 'off', 'Left Project': 'off' } }
},
dataProvider: function(args) {
var array1 = [];
if(args.filterBy != null) {
if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) {
switch(args.filterBy.search.by) {
case "name":
if(args.filterBy.search.value.length > 0)
array1.push("&keyword=" + args.filterBy.search.value);
break;
}
}
}
var apiCmd = "listProjects&page=" + args.page + "&pagesize=" + pageSize + array1.join("") + '&listAll=true';
dataProvider: function(args) {
var array1 = [];
if(args.filterBy != null) {
if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) {
switch(args.filterBy.search.by) {
case "name":
if(args.filterBy.search.value.length > 0)
array1.push("&keyword=" + args.filterBy.search.value);
break;
}
}
}
var apiCmd = "listProjects&page=" + args.page + "&pagesize=" + pageSize + array1.join("") + '&listAll=true';
$.ajax({
url: createURL(apiCmd, { ignoreProject: true }),
dataType: 'json',
@ -623,107 +652,218 @@
}
}
}
},
}
},
disable: {
label: 'Suspend project',
action: function(args) {
$.ajax({
url: createURL('suspendProject'),
data: {
id: args.context.projects[0].id
},
success: function(json) {
args.response.success({
_custom: {
jobId: json.suspendprojectresponse.jobid,
getUpdatedItem: function() {
return { state: 'Suspended' };
}
}
});
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
},
messages: {
confirm: function() { return 'Are you sure you want to suspend this project?'; },
notification: function() { return 'Suspended project'; }
},
notification: { poll: pollAsyncJobResult }
},
enable: {
label: 'Activate project',
action: function(args) {
$.ajax({
url: createURL('activateProject'),
data: {
id: args.context.projects[0].id
},
success: function(json) {
args.response.success({
_custom: {
jobId: json.activaterojectresponse.jobid, // NOTE: typo
getUpdatedItem: function() {
return { state: 'Active' };
}
}
});
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
},
messages: {
confirm: function() { return 'Are you sure you want to activate this project?'; },
notification: function() { return 'Activated project'; }
},
notification: { poll: pollAsyncJobResult }
},
destroy: {
label: 'Remove project',
action: function(args) {
$.ajax({
url: createURL('deleteProject', { ignoreProject: true }),
data: {
id: args.data.id
},
dataType: 'json',
async: true,
success: function(data) {
args.response.success({
_custom: {
getUpdatedItem: function(data) {
return $.extend(data, { state: 'Destroyed' });
},
getActionFilter: function(args) {
return function() {
return [];
};
},
jobId: data.deleteprojectresponse.jobid
}
});
}
});
},
messages: {
confirm: function(args) {
return 'Are you sure you want to remove ' + args.name + '?';
detailView: {
actions: {
edit: {
label: 'Edit',
action: function(args) {
$.ajax({
url: createURL('updateProject'),
data: $.extend(true, {}, args.context.projects[0], args.data),
success: function(json) {
args.response.success();
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
},
notification: function(args) {
return 'Removed project';
messages: {
notification: function(args) { return 'Edited project details'; }
}
},
disable: {
label: 'Suspend project',
action: function(args) {
$.ajax({
url: createURL('suspendProject'),
data: {
id: args.context.projects[0].id
},
success: function(json) {
args.response.success({
_custom: {
jobId: json.suspendprojectresponse.jobid,
getUpdatedItem: function() {
return { state: 'Suspended' };
}
}
});
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
},
messages: {
confirm: function() { return 'Are you sure you want to suspend this project?'; },
notification: function() { return 'Suspended project'; }
},
notification: { poll: pollAsyncJobResult }
},
enable: {
label: 'Activate project',
action: function(args) {
$.ajax({
url: createURL('activateProject'),
data: {
id: args.context.projects[0].id
},
success: function(json) {
args.response.success({
_custom: {
jobId: json.activaterojectresponse.jobid, // NOTE: typo
getUpdatedItem: function() {
return { state: 'Active' };
}
}
});
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
},
messages: {
confirm: function() { return 'Are you sure you want to activate this project?'; },
notification: function() { return 'Activated project'; }
},
notification: { poll: pollAsyncJobResult }
},
destroy: {
label: 'Remove project',
action: function(args) {
$.ajax({
url: createURL('deleteProject', { ignoreProject: true }),
data: {
id: args.data.id
},
dataType: 'json',
async: true,
success: function(data) {
args.response.success({
_custom: {
getUpdatedItem: function(data) {
return $.extend(data, { state: 'Destroyed' });
},
getActionFilter: function(args) {
return function() {
return [];
};
},
jobId: data.deleteprojectresponse.jobid
}
});
}
});
},
messages: {
confirm: function(args) {
return 'Are you sure you want to remove ' + args.name + '?';
},
notification: function(args) {
return 'Removed project';
}
},
notification: {
poll: pollAsyncJobResult
}
}
},
tabFilter: function(args) {
var project = args.context.projects[0];
var projectOwner = project.account;
var currentAccount = args.context.users[0].account;
if ((!isAdmin() && !isDomainAdmin()) &&
(currentAccount != projectOwner)) return ['accounts', 'invitations', 'resources'];
if (!cloudStack.projects.requireInvitation()) {
return ['invitations'];
}
return [];
},
tabs: {
details: {
title: 'Details',
fields: [
{
name: { label: 'Name' }
},
{
displaytext: { label: 'Display text', isEditable: true },
domain: { label: 'Domain' },
account: { label: 'Account'},
state: { label: 'State' }
}
],
dataProvider: function(args) {
var projectID = args.context.projects[0].id;
$.ajax({
url: createURL('listProjects'),
data: {
listAll: true,
id: projectID
},
success: function(json) {
args.response.success({
data: json.listprojectsresponse.project[0],
actionFilter: projectsActionFilter
});
}
});
}
},
notification: {
poll: pollAsyncJobResult
accounts: {
title: 'Accounts',
custom: function(args) {
var project = args.context.projects[0];
var multiEditArgs = $.extend(
true, {},
cloudStack.projects.addUserForm,
{
context: { projects: [project] }
}
);
var $users = $('<div>').multiEdit(multiEditArgs);
return $users;
}
},
invitations: {
title: 'Invitations',
custom: function(args) {
var project = args.context.projects[0];
var $invites = cloudStack.uiCustom.projectsTabs.userManagement({
useInvites: true,
context: { projects: [project] }
});
return $invites;
}
},
resources: {
title: 'Resources',
custom: function(args) {
var $resources = cloudStack.uiCustom
.projectsTabs.dashboardTabs.resources({
projectID: args.context.projects[0].id
});
return $('<div>').addClass('project-dashboard').append($resources);
}
}
}
}
@ -751,20 +891,73 @@
$.ajax({
url: createURL('listProjectInvitations'),
data: {
account: cloudStack.context.users[0].account,
domainid: cloudStack.context.users[0].domainid,
state: 'Pending'
},
success: function(data) {
args.response.success({
actionFilter: projectInvitationActionFilter,
data: data.listprojectinvitationsresponse.projectinvitation
data: data.listprojectinvitationsresponse.projectinvitation ?
data.listprojectinvitationsresponse.projectinvitation : []
});
}
});
},
actions: {
enterToken: {
label: 'Enter Token',
isHeader: true,
addRow: false,
preFilter: function(args) {
var invitationsPresent = false;
$.ajax({
url: createURL('listProjectInvitations'),
data: { state: 'Pending' },
async: false,
success: function(json) {
if (json.listprojectinvitationsresponse.count) {
invitationsPresent = true;
}
}
});
return !invitationsPresent;
},
createForm: {
desc: 'Please enter the token that you were given in your invite e-mail.',
fields: {
projectid: { label: 'Project ID', validation: { required: true }},
token: { label: 'Token', validation: { required: true }}
}
},
action: function(args) {
$.ajax({
url: createURL('updateProjectInvitation'),
data: args.data,
success: function(json) {
args.response.success({
_custom: {
jobId: json.updateprojectinvitationresponse.jobid
}
});
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
},
messages: {
notification: function() {
return 'Accepted project invitation';
},
complete: function() {
return 'You have now joined a project. Please switch to Project view to see the project.';
}
},
notification: { poll: pollAsyncJobResult }
},
accept: {
label: 'Accept Invitation',
action: function(args) {
@ -827,7 +1020,7 @@
};
var projectsActionFilter = function(args) {
var allowedActions = ['destroy'];
var allowedActions = ['destroy', 'edit'];
if (args.context.item.account == cloudStack.context.users[0].account ||
isAdmin() || isDomainAdmin()) {

View File

@ -1,5 +1,5 @@
(function(cloudStack, $) {
var pageElems = {
var pageElems = cloudStack.uiCustom.projectsTabs = {
/**
* User management multi-edit
*/
@ -8,9 +8,56 @@
cloudStack.projects.addUserForm :
cloudStack.projects.inviteForm;
return $('<div>').multiEdit($.extend(true, {}, multiEdit, {
var $multi = $('<div>').multiEdit($.extend(true, {}, multiEdit, {
context: args.context
}));
if (args.useInvites) {
var $fields = $multi.find('form table').find('th, td');
var $accountFields = $fields.filter(function() {
return $(this).hasClass('account');
});
var $emailFields = $fields.filter(function() {
return $(this).hasClass('email');
});
$multi.prepend(
$('<div>').addClass('add-by')
.append($('<span>').html('Add by:'))
.append(
$('<div>').addClass('selection')
.append(
$('<input>').attr({
type: 'radio',
name: 'add-by',
checked: 'checked'
}).click(function() {
$accountFields.show();
$emailFields.hide();
$emailFields.find('input').val('');
return true;
}).click()
)
.append($('<label>').html('Account'))
.append(
$('<input>').attr({
type: 'radio',
name: 'add-by'
}).click(function() {
$accountFields.hide();
$accountFields.find('input').val('');
$emailFields.show();
return true;
})
)
.append($('<label>').html('E-mail'))
)
);
}
return $multi;
},
dashboardTabs: {
@ -87,7 +134,9 @@
return $('<div>').addClass('management-invite').data('tab-title', 'Invitations');
},
resources: function() {
resources: function(options) {
if (!options) options = {};
var $resources = $('<div>').addClass('resources').data('tab-title', 'Resources');
var $form = $('<form>');
var $submit = $('<input>').attr({
@ -136,7 +185,7 @@
});
}
}
});
}, options.projectID);
return false;
});
@ -145,7 +194,7 @@
$form.appendTo($resources);
}
}
});
}, options.projectID);
return $resources;
}
@ -303,7 +352,11 @@
});
var $nextButton = $('<div>').addClass('button confirm next').html('Next');
$newProject.find('.title').html('Add Accounts to ' + args.data.name);
$newProject.find('.title').html(
cloudStack.projects.requireInvitation() ?
'Invite to ' + args.data.name :
'Add Accounts to ' + args.data.name
);
$nextButton.appendTo($userManagement).click(function() {
$newProject.find('.title').html('Review');
$userManagement.replaceWith(function() {
@ -329,7 +382,10 @@
.append(
// Users tab
$('<li>').addClass('first').append(
$('<a>').attr({ href: '#new-project-review-tabs-users'}).html('Accounts')
$('<a>').attr({ href: '#new-project-review-tabs-users'}).html(
cloudStack.projects.requireInvitation() ?
'Invitations' : 'Accounts'
)
)
);
@ -362,7 +418,7 @@
fields: !cloudStack.projects.requireInvitation() ? {
username: { label: 'Account' }
} : {
email: { label: 'E-mail invite'}
account: { label: 'Invited Accounts'}
},
actions: !cloudStack.projects.requireInvitation() ? {
destroy: {
@ -398,7 +454,7 @@
return !cloudStack.projects.requireInvitation() ? {
username: $(elem).find('td.username span').html()
} : {
email: $(elem).find('td.email span').html()
account: $(elem).find('td.account span').html()
};
})
});

View File

@ -269,6 +269,26 @@
// Validation
$.extend($.validator.messages, { required: 'Required field' });
// Check for pending project invitations
cloudStack.projects.invitationCheck({
context: cloudStack.context,
response: {
success: function(args) {
if (!args.data.length) return;
var projectList = $.map(args.data, function(invitation) {
return '<li>' + invitation.project + '</li>';
}).join('');
cloudStack.dialog.notice({
message: 'You have pending project invitations for:' +
'<ul>' + projectList + '</ul>' +
'<p>To view, please go to the projects section, then select invitations from the drop-down.</p>'
});
}
}
});
return this;
};

View File

@ -162,34 +162,48 @@
message: messages.complete(args.data)
});
}
if (options.complete) {
options.complete(args);
}
},
{},
// Error
function(args) {
if ($instanceRow.data('list-view-new-item')) {
// For create forms
$instanceRow.remove();
} else {
// For standard actions
replaceItem(
$instanceRow,
$.extend($instanceRow.data('json-obj'), args.data),
args.actionFilter ?
args.actionFilter :
$instanceRow.data('list-view-action-filter')
);
if (!isHeader) {
if ($instanceRow.data('list-view-new-item')) {
// For create forms
$instanceRow.remove();
} else {
// For standard actions
replaceItem(
$instanceRow,
$.extend($instanceRow.data('json-obj'), args.data),
args.actionFilter ?
args.actionFilter :
$instanceRow.data('list-view-action-filter')
);
}
}
if (options.error) {
options.error(args);
}
}
);
},
error: function(message) {
if (($.isPlainObject(args.action.createForm) && args.action.addRow != 'false') ||
(!args.action.createForm && args.action.addRow == 'true')) {
$instanceRow.remove();
if (!isHeader) {
if (($.isPlainObject(args.action.createForm) && args.action.addRow != 'false') ||
(!args.action.createForm && args.action.addRow == 'true')) {
$instanceRow.remove();
}
}
if (options.error) options.error(message);
if (message) cloudStack.dialog.notice({ message: message });
}
}
@ -262,11 +276,21 @@
$form: args.$form
});
} else {
var $loading = $('<div>').addClass('loading-overlay');
$loading.appendTo($listView);
performAction(args.data, {
ref: args.ref,
context: createFormContext,
$form: args.$form,
isHeader: isHeader
isHeader: isHeader,
complete: function(args) {
$loading.remove();
$listView.listView('refresh');
},
error: function(args) {
$loading.remove();
}
});
}
},

View File

@ -827,6 +827,10 @@
});
};
if (args.hideForm && args.hideForm()){
$multiForm.find('tbody').detach();
}
// Get existing data
getData();