Allow users to share templates with Accounts or Projects through the UI

* Allow users to share templates with Accounts or Projects through the
updateTemplate permissions API

* Change behaviour to show only supported projects and accounts with update template permissions

* Allow admins to see accounts dropdown and only hide lists for users

* Don't allow sharing project owned templates as you cannot retrieve them in list api calls
This commit is contained in:
Rohit Yadav 2019-07-18 22:12:55 +05:30 committed by Paul Angus
parent a1a9fb8977
commit 5d8157422d
15 changed files with 424 additions and 54 deletions

View File

@ -45,7 +45,7 @@ public abstract class BaseUpdateTemplateOrIsoPermissionsCmd extends BaseCmd {
@Parameter(name = ApiConstants.ACCOUNTS,
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "a comma delimited list of accounts. If specified, \"op\" parameter has to be passed in.")
description = "a comma delimited list of accounts within caller's domain. If specified, \"op\" parameter has to be passed in.")
private List<String> accountNames;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class, required = true, description = "the template ID")
@ -80,7 +80,6 @@ public abstract class BaseUpdateTemplateOrIsoPermissionsCmd extends BaseCmd {
if (accountNames != null && projectIds != null) {
throw new InvalidParameterValueException("Accounts and projectIds can't be specified together");
}
return accountNames;
}

View File

@ -59,6 +59,7 @@ public class ListCapabilitiesCmd extends BaseCmd {
response.setKVMSnapshotEnabled((Boolean)capabilities.get("KVMSnapshotEnabled"));
response.setAllowUserViewDestroyedVM((Boolean)capabilities.get("allowUserViewDestroyedVM"));
response.setAllowUserExpungeRecoverVM((Boolean)capabilities.get("allowUserExpungeRecoverVM"));
response.setAllowUserViewAllDomainAccounts((Boolean)capabilities.get("allowUserViewAllDomainAccounts"));
if (capabilities.containsKey("apiLimitInterval")) {
response.setApiLimitInterval((Integer)capabilities.get("apiLimitInterval"));
}

View File

@ -84,6 +84,10 @@ public class CapabilitiesResponse extends BaseResponse {
@Param(description = "true if the user can recover and expunge virtualmachines, false otherwise", since = "4.6.0")
private boolean allowUserExpungeRecoverVM;
@SerializedName("allowuserviewalldomainaccounts")
@Param(description = "true if users can see all accounts within the same domain, false otherwise")
private boolean allowUserViewAllDomainAccounts;
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
this.securityGroupsEnabled = securityGroupsEnabled;
}
@ -143,4 +147,8 @@ public class CapabilitiesResponse extends BaseResponse {
public void setAllowUserExpungeRecoverVM(boolean allowUserExpungeRecoverVM) {
this.allowUserExpungeRecoverVM = allowUserExpungeRecoverVM;
}
public void setAllowUserViewAllDomainAccounts(boolean allowUserViewAllDomainAccounts) {
this.allowUserViewAllDomainAccounts = allowUserViewAllDomainAccounts;
}
}

View File

@ -103,6 +103,10 @@ public interface QueryService {
"network offering, zones), we use the flag to determine if the entities should be sorted ascending (when flag is true) " +
"or descending (when flag is false). Within the scope of the config all users see the same result.", true, ConfigKey.Scope.Global);
public static final ConfigKey<Boolean> AllowUserViewAllDomainAccounts = new ConfigKey<>("Advanced", Boolean.class,
"allow.user.view.all.domain.accounts", "false",
"Determines whether users can view all user accounts within the same domain", true, ConfigKey.Scope.Domain);
ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException;
ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd);

View File

@ -1808,6 +1808,11 @@ public class ApiResponseHelper implements ResponseGenerator {
List<String> regularAccounts = new ArrayList<String>();
for (String accountName : accountNames) {
Account account = ApiDBUtils.findAccountByNameDomain(accountName, templateOwner.getDomainId());
if (account == null) {
s_logger.error("Missing Account " + accountName + " in domain " + templateOwner.getDomainId());
continue;
}
if (account.getType() != Account.ACCOUNT_TYPE_PROJECT) {
regularAccounts.add(accountName);
} else {

View File

@ -394,6 +394,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
* com.cloud.api.query.QueryService#searchForUsers(org.apache.cloudstack
* .api.command.admin.user.ListUsersCmd)
*/
@Override
public ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException {
Pair<List<UserAccountJoinVO>, Integer> result = searchForUsersInternal(cmd);
@ -1980,7 +1981,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
// if no "id" specified...
if (accountId == null) {
// listall only has significance if they are an admin
if (listAll && callerIsAdmin) {
boolean isDomainListAllAllowed = AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId());
if ((listAll && callerIsAdmin) || isDomainListAllAllowed) {
// if no domain id specified, use caller's domain
if (domainId == null) {
domainId = caller.getDomainId();
@ -2026,6 +2028,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
sb.and("needsCleanup", sb.entity().isNeedsCleanup(), SearchCriteria.Op.EQ);
sb.and("typeNEQ", sb.entity().getType(), SearchCriteria.Op.NEQ);
sb.and("idNEQ", sb.entity().getId(), SearchCriteria.Op.NEQ);
sb.and("type2NEQ", sb.entity().getType(), SearchCriteria.Op.NEQ);
if (domainId != null && isRecursive) {
sb.and("path", sb.entity().getDomainPath(), SearchCriteria.Op.LIKE);
@ -2035,9 +2038,15 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
// don't return account of type project to the end user
sc.setParameters("typeNEQ", Account.ACCOUNT_TYPE_PROJECT);
// don't return system account...
sc.setParameters("idNEQ", Account.ACCOUNT_ID_SYSTEM);
// do not return account of type domain admin to the end user
if (!callerIsAdmin) {
sc.setParameters("type2NEQ", Account.ACCOUNT_TYPE_DOMAIN_ADMIN);
}
if (keyword != null) {
SearchCriteria<AccountJoinVO> ssc = _accountJoinDao.createSearchCriteria();
ssc.addOr("accountName", SearchCriteria.Op.LIKE, "%" + keyword + "%");
@ -3836,6 +3845,6 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {AllowUserViewDestroyedVM, UserVMBlacklistedDetails, UserVMReadOnlyUIDetails, SortKeyAscending};
return new ConfigKey<?>[] {AllowUserViewDestroyedVM, UserVMBlacklistedDetails, UserVMReadOnlyUIDetails, SortKeyAscending, AllowUserViewAllDomainAccounts};
}
}

View File

@ -37,12 +37,12 @@ import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.query.QueryService;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.api.ApiDBUtils;
import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.QueryManagerImpl;
import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.gpu.GPU;
import com.cloud.service.ServiceOfferingDetailsVO;
@ -315,14 +315,14 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
}
// Remove blacklisted settings if user is not admin
if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
String[] userVmSettingsToHide = QueryManagerImpl.UserVMBlacklistedDetails.value().split(",");
String[] userVmSettingsToHide = QueryService.UserVMBlacklistedDetails.value().split(",");
for (String key : userVmSettingsToHide) {
resourceDetails.remove(key.trim());
}
}
userVmResponse.setDetails(resourceDetails);
if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
userVmResponse.setReadOnlyUIDetails(QueryManagerImpl.UserVMReadOnlyUIDetails.value());
userVmResponse.setReadOnlyUIDetails(QueryService.UserVMReadOnlyUIDetails.value());
}
}

View File

@ -535,6 +535,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.resourcedetail.dao.GuestOsDetailsDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
@ -555,7 +556,6 @@ import com.cloud.alert.AlertManager;
import com.cloud.alert.AlertVO;
import com.cloud.alert.dao.AlertDao;
import com.cloud.api.ApiDBUtils;
import com.cloud.api.query.QueryManagerImpl;
import com.cloud.capacity.Capacity;
import com.cloud.capacity.CapacityVO;
import com.cloud.capacity.dao.CapacityDao;
@ -3486,9 +3486,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final Integer apiLimitInterval = Integer.valueOf(_configDao.getValue(Config.ApiLimitInterval.key()));
final Integer apiLimitMax = Integer.valueOf(_configDao.getValue(Config.ApiLimitMax.key()));
final boolean allowUserViewDestroyedVM = (QueryManagerImpl.AllowUserViewDestroyedVM.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserViewDestroyedVM = (QueryService.AllowUserViewDestroyedVM.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserExpungeRecoverVM = (UserVmManager.AllowUserExpungeRecoverVm.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId()));
// check if region-wide secondary storage is used
boolean regionSecondaryEnabled = false;
final List<ImageStoreVO> imgStores = _imgStoreDao.findRegionImageStores();
@ -3508,6 +3510,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
capabilities.put("KVMSnapshotEnabled", KVMSnapshotEnabled);
capabilities.put("allowUserViewDestroyedVM", allowUserViewDestroyedVM);
capabilities.put("allowUserExpungeRecoverVM", allowUserExpungeRecoverVM);
capabilities.put("allowUserViewAllDomainAccounts", allowUserViewAllDomainAccounts);
if (apiLimitEnabled) {
capabilities.put("apiLimitInterval", apiLimitInterval);
capabilities.put("apiLimitMax", apiLimitMax);

View File

@ -32,28 +32,6 @@ import java.util.concurrent.Executors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.deploy.DeployDestination;
import com.cloud.storage.ImageStoreUploadMonitorImpl;
import com.cloud.utils.StringUtils;
import com.cloud.utils.EncryptionUtil;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.EnumUtils;
import com.cloud.vm.VmDetailConstants;
import com.google.common.base.Joiner;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd;
import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListTemplateOrIsoPermissionsCmd;
@ -61,6 +39,7 @@ import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd;
import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoPermissionsCmd;
import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd;
import org.apache.cloudstack.api.command.user.iso.ExtractIsoCmd;
import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd;
import org.apache.cloudstack.api.command.user.iso.ListIsoPermissionsCmd;
import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd;
import org.apache.cloudstack.api.command.user.iso.UpdateIsoCmd;
@ -69,6 +48,7 @@ import org.apache.cloudstack.api.command.user.template.CopyTemplateCmd;
import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd;
import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd;
import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd;
import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd;
import org.apache.cloudstack.api.command.user.template.ListTemplatePermissionsCmd;
import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd;
import org.apache.cloudstack.api.command.user.template.UpdateTemplateCmd;
@ -95,6 +75,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.Templa
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -104,6 +85,9 @@ import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.storage.command.AttachCommand;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.DettachCommand;
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@ -111,6 +95,12 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
@ -129,6 +119,7 @@ import com.cloud.configuration.Resource.ResourceType;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.deploy.DeployDestination;
import com.cloud.domain.Domain;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
@ -148,6 +139,7 @@ import com.cloud.projects.Project;
import com.cloud.projects.ProjectManager;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.ImageStoreUploadMonitorImpl;
import com.cloud.storage.LaunchPermissionVO;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
@ -186,6 +178,11 @@ import com.cloud.user.AccountVO;
import com.cloud.user.ResourceLimitService;
import com.cloud.user.dao.AccountDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.DateUtil;
import com.cloud.utils.EncryptionUtil;
import com.cloud.utils.EnumUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory;
@ -200,11 +197,12 @@ import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.google.common.base.Joiner;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class TemplateManagerImpl extends ManagerBase implements TemplateManager, TemplateApiService, Configurable {
private final static Logger s_logger = Logger.getLogger(TemplateManagerImpl.class);
@ -1483,9 +1481,24 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
throw new InvalidParameterValueException("unable to update permissions for " + mediaType + " with id " + id);
}
boolean isAdmin = _accountMgr.isAdmin(caller.getId());
Long ownerId = template.getAccountId();
Account owner = _accountMgr.getAccount(ownerId);
if (ownerId == null) {
// if there is no owner of the template then it's probably already a
// public template (or domain private template) so
// publishing to individual users is irrelevant
throw new InvalidParameterValueException("Update template permissions is an invalid operation on template " + template.getName());
}
if (owner.getType() == Account.ACCOUNT_TYPE_PROJECT) {
// Currently project owned templates cannot be shared outside project but is available to all users within project by default.
throw new InvalidParameterValueException("Update template permissions is an invalid operation on template " + template.getName() +
". Project owned templates cannot be shared outside template.");
}
// check configuration parameter(allow.public.user.templates) value for
// the template owner
boolean isAdmin = _accountMgr.isAdmin(caller.getId());
boolean allowPublicUserTemplates = AllowPublicUserTemplates.valueIn(template.getAccountId());
if (!isAdmin && !allowPublicUserTemplates && isPublic != null && isPublic) {
throw new InvalidParameterValueException("Only private " + mediaType + "s can be created.");
@ -1499,14 +1512,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
}
}
Long ownerId = template.getAccountId();
if (ownerId == null) {
// if there is no owner of the template then it's probably already a
// public template (or domain private template) so
// publishing to individual users is irrelevant
throw new InvalidParameterValueException("Update template permissions is an invalid operation on template " + template.getName());
}
//Only admin or owner of the template should be able to change its permissions
if (caller.getId() != ownerId && !isAdmin) {
throw new InvalidParameterValueException("Unable to grant permission to account " + caller.getAccountName() + " as it is neither admin nor owner or the template");
@ -1540,7 +1545,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
}
//Derive the domain id from the template owner as updateTemplatePermissions is not cross domain operation
Account owner = _accountMgr.getAccount(ownerId);
final Domain domain = _domainDao.findById(owner.getDomainId());
if ("add".equalsIgnoreCase(operation)) {
final List<String> accountNamesFinal = accountNames;

View File

@ -12421,6 +12421,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
background-position: -35px -707px;
}
.shareTemplate .icon {
background-position: -165px -122px;
}
.shareTemplate:hover .icon {
background-position: -165px -704px;
}
.createVolume .icon {
background-position: -70px -124px;
}

View File

@ -92,6 +92,7 @@ var dictionary = {
"label.about.app":"About CloudStack",
"label.accept.project.invitation":"Accept project invitation",
"label.account":"Account",
"label.accounts":"Accounts",
"label.account.and.security.group":"Account, Security group",
"label.account.details":"Account details",
"label.account.id":"Account ID",
@ -279,6 +280,7 @@ var dictionary = {
"label.action.run.diagnostics":"Run Diagnostics",
"label.action.secure.host":"Provision Host Security Keys",
"label.action.start.instance":"Start Instance",
"label.action.share.template": "Update Template Permissions",
"label.action.start.instance.processing":"Starting Instance....",
"label.action.start.router":"Start Router",
"label.action.start.router.processing":"Starting Router....",
@ -1253,6 +1255,7 @@ var dictionary = {
"label.opendaylight.controller":"OpenDaylight Controller",
"label.opendaylight.controllerdetail":"OpenDaylight Controller Details",
"label.opendaylight.controllers":"OpenDaylight Controllers",
"label.operation": "Operation",
"label.operator":"Operator",
"label.optional":"Optional",
"label.order":"Order",
@ -1342,6 +1345,7 @@ var dictionary = {
"label.project":"Project",
"label.project.dashboard":"Project dashboard",
"label.project.id":"Project ID",
"label.project.ids":"Project IDs",
"label.project.invite":"Invite to project",
"label.project.name":"Project name",
"label.project.view":"Project View",
@ -1576,6 +1580,7 @@ var dictionary = {
"label.setup.network":"Set up Network",
"label.setup.zone":"Set up Zone",
"label.shared":"Shared",
"label.share.with":"Share With",
"label.show.advanced.settings":"Show advanced settings",
"label.show.ingress.rule":"Show Ingress Rule",
"label.shutdown.provider":"Shutdown provider",

View File

@ -151,6 +151,8 @@
g_userProjectsEnabled = json.listcapabilitiesresponse.capability.allowusercreateprojects;
g_cloudstackversion = json.listcapabilitiesresponse.capability.cloudstackversion;
// Allow users to see all accounts within a domain
g_allowUserViewAllDomainAccounts = json.listcapabilitiesresponse.capability.allowuserviewalldomainaccounts;
if (json.listcapabilitiesresponse.capability.apilimitinterval != null && json.listcapabilitiesresponse.capability.apilimitmax != null) {
var intervalLimit = ((json.listcapabilitiesresponse.capability.apilimitinterval * 1000) / json.listcapabilitiesresponse.capability.apilimitmax) * 3; //multiply 3 to be on safe side

View File

@ -1368,29 +1368,41 @@ cloudStack.docs = {
desc: 'Pass user and meta data to VMs (via ConfigDrive)',
externalLink: ''
},
helpComputeOfferingMinCPUCores: {
desc: 'This will be used for the setting the range (min-max) of the number of cpu cores that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingMaxCPUCores: {
desc: 'This will be used for the setting the range (min-max) of the number of cpu cores that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingMinMemory: {
desc: 'This will be used for the setting the range (min-max) amount of memory that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingMaxMemory: {
desc: 'This will be used for the setting the range (min-max) amount of memory that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingType: {
desc: 'This will be used for setting the type of compute offering - whether it is fixed, custom constrained or custom unconstrained.',
externalLink: ''
},
// Update Template Permissions Helper
helpUpdateTemplateOperation: {
desc: 'Select the permission operator. Add is for sharing with user/project and Reset simply removes all the accounts and projects which template has been shared with.'
},
helpUpdateTemplateShareWith: {
desc: 'Select account or project with which template is to be shared with.'
},
helpUpdateTemplateAccounts: {
desc: 'Choose one or more accounts to share this template. Ctrl+Click to select multiple accounts to share with. Selecting "Add > Accounts" shows list of accounts that do not have permissions. Selecting "Remove > Accounts" shows list of accounts that already have permissions.'
},
helpUpdateTemplateProjectIds: {
desc: 'Choose one or more projects to share this template. Ctrl+Click to select multiple projects to share with. Selecting "Add > Projects" shows list of projects that do not have permissions. Selecting "Remove > Projects" shows list of projects that already have permissions.'
},
helpUpdateTemplateAccountList: {
desc: 'A comma seperated list of accounts to share the template with. Must be specified with the Add/Remove operation, leave Project ID blank if this is specified.'
}
};

View File

@ -36,6 +36,7 @@ var g_queryAsyncJobResultInterval = 3000;
var g_idpList = null;
var g_appendIdpDomain = false;
var g_sortKeyIsAscending = false;
var g_allowUserViewAllDomainAccounts= false;
//keyboard keycode
var keycode_Enter = 13;

319
ui/scripts/templates.js Executable file → Normal file
View File

@ -1507,8 +1507,316 @@
notification: {
poll: pollAsyncJobResult
}
}
},
// Share template
shareTemplate: {
label: 'label.action.share.template',
messages: {
notification: function (args) {
return 'label.action.share.template';
}
},
createForm: {
title: 'label.action.share.template',
desc: '',
fields: {
operation: {
label: 'label.operation',
docID: 'helpUpdateTemplateOperation',
validation: {
required: true
},
select: function (args) {
var items = [];
items.push({
id: "add",
description: "Add"
});
items.push({
id: "remove",
description: "Remove"
});
items.push({
id: "reset",
description: "Reset"
});
args.response.success({
data: items
});
// Select change
args.$select.change(function () {
var $form = $(this).closest('form');
var selectedOperation = $(this).val();
if (selectedOperation === "reset") {
$form.find('[rel=projects]').hide();
$form.find('[rel=sharewith]').hide();
$form.find('[rel=accounts]').hide();
$form.find('[rel=accountlist]').hide();
} else {
// allow.user.view.domain.accounts = true
// Populate List of accounts in domain as dropdown multiselect
$form.find('[rel=sharewith]').css('display', 'inline-block');
if (!isUser() || g_allowUserViewAllDomainAccounts === true) {
$form.find('[rel=projects]').css('display', 'inline-block');
$form.find('[rel=accounts]').css('display', 'inline-block');
$form.find('[rel=accountlist]').hide();
} else {
// If users are not allowed to see accounts in the domain, show input text field for Accounts
// Projects will always be shown as dropdown multiselect
$form.find('[rel=projects]').css('display', 'inline-block');
$form.find('[rel=accountslist]').css('display', 'inline-block');
$form.find('[rel=accounts]').hide();
}
}
});
}
},
shareWith: {
label: 'label.share.with',
docID: 'helpUpdateTemplateShareWith',
validation: {
required: true
},
dependsOn: 'operation',
select: function (args) {
var items = [];
items.push({
id: "account",
description: "Account"
});
items.push({
id: "project",
description: "Project"
});
args.response.success({ data: items });
// Select change
args.$select.change(function () {
var $form = $(this).closest('form');
var sharedWith = $(this).val();
if (args.operation !== "reset") {
if (sharedWith === "project") {
$form.find('[rel=accounts]').hide();
$form.find('[rel=accountlist]').hide();
$form.find('[rel=projects]').css('display', 'inline-block');
} else {
// allow.user.view.domain.accounts = true
// Populate List of accounts in domain as dropdown multiselect
if (!isUser() || g_allowUserViewAllDomainAccounts === true) {
$form.find('[rel=projects]').hide();
$form.find('[rel=accountlist]').hide();
$form.find('[rel=accounts]').css('display', 'inline-block');
} else {
// If users are not allowed to see accounts in the domain, show input text field for Accounts
// Projects will always be shown as dropdown multiselect
$form.find('[rel=projects]').hide();
$form.find('[rel=accounts]').hide();
$form.find('[rel=accountlist]').css('display', 'inline-block');
}
}
}
});
}
},
accountlist: {
label: 'label.accounts',
docID: 'helpUpdateTemplateAccountList'
},
accounts: {
label: 'label.accounts',
docID: 'helpUpdateTemplateAccounts',
dependsOn: 'shareWith',
isMultiple: true,
select: function (args) {
var operation = args.operation;
if (operation !== "reset") {
$.ajax({
url: createURL("listAccounts&listall=true"),
dataType: "json",
async: true,
success: function (jsonAccounts) {
var accountByName = {};
$.each(jsonAccounts.listaccountsresponse.account, function(idx, account) {
// Only add current domain's accounts as update template permissions supports that
if (account.domainid === g_domainid) {
accountByName[account.name] = {
projName: account.name,
hasPermission: false
};
}
});
$.ajax({
url: createURL('listTemplatePermissions&id=' + args.context.templates[0].id),
dataType: "json",
async: true,
success: function (json) {
items = json.listtemplatepermissionsresponse.templatepermission.account;
$.each(items, function(idx, accountName) {
accountByName[accountName].hasPermission = true;
});
var accountObjs = [];
if (operation === "add") {
// Skip already permitted accounts
$.each(Object.keys(accountByName), function(idx, accountName) {
if (accountByName[accountName].hasPermission == false) {
accountObjs.push({
name: accountName,
description: accountName
});
}
});
} else if (items != null) {
$.each(items, function(idx, accountName) {
if (accountName !== g_account) {
accountObjs.push({
name: accountName,
description: accountName
});
}
});
}
args.$select.html('');
args.response.success({data: accountObjs});
}
});
}
});
}
}
},
projects: {
label: 'label.projects',
docID: 'helpUpdateTemplateProjectIds',
dependsOn: 'shareWith',
isMultiple: true,
select: function (args) {
var operation = args.operation;
if (operation !== "reset") {
$.ajax({
url: createURL("listProjects&listall=true"),
dataType: "json",
async: true,
success: function (jsonProjects) {
var projectsByIds = {};
$.each(jsonProjects.listprojectsresponse.project, function(idx, project) {
// Only add current domain's projects as update template permissions supports that
if (project.domainid === g_domainid) {
projectsByIds[project.id] = {
projName: project.name,
hasPermission: false
};
}
});
$.ajax({
url: createURL('listTemplatePermissions&id=' + args.context.templates[0].id),
dataType: "json",
async: true,
success: function (json) {
items = json.listtemplatepermissionsresponse.templatepermission.projectids;
$.each(items, function(idx, projectId) {
projectsByIds[projectId].hasPermission = true;
});
var projectObjs = [];
if (operation === "add") {
// Skip already permitted accounts
$.each(Object.keys(projectsByIds), function(idx, projectId) {
if (projectsByIds[projectId].hasPermission == false) {
projectObjs.push({
id: projectId,
description: projectsByIds[projectId].projName
});
}
});
} else if (items != null) {
$.each(items, function(idx, projectId) {
if (projectId !== g_account) {
projectObjs.push({
id: projectId,
description: projectsByIds[projectId] ? projectsByIds[projectId].projName : projectId
});
}
});
}
args.$select.html('');
args.response.success({data: projectObjs});
}
});
}
});
}
}
}
}
},
action: function (args) {
// Load data from form
var data = {
id: args.context.templates[0].id,
op: args.data.operation
};
var selectedOperation = args.data.operation;
if (selectedOperation === "reset") {
// Do not append Project ID or Account to data object
} else {
var projects = args.data.projects;
var accounts = args.data.accounts;
var accountList = args.data.accountlist;
if (accounts !== undefined || (accountList !== undefined && accountList.length > 0)) {
var accountNames = "";
if (accountList !== undefined && accounts === undefined) {
accountNames = accountList;
} else {
if (Object.prototype.toString.call(accounts) === '[object Array]') {
accountNames = accounts.join(",");
} else {
accountNames = accounts;
}
}
$.extend(data, {
accounts: accountNames
});
}
if (projects !== undefined) {
var projectIds = "";
if (Object.prototype.toString.call(projects) === '[object Array]') {
projectIds = projects.join(",");
} else {
projectIds = projects;
}
$.extend(data, {
projectids: projectIds
});
}
}
$.ajax({
url: createURL('updateTemplatePermissions'),
data: data,
dataType: "json",
async: false,
success: function (json) {
var item = json.updatetemplatepermissionsresponse.success;
args.response.success({
data: item
});
}
}); //end ajax
}
}
},
tabs: {
details: {
@ -1882,11 +2190,11 @@
}else if(args.page == 1) {
args.response.success({
data: []
});
});
} else {
args.response.success({
data: []
});
});
}
}
});
@ -2202,7 +2510,7 @@
}
}
newDetails += 'details[0].' + data.name + '=' + data.value;
$.ajax({
url: createURL('updateTemplate&id=' + args.context.templates[0].id + '&' + newDetails),
success: function(json) {
@ -3429,7 +3737,7 @@
allowedActions.push("copyTemplate");
}
// "Download Template"
// "Download Template" , "Update Template Permissions"
if (((isAdmin() == false && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account) && !(jsonObj.domainid == g_domainid && cloudStack.context.projects && jsonObj.projectid == cloudStack.context.projects[0].id))) //if neither root-admin, nor the same account, nor the same project
|| (jsonObj.isready == false) || jsonObj.templatetype == "SYSTEM") {
//do nothing
@ -3437,6 +3745,7 @@
if (jsonObj.isextractable){
allowedActions.push("downloadTemplate");
}
allowedActions.push("shareTemplate");
}
// "Delete Template"