Merge branch 'devel' of https://github.com/ansible/ansible-tower into wf_rbac_prompt

This commit is contained in:
AlanCoding
2016-09-26 13:19:12 -04:00
92 changed files with 2051 additions and 922 deletions

View File

@@ -298,12 +298,10 @@ requirements_tower_dev:
# Install third-party requirements needed for running unittests in jenkins # Install third-party requirements needed for running unittests in jenkins
requirements_jenkins: requirements_jenkins:
if [ "$(VENV_BASE)" ]; then \ if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/tower/bin/activate; \ . $(VENV_BASE)/tower/bin/activate && pip install -Ir requirements/requirements_jenkins.txt; \
$(VENV_BASE)/tower/bin/pip install -Ir requirements/requirements_jenkins.txt; \
else \ else \
pip install -Ir requirements/requirements_jenkins.txt; \ pip install -Ir requirements/requirements_jenkins.txt; \
fi && \ fi
$(NPM_BIN) install csslint
requirements: requirements_ansible requirements_tower requirements: requirements_ansible requirements_tower
@@ -317,8 +315,8 @@ develop:
pip uninstall -y awx; \ pip uninstall -y awx; \
$(PYTHON) setup.py develop; \ $(PYTHON) setup.py develop; \
else \ else \
sudo pip uninstall -y awx; \ pip uninstall -y awx; \
sudo $(PYTHON) setup.py develop; \ $(PYTHON) setup.py develop; \
fi fi
version_file: version_file:
@@ -448,7 +446,7 @@ pylint: reports
check: flake8 pep8 # pyflakes pylint check: flake8 pep8 # pyflakes pylint
TEST_DIRS=awx/main/tests TEST_DIRS ?= awx/main/tests
# Run all API unit tests. # Run all API unit tests.
test: test:
@if [ "$(VENV_BASE)" ]; then \ @if [ "$(VENV_BASE)" ]; then \

View File

@@ -1,199 +0,0 @@
import '../support/node';
import adhocModule from 'inventories/manage/adhoc/main';
import RestStub from '../support/rest-stub';
describe("adhoc.controller", function() {
var $scope, $rootScope, $location, $stateParams, $stateExtender,
CheckPasswords, PromptForPasswords, CreateLaunchDialog, AdhocForm,
GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, GetChoices,
KindChange, LookUpInit, CredentialList, Empty, Wait;
var $controller, ctrl, generateFormCallback, waitCallback, locationCallback,
getBasePath, processErrorsCallback, restCallback, stateExtenderCallback;
beforeEach("instantiate the adhoc module", function() {
angular.mock.module(adhocModule.name);
});
before("create spies", function() {
getBasePath = function(path) {
return '/' + path + '/';
};
generateFormCallback = {
inject: angular.noop
};
waitCallback = sinon.spy();
locationCallback = {
path: sinon.spy()
};
processErrorsCallback = sinon.spy();
restCallback = new RestStub();
stateExtenderCallback = {
addState: angular.noop
}
});
beforeEach("mock dependencies", angular.mock.module(['$provide', function(_provide_) {
var $provide = _provide_;
$provide.value('$location', locationCallback);
$provide.value('CheckPasswords', angular.noop);
$provide.value('PromptForPasswords', angular.noop);
$provide.value('CreateLaunchDialog', angular.noop);
$provide.value('AdhocForm', angular.noop);
$provide.value('GenerateForm', generateFormCallback);
$provide.value('Rest', restCallback);
$provide.value('ProcessErrors', processErrorsCallback);
$provide.value('ClearScope', angular.noop);
$provide.value('GetBasePath', getBasePath);
$provide.value('GetChoices', angular.noop);
$provide.value('KindChange', angular.noop);
$provide.value('LookUpInit', angular.noop);
$provide.value('CredentialList', angular.noop);
$provide.value('Empty', angular.noop);
$provide.value('Wait', waitCallback);
$provide.value('$stateExtender', stateExtenderCallback);
$provide.value('$stateParams', angular.noop);
$provide.value('$state', angular.noop);
}]));
beforeEach("put the controller in scope", inject(function($rootScope, $controller) {
var scope = $rootScope.$new();
ctrl = $controller('adhocController', {$scope: scope});
}));
beforeEach("put $q in scope", window.inject(['$q', function($q) {
restCallback.$q = $q;
}]));
/*
describe("setAvailableUrls", function() {
it('should only have the specified urls ' +
'available for adhoc commands', function() {
var urls = ctrl.privateFn.setAvailableUrls();
expect(urls).to.have.keys('adhocUrl', 'inventoryUrl',
'machineCredentialUrl');
var count = 0;
var i;
for (i in urls) {
if (urls.hasOwnProperty(i)) {
count++;
}
}
expect(count).to.equal(3);
});
});
describe("setFieldDefaults", function() {
it('should set the select form field defaults' +
'based on user settings', function() {
var verbosity_options = [
{label: "0 (Foo)", value: 0, name: "0 (Foo)",
isDefault: false},
{label: "1 (Bar)", value: 1, name: "1 (Bar)",
isDefault: true},
],
forks_field = {};
forks_field.default = 3;
$scope.$apply(function() {
ctrl.privateFn.setFieldDefaults(verbosity_options,
forks_field.default);
});
expect($scope.forks).to.equal(forks_field.default);
expect($scope.verbosity.value).to.equal(1);
});
});
describe("setLoadingStartStop", function() {
it('should start the controller working state when the form is ' +
'loading', function() {
waitCallback.reset();
ctrl.privateFn.setLoadingStartStop();
expect(waitCallback).to.have.been.calledWith("start");
});
it('should stop the indicator after all REST calls in the form load have ' +
'completed', function() {
var forks_field = {},
adhoc_verbosity_options = {};
forks_field.default = "1";
$scope.$apply(function() {
$scope.forks_field = forks_field;
$scope.adhoc_verbosity_options = adhoc_verbosity_options;
});
waitCallback.reset();
$scope.$emit('adhocFormReady');
$scope.$emit('adhocFormReady');
expect(waitCallback).to.have.been.calledWith("stop");
});
});
describe("instantiateArgumentHelp", function() {
it("should initially provide a canned argument help response", function() {
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
'with the specified module.</p>');
});
it("should change the help response when the module changes", function() {
$scope.$apply(function () {
$scope.module_name = {value: 'foo'};
});
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
'with the specified module. You can find information about ' +
'the foo module <a ' +
'id=\"adhoc_module_arguments_docs_link_for_module_foo\" ' +
'href=\"http://docs.ansible.com/foo_module.html\" ' +
'target=\"_blank\">here</a>.</p>');
});
it("should change the help response when the module changes again", function() {
$scope.$apply(function () {
$scope.module_name = {value: 'bar'};
});
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
'with the specified module. You can find information about ' +
'the bar module <a ' +
'id=\"adhoc_module_arguments_docs_link_for_module_bar\" ' +
'href=\"http://docs.ansible.com/bar_module.html\" ' +
'target=\"_blank\">here</a>.</p>');
});
it("should change the help response back to the canned response " +
"when no module is selected", function() {
$scope.$apply(function () {
$scope.module_name = null;
});
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
'with the specified module.</p>');
});
});
describe("instantiateHostPatterns", function() {
it("should initialize the limit object based on the provided host " +
"pattern", function() {
ctrl.privateFn.instantiateHostPatterns("foo:bar");
expect($scope.limit).to.equal("foo:bar");
});
it("should set the providedHostPatterns variable to the provided host " +
"pattern so it is accesible on form reset", function() {
ctrl.privateFn.instantiateHostPatterns("foo:bar");
expect($scope.providedHostPatterns).to.equal("foo:bar");
});
it("should remove the hostPattern from rootScope after it has been " +
"utilized", function() {
$rootScope.hostPatterns = "foo";
expect($rootScope.hostPatterns).to.exist;
ctrl.privateFn.instantiateHostPatterns("foo");
expect($rootScope.hostPatterns).to.not.exist;
});
});
*/
});

View File

@@ -235,6 +235,13 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
def get_queryset(self): def get_queryset(self):
return self.request.user.get_queryset(self.model) return self.request.user.get_queryset(self.model)
def paginate_queryset(self, queryset):
page = super(ListAPIView, self).paginate_queryset(queryset)
# Queries RBAC info & stores into list objects
if hasattr(self, 'capabilities_prefetch') and page is not None:
cache_list_capabilities(page, self.capabilities_prefetch, self.model, self.request.user)
return page
def get_description_context(self): def get_description_context(self):
opts = self.model._meta opts = self.model._meta
if 'username' in opts.get_all_field_names(): if 'username' in opts.get_all_field_names():

View File

@@ -49,6 +49,9 @@ class ModelAccessPermission(permissions.BasePermission):
if not check_user_access(request.user, view.parent_model, 'read', if not check_user_access(request.user, view.parent_model, 'read',
parent_obj): parent_obj):
return False return False
if hasattr(view, 'parent_key'):
if not check_user_access(request.user, view.model, 'add', {view.parent_key: parent_obj.pk}):
return False
return True return True
elif getattr(view, 'is_job_start', False): elif getattr(view, 'is_job_start', False):
if not obj: if not obj:
@@ -206,6 +209,8 @@ class ProjectUpdatePermission(ModelAccessPermission):
class UserPermission(ModelAccessPermission): class UserPermission(ModelAccessPermission):
def check_post_permissions(self, request, view, obj=None): def check_post_permissions(self, request, view, obj=None):
if request.user.is_superuser: if not request.data:
return request.user.admin_of_organizations.exists()
elif request.user.is_superuser:
return True return True
raise PermissionDenied() raise PermissionDenied()

View File

@@ -37,6 +37,7 @@ from polymorphic import PolymorphicModel
# AWX # AWX
from awx.main.constants import SCHEDULEABLE_PROVIDERS from awx.main.constants import SCHEDULEABLE_PROVIDERS
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.access import get_user_capabilities
from awx.main.fields import ImplicitRoleField from awx.main.fields import ImplicitRoleField
from awx.main.utils import get_type_for_model, get_model_for_type, build_url, timestamp_apiformat, camelcase_to_underscore, getattrd from awx.main.utils import get_type_for_model, get_model_for_type, build_url, timestamp_apiformat, camelcase_to_underscore, getattrd
from awx.main.conf import tower_settings from awx.main.conf import tower_settings
@@ -345,6 +346,19 @@ class BaseSerializer(serializers.ModelSerializer):
} }
if len(roles) > 0: if len(roles) > 0:
summary_fields['object_roles'] = roles summary_fields['object_roles'] = roles
# Advance display of RBAC capabilities
if hasattr(self, 'show_capabilities'):
view = self.context.get('view', None)
parent_obj = None
if view and hasattr(view, 'parent_model'):
parent_obj = view.get_parent_object()
if view and view.request and view.request.user:
user_capabilities = get_user_capabilities(
view.request.user, obj, method_list=self.show_capabilities, parent_obj=parent_obj)
if user_capabilities:
summary_fields['user_capabilities'] = user_capabilities
return summary_fields return summary_fields
def get_created(self, obj): def get_created(self, obj):
@@ -553,6 +567,7 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
class UnifiedJobSerializer(BaseSerializer): class UnifiedJobSerializer(BaseSerializer):
show_capabilities = ['start', 'delete']
result_stdout = serializers.SerializerMethodField() result_stdout = serializers.SerializerMethodField()
@@ -697,11 +712,12 @@ class UserSerializer(BaseSerializer):
ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True) ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True)
external_account = serializers.SerializerMethodField(help_text='Set if the account is managed by an external service') external_account = serializers.SerializerMethodField(help_text='Set if the account is managed by an external service')
is_system_auditor = serializers.BooleanField(default=False) is_system_auditor = serializers.BooleanField(default=False)
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = User model = User
fields = ('*', '-name', '-description', '-modified', fields = ('*', '-name', '-description', '-modified',
'-summary_fields', 'username', 'first_name', 'last_name', 'username', 'first_name', 'last_name',
'email', 'is_superuser', 'is_system_auditor', 'password', 'ldap_dn', 'external_account') 'email', 'is_superuser', 'is_system_auditor', 'password', 'ldap_dn', 'external_account')
def to_representation(self, obj): def to_representation(self, obj):
@@ -822,6 +838,7 @@ class UserSerializer(BaseSerializer):
class OrganizationSerializer(BaseSerializer): class OrganizationSerializer(BaseSerializer):
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = Organization model = Organization
@@ -906,6 +923,7 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
status = serializers.ChoiceField(choices=Project.PROJECT_STATUS_CHOICES, read_only=True) status = serializers.ChoiceField(choices=Project.PROJECT_STATUS_CHOICES, read_only=True)
last_update_failed = serializers.BooleanField(read_only=True) last_update_failed = serializers.BooleanField(read_only=True)
last_updated = serializers.DateTimeField(read_only=True) last_updated = serializers.DateTimeField(read_only=True)
show_capabilities = ['start', 'schedule', 'edit', 'delete']
class Meta: class Meta:
model = Project model = Project
@@ -1014,6 +1032,7 @@ class BaseSerializerWithVariables(BaseSerializer):
class InventorySerializer(BaseSerializerWithVariables): class InventorySerializer(BaseSerializerWithVariables):
show_capabilities = ['edit', 'delete', 'adhoc']
class Meta: class Meta:
model = Inventory model = Inventory
@@ -1064,12 +1083,14 @@ class InventoryDetailSerializer(InventorySerializer):
class InventoryScriptSerializer(InventorySerializer): class InventoryScriptSerializer(InventorySerializer):
show_capabilities = ['copy', 'edit', 'delete']
class Meta: class Meta:
fields = () fields = ()
class HostSerializer(BaseSerializerWithVariables): class HostSerializer(BaseSerializerWithVariables):
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = Host model = Host
@@ -1180,6 +1201,7 @@ class HostSerializer(BaseSerializerWithVariables):
class GroupSerializer(BaseSerializerWithVariables): class GroupSerializer(BaseSerializerWithVariables):
show_capabilities = ['start', 'copy', 'schedule', 'edit', 'delete']
class Meta: class Meta:
model = Group model = Group
@@ -1284,6 +1306,7 @@ class GroupVariableDataSerializer(BaseVariableDataSerializer):
class CustomInventoryScriptSerializer(BaseSerializer): class CustomInventoryScriptSerializer(BaseSerializer):
script = serializers.CharField(trim_whitespace=False) script = serializers.CharField(trim_whitespace=False)
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = CustomInventoryScript model = CustomInventoryScript
@@ -1454,6 +1477,7 @@ class InventoryUpdateCancelSerializer(InventoryUpdateSerializer):
class TeamSerializer(BaseSerializer): class TeamSerializer(BaseSerializer):
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = Team model = Team
@@ -1522,8 +1546,12 @@ class RoleSerializer(BaseSerializer):
return ret return ret
class RoleSerializerWithParentAccess(RoleSerializer):
show_capabilities = ['unattach']
class ResourceAccessListElementSerializer(UserSerializer): class ResourceAccessListElementSerializer(UserSerializer):
show_capabilities = [] # Clear fields from UserSerializer parent class
def to_representation(self, user): def to_representation(self, user):
''' '''
@@ -1539,18 +1567,25 @@ class ResourceAccessListElementSerializer(UserSerializer):
ret = super(ResourceAccessListElementSerializer, self).to_representation(user) ret = super(ResourceAccessListElementSerializer, self).to_representation(user)
object_id = self.context['view'].object_id object_id = self.context['view'].object_id
obj = self.context['view'].resource_model.objects.get(pk=object_id) obj = self.context['view'].resource_model.objects.get(pk=object_id)
if self.context['view'].request is not None:
requesting_user = self.context['view'].request.user
else:
requesting_user = None
if 'summary_fields' not in ret: if 'summary_fields' not in ret:
ret['summary_fields'] = {} ret['summary_fields'] = {}
def format_role_perm(role): def format_role_perm(role):
role_dict = { 'id': role.id, 'name': role.name, 'description': role.description} role_dict = { 'id': role.id, 'name': role.name, 'description': role.description}
try: if role.content_type is not None:
role_dict['resource_name'] = role.content_object.name role_dict['resource_name'] = role.content_object.name
role_dict['resource_type'] = role.content_type.name role_dict['resource_type'] = role.content_type.name
role_dict['related'] = reverse_gfk(role.content_object) role_dict['related'] = reverse_gfk(role.content_object)
except: role_dict['user_capabilities'] = {'unattach': requesting_user.can_access(
pass Role, 'unattach', role, user, 'members', data={}, skip_sub_obj_read_check=False)}
else:
# Singleton roles should not be managed from this view, as per copy/edit rework spec
role_dict['user_capabilities'] = {'unattach': False}
return { 'role': role_dict, 'descendant_roles': get_roles_on_resource(obj, role)} return { 'role': role_dict, 'descendant_roles': get_roles_on_resource(obj, role)}
def format_team_role_perm(team_role, permissive_role_ids): def format_team_role_perm(team_role, permissive_role_ids):
@@ -1563,20 +1598,21 @@ class ResourceAccessListElementSerializer(UserSerializer):
'team_id': team_role.object_id, 'team_id': team_role.object_id,
'team_name': team_role.content_object.name 'team_name': team_role.content_object.name
} }
try: if role.content_type is not None:
role_dict['resource_name'] = role.content_object.name role_dict['resource_name'] = role.content_object.name
role_dict['resource_type'] = role.content_type.name role_dict['resource_type'] = role.content_type.name
role_dict['related'] = reverse_gfk(role.content_object) role_dict['related'] = reverse_gfk(role.content_object)
except: role_dict['user_capabilities'] = {'unattach': requesting_user.can_access(
pass Role, 'unattach', role, team_role, 'parents', data={}, skip_sub_obj_read_check=False)}
else:
# Singleton roles should not be managed from this view, as per copy/edit rework spec
role_dict['user_capabilities'] = {'unattach': False}
ret.append({ 'role': role_dict, 'descendant_roles': get_roles_on_resource(obj, team_role)}) ret.append({ 'role': role_dict, 'descendant_roles': get_roles_on_resource(obj, team_role)})
return ret return ret
team_content_type = ContentType.objects.get_for_model(Team) team_content_type = ContentType.objects.get_for_model(Team)
content_type = ContentType.objects.get_for_model(obj) content_type = ContentType.objects.get_for_model(obj)
content_type = ContentType.objects.get_for_model(obj)
direct_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('id', flat=True) direct_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('id', flat=True)
all_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('ancestors__id', flat=True) all_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('ancestors__id', flat=True)
@@ -1621,6 +1657,7 @@ class ResourceAccessListElementSerializer(UserSerializer):
class CredentialSerializer(BaseSerializer): class CredentialSerializer(BaseSerializer):
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = Credential model = Credential
@@ -1825,6 +1862,7 @@ class JobOptionsSerializer(BaseSerializer):
class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer): class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
show_capabilities = ['start', 'schedule', 'copy', 'edit', 'delete']
status = serializers.ChoiceField(choices=JobTemplate.JOB_TEMPLATE_STATUS_CHOICES, read_only=True, required=False) status = serializers.ChoiceField(choices=JobTemplate.JOB_TEMPLATE_STATUS_CHOICES, read_only=True, required=False)
@@ -1860,21 +1898,6 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
d = super(JobTemplateSerializer, self).get_summary_fields(obj) d = super(JobTemplateSerializer, self).get_summary_fields(obj)
if obj.survey_spec is not None and ('name' in obj.survey_spec and 'description' in obj.survey_spec): if obj.survey_spec is not None and ('name' in obj.survey_spec and 'description' in obj.survey_spec):
d['survey'] = dict(title=obj.survey_spec['name'], description=obj.survey_spec['description']) d['survey'] = dict(title=obj.survey_spec['name'], description=obj.survey_spec['description'])
request = self.context.get('request', None)
# Check for conditions that would create a validation error if coppied
validation_errors, resources_needed_to_start = obj.resource_validation_data()
if request is None or request.user is None:
d['can_copy'] = False
d['can_edit'] = False
elif request.user.is_superuser:
d['can_copy'] = not validation_errors
d['can_edit'] = True
else:
d['can_copy'] = (not validation_errors) and request.user.can_access(JobTemplate, 'add', {"reference_obj": obj})
d['can_edit'] = request.user.can_access(JobTemplate, 'change', obj, {})
d['recent_jobs'] = self._recent_jobs(obj) d['recent_jobs'] = self._recent_jobs(obj)
return d return d
@@ -2561,6 +2584,7 @@ class JobLaunchSerializer(BaseSerializer):
return attrs return attrs
class NotificationTemplateSerializer(BaseSerializer): class NotificationTemplateSerializer(BaseSerializer):
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = NotificationTemplate model = NotificationTemplate
@@ -2674,6 +2698,7 @@ class LabelSerializer(BaseSerializer):
return res return res
class ScheduleSerializer(BaseSerializer): class ScheduleSerializer(BaseSerializer):
show_capabilities = ['edit', 'delete']
class Meta: class Meta:
model = Schedule model = Schedule

View File

@@ -854,7 +854,7 @@ class TeamUsersList(BaseUsersList):
class TeamRolesList(SubListCreateAttachDetachAPIView): class TeamRolesList(SubListCreateAttachDetachAPIView):
model = Role model = Role
serializer_class = RoleSerializer serializer_class = RoleSerializerWithParentAccess
metadata_class = RoleMetadata metadata_class = RoleMetadata
parent_model = Team parent_model = Team
relationship='member_role.children' relationship='member_role.children'
@@ -953,6 +953,7 @@ class ProjectList(ListCreateAPIView):
model = Project model = Project
serializer_class = ProjectSerializer serializer_class = ProjectSerializer
capabilities_prefetch = ['admin', 'update']
def get_queryset(self): def get_queryset(self):
projects_qs = Project.accessible_objects(self.request.user, 'read_role') projects_qs = Project.accessible_objects(self.request.user, 'read_role')
@@ -1151,6 +1152,7 @@ class UserList(ListCreateAPIView):
model = User model = User
serializer_class = UserSerializer serializer_class = UserSerializer
capabilities_prefetch = ['admin']
permission_classes = (UserPermission,) permission_classes = (UserPermission,)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@@ -1191,7 +1193,7 @@ class UserTeamsList(ListAPIView):
class UserRolesList(SubListCreateAttachDetachAPIView): class UserRolesList(SubListCreateAttachDetachAPIView):
model = Role model = Role
serializer_class = RoleSerializer serializer_class = RoleSerializerWithParentAccess
metadata_class = RoleMetadata metadata_class = RoleMetadata
parent_model = User parent_model = User
relationship='roles' relationship='roles'
@@ -1511,6 +1513,7 @@ class InventoryList(ListCreateAPIView):
model = Inventory model = Inventory
serializer_class = InventorySerializer serializer_class = InventorySerializer
capabilities_prefetch = ['admin', 'adhoc']
def get_queryset(self): def get_queryset(self):
qs = Inventory.accessible_objects(self.request.user, 'read_role') qs = Inventory.accessible_objects(self.request.user, 'read_role')
@@ -1742,6 +1745,7 @@ class GroupList(ListCreateAPIView):
model = Group model = Group
serializer_class = GroupSerializer serializer_class = GroupSerializer
capabilities_prefetch = ['inventory.admin', 'inventory.adhoc', 'inventory.update']
''' '''
Useful when you have a self-refering ManyToManyRelationship. Useful when you have a self-refering ManyToManyRelationship.
@@ -2210,6 +2214,10 @@ class JobTemplateList(ListCreateAPIView):
model = JobTemplate model = JobTemplate
serializer_class = JobTemplateSerializer serializer_class = JobTemplateSerializer
always_allow_superuser = False always_allow_superuser = False
capabilities_prefetch = [
'admin', 'execute',
{'copy': ['project.use', 'inventory.use', 'credential.use', 'cloud_credential.use', 'network_credential.use']}
]
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
ret = super(JobTemplateList, self).post(request, *args, **kwargs) ret = super(JobTemplateList, self).post(request, *args, **kwargs)
@@ -3680,6 +3688,7 @@ class NotificationTemplateTest(GenericAPIView):
model = NotificationTemplate model = NotificationTemplate
serializer_class = EmptySerializer serializer_class = EmptySerializer
new_in_300 = True new_in_300 = True
is_job_start = True
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
obj = self.get_object() obj = self.get_object()

View File

@@ -116,6 +116,18 @@ def check_user_access(user, model_class, action, *args, **kwargs):
return result return result
return False return False
def get_user_capabilities(user, instance, **kwargs):
'''
Returns a dictionary of capabilities the user has on the particular
instance. *NOTE* This is not a direct mapping of can_* methods into this
dictionary, it is intended to munge some queries in a way that is
convenient for the user interface to consume and hide or show various
actions in the interface.
'''
for access_class in access_registry.get(type(instance), []):
return access_class(user).get_user_capabilities(instance, **kwargs)
return None
def check_superuser(func): def check_superuser(func):
''' '''
check_superuser is a decorator that provides a simple short circuit check_superuser is a decorator that provides a simple short circuit
@@ -207,6 +219,78 @@ class BaseAccess(object):
elif "features" not in validation_info: elif "features" not in validation_info:
raise LicenseForbids("Features not found in active license.") raise LicenseForbids("Features not found in active license.")
def get_user_capabilities(self, obj, method_list=[], parent_obj=None):
if obj is None:
return {}
user_capabilities = {}
# Custom ordering to loop through methods so we can reuse earlier calcs
for display_method in ['edit', 'delete', 'start', 'schedule', 'copy', 'adhoc', 'unattach']:
if display_method not in method_list:
continue
# Validation consistency checks
if display_method == 'copy' and isinstance(obj, JobTemplate):
validation_errors, resources_needed_to_start = obj.resource_validation_data()
if validation_errors:
user_capabilities[display_method] = False
continue
elif display_method == 'start' and isinstance(obj, Group):
if obj.inventory_source and not obj.inventory_source._can_update():
user_capabilities[display_method] = False
continue
# Grab the answer from the cache, if available
if hasattr(obj, 'capabilities_cache') and display_method in obj.capabilities_cache:
user_capabilities[display_method] = obj.capabilities_cache[display_method]
continue
# Aliases for going form UI language to API language
if display_method == 'edit':
method = 'change'
elif display_method == 'copy':
method = 'add'
elif display_method == 'adhoc':
method = 'run_ad_hoc_commands'
else:
method = display_method
# Shortcuts in certain cases by deferring to earlier property
if display_method == 'schedule':
user_capabilities['schedule'] = user_capabilities['edit']
continue
elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob)):
user_capabilities['delete'] = user_capabilities['edit']
continue
elif display_method == 'copy' and isinstance(obj, (Group, Host)):
user_capabilities['copy'] = user_capabilities['edit']
continue
# Preprocessing before the access method is called
data = {}
if method == 'add':
if isinstance(obj, JobTemplate):
data['reference_obj'] = obj
# Compute permission
access_method = getattr(self, "can_%s" % method)
if method in ['change']: # 3 args
user_capabilities[display_method] = access_method(obj, data)
elif method in ['delete', 'start', 'run_ad_hoc_commands']: # 2 args
user_capabilities[display_method] = access_method(obj)
elif method in ['add']: # 2 args with data
user_capabilities[display_method] = access_method(data)
elif method in ['attach', 'unattach']: # parent/sub-object call
if type(parent_obj) == Team:
relationship = 'parents'
parent_obj = parent_obj.member_role
else:
relationship = 'members'
user_capabilities[display_method] = access_method(
obj, parent_obj, relationship, skip_sub_obj_read_check=True, data=data)
return user_capabilities
class UserAccess(BaseAccess): class UserAccess(BaseAccess):
''' '''
@@ -526,6 +610,12 @@ class GroupAccess(BaseAccess):
"active_jobs": active_jobs}) "active_jobs": active_jobs})
return True return True
def can_start(self, obj):
# Used as another alias to inventory_source start access for user_capabilities
if obj and obj.inventory_source:
return self.user.can_access(InventorySource, 'start', obj.inventory_source)
return False
class InventorySourceAccess(BaseAccess): class InventorySourceAccess(BaseAccess):
''' '''
I can see inventory sources whenever I can see their group or inventory. I can see inventory sources whenever I can see their group or inventory.
@@ -594,6 +684,13 @@ class InventoryUpdateAccess(BaseAccess):
# Inventory cascade deletes to inventory update, descends from org admin # Inventory cascade deletes to inventory update, descends from org admin
return self.user in obj.inventory_source.inventory.admin_role return self.user in obj.inventory_source.inventory.admin_role
def can_start(self, obj):
# For relaunching
if obj and obj.inventory_source:
access = InventorySourceAccess(self.user)
return access.can_start(obj.inventory_source)
return False
@check_superuser @check_superuser
def can_delete(self, obj): def can_delete(self, obj):
return self.user in obj.inventory_source.inventory.admin_role return self.user in obj.inventory_source.inventory.admin_role
@@ -815,6 +912,12 @@ class ProjectUpdateAccess(BaseAccess):
# Project updates cascade delete with project, admin role descends from org admin # Project updates cascade delete with project, admin role descends from org admin
return self.user in obj.project.admin_role return self.user in obj.project.admin_role
def can_start(self, obj):
# for relaunching
if obj and obj.project:
return self.user in obj.project.update_role
return False
@check_superuser @check_superuser
def can_delete(self, obj): def can_delete(self, obj):
return obj and self.user in obj.project.admin_role return obj and self.user in obj.project.admin_role
@@ -855,7 +958,9 @@ class JobTemplateAccess(BaseAccess):
Users who are able to create deploy jobs can also run normal and check (dry run) jobs. Users who are able to create deploy jobs can also run normal and check (dry run) jobs.
''' '''
if not data: # So the browseable API will work if not data: # So the browseable API will work
return True return (
Project.accessible_objects(self.user, 'use_role').exists() or
Inventory.accessible_objects(self.user, 'use_role').exists())
# if reference_obj is provided, determine if it can be coppied # if reference_obj is provided, determine if it can be coppied
reference_obj = data.pop('reference_obj', None) reference_obj = data.pop('reference_obj', None)
@@ -1132,6 +1237,10 @@ class SystemJobAccess(BaseAccess):
''' '''
model = SystemJob model = SystemJob
def can_start(self, obj):
return False # no relaunching of system jobs
# TODO:
class WorkflowJobTemplateNodeAccess(BaseAccess): class WorkflowJobTemplateNodeAccess(BaseAccess):
''' '''
I can see/use a WorkflowJobTemplateNode if I have read permission I can see/use a WorkflowJobTemplateNode if I have read permission
@@ -1762,6 +1871,12 @@ class NotificationTemplateAccess(BaseAccess):
def can_delete(self, obj): def can_delete(self, obj):
return self.can_change(obj, None) return self.can_change(obj, None)
@check_superuser
def can_start(self, obj):
if obj.organization is None:
return False
return self.user in obj.organization.admin_role
class NotificationAccess(BaseAccess): class NotificationAccess(BaseAccess):
''' '''
I can see/use a notification if I have permission to I can see/use a notification if I have permission to
@@ -1970,8 +2085,13 @@ class RoleAccess(BaseAccess):
@check_superuser @check_superuser
def can_unattach(self, obj, sub_obj, relationship, data=None, skip_sub_obj_read_check=False): def can_unattach(self, obj, sub_obj, relationship, data=None, skip_sub_obj_read_check=False):
if not skip_sub_obj_read_check and relationship in ['members', 'member_role.parents']: if not skip_sub_obj_read_check and relationship in ['members', 'member_role.parents', 'parents']:
if not check_user_access(self.user, sub_obj.__class__, 'read', sub_obj): # If we are unattaching a team Role, check the Team read access
if relationship == 'parents':
sub_obj_resource = sub_obj.content_object
else:
sub_obj_resource = sub_obj
if not check_user_access(self.user, sub_obj_resource.__class__, 'read', sub_obj_resource):
return False return False
if isinstance(obj.content_object, ResourceMixin) and \ if isinstance(obj.content_object, ResourceMixin) and \

View File

@@ -47,3 +47,4 @@ class Command(BaseCommand):
inventory=i, inventory=i,
credential=c) credential=c)
print('Default organization added.') print('Default organization added.')
print('Demo Credential, Inventory, and Job Template added.')

View File

@@ -340,6 +340,10 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
errors.append("'%s' value missing" % survey_element['variable']) errors.append("'%s' value missing" % survey_element['variable'])
elif survey_element['type'] in ["textarea", "text", "password"]: elif survey_element['type'] in ["textarea", "text", "password"]:
if survey_element['variable'] in data: if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (str, unicode):
errors.append("Value %s for '%s' expected to be a string." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']): if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']):
errors.append("'%s' value %s is too small (length is %s must be at least %s)." % errors.append("'%s' value %s is too small (length is %s must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], len(data[survey_element['variable']]), survey_element['min'])) (survey_element['variable'], data[survey_element['variable']], len(data[survey_element['variable']]), survey_element['min']))
@@ -348,6 +352,10 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
(survey_element['variable'], data[survey_element['variable']], survey_element['max'])) (survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'integer': elif survey_element['type'] == 'integer':
if survey_element['variable'] in data: if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != int:
errors.append("Value %s for '%s' expected to be an integer." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and survey_element['variable'] in data and \ if 'min' in survey_element and survey_element['min'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] < int(survey_element['min']): data[survey_element['variable']] < int(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." % errors.append("'%s' value %s is too small (must be at least %s)." %
@@ -356,20 +364,18 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
data[survey_element['variable']] > int(survey_element['max']): data[survey_element['variable']] > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." % errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max'])) (survey_element['variable'], data[survey_element['variable']], survey_element['max']))
if type(data[survey_element['variable']]) != int:
errors.append("Value %s for '%s' expected to be an integer." % (data[survey_element['variable']],
survey_element['variable']))
elif survey_element['type'] == 'float': elif survey_element['type'] == 'float':
if survey_element['variable'] in data: if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (float, int):
errors.append("Value %s for '%s' expected to be a numeric type." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and data[survey_element['variable']] < float(survey_element['min']): if 'min' in survey_element and survey_element['min'] not in ["", None] and data[survey_element['variable']] < float(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." % errors.append("'%s' value %s is too small (must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['min'])) (survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and data[survey_element['variable']] > float(survey_element['max']): if 'max' in survey_element and survey_element['max'] not in ["", None] and data[survey_element['variable']] > float(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." % errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max'])) (survey_element['variable'], data[survey_element['variable']], survey_element['max']))
if type(data[survey_element['variable']]) not in (float, int):
errors.append("Value %s for '%s' expected to be a numeric type." % (data[survey_element['variable']],
survey_element['variable']))
elif survey_element['type'] == 'multiselect': elif survey_element['type'] == 'multiselect':
if survey_element['variable'] in data: if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != list: if type(data[survey_element['variable']]) != list:

View File

@@ -177,7 +177,6 @@ class WorkflowJobNode(WorkflowNodeBase):
# TODO: merge artifacts, add ancestor_artifacts to kwargs # TODO: merge artifacts, add ancestor_artifacts to kwargs
if extra_vars: if extra_vars:
data['extra_vars'] = extra_vars data['extra_vars'] = extra_vars
print ' job KV data: ' + str(data)
return data return data
class WorkflowJobOptions(BaseModel): class WorkflowJobOptions(BaseModel):

View File

@@ -654,7 +654,8 @@ class BaseTask(Task):
if status == 'canceled': if status == 'canceled':
raise Exception("Task %s(pk:%s) was canceled (rc=%s)" % (str(self.model.__class__), str(pk), str(rc))) raise Exception("Task %s(pk:%s) was canceled (rc=%s)" % (str(self.model.__class__), str(pk), str(rc)))
else: else:
raise Exception("Task %s(pk:%s) encountered an error (rc=%s)" % (str(self.model.__class__), str(pk), str(rc))) raise Exception("Task %s(pk:%s) encountered an error (rc=%s), please see task stdout for details." %
(str(self.model.__class__), str(pk), str(rc)))
if not hasattr(settings, 'CELERY_UNIT_TEST'): if not hasattr(settings, 'CELERY_UNIT_TEST'):
self.signal_finished(pk) self.signal_finished(pk)

View File

@@ -3,12 +3,11 @@ import mock
# AWX # AWX
from awx.api.serializers import JobTemplateSerializer, JobLaunchSerializer from awx.api.serializers import JobTemplateSerializer, JobLaunchSerializer
from awx.main.models.jobs import JobTemplate, Job from awx.main.models.jobs import Job
from awx.main.models.projects import ProjectOptions from awx.main.models.projects import ProjectOptions
from awx.main.migrations import _save_password_keys as save_password_keys from awx.main.migrations import _save_password_keys as save_password_keys
# Django # Django
from django.test.client import RequestFactory
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.apps import apps from django.apps import apps
@@ -141,131 +140,7 @@ def test_job_template_role_user(post, organization_factory, job_template_factory
response = post(url, dict(id=jt_objects.job_template.execute_role.pk), objects.superusers.admin) response = post(url, dict(id=jt_objects.job_template.execute_role.pk), objects.superusers.admin)
assert response.status_code == 204 assert response.status_code == 204
# Test protection against limited set of validation problems
@pytest.mark.django_db
def test_bad_data_copy_edit(admin_user, project):
"""
If a required resource (inventory here) was deleted, copying not allowed
because doing so would caues a validation error
"""
jt_res = JobTemplate.objects.create(
job_type='run',
project=project,
inventory=None, ask_inventory_on_launch=False, # not allowed
credential=None, ask_credential_on_launch=True,
name='deploy-job-template'
)
serializer = JobTemplateSerializer(jt_res)
request = RequestFactory().get('/api/v1/job_templates/12/')
request.user = admin_user
serializer.context['request'] = request
response = serializer.to_representation(jt_res)
assert not response['summary_fields']['can_copy']
assert response['summary_fields']['can_edit']
# Tests for correspondence between view info and actual access
@pytest.mark.django_db
def test_admin_copy_edit(jt_copy_edit, admin_user):
"Absent a validation error, system admins can do everything"
# Serializer can_copy/can_edit fields
serializer = JobTemplateSerializer(jt_copy_edit)
request = RequestFactory().get('/api/v1/job_templates/12/')
request.user = admin_user
serializer.context['request'] = request
response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['can_copy']
assert response['summary_fields']['can_edit']
@pytest.mark.django_db
def test_org_admin_copy_edit(jt_copy_edit, org_admin):
"Organization admins SHOULD be able to copy a JT firmly in their org"
# Serializer can_copy/can_edit fields
serializer = JobTemplateSerializer(jt_copy_edit)
request = RequestFactory().get('/api/v1/job_templates/12/')
request.user = org_admin
serializer.context['request'] = request
response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['can_copy']
assert response['summary_fields']['can_edit']
@pytest.mark.django_db
def test_org_admin_foreign_cred_no_copy_edit(jt_copy_edit, org_admin, machine_credential):
"""
Organization admins without access to the 3 related resources:
SHOULD NOT be able to copy JT
SHOULD be able to edit that job template, for nonsensitive changes
"""
# Attach credential to JT that org admin can not use
jt_copy_edit.credential = machine_credential
jt_copy_edit.save()
# Serializer can_copy/can_edit fields
serializer = JobTemplateSerializer(jt_copy_edit)
request = RequestFactory().get('/api/v1/job_templates/12/')
request.user = org_admin
serializer.context['request'] = request
response = serializer.to_representation(jt_copy_edit)
assert not response['summary_fields']['can_copy']
assert response['summary_fields']['can_edit']
@pytest.mark.django_db
def test_jt_admin_copy_edit(jt_copy_edit, rando):
"""
JT admins wihout access to associated resources SHOULD NOT be able to copy
SHOULD be able to make nonsensitive changes"""
# random user given JT admin access only
jt_copy_edit.admin_role.members.add(rando)
jt_copy_edit.save()
# Serializer can_copy/can_edit fields
serializer = JobTemplateSerializer(jt_copy_edit)
request = RequestFactory().get('/api/v1/job_templates/12/')
request.user = rando
serializer.context['request'] = request
response = serializer.to_representation(jt_copy_edit)
assert not response['summary_fields']['can_copy']
assert response['summary_fields']['can_edit']
@pytest.mark.django_db
def test_proj_jt_admin_copy_edit(jt_copy_edit, rando):
"JT admins with access to associated resources SHOULD be able to copy"
# random user given JT and project admin abilities
jt_copy_edit.admin_role.members.add(rando)
jt_copy_edit.save()
jt_copy_edit.project.admin_role.members.add(rando)
jt_copy_edit.project.save()
# Serializer can_copy/can_edit fields
serializer = JobTemplateSerializer(jt_copy_edit)
request = RequestFactory().get('/api/v1/job_templates/12/')
request.user = rando
serializer.context['request'] = request
response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['can_copy']
assert response['summary_fields']['can_edit']
# Functional tests - create new JT with all returned fields, as the UI does
@pytest.mark.django_db
@mock.patch.object(ProjectOptions, "playbooks", project_playbooks)
def test_org_admin_copy_edit_functional(jt_copy_edit, org_admin, get, post):
get_response = get(reverse('api:job_template_detail', args=[jt_copy_edit.pk]), user=org_admin)
assert get_response.status_code == 200
assert get_response.data['summary_fields']['can_copy']
post_data = get_response.data
post_data['name'] = '%s @ 12:19:47 pm' % post_data['name']
post_response = post(reverse('api:job_template_list', args=[]), user=org_admin, data=post_data)
assert post_response.status_code == 201
assert post_response.data['name'] == 'copy-edit-job-template @ 12:19:47 pm'
@pytest.mark.django_db @pytest.mark.django_db
@mock.patch.object(ProjectOptions, "playbooks", project_playbooks) @mock.patch.object(ProjectOptions, "playbooks", project_playbooks)
@@ -277,7 +152,6 @@ def test_jt_admin_copy_edit_functional(jt_copy_edit, rando, get, post):
get_response = get(reverse('api:job_template_detail', args=[jt_copy_edit.pk]), user=rando) get_response = get(reverse('api:job_template_detail', args=[jt_copy_edit.pk]), user=rando)
assert get_response.status_code == 200 assert get_response.status_code == 200
assert not get_response.data['summary_fields']['can_copy']
post_data = get_response.data post_data = get_response.data
post_data['name'] = '%s @ 12:19:47 pm' % post_data['name'] post_data['name'] = '%s @ 12:19:47 pm' % post_data['name']

View File

@@ -0,0 +1,323 @@
import pytest
from django.core.urlresolvers import reverse
from django.test.client import RequestFactory
from awx.main.models.jobs import JobTemplate
from awx.main.models import Role, Group
from awx.main.access import (
access_registry,
get_user_capabilities
)
from awx.main.utils import cache_list_capabilities
from awx.api.serializers import JobTemplateSerializer
# This file covers special-cases of displays of user_capabilities
# general functionality should be covered fully by unit tests, see:
# awx/main/tests/unit/api/test_serializers.py ::
# TestJobTemplateSerializerGetSummaryFields.test_copy_edit_standard
# awx/main/tests/unit/test_access.py ::
# test_user_capabilities_method
@pytest.mark.django_db
class TestOptionsRBAC:
"""
Several endpoints are relied-upon by the UI to list POST as an
allowed action or not depending on whether the user has permission
to create a resource.
"""
def test_inventory_group_host_can_add(self, inventory, alice, options):
inventory.admin_role.members.add(alice)
response = options(reverse('api:inventory_hosts_list', args=[inventory.pk]), alice)
assert 'POST' in response.data['actions']
response = options(reverse('api:inventory_groups_list', args=[inventory.pk]), alice)
assert 'POST' in response.data['actions']
def test_inventory_group_host_can_not_add(self, inventory, bob, options):
inventory.read_role.members.add(bob)
response = options(reverse('api:inventory_hosts_list', args=[inventory.pk]), bob)
assert 'POST' not in response.data['actions']
response = options(reverse('api:inventory_groups_list', args=[inventory.pk]), bob)
assert 'POST' not in response.data['actions']
def test_user_list_can_add(self, org_member, org_admin, options):
response = options(reverse('api:user_list'), org_admin)
assert 'POST' in response.data['actions']
def test_user_list_can_not_add(self, org_member, org_admin, options):
response = options(reverse('api:user_list'), org_member)
assert 'POST' not in response.data['actions']
@pytest.mark.django_db
class TestJobTemplateCopyEdit:
"""
Tests contain scenarios that were raised as issues in the past,
which resulted from failed copy/edit actions even though the buttons
to do these actions were displayed.
"""
@pytest.fixture
def jt_copy_edit(self, job_template_factory, project):
objects = job_template_factory(
'copy-edit-job-template',
project=project)
return objects.job_template
def fake_context(self, user):
request = RequestFactory().get('/api/v1/resource/42/')
request.user = user
class FakeView(object):
pass
fake_view = FakeView()
fake_view.request = request
context = {}
context['view'] = fake_view
context['request'] = request
return context
def test_validation_bad_data_copy_edit(self, admin_user, project):
"""
If a required resource (inventory here) was deleted, copying not allowed
because doing so would caues a validation error
"""
jt_res = JobTemplate.objects.create(
job_type='run',
project=project,
inventory=None, ask_inventory_on_launch=False, # not allowed
credential=None, ask_credential_on_launch=True,
name='deploy-job-template'
)
serializer = JobTemplateSerializer(jt_res)
serializer.context = self.fake_context(admin_user)
response = serializer.to_representation(jt_res)
assert not response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit']
def test_sys_admin_copy_edit(self, jt_copy_edit, admin_user):
"Absent a validation error, system admins can do everything"
serializer = JobTemplateSerializer(jt_copy_edit)
serializer.context = self.fake_context(admin_user)
response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit']
def test_org_admin_copy_edit(self, jt_copy_edit, org_admin):
"Organization admins SHOULD be able to copy a JT firmly in their org"
serializer = JobTemplateSerializer(jt_copy_edit)
serializer.context = self.fake_context(org_admin)
response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit']
def test_org_admin_foreign_cred_no_copy_edit(self, jt_copy_edit, org_admin, machine_credential):
"""
Organization admins without access to the 3 related resources:
SHOULD NOT be able to copy JT
SHOULD be able to edit that job template, for nonsensitive changes
"""
# Attach credential to JT that org admin can not use
jt_copy_edit.credential = machine_credential
jt_copy_edit.save()
serializer = JobTemplateSerializer(jt_copy_edit)
serializer.context = self.fake_context(org_admin)
response = serializer.to_representation(jt_copy_edit)
assert not response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit']
def test_jt_admin_copy_edit(self, jt_copy_edit, rando):
"""
JT admins wihout access to associated resources SHOULD NOT be able to copy
SHOULD be able to make nonsensitive changes"""
# random user given JT admin access only
jt_copy_edit.admin_role.members.add(rando)
jt_copy_edit.save()
serializer = JobTemplateSerializer(jt_copy_edit)
serializer.context = self.fake_context(rando)
response = serializer.to_representation(jt_copy_edit)
assert not response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit']
def test_proj_jt_admin_copy_edit(self, jt_copy_edit, rando):
"JT admins with access to associated resources SHOULD be able to copy"
# random user given JT and project admin abilities
jt_copy_edit.admin_role.members.add(rando)
jt_copy_edit.save()
jt_copy_edit.project.admin_role.members.add(rando)
jt_copy_edit.project.save()
serializer = JobTemplateSerializer(jt_copy_edit)
serializer.context = self.fake_context(rando)
response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit']
@pytest.fixture
def mock_access_method(mocker):
mock_method = mocker.MagicMock()
mock_method.return_value = 'foobar'
mock_method.__name__ = 'bars' # Required for a logging statement
return mock_method
@pytest.mark.django_db
class TestAccessListCapabilities:
"""
Test that the access_list serializer shows the exact output of the RoleAccess.can_attach
- looks at /api/v1/inventories/N/access_list/
- test for types: direct, indirect, and team access
"""
extra_kwargs = dict(skip_sub_obj_read_check=False, data={})
def _assert_one_in_list(self, data, sublist='direct_access'):
"Establish that exactly 1 type of access exists so we know the entry is the right one"
assert len(data['results']) == 1
assert len(data['results'][0]['summary_fields'][sublist]) == 1
def test_access_list_direct_access_capability(
self, inventory, rando, get, mocker, mock_access_method):
inventory.admin_role.members.add(rando)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:inventory_access_list', args=(inventory.id,)), rando)
mock_access_method.assert_called_once_with(inventory.admin_role, rando, 'members', **self.extra_kwargs)
self._assert_one_in_list(response.data)
direct_access_list = response.data['results'][0]['summary_fields']['direct_access']
assert direct_access_list[0]['role']['user_capabilities']['unattach'] == 'foobar'
def test_access_list_indirect_access_capability(
self, inventory, organization, org_admin, get, mocker, mock_access_method):
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:inventory_access_list', args=(inventory.id,)), org_admin)
mock_access_method.assert_called_once_with(organization.admin_role, org_admin, 'members', **self.extra_kwargs)
self._assert_one_in_list(response.data, sublist='indirect_access')
indirect_access_list = response.data['results'][0]['summary_fields']['indirect_access']
assert indirect_access_list[0]['role']['user_capabilities']['unattach'] == 'foobar'
def test_access_list_team_direct_access_capability(
self, inventory, team, team_member, get, mocker, mock_access_method):
team.member_role.children.add(inventory.admin_role)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:inventory_access_list', args=(inventory.id,)), team_member)
mock_access_method.assert_called_once_with(inventory.admin_role, team.member_role, 'parents', **self.extra_kwargs)
self._assert_one_in_list(response.data)
direct_access_list = response.data['results'][0]['summary_fields']['direct_access']
assert direct_access_list[0]['role']['user_capabilities']['unattach'] == 'foobar'
@pytest.mark.django_db
def test_team_roles_unattach(mocker, team, team_member, inventory, mock_access_method, get):
team.member_role.children.add(inventory.admin_role)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:team_roles_list', args=(team.id,)), team_member)
# Did we assess whether team_member can remove team's permission to the inventory?
mock_access_method.assert_called_once_with(
inventory.admin_role, team.member_role, 'parents', skip_sub_obj_read_check=True, data={})
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
@pytest.mark.django_db
def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_method, get):
# Add to same organization so that alice and bob can see each other
organization.member_role.members.add(alice)
organization.member_role.members.add(bob)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:user_roles_list', args=(alice.id,)), bob)
# Did we assess whether bob can remove alice's permission to the inventory?
mock_access_method.assert_called_once_with(
organization.member_role, alice, 'members', skip_sub_obj_read_check=True, data={})
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
@pytest.mark.django_db
def test_team_roles_unattach_functional(team, team_member, inventory, get):
team.member_role.children.add(inventory.admin_role)
response = get(reverse('api:team_roles_list', args=(team.id,)), team_member)
# Team member should be able to remove access to inventory, becauase
# the inventory admin_role grants that ability
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach']
@pytest.mark.django_db
def test_user_roles_unattach_functional(organization, alice, bob, get):
organization.member_role.members.add(alice)
organization.member_role.members.add(bob)
response = get(reverse('api:user_roles_list', args=(alice.id,)), bob)
# Org members can not revoke the membership of other members
assert not response.data['results'][0]['summary_fields']['user_capabilities']['unattach']
@pytest.mark.django_db
def test_prefetch_jt_capabilities(job_template, rando):
job_template.execute_role.members.add(rando)
qs = JobTemplate.objects.all()
cache_list_capabilities(qs, ['admin', 'execute'], JobTemplate, rando)
assert qs[0].capabilities_cache == {'edit': False, 'start': True}
@pytest.mark.django_db
def test_prefetch_group_capabilities(group, rando):
group.inventory.adhoc_role.members.add(rando)
qs = Group.objects.all()
cache_list_capabilities(qs, ['inventory.admin', 'inventory.adhoc'], Group, rando)
assert qs[0].capabilities_cache == {'edit': False, 'adhoc': True}
@pytest.mark.django_db
def test_prefetch_jt_copy_capability(job_template, project, inventory, machine_credential, rando):
job_template.project = project
job_template.inventory = inventory
job_template.credential = machine_credential
job_template.save()
qs = JobTemplate.objects.all()
cache_list_capabilities(qs, [{'copy': [
'project.use', 'inventory.use', 'credential.use',
'cloud_credential.use', 'network_credential.use'
]}], JobTemplate, rando)
assert qs[0].capabilities_cache == {'copy': False}
project.use_role.members.add(rando)
inventory.use_role.members.add(rando)
machine_credential.use_role.members.add(rando)
cache_list_capabilities(qs, [{'copy': [
'project.use', 'inventory.use', 'credential.use',
'cloud_credential.use', 'network_credential.use'
]}], JobTemplate, rando)
assert qs[0].capabilities_cache == {'copy': True}
@pytest.mark.django_db
def test_group_update_capabilities_possible(group, inventory_source, admin_user):
group.inventory_source = inventory_source
group.save()
capabilities = get_user_capabilities(admin_user, group, method_list=['start'])
assert capabilities['start']
@pytest.mark.django_db
def test_group_update_capabilities_impossible(group, inventory_source, admin_user):
inventory_source.source = ""
inventory_source.save()
group.inventory_source = inventory_source
group.save()
capabilities = get_user_capabilities(admin_user, group, method_list=['start'])
assert not capabilities['start']

View File

@@ -6,9 +6,13 @@ import mock
from awx.api.serializers import ( from awx.api.serializers import (
JobTemplateSerializer, JobTemplateSerializer,
) )
from awx.api.views import JobTemplateDetail
from awx.main.models import ( from awx.main.models import (
Role,
User,
Job, Job,
) )
from rest_framework.test import APIRequestFactory
#DRF #DRF
from rest_framework import serializers from rest_framework import serializers
@@ -77,21 +81,33 @@ class TestJobTemplateSerializerGetSummaryFields():
summary = get_summary_fields_mock_and_run(JobTemplateSerializer, job_template) summary = get_summary_fields_mock_and_run(JobTemplateSerializer, job_template)
assert 'survey' not in summary assert 'survey' not in summary
@pytest.mark.skip(reason="RBAC needs to land") def test_copy_edit_standard(self, mocker, job_template_factory):
def test_can_copy_true(self, mocker, job_template): """Verify that the exact output of the access.py methods
pass are put into the serializer user_capabilities"""
@pytest.mark.skip(reason="RBAC needs to land") jt_obj = job_template_factory('testJT', project='proj1', persisted=False).job_template
def test_can_copy_false(self, mocker, job_template): jt_obj.id = 5
pass jt_obj.admin_role = Role(id=9, role_field='admin_role')
jt_obj.execute_role = Role(id=8, role_field='execute_role')
jt_obj.read_role = Role(id=7, role_field='execute_role')
user = User(username="auser")
serializer = JobTemplateSerializer(job_template)
serializer.show_capabilities = ['copy', 'edit']
serializer._summary_field_labels = lambda self: []
serializer._recent_jobs = lambda self: []
request = APIRequestFactory().get('/api/v1/job_templates/42/')
request.user = user
view = JobTemplateDetail()
view.request = request
serializer.context['view'] = view
@pytest.mark.skip(reason="RBAC needs to land") with mocker.patch("awx.main.models.rbac.Role.get_description", return_value='Can eat pie'):
def test_can_edit_true(self, mocker, job_template): with mocker.patch("awx.main.access.JobTemplateAccess.can_change", return_value='foobar'):
pass with mocker.patch("awx.main.access.JobTemplateAccess.can_add", return_value='foo'):
response = serializer.get_summary_fields(jt_obj)
@pytest.mark.skip(reason="RBAC needs to land") assert response['user_capabilities']['copy'] == 'foo'
def test_can_edit_false(self, mocker, job_template): assert response['user_capabilities']['edit'] == 'foobar'
pass
class TestJobTemplateSerializerValidation(object): class TestJobTemplateSerializerValidation(object):

View File

@@ -50,3 +50,32 @@ def test_job_template_survey_password_redaction(job_template_with_survey_passwor
"""Tests the JobTemplate model's funciton to redact passwords from """Tests the JobTemplate model's funciton to redact passwords from
extra_vars - used when creating a new job""" extra_vars - used when creating a new job"""
assert job_template_with_survey_passwords_unit.survey_password_variables() == ['secret_key', 'SSN'] assert job_template_with_survey_passwords_unit.survey_password_variables() == ['secret_key', 'SSN']
def test_job_template_survey_variable_validation(job_template_factory):
objects = job_template_factory(
'survey_variable_validation',
organization='org1',
inventory='inventory1',
credential='cred1',
persisted=False,
)
obj = objects.job_template
obj.survey_spec = {
"description": "",
"spec": [
{
"required": True,
"min": 0,
"default": "5",
"max": 1024,
"question_description": "",
"choices": "",
"variable": "a",
"question_name": "Whosyourdaddy",
"type": "text"
}
],
"name": ""
}
obj.survey_enabled = True
assert obj.survey_variable_validation({"a": 5}) == ["Value 5 for 'a' expected to be a string."]

View File

@@ -134,3 +134,28 @@ class TestWorkflowAccessMethods:
with mock.patch('awx.main.access.get_object_or_400', mock_get_object): with mock.patch('awx.main.access.get_object_or_400', mock_get_object):
assert access.can_add({'organization': 1}) assert access.can_add({'organization': 1})
@pytest.mark.django_db
def test_user_capabilities_method():
"""Unit test to verify that the user_capabilities method will defer
to the appropriate sub-class methods of the access classes.
Note that normal output is True/False, but a string is returned
in these tests to establish uniqueness.
"""
class FooAccess(BaseAccess):
def can_change(self, obj, data):
return 'bar'
def can_add(self, data):
return 'foobar'
user = User(username='auser')
foo_access = FooAccess(user)
foo = object()
foo_capabilities = foo_access.get_user_capabilities(foo, ['edit', 'copy'])
assert foo_capabilities == {
'edit': 'bar',
'copy': 'foobar'
}

View File

@@ -74,6 +74,7 @@ def test_net_cred_ssh_agent(mocker, get_ssh_version):
mocker.patch.object(run_job, 'should_use_proot', return_value=False) mocker.patch.object(run_job, 'should_use_proot', return_value=False)
mocker.patch.object(run_job, 'run_pexpect', return_value=('successful', 0)) mocker.patch.object(run_job, 'run_pexpect', return_value=('successful', 0))
mocker.patch.object(run_job, 'open_fifo_write', return_value=None) mocker.patch.object(run_job, 'open_fifo_write', return_value=None)
mocker.patch.object(run_job, 'post_run_hook', return_value=None)
run_job.run(mock_job.id) run_job.run(mock_job.id)
assert run_job.update_model.call_count == 3 assert run_job.update_model.call_count == 3

View File

@@ -33,7 +33,7 @@ logger = logging.getLogger('awx.main.utils')
__all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore', 'memoize', __all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore', 'memoize',
'get_ansible_version', 'get_ssh_version', 'get_awx_version', 'update_scm_url', 'get_ansible_version', 'get_ssh_version', 'get_awx_version', 'update_scm_url',
'get_type_for_model', 'get_model_for_type', 'to_python_boolean', 'get_type_for_model', 'get_model_for_type', 'cache_list_capabilities', 'to_python_boolean',
'ignore_inventory_computed_fields', 'ignore_inventory_group_removal', 'ignore_inventory_computed_fields', 'ignore_inventory_group_removal',
'_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided', '_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided',
'get_current_apps', 'set_current_apps'] 'get_current_apps', 'set_current_apps']
@@ -409,6 +409,72 @@ def get_model_for_type(type):
return ct_model return ct_model
def cache_list_capabilities(page, prefetch_list, model, user):
'''
Given a `page` list of objects, the specified roles for the specified user
are save on each object in the list, using 1 query for each role type
Examples:
capabilities_prefetch = ['admin', 'execute']
--> prefetch the admin (edit) and execute (start) permissions for
items in list for current user
capabilities_prefetch = ['inventory.admin']
--> prefetch the related inventory FK permissions for current user,
and put it into the object's cache
capabilities_prefetch = [{'copy': ['inventory.admin', 'project.admin']}]
--> prefetch logical combination of admin permission to inventory AND
project, put into cache dictionary as "copy"
'''
from django.db.models import Q
page_ids = [obj.id for obj in page]
for obj in page:
obj.capabilities_cache = {}
for prefetch_entry in prefetch_list:
display_method = None
if type(prefetch_entry) is dict:
display_method = prefetch_entry.keys()[0]
paths = prefetch_entry[display_method]
else:
paths = prefetch_entry
if type(paths) is not list:
paths = [paths]
# Build the query for accessible_objects according the user & role(s)
qs_obj = None
for role_path in paths:
if '.' in role_path:
res_path = '__'.join(role_path.split('.')[:-1])
role_type = role_path.split('.')[-1]
if qs_obj is None:
qs_obj = model.objects
parent_model = model._meta.get_field(res_path).related_model
kwargs = {'%s__in' % res_path: parent_model.accessible_objects(user, '%s_role' % role_type)}
qs_obj = qs_obj.filter(Q(**kwargs) | Q(**{'%s__isnull' % res_path: True}))
else:
role_type = role_path
qs_obj = model.accessible_objects(user, '%s_role' % role_type)
if display_method is None:
# Role name translation to UI names for methods
display_method = role_type
if role_type == 'admin':
display_method = 'edit'
elif role_type in ['execute', 'update']:
display_method = 'start'
# Union that query with the list of items on page
ids_with_role = set(qs_obj.filter(pk__in=page_ids).values_list('pk', flat=True))
# Save data item-by-item
for obj in page:
obj.capabilities_cache[display_method] = False
if obj.pk in ids_with_role:
obj.capabilities_cache[display_method] = True
def get_system_task_capacity(): def get_system_task_capacity():
''' '''
Measure system memory and use it as a baseline for determining the system's capacity Measure system memory and use it as a baseline for determining the system's capacity

1
awx/ui/.npmrc Normal file
View File

@@ -0,0 +1 @@
progress=false

View File

@@ -1,13 +1,13 @@
<div class="RoleList-tagContainer" <div class="RoleList-tagContainer"
ng-repeat="entry in access_list"> ng-repeat="entry in access_list">
<div class="RoleList-deleteContainer" <div class="RoleList-deleteContainer"
ng-if="entry.explicit" ng-if="entry.explicit && entry.user_capabilities.unattach"
ng-click="deletePermission(permission, entry)"> ng-click="deletePermission(permission, entry)">
<i ng-if="entry.explicit" <i ng-if="entry.explicit && entry.user_capabilities.unattach"
class="fa fa-times RoleList-tagDelete"></i> class="fa fa-times RoleList-tagDelete"></i>
</div> </div>
<div class="RoleList-tag" <div class="RoleList-tag"
ng-class="{'RoleList-tag--deletable': entry.explicit, ng-class="{'RoleList-tag--deletable': entry.explicit && entry.user_capabilities.unattach,
'RoleList-tag--team': entry.team_id}" 'RoleList-tag--team': entry.team_id}"
aw-tool-tip='{{entry.team_name | sanitize}}' aw-tip-placement='bottom'> aw-tool-tip='{{entry.team_name | sanitize}}' aw-tip-placement='bottom'>
<span class="RoleList-name">{{ entry.name }}</span> <span class="RoleList-name">{{ entry.name }}</span>

View File

@@ -839,6 +839,7 @@ var tower = angular.module('Tower', [
// If browser refresh, set the user_is_superuser value // If browser refresh, set the user_is_superuser value
$rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser'); $rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser');
$rootScope.user_is_system_auditor = Authorization.getUserInfo('is_system_auditor'); $rootScope.user_is_system_auditor = Authorization.getUserInfo('is_system_auditor');
// state the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket) // state the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket)
if (!_.contains($location.$$url, '/login')) { if (!_.contains($location.$$url, '/login')) {
ConfigService.getConfig().then(function() { ConfigService.getConfig().then(function() {

View File

@@ -14,9 +14,14 @@
export function CredentialsList($scope, $rootScope, $location, $log, export function CredentialsList($scope, $rootScope, $location, $log,
$stateParams, Rest, Alert, CredentialList, GenerateList, Prompt, SearchInit, $stateParams, Rest, Alert, CredentialList, GenerateList, Prompt, SearchInit,
PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
SelectionInit, GetChoices, Wait, $state, $filter) { SelectionInit, GetChoices, Wait, $state, $filter, rbacUiControlService) {
ClearScope(); ClearScope();
rbacUiControlService.canAdd('credentials')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
Wait('start'); Wait('start');
var list = CredentialList, var list = CredentialList,
@@ -129,7 +134,7 @@ CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'CredentialList', 'generateList', 'Prompt', '$stateParams', 'Rest', 'Alert', 'CredentialList', 'generateList', 'Prompt',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'GetChoices', 'Wait', 'ProcessErrors', 'GetBasePath', 'SelectionInit', 'GetChoices', 'Wait',
'$state', '$filter' '$state', '$filter', 'rbacUiControlService'
]; ];
@@ -138,7 +143,6 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
ReturnToCaller, ClearScope, GenerateList, SearchInit, PaginateInit, ReturnToCaller, ClearScope, GenerateList, SearchInit, PaginateInit,
LookUpInit, OrganizationList, GetBasePath, GetChoices, Empty, KindChange, LookUpInit, OrganizationList, GetBasePath, GetChoices, Empty, KindChange,
OwnerChange, FormSave, $state, CreateSelect2) { OwnerChange, FormSave, $state, CreateSelect2) {
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view
@@ -337,6 +341,7 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
} }
ClearScope(); ClearScope();
var defaultUrl = GetBasePath('credentials'), var defaultUrl = GetBasePath('credentials'),
generator = GenerateForm, generator = GenerateForm,
form = CredentialForm, form = CredentialForm,
@@ -344,10 +349,17 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
master = {}, master = {},
id = $stateParams.credential_id, id = $stateParams.credential_id,
relatedSets = {}; relatedSets = {};
generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset(); generator.reset();
$scope.id = id; $scope.id = id;
$scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.canShareCredential = false; $scope.canShareCredential = false;
if ($rootScope.current_user.is_superuser) { if ($rootScope.current_user.is_superuser) {

View File

@@ -13,7 +13,7 @@
export function JobsListController ($rootScope, $log, $scope, $compile, $stateParams, export function JobsListController ($rootScope, $log, $scope, $compile, $stateParams,
ClearScope, LoadSchedulesScope, ClearScope, LoadSchedulesScope,
LoadJobsScope, AllJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait) { LoadJobsScope, AllJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait, $state) {
ClearScope(); ClearScope();
@@ -61,6 +61,11 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
} }
} }
jobs_scope = $scope.$new(true); jobs_scope = $scope.$new(true);
jobs_scope.viewJob = function (id) {
$state.transitionTo('jobDetail', {id: id});
};
jobs_scope.showJobType = true; jobs_scope.showJobType = true;
LoadJobsScope({ LoadJobsScope({
parent_scope: $scope, parent_scope: $scope,
@@ -153,4 +158,4 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
JobsListController.$inject = ['$rootScope', '$log', '$scope', '$compile', '$stateParams', JobsListController.$inject = ['$rootScope', '$log', '$scope', '$compile', '$stateParams',
'ClearScope', 'LoadSchedulesScope', 'LoadJobsScope', 'ClearScope', 'LoadSchedulesScope', 'LoadJobsScope',
'AllJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait']; 'AllJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', '$state'];

View File

@@ -15,10 +15,16 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, ProjectList, GenerateList, Prompt, SearchInit, Rest, Alert, ProjectList, GenerateList, Prompt, SearchInit,
PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
SelectionInit, ProjectUpdate, Refresh, Wait, GetChoices, Empty, SelectionInit, ProjectUpdate, Refresh, Wait, GetChoices, Empty,
Find, GetProjectIcon, GetProjectToolTip, $filter, $state) { Find, GetProjectIcon, GetProjectToolTip, $filter, $state, rbacUiControlService) {
ClearScope(); ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd('projects')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
Wait('start'); Wait('start');
var list = ProjectList, var list = ProjectList,
@@ -369,7 +375,7 @@ ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'ProjectUpdate', 'ProcessErrors', 'GetBasePath', 'SelectionInit', 'ProjectUpdate',
'Refresh', 'Wait', 'GetChoices', 'Empty', 'Find', 'Refresh', 'Wait', 'GetChoices', 'Empty', 'Find',
'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state' 'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state', 'rbacUiControlService'
]; ];
@@ -379,6 +385,15 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
OrganizationList, CredentialList, GetChoices, DebugForm, Wait, $state, OrganizationList, CredentialList, GetChoices, DebugForm, Wait, $state,
CreateSelect2) { CreateSelect2) {
Rest.setUrl(GetBasePath('projects'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a project.', 'alert-info');
}
});
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view
@@ -559,6 +574,12 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
ClearScope('htmlTemplate'); ClearScope('htmlTemplate');
$scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
// Inject dynamic view // Inject dynamic view
var form = ProjectsForm(), var form = ProjectsForm(),
generator = GenerateForm, generator = GenerateForm,

View File

@@ -14,10 +14,16 @@
export function TeamsList($scope, $rootScope, $location, $log, $stateParams, export function TeamsList($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, TeamList, GenerateList, Prompt, SearchInit, PaginateInit, Rest, Alert, TeamList, GenerateList, Prompt, SearchInit, PaginateInit,
ReturnToCaller, ClearScope, ProcessErrors, SetTeamListeners, GetBasePath, ReturnToCaller, ClearScope, ProcessErrors, SetTeamListeners, GetBasePath,
SelectionInit, Wait, $state, Refresh, $filter) { SelectionInit, Wait, $state, Refresh, $filter, rbacUiControlService) {
ClearScope(); ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd('teams')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
var list = TeamList, var list = TeamList,
defaultUrl = GetBasePath('teams'), defaultUrl = GetBasePath('teams'),
generator = GenerateList, generator = GenerateList,
@@ -126,7 +132,7 @@ TeamsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'TeamList', 'generateList', 'Prompt', '$stateParams', 'Rest', 'Alert', 'TeamList', 'generateList', 'Prompt',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'SetTeamListeners', 'GetBasePath', 'SelectionInit', 'Wait', 'ProcessErrors', 'SetTeamListeners', 'GetBasePath', 'SelectionInit', 'Wait',
'$state', 'Refresh', '$filter' '$state', 'Refresh', '$filter', 'rbacUiControlService'
]; ];
@@ -137,6 +143,15 @@ export function TeamsAdd($scope, $rootScope, $compile, $location, $log,
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//$scope. //$scope.
Rest.setUrl(GetBasePath('teams'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a team.', 'alert-info');
}
});
// Inject dynamic view // Inject dynamic view
var defaultUrl = GetBasePath('teams'), var defaultUrl = GetBasePath('teams'),
form = TeamForm, form = TeamForm,
@@ -208,6 +223,12 @@ export function TeamsEdit($scope, $rootScope, $location,
$scope.team_id = id; $scope.team_id = id;
$scope.$watch('team_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset(); generator.reset();

View File

@@ -34,10 +34,16 @@ function user_type_sync($scope) {
export function UsersList($scope, $rootScope, $location, $log, $stateParams, export function UsersList($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, UserList, GenerateList, Prompt, SearchInit, PaginateInit, Rest, Alert, UserList, GenerateList, Prompt, SearchInit, PaginateInit,
ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit,
Wait, $state, Refresh, $filter) { Wait, $state, Refresh, $filter, rbacUiControlService) {
ClearScope(); ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd('users')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
var list = UserList, var list = UserList,
defaultUrl = GetBasePath('users'), defaultUrl = GetBasePath('users'),
generator = GenerateList, generator = GenerateList,
@@ -136,7 +142,7 @@ UsersList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'UserList', 'generateList', 'Prompt', '$stateParams', 'Rest', 'Alert', 'UserList', 'generateList', 'Prompt',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'Wait', '$state', 'ProcessErrors', 'GetBasePath', 'SelectionInit', 'Wait', '$state',
'Refresh', '$filter' 'Refresh', '$filter', 'rbacUiControlService'
]; ];
@@ -148,6 +154,15 @@ export function UsersAdd($scope, $rootScope, $compile, $location, $log,
ReturnToCaller, ClearScope, GetBasePath, LookUpInit, OrganizationList, ReturnToCaller, ClearScope, GetBasePath, LookUpInit, OrganizationList,
ResetForm, Wait, CreateSelect2, $state) { ResetForm, Wait, CreateSelect2, $state) {
Rest.setUrl(GetBasePath('users'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a user.', 'alert-info');
}
});
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view
@@ -279,6 +294,12 @@ export function UsersEdit($scope, $rootScope, $location,
$scope.user_type = user_type_options[0]; $scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope)); $scope.$watch('user_type', user_type_sync($scope));
$scope.$watch('user_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
var setScopeFields = function(data){ var setScopeFields = function(data){
_(data) _(data)
.pick(function(value, key){ .pick(function(value, key){

View File

@@ -32,13 +32,15 @@ export default
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
autocomplete: false autocomplete: false,
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
organization: { organization: {
addRequired: false, addRequired: false,
@@ -52,7 +54,8 @@ export default
awPopOver: "<p>If no organization is given, the credential can only be used by the user that creates the credential. Organization admins and system administrators can assign an organization so that roles for the credential can be assigned to users and teams in that organization.</p>", awPopOver: "<p>If no organization is given, the credential can only be used by the user that creates the credential. Organization admins and system administrators can assign an organization so that roles for the credential can be assigned to users and teams in that organization.</p>",
dataTitle: 'Organization ', dataTitle: 'Organization ',
dataPlacement: 'bottom', dataPlacement: 'bottom',
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
kind: { kind: {
label: 'Type', label: 'Type',
@@ -83,7 +86,8 @@ export default
dataTitle: 'Type', dataTitle: 'Type',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
hasSubForm: true hasSubForm: true,
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
access_key: { access_key: {
label: 'Access Key', label: 'Access Key',
@@ -96,12 +100,13 @@ export default
autocomplete: false, autocomplete: false,
apiField: 'username', apiField: 'username',
subForm: 'credentialSubForm', subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
secret_key: { secret_key: {
label: 'Secret Key', label: 'Secret Key',
type: 'sensitive', type: 'sensitive',
ngShow: "kind.value == 'aws'", ngShow: "kind.value == 'aws'",
ngDisabled: "secret_key_ask", ngDisabled: "secret_key_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
awRequiredWhen: { awRequiredWhen: {
reqExpression: "aws_required", reqExpression: "aws_required",
init: false init: false
@@ -123,7 +128,8 @@ export default
dataTitle: 'STS Token', dataTitle: 'STS Token',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"host": { "host": {
labelBind: 'hostLabel', labelBind: 'hostLabel',
@@ -139,7 +145,8 @@ export default
reqExpression: 'host_required', reqExpression: 'host_required',
init: false init: false
}, },
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"subscription": { "subscription": {
label: "Subscription ID", label: "Subscription ID",
@@ -156,7 +163,8 @@ export default
dataTitle: 'Subscription ID', dataTitle: 'Subscription ID',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"username": { "username": {
labelBind: 'usernameLabel', labelBind: 'usernameLabel',
@@ -168,7 +176,8 @@ export default
init: false init: false
}, },
autocomplete: false, autocomplete: false,
subForm: "credentialSubForm" subForm: "credentialSubForm",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"email_address": { "email_address": {
labelBind: 'usernameLabel', labelBind: 'usernameLabel',
@@ -183,7 +192,8 @@ export default
dataTitle: 'Email', dataTitle: 'Email',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"api_key": { "api_key": {
label: 'API Key', label: 'API Key',
@@ -196,7 +206,8 @@ export default
autocomplete: false, autocomplete: false,
hasShowInputButton: true, hasShowInputButton: true,
clear: false, clear: false,
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"password": { "password": {
labelBind: 'passwordLabel', labelBind: 'passwordLabel',
@@ -209,13 +220,14 @@ export default
reqExpression: "password_required", reqExpression: "password_required",
init: false init: false
}, },
subForm: "credentialSubForm" subForm: "credentialSubForm",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"ssh_password": { "ssh_password": {
label: 'Password', label: 'Password',
type: 'sensitive', type: 'sensitive',
ngShow: "kind.value == 'ssh'", ngShow: "kind.value == 'ssh'",
ngDisabled: "ssh_password_ask", ngDisabled: "ssh_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
subCheckbox: { subCheckbox: {
@@ -247,7 +259,8 @@ export default
dataTitle: 'Private Key', dataTitle: 'Private Key',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
subForm: "credentialSubForm" subForm: "credentialSubForm",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"ssh_key_unlock": { "ssh_key_unlock": {
label: 'Private Key Passphrase', label: 'Private Key Passphrase',
@@ -255,7 +268,7 @@ export default
ngShow: "kind.value == 'ssh' || kind.value == 'scm'", ngShow: "kind.value == 'ssh' || kind.value == 'scm'",
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
ngDisabled: "keyEntered === false || ssh_key_unlock_ask", ngDisabled: "keyEntered === false || ssh_key_unlock_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
subCheckbox: { subCheckbox: {
variable: 'ssh_key_unlock_ask', variable: 'ssh_key_unlock_ask',
ngShow: "kind.value == 'ssh'", ngShow: "kind.value == 'ssh'",
@@ -278,7 +291,8 @@ export default
"<code>sudo | su | pbrun | pfexec | runas</code> <br>(defaults to <code>sudo</code>)</p>", "<code>sudo | su | pbrun | pfexec | runas</code> <br>(defaults to <code>sudo</code>)</p>",
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"become_username": { "become_username": {
labelBind: 'becomeUsernameLabel', labelBind: 'becomeUsernameLabel',
@@ -287,13 +301,14 @@ export default
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
autocomplete: false, autocomplete: false,
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"become_password": { "become_password": {
labelBind: 'becomePasswordLabel', labelBind: 'becomePasswordLabel',
type: 'sensitive', type: 'sensitive',
ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ", ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ",
ngDisabled: "become_password_ask", ngDisabled: "become_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
subCheckbox: { subCheckbox: {
@@ -309,7 +324,8 @@ export default
type: 'text', type: 'text',
label: 'Client ID', label: 'Client ID',
subForm: 'credentialSubForm', subForm: 'credentialSubForm',
ngShow: "kind.value === 'azure_rm'" ngShow: "kind.value === 'azure_rm'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
secret:{ secret:{
type: 'sensitive', type: 'sensitive',
@@ -317,20 +333,23 @@ export default
autocomplete: false, autocomplete: false,
label: 'Client Secret', label: 'Client Secret',
subForm: 'credentialSubForm', subForm: 'credentialSubForm',
ngShow: "kind.value === 'azure_rm'" ngShow: "kind.value === 'azure_rm'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
tenant: { tenant: {
type: 'text', type: 'text',
label: 'Tenant ID', label: 'Tenant ID',
subForm: 'credentialSubForm', subForm: 'credentialSubForm',
ngShow: "kind.value === 'azure_rm'" ngShow: "kind.value === 'azure_rm'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
authorize: { authorize: {
label: 'Authorize', label: 'Authorize',
type: 'checkbox', type: 'checkbox',
ngChange: "toggleCallback('host_config_key')", ngChange: "toggleCallback('host_config_key')",
subForm: 'credentialSubForm', subForm: 'credentialSubForm',
ngShow: "kind.value === 'net'" ngShow: "kind.value === 'net'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
authorize_password: { authorize_password: {
label: 'Authorize Password', label: 'Authorize Password',
@@ -339,6 +358,7 @@ export default
autocomplete: false, autocomplete: false,
subForm: 'credentialSubForm', subForm: 'credentialSubForm',
ngShow: "authorize && authorize !== 'false'", ngShow: "authorize && authorize !== 'false'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"project": { "project": {
labelBind: 'projectLabel', labelBind: 'projectLabel',
@@ -355,7 +375,8 @@ export default
reqExpression: 'project_required', reqExpression: 'project_required',
init: false init: false
}, },
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"domain": { "domain": {
labelBind: 'domainLabel', labelBind: 'domainLabel',
@@ -371,13 +392,14 @@ export default
dataContainer: "body", dataContainer: "body",
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
subForm: 'credentialSubForm' subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
"vault_password": { "vault_password": {
label: "Vault Password", label: "Vault Password",
type: 'sensitive', type: 'sensitive',
ngShow: "kind.value == 'ssh'", ngShow: "kind.value == 'ssh'",
ngDisabled: "vault_password_ask", ngDisabled: "vault_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
subCheckbox: { subCheckbox: {
@@ -394,11 +416,17 @@ export default
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()', ngClick: 'formCancel()',
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
label: 'Save', label: 'Save',
ngClick: 'formSave()', //$scope.function to call on click, optional ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: true //Disable when $pristine or $invalid, optional ngDisabled: true,
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)' //Disable when $pristine or $invalid, optional
} }
}, },
@@ -421,7 +449,8 @@ export default
label: 'Add', label: 'Add',
awToolTip: 'Add a permission', awToolTip: 'Add a permission',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },

View File

@@ -26,14 +26,16 @@ export default
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
tab: 'properties' tab: 'properties',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
tab: 'properties' tab: 'properties',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
variables: { variables: {
label: 'Variables', label: 'Variables',
@@ -65,7 +67,8 @@ export default
ngChange: 'sourceChange(source)', ngChange: 'sourceChange(source)',
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
ngModel: 'source' ngModel: 'source',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
credential: { credential: {
label: 'Cloud Credential', label: 'Cloud Credential',
@@ -77,7 +80,8 @@ export default
awRequiredWhen: { awRequiredWhen: {
reqExpression: "cloudCredentialRequired", reqExpression: "cloudCredentialRequired",
init: "false" init: "false"
} },
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
source_regions: { source_regions: {
label: 'Regions', label: 'Regions',
@@ -92,7 +96,8 @@ export default
awPopOver: "<p>Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " + awPopOver: "<p>Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " +
"or choose <em>All</em> to include all regions. Tower will only be updated with Hosts associated with the selected regions." + "or choose <em>All</em> to include all regions. Tower will only be updated with Hosts associated with the selected regions." +
"</p>", "</p>",
dataContainer: 'body' dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
instance_filters: { instance_filters: {
label: 'Instance Filters', label: 'Instance Filters',
@@ -112,7 +117,8 @@ export default
"<blockquote>tag:Name=test*</blockquote>\n" + "<blockquote>tag:Name=test*</blockquote>\n" +
"<p>View the <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html\" target=\"_blank\">Describe Instances documentation</a> " + "<p>View the <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html\" target=\"_blank\">Describe Instances documentation</a> " +
"for a complete list of supported filters.</p>", "for a complete list of supported filters.</p>",
dataContainer: 'body' dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
group_by: { group_by: {
label: 'Only Group By', label: 'Only Group By',
@@ -137,7 +143,8 @@ export default
"<li>VPC ID: <strong>vpcs &raquo; vpc-5ca1ab1e</strong></li>" + "<li>VPC ID: <strong>vpcs &raquo; vpc-5ca1ab1e</strong></li>" +
"<li>Tag None: <strong>tags &raquo; tag_none</strong></li>" + "<li>Tag None: <strong>tags &raquo; tag_none</strong></li>" +
"</ul><p>If blank, all groups above are created except <em>Instance ID</em>.</p>", "</ul><p>If blank, all groups above are created except <em>Instance ID</em>.</p>",
dataContainer: 'body' dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
inventory_script: { inventory_script: {
label : "Custom Inventory Script", label : "Custom Inventory Script",
@@ -149,6 +156,7 @@ export default
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
ngRequired: "source && source.value === 'custom'", ngRequired: "source && source.value === 'custom'",
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
}, },
custom_variables: { custom_variables: {
id: 'custom_variables', id: 'custom_variables',
@@ -269,7 +277,8 @@ export default
dataTitle: 'Overwrite', dataTitle: 'Overwrite',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right', dataPlacement: 'right',
labelClass: 'checkbox-options' labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, { }, {
name: 'overwrite_vars', name: 'overwrite_vars',
label: 'Overwrite Variables', label: 'Overwrite Variables',
@@ -283,7 +292,8 @@ export default
dataTitle: 'Overwrite Variables', dataTitle: 'Overwrite Variables',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right', dataPlacement: 'right',
labelClass: 'checkbox-options' labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, { }, {
name: 'update_on_launch', name: 'update_on_launch',
label: 'Update on Launch', label: 'Update on Launch',
@@ -296,7 +306,8 @@ export default
dataTitle: 'Update on Launch', dataTitle: 'Update on Launch',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right', dataPlacement: 'right',
labelClass: 'checkbox-options' labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}] }]
}, },
update_cache_timeout: { update_cache_timeout: {
@@ -321,11 +332,17 @@ export default
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()' ngClick: 'formCancel()',
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', ngClick: 'formSave()',
ngDisabled: true ngDisabled: true,
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },

View File

@@ -46,13 +46,15 @@ export default
"</blockquote>", "</blockquote>",
dataTitle: 'Host Name', dataTitle: 'Host Name',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: 'body' dataContainer: 'body',
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)'
}, },
variables: { variables: {
label: 'Variables', label: 'Variables',
@@ -83,10 +85,16 @@ export default
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()', ngClick: 'formCancel()',
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', ngClick: 'formSave()',
ngDisabled: true ngDisabled: true,
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },

View File

@@ -26,14 +26,16 @@ export default
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: false capitalize: false,
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
inventory_description: { inventory_description: {
realName: 'description', realName: 'description',
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
organization: { organization: {
label: 'Organization', label: 'Organization',
@@ -44,7 +46,8 @@ export default
awRequiredWhen: { awRequiredWhen: {
reqExpression: "organizationrequired", reqExpression: "organizationrequired",
init: "true" init: "true"
} },
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
variables: { variables: {
label: 'Variables', label: 'Variables',
@@ -63,17 +66,24 @@ export default
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>', '<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataTitle: 'Inventory Variables', dataTitle: 'Inventory Variables',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: 'body' dataContainer: 'body',
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
} }
}, },
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()' ngClick: 'formCancel()',
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngHide: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', ngClick: 'formSave()',
ngDisabled: true ngDisabled: true,
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
@@ -94,7 +104,8 @@ export default
label: 'Add', label: 'Add',
awToolTip: 'Add a permission', awToolTip: 'Add a permission',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },

View File

@@ -27,14 +27,16 @@ export default
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
column: 1 column: 1,
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
column: 1 column: 1,
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
job_type: { job_type: {
label: 'Job Type', label: 'Job Type',
@@ -56,7 +58,8 @@ export default
variable: 'ask_job_type_on_launch', variable: 'ask_job_type_on_launch',
ngShow: "!job_type.value || job_type.value !== 'scan'", ngShow: "!job_type.value || job_type.value !== 'scan'",
text: 'Prompt on launch' text: 'Prompt on launch'
} },
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
inventory: { inventory: {
label: 'Inventory', label: 'Inventory',
@@ -78,7 +81,8 @@ export default
variable: 'ask_inventory_on_launch', variable: 'ask_inventory_on_launch',
ngShow: "!job_type.value || job_type.value !== 'scan'", ngShow: "!job_type.value || job_type.value !== 'scan'",
text: 'Prompt on launch' text: 'Prompt on launch'
} },
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
project: { project: {
label: 'Project', label: 'Project',
@@ -100,12 +104,13 @@ export default
dataTitle: 'Project', dataTitle: 'Project',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
playbook: { playbook: {
label: 'Playbook', label: 'Playbook',
type:'select', type:'select',
ngOptions: 'book for book in playbook_options track by book', ngOptions: 'book for book in playbook_options track by book',
ngDisabled: "job_type.value === 'scan' && project_name === 'Default'", ngDisabled: "(job_type.value === 'scan' && project_name === 'Default') || !(job_template_obj.summary_fields.user_capabilities.edit || canAdd)",
id: 'playbook-select', id: 'playbook-select',
awRequiredWhen: { awRequiredWhen: {
reqExpression: "playbookrequired", reqExpression: "playbookrequired",
@@ -138,7 +143,8 @@ export default
subCheckbox: { subCheckbox: {
variable: 'ask_credential_on_launch', variable: 'ask_credential_on_launch',
text: 'Prompt on launch' text: 'Prompt on launch'
} },
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
cloud_credential: { cloud_credential: {
label: 'Cloud Credential', label: 'Cloud Credential',
@@ -153,7 +159,8 @@ export default
"running playbook, allowing provisioning into the cloud without manually passing parameters to the included modules.</p>", "running playbook, allowing provisioning into the cloud without manually passing parameters to the included modules.</p>",
dataTitle: 'Cloud Credential', dataTitle: 'Cloud Credential',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
network_credential: { network_credential: {
label: 'Network Credential', label: 'Network Credential',
@@ -167,7 +174,8 @@ export default
awPopOver: "<p>Network credentials are used by Ansible networking modules to connect to and manage networking devices.</p>", awPopOver: "<p>Network credentials are used by Ansible networking modules to connect to and manage networking devices.</p>",
dataTitle: 'Network Credential', dataTitle: 'Network Credential',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
forks: { forks: {
label: 'Forks', label: 'Forks',
@@ -186,7 +194,8 @@ export default
' target=\"_blank\">ansible configuration file</a>.</p>', ' target=\"_blank\">ansible configuration file</a>.</p>',
dataTitle: 'Forks', dataTitle: 'Forks',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
}, },
limit: { limit: {
label: 'Limit', label: 'Limit',
@@ -203,7 +212,8 @@ export default
subCheckbox: { subCheckbox: {
variable: 'ask_limit_on_launch', variable: 'ask_limit_on_launch',
text: 'Prompt on launch' text: 'Prompt on launch'
} },
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
verbosity: { verbosity: {
label: 'Verbosity', label: 'Verbosity',
@@ -216,7 +226,8 @@ export default
awPopOver: "<p>Control the level of output ansible will produce as the playbook executes.</p>", awPopOver: "<p>Control the level of output ansible will produce as the playbook executes.</p>",
dataTitle: 'Verbosity', dataTitle: 'Verbosity',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
job_tags: { job_tags: {
label: 'Job Tags', label: 'Job Tags',
@@ -235,7 +246,8 @@ export default
subCheckbox: { subCheckbox: {
variable: 'ask_tags_on_launch', variable: 'ask_tags_on_launch',
text: 'Prompt on launch' text: 'Prompt on launch'
} },
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
skip_tags: { skip_tags: {
label: 'Skip Tags', label: 'Skip Tags',
@@ -254,7 +266,8 @@ export default
subCheckbox: { subCheckbox: {
variable: 'ask_skip_tags_on_launch', variable: 'ask_skip_tags_on_launch',
text: 'Prompt on launch' text: 'Prompt on launch'
} },
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
checkbox_group: { checkbox_group: {
label: 'Options', label: 'Options',
@@ -270,7 +283,8 @@ export default
dataPlacement: 'right', dataPlacement: 'right',
dataTitle: 'Become Privilege Escalation', dataTitle: 'Become Privilege Escalation',
dataContainer: "body", dataContainer: "body",
labelClass: 'stack-inline' labelClass: 'stack-inline',
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, { }, {
name: 'allow_callbacks', name: 'allow_callbacks',
label: 'Allow Provisioning Callbacks', label: 'Allow Provisioning Callbacks',
@@ -284,7 +298,8 @@ export default
dataPlacement: 'right', dataPlacement: 'right',
dataTitle: 'Allow Provisioning Callbacks', dataTitle: 'Allow Provisioning Callbacks',
dataContainer: "body", dataContainer: "body",
labelClass: 'stack-inline' labelClass: 'stack-inline',
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}] }]
}, },
callback_url: { callback_url: {
@@ -299,7 +314,8 @@ export default
awPopOverWatch: "callback_help", awPopOverWatch: "callback_help",
dataPlacement: 'top', dataPlacement: 'top',
dataTitle: 'Provisioning Callback URL', dataTitle: 'Provisioning Callback URL',
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
host_config_key: { host_config_key: {
label: 'Host Config Key', label: 'Host Config Key',
@@ -312,7 +328,8 @@ export default
awPopOverWatch: "callback_help", awPopOverWatch: "callback_help",
dataPlacement: 'right', dataPlacement: 'right',
dataTitle: "Host Config Key", dataTitle: "Host Config Key",
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
labels: { labels: {
label: 'Labels', label: 'Labels',
@@ -325,7 +342,8 @@ export default
dataTitle: 'Labels', dataTitle: 'Labels',
dataPlacement: 'right', dataPlacement: 'right',
awPopOver: "<p>Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs in the Tower display.</p>", awPopOver: "<p>Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs in the Tower display.</p>",
dataContainer: 'body' dataContainer: 'body',
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
variables: { variables: {
label: 'Extra Variables', label: 'Extra Variables',
@@ -348,14 +366,15 @@ export default
subCheckbox: { subCheckbox: {
variable: 'ask_variables_on_launch', variable: 'ask_variables_on_launch',
text: 'Prompt on launch' text: 'Prompt on launch'
} },
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
} }
}, },
buttons: { //for now always generates <button> tags buttons: { //for now always generates <button> tags
add_survey: { add_survey: {
ngClick: 'addSurvey()', ngClick: 'addSurvey()',
ngShow: 'job_type.value !== "scan" && !survey_exists', ngShow: 'job_type.value !== "scan" && !survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || canAdd)',
awFeature: 'surveys', awFeature: 'surveys',
awToolTip: 'Surveys allow users to be prompted at job launch with a series of questions related to the job. This allows for variables to be defined that affect the playbook run at time of launch.', awToolTip: 'Surveys allow users to be prompted at job launch with a series of questions related to the job. This allows for variables to be defined that affect the playbook run at time of launch.',
dataPlacement: 'top' dataPlacement: 'top'
@@ -363,14 +382,25 @@ export default
edit_survey: { edit_survey: {
ngClick: 'editSurvey()', ngClick: 'editSurvey()',
awFeature: 'surveys', awFeature: 'surveys',
ngShow: 'job_type.value !== "scan" && survey_exists' ngShow: 'job_type.value !== "scan" && survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
},
view_survey: {
ngClick: 'editSurvey()',
awFeature: 'surveys',
ngShow: 'job_type.value !== "scan" && survey_exists && !(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
cancel: { cancel: {
ngClick: 'formCancel()' ngClick: 'formCancel()',
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', //$scope.function to call on click, optional ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: "job_templates_form.$invalid || can_edit!==true"//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons ngDisabled: "job_templates_form.$invalid",//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
@@ -394,7 +424,8 @@ export default
label: 'Add', label: 'Add',
awToolTip: 'Add a permission', awToolTip: 'Add a permission',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },

View File

@@ -25,23 +25,31 @@ export default
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: false capitalize: false,
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
buttons: { //for now always generates <button> tags buttons: { //for now always generates <button> tags
cancel: { cancel: {
ngClick: 'formCancel()' ngClick: 'formCancel()',
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', //$scope.function to call on click, optional ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: true //Disable when $pristine or $invalid, optional ngDisabled: true,
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
@@ -62,7 +70,8 @@ export default
label: 'Add', label: 'Add',
awToolTip: 'Add a permission', awToolTip: 'Add a permission',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },

View File

@@ -30,13 +30,15 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: false capitalize: false,
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
organization: { organization: {
label: 'Organization', label: 'Organization',
@@ -50,7 +52,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
}, },
dataTitle: 'Organization', dataTitle: 'Organization',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right' dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
scm_type: { scm_type: {
label: 'SCM Type', label: 'SCM Type',
@@ -61,6 +64,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
hasSubForm: true, hasSubForm: true,
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
missing_path_alert: { missing_path_alert: {
type: 'alertblock', type: 'alertblock',
@@ -82,7 +86,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
'<p>Use PROJECTS_ROOT in your environment settings file to determine the base path value.</p>', '<p>Use PROJECTS_ROOT in your environment settings file to determine the base path value.</p>',
dataTitle: 'Project Base Path', dataTitle: 'Project Base Path',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right' dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
local_path: { local_path: {
label: 'Playbook Directory', label: 'Playbook Directory',
@@ -99,7 +104,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
'<p>Use PROJECTS_ROOT in your environment settings file to determine the base path value.</p>', '<p>Use PROJECTS_ROOT in your environment settings file to determine the base path value.</p>',
dataTitle: 'Project Path', dataTitle: 'Project Path',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right' dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
scm_url: { scm_url: {
label: 'SCM URL', label: 'SCM URL',
@@ -115,7 +121,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
awPopOver: "set in controllers/projects", awPopOver: "set in controllers/projects",
dataTitle: 'SCM URL', dataTitle: 'SCM URL',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right' dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
scm_branch: { scm_branch: {
labelBind: "scmBranchLabel", labelBind: "scmBranchLabel",
@@ -123,7 +130,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
ngShow: "scm_type && scm_type.value !== 'manual'", ngShow: "scm_type && scm_type.value !== 'manual'",
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
subForm: 'sourceSubForm' subForm: 'sourceSubForm',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
credential: { credential: {
label: 'SCM Credential', label: 'SCM Credential',
@@ -134,7 +142,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
ngClick: 'lookUpCredential()', ngClick: 'lookUpCredential()',
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
subForm: 'sourceSubForm' subForm: 'sourceSubForm',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
checkbox_group: { checkbox_group: {
label: 'SCM Update Options', label: 'SCM Update Options',
@@ -151,7 +160,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: 'SCM Clean', dataTitle: 'SCM Clean',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right', dataPlacement: 'right',
labelClass: 'checkbox-options stack-inline' labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, { }, {
name: 'scm_delete_on_update', name: 'scm_delete_on_update',
label: 'Delete on Update', label: 'Delete on Update',
@@ -163,7 +173,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: 'SCM Delete', dataTitle: 'SCM Delete',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right', dataPlacement: 'right',
labelClass: 'checkbox-options stack-inline' labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, { }, {
name: 'scm_update_on_launch', name: 'scm_update_on_launch',
label: 'Update on Launch', label: 'Update on Launch',
@@ -174,7 +185,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: 'SCM Update', dataTitle: 'SCM Update',
dataContainer: 'body', dataContainer: 'body',
dataPlacement: 'right', dataPlacement: 'right',
labelClass: 'checkbox-options stack-inline' labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}] }]
}, },
scm_update_cache_timeout: { scm_update_cache_timeout: {
@@ -193,17 +205,24 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
'and a new project update will be performed.</p>', 'and a new project update will be performed.</p>',
dataTitle: 'Cache Timeout', dataTitle: 'Cache Timeout',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body" dataContainer: "body",
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
} }
}, },
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()' ngClick: 'formCancel()',
ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', ngClick: 'formSave()',
ngDisabled: true ngDisabled: true,
ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
@@ -224,7 +243,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
label: 'Add', label: 'Add',
awToolTip: 'Add a permission', awToolTip: 'Add a permission',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },

View File

@@ -25,13 +25,15 @@ export default
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: false capitalize: false,
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
organization: { organization: {
label: 'Organization', label: 'Organization',
@@ -44,17 +46,24 @@ export default
awRequiredWhen: { awRequiredWhen: {
reqExpression: "orgrequired", reqExpression: "orgrequired",
init: true init: true
} },
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()' ngClick: 'formCancel()',
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', ngClick: 'formSave()',
ngDisabled: true ngDisabled: true,
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
@@ -75,7 +84,8 @@ export default
label: 'Add', label: 'Add',
awToolTip: 'Add user to team', awToolTip: 'Add user to team',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
@@ -132,7 +142,8 @@ export default
'class': "List-actionButton--delete", 'class': "List-actionButton--delete",
iconClass: 'fa fa-times', iconClass: 'fa fa-times',
awToolTip: 'Dissasociate permission from team', awToolTip: 'Dissasociate permission from team',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'permission.summary_fields.user_capabilities.unattach'
} }
}, },
hideOnSuperuser: true hideOnSuperuser: true

View File

@@ -26,21 +26,24 @@ export default
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: true capitalize: true,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
last_name: { last_name: {
label: 'Last Name', label: 'Last Name',
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: true capitalize: true,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
email: { email: {
label: 'Email', label: 'Email',
type: 'email', type: 'email',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
autocomplete: false autocomplete: false,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
username: { username: {
label: 'Username', label: 'Username',
@@ -49,7 +52,8 @@ export default
reqExpression: "not_ldap_user && external_account === null", reqExpression: "not_ldap_user && external_account === null",
init: true init: true
}, },
autocomplete: false autocomplete: false,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
organization: { organization: {
label: 'Organization', label: 'Organization',
@@ -63,7 +67,8 @@ export default
awRequiredWhen: { awRequiredWhen: {
reqExpression: "orgrequired", reqExpression: "orgrequired",
init: true init: true
} },
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
password: { password: {
label: 'Password', label: 'Password',
@@ -74,7 +79,8 @@ export default
editRequired: false, editRequired: false,
ngChange: "clearPWConfirm('password_confirm')", ngChange: "clearPWConfirm('password_confirm')",
autocomplete: false, autocomplete: false,
chkPass: true chkPass: true,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
password_confirm: { password_confirm: {
label: 'Confirm Password', label: 'Confirm Password',
@@ -85,7 +91,8 @@ export default
editRequired: false, editRequired: false,
awPassMatch: true, awPassMatch: true,
associated: 'password', associated: 'password',
autocomplete: false autocomplete: false,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
user_type: { user_type: {
label: 'User Type', label: 'User Type',
@@ -94,16 +101,23 @@ export default
disableChooseOption: true, disableChooseOption: true,
ngModel: 'user_type', ngModel: 'user_type',
ngShow: 'current_user["is_superuser"]', ngShow: 'current_user["is_superuser"]',
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
}, },
buttons: { buttons: {
cancel: { cancel: {
ngClick: 'formCancel()' ngClick: 'formCancel()',
ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', ngClick: 'formSave()',
ngDisabled: true ngDisabled: true,
ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
}, },
@@ -186,7 +200,8 @@ export default
label: 'Remove', label: 'Remove',
ngClick: 'deletePermissionFromUser(user_id, username, permission.name, permission.summary_fields.resource_name, permission.related.users)', ngClick: 'deletePermissionFromUser(user_id, username, permission.name, permission.summary_fields.resource_name, permission.related.users)',
iconClass: 'fa fa-times', iconClass: 'fa fa-times',
awToolTip: 'Dissasociate permission from user' awToolTip: 'Dissasociate permission from user',
ngShow: 'permission.summary_fields.user_capabilities.unattach'
} }
}, },
hideOnSuperuser: true hideOnSuperuser: true

View File

@@ -46,7 +46,7 @@ export default
function createField(onChange, onReady, fld) { function createField(onChange, onReady, fld) {
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml) //hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
scope[fld + 'codeMirror'] = AngularCodeMirror(); scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly);
scope[fld + 'codeMirror'].addModes(global.$AnsibleConfig.variable_edit_modes); scope[fld + 'codeMirror'].addModes(global.$AnsibleConfig.variable_edit_modes);
scope[fld + 'codeMirror'].showTextArea({ scope[fld + 'codeMirror'].showTextArea({
scope: scope, scope: scope,

View File

@@ -3,7 +3,7 @@
* *
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
/** /**
* @ngdoc function * @ngdoc function
* @name helpers.function:ProjectPath * @name helpers.function:ProjectPath
@@ -47,11 +47,13 @@ export default
Rest.get() Rest.get()
.success(function (data) { .success(function (data) {
var opts = [], i; var opts = [], i;
for (i = 0; i < data.project_local_paths.length; i++) { if (data.project_local_paths) {
opts.push({ for (i = 0; i < data.project_local_paths.length; i++) {
label: data.project_local_paths[i], opts.push({
value: data.project_local_paths[i] label: data.project_local_paths[i],
}); value: data.project_local_paths[i]
});
}
} }
if (scope.local_path) { if (scope.local_path) {
// List only includes paths not assigned to projects, so add the // List only includes paths not assigned to projects, so add the

View File

@@ -16,6 +16,15 @@ function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
$state) { $state) {
Rest.setUrl(GetBasePath('inventory'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add an inventory.', 'alert-info');
}
});
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view

View File

@@ -17,7 +17,6 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
ParseVariableString, RelatedSearchInit, RelatedPaginateInit, ParseVariableString, RelatedSearchInit, RelatedPaginateInit,
Prompt, InitiatePlaybookRun, CreateDialog, deleteJobTemplate, $state, Prompt, InitiatePlaybookRun, CreateDialog, deleteJobTemplate, $state,
$filter) { $filter) {
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view
@@ -32,6 +31,13 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
form.formLabelSize = null; form.formLabelSize = null;
form.formFieldSize = null; form.formFieldSize = null;
$scope.inventory_id = inventory_id; $scope.inventory_id = inventory_id;
$scope.$watch('invnentory_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset(); generator.reset();

View File

@@ -14,7 +14,14 @@ function InventoriesList($scope, $rootScope, $location, $log,
$stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList,
generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller,
ClearScope, ProcessErrors, GetBasePath, Wait, ClearScope, ProcessErrors, GetBasePath, Wait,
Find, Empty, $state) { Find, Empty, $state, rbacUiControlService) {
$scope.canAdd = false;
rbacUiControlService.canAdd('inventory')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
var list = InventoryList, var list = InventoryList,
defaultUrl = GetBasePath('inventory') + ($stateParams.status === 'sync-failed' ? '?not__inventory_sources_with_failures=0' : ''), defaultUrl = GetBasePath('inventory') + ($stateParams.status === 'sync-failed' ? '?not__inventory_sources_with_failures=0' : ''),
@@ -376,4 +383,4 @@ function InventoriesList($scope, $rootScope, $location, $log,
export default ['$scope', '$rootScope', '$location', '$log', export default ['$scope', '$rootScope', '$location', '$log',
'$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList',
'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', InventoriesList]; 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'rbacUiControlService', InventoriesList];

View File

@@ -6,9 +6,9 @@
export default export default
['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList', 'inventoryScriptsListObject', 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'LookUpInit', ['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList', 'inventoryScriptsListObject', 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'LookUpInit',
'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'rbacUiControlService',
function($state, $stateParams, $scope, GroupForm, CredentialList, InventoryScriptsList, ParseTypeChange, GenerateForm, inventoryData, LookUpInit, function($state, $stateParams, $scope, GroupForm, CredentialList, InventoryScriptsList, ParseTypeChange, GenerateForm, inventoryData, LookUpInit,
GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions){ GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, rbacUiControlService){
var generator = GenerateForm, var generator = GenerateForm,
form = GroupForm(); form = GroupForm();
@@ -16,6 +16,11 @@
CredentialList = _.cloneDeep(CredentialList); CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true; CredentialList.fields.kind.noSearch = true;
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups")
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
$scope.formCancel = function(){ $scope.formCancel = function(){
$state.go('^'); $state.go('^');
}; };

View File

@@ -18,6 +18,12 @@
CredentialList = _.cloneDeep(CredentialList); CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true; CredentialList.fields.kind.noSearch = true;
$scope.$watch('group_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.formCancel = function(){ $scope.formCancel = function(){
$state.go('^'); $state.go('^');
}; };
@@ -256,6 +262,7 @@
$scope.credential_name = inventorySourceData.summary_fields.credential.name; $scope.credential_name = inventorySourceData.summary_fields.credential.name;
} }
$scope = angular.extend($scope, groupData); $scope = angular.extend($scope, groupData);
$scope.group_obj = groupData;
// instantiate lookup fields // instantiate lookup fields
if (inventorySourceData.source !== 'custom'){ if (inventorySourceData.source !== 'custom'){

View File

@@ -5,13 +5,22 @@
*************************************************/ *************************************************/
export default export default
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroups', 'generateList', 'InventoryUpdate', 'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus', ['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroups', 'generateList', 'InventoryUpdate', 'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus',
'InventoryManageService', 'groupsUrl', 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', 'InventoryManageService', 'groupsUrl', 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Rest', 'GetBasePath', 'rbacUiControlService',
function($scope, $rootScope, $state, $stateParams, InventoryGroups, generateList, InventoryUpdate, GroupManageService, GroupsCancelUpdate, ViewUpdateStatus, function($scope, $rootScope, $state, $stateParams, InventoryGroups, generateList, InventoryUpdate, GroupManageService, GroupsCancelUpdate, ViewUpdateStatus,
InventoryManageService, groupsUrl, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg){ InventoryManageService, groupsUrl, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg, Rest, GetBasePath, rbacUiControlService){
var list = InventoryGroups, var list = InventoryGroups,
view = generateList, view = generateList,
pageSize = 20; pageSize = 20;
$scope.inventory_id = $stateParams.inventory_id; $scope.inventory_id = $stateParams.inventory_id;
$scope.canAdd = false;
rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/groups")
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// The ncy breadcrumb directive will look at this attribute when attempting to bind to the correct scope. // The ncy breadcrumb directive will look at this attribute when attempting to bind to the correct scope.
// In this case, we don't want to incidentally bind to this scope when editing a host or a group. See: // In this case, we don't want to incidentally bind to this scope when editing a host or a group. See:
// https://github.com/ncuillery/angular-breadcrumb/issues/42 for a little more information on the // https://github.com/ncuillery/angular-breadcrumb/issues/42 for a little more information on the

View File

@@ -5,10 +5,18 @@
*************************************************/ *************************************************/
export default export default
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', ['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'rbacUiControlService', 'GetBasePath',
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService){ function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, rbacUiControlService, GetBasePath){
var generator = GenerateForm, var generator = GenerateForm,
form = HostForm; form = HostForm;
$scope.canAdd = false;
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/hosts")
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
$scope.parseType = 'yaml'; $scope.parseType = 'yaml';
$scope.formCancel = function(){ $scope.formCancel = function(){
$state.go('^'); $state.go('^');

View File

@@ -9,6 +9,13 @@
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host){ function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host){
var generator = GenerateForm, var generator = GenerateForm,
form = HostForm; form = HostForm;
$scope.$watch('host.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.parseType = 'yaml'; $scope.parseType = 'yaml';
$scope.formCancel = function(){ $scope.formCancel = function(){
$state.go('^'); $state.go('^');

View File

@@ -5,12 +5,23 @@
*************************************************/ *************************************************/
export default export default
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryHosts', 'generateList', 'InventoryManageService', 'HostManageService', ['$scope', '$rootScope', '$state', '$stateParams', 'InventoryHosts', 'generateList', 'InventoryManageService', 'HostManageService',
'hostsUrl', 'SearchInit', 'PaginateInit', 'SetStatus', 'Prompt', 'Wait', 'inventoryData', '$filter', 'hostsUrl', 'SearchInit', 'PaginateInit', 'SetStatus', 'Prompt', 'Wait', 'inventoryData', '$filter', 'Rest', 'GetBasePath', 'rbacUiControlService',
function($scope, $rootScope, $state, $stateParams, InventoryHosts, generateList, InventoryManageService, HostManageService, function($scope, $rootScope, $state, $stateParams, InventoryHosts, generateList, InventoryManageService, HostManageService,
hostsUrl, SearchInit, PaginateInit, SetStatus, Prompt, Wait, inventoryData, $filter){ hostsUrl, SearchInit, PaginateInit, SetStatus, Prompt, Wait, inventoryData, $filter, Rest, GetBasePath, rbacUiControlService){
var list = InventoryHosts, var list = InventoryHosts,
view = generateList, view = generateList,
pageSize = 20; pageSize = 20;
$scope.inventory_id = $stateParams.inventory_id;
$scope.canAdd = false;
rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/hosts")
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// The ncy breadcrumb directive will look at this attribute when attempting to bind to the correct scope. // The ncy breadcrumb directive will look at this attribute when attempting to bind to the correct scope.
// In this case, we don't want to incidentally bind to this scope when editing a host or a group. See: // In this case, we don't want to incidentally bind to this scope when editing a host or a group. See:
// https://github.com/ncuillery/angular-breadcrumb/issues/42 for a little more information on the // https://github.com/ncuillery/angular-breadcrumb/issues/42 for a little more information on the

View File

@@ -3,21 +3,26 @@
* *
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export default export default
['$scope', '$state', function($scope, $state){ ['$scope', '$state', 'inventoryData', function($scope, $state, inventoryData){
$scope.groupsSelected = false; $scope.groupsSelected = false;
$scope.hostsSelected = false; $scope.hostsSelected = false;
$scope.hostsSelectedItems = []; $scope.hostsSelectedItems = [];
$scope.groupsSelectedItems = []; $scope.groupsSelectedItems = [];
$scope.setAdhocPattern = function(){
var pattern = _($scope.groupsSelectedItems) $scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc;
.concat($scope.hostsSelectedItems)
.map(function(item){ $scope.setAdhocPattern = function(){
return item.name; var pattern = _($scope.groupsSelectedItems)
}).value().join(':'); .concat($scope.hostsSelectedItems)
$state.go('inventoryManage.adhoc', {pattern: pattern}); .map(function(item){
}; return item.name;
}).value().join(':');
$state.go('inventoryManage.adhoc', {pattern: pattern});
};
$scope.$watchGroup(['groupsSelected', 'hostsSelected'], function(newVals) { $scope.$watchGroup(['groupsSelected', 'hostsSelected'], function(newVals) {
$scope.adhocCommandTooltip = (newVals[0] || newVals[1]) ? "Run a command on the selected inventory" : "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups."; $scope.adhocCommandTooltip = (newVals[0] || newVals[1]) ? "Run a command on the selected inventory" : "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups.";
}); });
}]; }];

View File

@@ -8,20 +8,27 @@ export default
[ '$rootScope', 'pagination', '$compile','SchedulerInit', 'Rest', 'Wait', [ '$rootScope', 'pagination', '$compile','SchedulerInit', 'Rest', 'Wait',
'inventoryScriptsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty', 'inventoryScriptsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty',
'GenerateForm', 'SearchInit' , 'PaginateInit', 'GenerateForm', 'SearchInit' , 'PaginateInit',
'LookUpInit', 'OrganizationList', '$scope', '$state', 'LookUpInit', 'OrganizationList', '$scope', '$state', 'Alert',
function( function(
$rootScope, pagination, $compile, SchedulerInit, Rest, Wait, $rootScope, pagination, $compile, SchedulerInit, Rest, Wait,
inventoryScriptsFormObject, ProcessErrors, GetBasePath, Empty, inventoryScriptsFormObject, ProcessErrors, GetBasePath, Empty,
GenerateForm, SearchInit, PaginateInit, GenerateForm, SearchInit, PaginateInit,
LookUpInit, OrganizationList, $scope, $state LookUpInit, OrganizationList, $scope, $state, Alert
) { ) {
Rest.setUrl(GetBasePath('inventory_scripts'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add an inventory script.', 'alert-info');
}
});
var scope = $scope, var scope = $scope,
generator = GenerateForm, generator = GenerateForm,
form = inventoryScriptsFormObject, form = inventoryScriptsFormObject,
url = GetBasePath('inventory_scripts'); url = GetBasePath('inventory_scripts');
$scope.canEdit = true;
generator.inject(form, { generator.inject(form, {
mode: 'add' , mode: 'add' ,
scope:scope, scope:scope,

View File

@@ -17,6 +17,7 @@ export default
LookUpInit, OrganizationList, inventory_script, LookUpInit, OrganizationList, inventory_script,
$scope, $state $scope, $state
) { ) {
var generator = GenerateForm, var generator = GenerateForm,
id = inventory_script.id, id = inventory_script.id,
form = inventoryScriptsFormObject, form = inventoryScriptsFormObject,
@@ -24,6 +25,13 @@ export default
url = GetBasePath('inventory_scripts'); url = GetBasePath('inventory_scripts');
$scope.inventory_script = inventory_script; $scope.inventory_script = inventory_script;
$scope.$watch('inventory_script_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
generator.inject(form, { generator.inject(form, {
mode: 'edit' , mode: 'edit' ,
scope:$scope, scope:$scope,

View File

@@ -24,13 +24,15 @@ export default function() {
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: false capitalize: false,
ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
organization: { organization: {
label: 'Organization', label: 'Organization',
@@ -41,7 +43,8 @@ export default function() {
}, },
sourceModel: 'organization', sourceModel: 'organization',
sourceField: 'name', sourceField: 'name',
ngClick: 'lookUpOrganization()' ngClick: 'lookUpOrganization()',
ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
script: { script: {
label: 'Custom Script', label: 'Custom Script',
@@ -51,7 +54,7 @@ export default function() {
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
awDropFile: true, awDropFile: true,
ngDisabled: '!canEdit', ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)',
rows: 10, rows: 10,
awPopOver: "<p>Drag and drop your custom inventory script file here or create one in the field to import your custom inventory. " + awPopOver: "<p>Drag and drop your custom inventory script file here or create one in the field to import your custom inventory. " +
"<br><br> Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python</p>", "<br><br> Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python</p>",
@@ -64,10 +67,16 @@ export default function() {
buttons: { //for now always generates <button> tags buttons: { //for now always generates <button> tags
cancel: { cancel: {
ngClick: 'formCancel()', ngClick: 'formCancel()',
ngShow: '(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', //$scope.function to call on click, optional ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: 'custom_inventory_form.$pristine || custom_inventory_form.$invalid || !canEdit' //Disable when $pristine or $invalid, optional ngDisabled: 'custom_inventory_form.$pristine || custom_inventory_form.$invalid', //Disable when $pristine or $invalid, optional
ngShow: '(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
} }
} }
}; };

View File

@@ -42,7 +42,8 @@ export default function(){
ngClick: 'addCustomInv()', ngClick: 'addCustomInv()',
awToolTip: 'Create a new custom inventory', awToolTip: 'Create a new custom inventory',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: 'canAdd'
} }
}, },
@@ -56,7 +57,16 @@ export default function(){
label: 'Edit', label: 'Edit',
"class": 'btn-sm', "class": 'btn-sm',
awToolTip: 'Edit inventory script', awToolTip: 'Edit inventory script',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'inventory_script.summary_fields.user_capabilities.edit'
},
view: {
ngClick: "editCustomInv(inventory_script.id)",
label: 'View',
"class": 'btn-sm',
awToolTip: 'View inventory script',
dataPlacement: 'top',
ngShow: '!inventory_script.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
ngClick: "deleteCustomInv(inventory_script.id, inventory_script.name)", ngClick: "deleteCustomInv(inventory_script.id, inventory_script.name)",
@@ -64,7 +74,8 @@ export default function(){
label: 'Delete', label: 'Delete',
"class": 'btn-sm', "class": 'btn-sm',
awToolTip: 'Delete inventory script', awToolTip: 'Delete inventory script',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'inventory_script.summary_fields.user_capabilities.delete'
} }
} }
}; };

View File

@@ -7,17 +7,24 @@
export default export default
[ '$rootScope','Wait', 'generateList', 'inventoryScriptsListObject', [ '$rootScope','Wait', 'generateList', 'inventoryScriptsListObject',
'GetBasePath' , 'SearchInit' , 'PaginateInit', 'Rest' , 'ProcessErrors', 'GetBasePath' , 'SearchInit' , 'PaginateInit', 'Rest' , 'ProcessErrors',
'Prompt', '$state', '$filter', 'Prompt', '$state', '$filter', 'rbacUiControlService',
function( function(
$rootScope,Wait, GenerateList, inventoryScriptsListObject, $rootScope,Wait, GenerateList, inventoryScriptsListObject,
GetBasePath, SearchInit, PaginateInit, GetBasePath, SearchInit, PaginateInit,
Rest, ProcessErrors, Prompt, $state, $filter Rest, ProcessErrors, Prompt, $state, $filter, rbacUiControlService
) { ) {
var scope = $rootScope.$new(), var scope = $rootScope.$new(),
defaultUrl = GetBasePath('inventory_scripts'), defaultUrl = GetBasePath('inventory_scripts'),
list = inventoryScriptsListObject, list = inventoryScriptsListObject,
view = GenerateList; view = GenerateList;
scope.canAdd = false;
rbacUiControlService.canAdd("inventory_scripts")
.then(function(canAdd) {
scope.canAdd = canAdd;
});
view.inject( list, { view.inject( list, {
mode: 'edit', mode: 'edit',
scope: scope scope: scope

View File

@@ -21,6 +21,15 @@
$state, CreateSelect2, $q $state, CreateSelect2, $q
) { ) {
Rest.setUrl(GetBasePath('job_templates'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a job template.', 'alert-info');
}
});
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view
var defaultUrl = GetBasePath('job_templates'), var defaultUrl = GetBasePath('job_templates'),

View File

@@ -36,6 +36,12 @@ export default
ClearScope(); ClearScope();
$scope.$watch('job_template_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
var defaultUrl = GetBasePath('job_templates'), var defaultUrl = GetBasePath('job_templates'),
generator = GenerateForm, generator = GenerateForm,
form = JobTemplateForm(), form = JobTemplateForm(),

View File

@@ -10,18 +10,24 @@ export default
'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'JobTemplateForm', 'CredentialList', 'ProcessErrors', 'GetBasePath', 'JobTemplateForm', 'CredentialList',
'LookUpInit', 'InitiatePlaybookRun', 'Wait', '$compile', 'LookUpInit', 'InitiatePlaybookRun', 'Wait', '$compile',
'$state', '$filter', '$state', '$filter', 'rbacUiControlService',
function( function(
$scope, $rootScope, $location, $log, $scope, $rootScope, $location, $log,
$stateParams, Rest, Alert, JobTemplateList, GenerateList, Prompt, $stateParams, Rest, Alert, JobTemplateList, GenerateList, Prompt,
SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors,
GetBasePath, JobTemplateForm, CredentialList, LookUpInit, InitiatePlaybookRun, GetBasePath, JobTemplateForm, CredentialList, LookUpInit, InitiatePlaybookRun,
Wait, $compile, $state, $filter Wait, $compile, $state, $filter, rbacUiControlService
) { ) {
ClearScope(); ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd("job_templates")
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
var list = JobTemplateList, var list = JobTemplateList,
defaultUrl = GetBasePath('job_templates'), defaultUrl = GetBasePath('job_templates'),
view = GenerateList, view = GenerateList,

View File

@@ -70,6 +70,12 @@
flex: 0 0 637px; flex: 0 0 637px;
max-width: 637px; max-width: 637px;
} }
.SurveyMaker-previewPanel--viewOnly {
flex: 0 0 1155px;
max-width: 1155px;
}
.SurveyMaker-separatorPanel { .SurveyMaker-separatorPanel {
display: flex; display: flex;
flex: 0 0 51px; flex: 0 0 51px;

View File

@@ -94,28 +94,33 @@ export default
fieldActions: { fieldActions: {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4', columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
"view": {
mode: "all",
ngClick: "viewJob(all_job.id)",
awToolTip: "View the job",
dataPlacement: "top"
},
submit: { submit: {
icon: 'icon-rocket', icon: 'icon-rocket',
mode: 'all', mode: 'all',
ngClick: 'relaunchJob($event, all_job.id)', ngClick: 'relaunchJob($event, all_job.id)',
awToolTip: 'Relaunch using the same parameters', awToolTip: 'Relaunch using the same parameters',
dataPlacement: 'top', dataPlacement: 'top',
ngHide: "all_job.type == 'system_job' " ngShow: "!(all_job.type == 'system_job') && all_job.summary_fields.user_capabilities.start"
}, },
cancel: { cancel: {
mode: 'all', mode: 'all',
ngClick: 'deleteJob(all_job.id)', ngClick: 'deleteJob(all_job.id)',
awToolTip: 'Cancel the job', awToolTip: 'Cancel the job',
dataPlacement: 'top', dataPlacement: 'top',
ngShow: "all_job.status === 'running'|| all_job.status === 'waiting' || all_job.status === 'pending'" ngShow: "(all_job.status === 'running'|| all_job.status === 'waiting' || all_job.status === 'pending') && all_job.summary_fields.user_capabilities.start"
}, },
"delete": { "delete": {
mode: 'all', mode: 'all',
ngClick: 'deleteJob(all_job.id)', ngClick: 'deleteJob(all_job.id)',
awToolTip: 'Delete the job', awToolTip: 'Delete the job',
dataPlacement: 'top', dataPlacement: 'top',
ngShow: "all_job.status !== 'running' && all_job.status !== 'waiting' && all_job.status !== 'pending'" ngShow: "(all_job.status !== 'running' && all_job.status !== 'waiting' && all_job.status !== 'pending') && all_job.summary_fields.user_capabilities.delete"
} }
} }
}); });

View File

@@ -100,13 +100,14 @@ export default
ngClick: 'relaunchJob($event, completed_job.id)', ngClick: 'relaunchJob($event, completed_job.id)',
awToolTip: 'Relaunch using the same parameters', awToolTip: 'Relaunch using the same parameters',
dataPlacement: 'top', dataPlacement: 'top',
ngHide: "completed_job.type == 'system_job' " ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start"
}, },
"delete": { "delete": {
mode: 'all', mode: 'all',
ngClick: 'deleteJob(completed_job.id)', ngClick: 'deleteJob(completed_job.id)',
awToolTip: 'Delete the job', awToolTip: 'Delete the job',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'completed_job.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -72,7 +72,17 @@ export default
label: 'Edit', label: 'Edit',
"class": 'btn-sm', "class": 'btn-sm',
awToolTip: 'Edit credential', awToolTip: 'Edit credential',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'credential.summary_fields.user_capabilities.edit'
},
view: {
ngClick: "editCredential(credential.id)",
label: 'View',
"class": 'btn-sm',
awToolTip: 'View credential',
dataPlacement: 'top',
ngShow: '!credential.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
@@ -81,7 +91,8 @@ export default
label: 'Delete', label: 'Delete',
"class": 'btn-sm', "class": 'btn-sm',
awToolTip: 'Delete credential', awToolTip: 'Delete credential',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'credential.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -91,7 +91,8 @@ export default
ngClick: 'addInventory()', ngClick: 'addInventory()',
awToolTip: 'Create a new inventory', awToolTip: 'Create a new inventory',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: 'canAdd'
} }
}, },
@@ -103,13 +104,22 @@ export default
label: 'Edit', label: 'Edit',
ngClick: 'editInventory(inventory.id)', ngClick: 'editInventory(inventory.id)',
awToolTip: 'Edit inventory', awToolTip: 'Edit inventory',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'inventory.summary_fields.user_capabilities.edit'
},
view: {
label: 'View',
ngClick: 'editInventory(inventory.id)',
awToolTip: 'View inventory',
dataPlacement: 'top',
ngShow: '!inventory.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
label: 'Delete', label: 'Delete',
ngClick: "deleteInventory(inventory.id, inventory.name)", ngClick: "deleteInventory(inventory.id, inventory.name)",
awToolTip: 'Delete inventory', awToolTip: 'Delete inventory',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'inventory.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -143,7 +143,8 @@ export default
actionClass: 'btn List-buttonDefault', actionClass: 'btn List-buttonDefault',
buttonContent: 'RUN COMMANDS', buttonContent: 'RUN COMMANDS',
showTipWhenDisabled: true, showTipWhenDisabled: true,
tooltipInnerClass: "Tooltip-wide" tooltipInnerClass: "Tooltip-wide",
ngShow: 'canAdhoc'
// TODO: set up a tip watcher and change text based on when // TODO: set up a tip watcher and change text based on when
// things are selected/not selected. This is started and // things are selected/not selected. This is started and
// commented out in the inventory controller within the watchers. // commented out in the inventory controller within the watchers.
@@ -155,7 +156,8 @@ export default
ngClick: "createGroup()", ngClick: "createGroup()",
awToolTip: "Create a new group", awToolTip: "Create a new group",
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD GROUP' buttonContent: '&#43; ADD GROUP',
ngShow: 'canAdd'
} }
}, },
@@ -169,8 +171,8 @@ export default
ngClick: 'updateGroup(group)', ngClick: 'updateGroup(group)',
awToolTip: "{{ group.launch_tooltip }}", awToolTip: "{{ group.launch_tooltip }}",
dataTipWatch: "group.launch_tooltip", dataTipWatch: "group.launch_tooltip",
ngShow: "group.status !== 'running' && group.status " + ngShow: "(group.status !== 'running' && group.status " +
"!== 'pending' && group.status !== 'updating'", "!== 'pending' && group.status !== 'updating') && group.summary_fields.user_capabilities.start",
ngClass: "group.launch_class", ngClass: "group.launch_class",
dataPlacement: "top", dataPlacement: "top",
}, },
@@ -180,8 +182,8 @@ export default
ngClick: "cancelUpdate(group.id)", ngClick: "cancelUpdate(group.id)",
awToolTip: "Cancel sync process", awToolTip: "Cancel sync process",
'class': 'red-txt', 'class': 'red-txt',
ngShow: "group.status == 'running' || group.status == 'pending' " + ngShow: "(group.status == 'running' || group.status == 'pending' " +
"|| group.status == 'updating'", "|| group.status == 'updating') && group.summary_fields.user_capabilities.start",
dataPlacement: "top", dataPlacement: "top",
iconClass: "fa fa-minus-circle" iconClass: "fa fa-minus-circle"
}, },
@@ -189,7 +191,7 @@ export default
mode: 'all', mode: 'all',
ngClick: "copyMoveGroup(group.id)", ngClick: "copyMoveGroup(group.id)",
awToolTip: 'Copy or move group', awToolTip: 'Copy or move group',
ngShow: "group.id > 0", ngShow: "group.id > 0 && group.summary_fields.user_capabilities.copy",
dataPlacement: "top" dataPlacement: "top"
}, },
schedule: { schedule: {
@@ -198,21 +200,31 @@ export default
awToolTip: "{{ group.group_schedule_tooltip }}", awToolTip: "{{ group.group_schedule_tooltip }}",
ngClass: "group.scm_type_class", ngClass: "group.scm_type_class",
dataPlacement: 'top', dataPlacement: 'top',
ngHide: "group.summary_fields.inventory_source.source === ''" ngShow: "!(group.summary_fields.inventory_source.source === '') && group.summary_fields.user_capabilities.schedule"
}, },
edit: { edit: {
//label: 'Edit', //label: 'Edit',
mode: 'all', mode: 'all',
ngClick: "editGroup(group.id)", ngClick: "editGroup(group.id)",
awToolTip: 'Edit group', awToolTip: 'Edit group',
dataPlacement: "top" dataPlacement: "top",
ngShow: "group.summary_fields.user_capabilities.edit"
},
view: {
//label: 'Edit',
mode: 'all',
ngClick: "editGroup(group.id)",
awToolTip: 'View group',
dataPlacement: "top",
ngShow: "!group.summary_fields.user_capabilities.edit"
}, },
"delete": { "delete": {
//label: 'Delete', //label: 'Delete',
mode: 'all', mode: 'all',
ngClick: "deleteGroup(group)", ngClick: "deleteGroup(group)",
awToolTip: 'Delete group', awToolTip: 'Delete group',
dataPlacement: "top" dataPlacement: "top",
ngShow: "group.summary_fields.user_capabilities.delete"
} }
} }
}); });

View File

@@ -78,21 +78,31 @@ export default
mode: 'all', mode: 'all',
ngClick: "copyMoveHost(host.id)", ngClick: "copyMoveHost(host.id)",
awToolTip: 'Copy or move host to another group', awToolTip: 'Copy or move host to another group',
dataPlacement: "top" dataPlacement: "top",
ngShow: 'host.summary_fields.user_capabilities.edit'
}, },
edit: { edit: {
//label: 'Edit', //label: 'Edit',
ngClick: "editHost(host.id)", ngClick: "editHost(host.id)",
icon: 'icon-edit', icon: 'icon-edit',
awToolTip: 'Edit host', awToolTip: 'Edit host',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'host.summary_fields.user_capabilities.edit'
},
view: {
//label: 'Edit',
ngClick: "editHost(host.id)",
awToolTip: 'View host',
dataPlacement: 'top',
ngShow: '!host.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
//label: 'Delete', //label: 'Delete',
ngClick: "deleteHost(host.id, host.name)", ngClick: "deleteHost(host.id, host.name)",
icon: 'icon-trash', icon: 'icon-trash',
awToolTip: 'Delete host', awToolTip: 'Delete host',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'host.summary_fields.user_capabilities.delete'
} }
}, },
@@ -122,7 +132,8 @@ export default
ngClick: "createHost()", ngClick: "createHost()",
awToolTip: "Create a new host", awToolTip: "Create a new host",
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD HOST' buttonContent: '&#43; ADD HOST',
ngShow: 'canAdd'
} }
} }

View File

@@ -55,7 +55,8 @@ export default
basePaths: ['job_templates'], basePaths: ['job_templates'],
awToolTip: 'Create a new template', awToolTip: 'Create a new template',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: 'canAdd'
} }
}, },
@@ -68,7 +69,8 @@ export default
mode: 'all', mode: 'all',
ngClick: 'submitJob(job_template.id)', ngClick: 'submitJob(job_template.id)',
awToolTip: 'Start a job using this template', awToolTip: 'Start a job using this template',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'job_template.summary_fields.user_capabilities.start'
}, },
schedule: { schedule: {
label: 'Schedule', label: 'Schedule',
@@ -76,6 +78,7 @@ export default
ngClick: 'scheduleJob(job_template.id)', ngClick: 'scheduleJob(job_template.id)',
awToolTip: 'Schedule future job template runs', awToolTip: 'Schedule future job template runs',
dataPlacement: 'top', dataPlacement: 'top',
ngShow: 'job_template.summary_fields.user_capabilities.schedule'
}, },
copy: { copy: {
label: 'Copy', label: 'Copy',
@@ -83,7 +86,7 @@ export default
"class": 'btn-danger btn-xs', "class": 'btn-danger btn-xs',
awToolTip: 'Copy template', awToolTip: 'Copy template',
dataPlacement: 'top', dataPlacement: 'top',
ngHide: 'job_template.summary_fields.can_copy===false' ngShow: 'job_template.summary_fields.user_capabilities.copy'
}, },
edit: { edit: {
label: 'Edit', label: 'Edit',
@@ -91,6 +94,15 @@ export default
awToolTip: 'Edit template', awToolTip: 'Edit template',
"class": 'btn-default btn-xs', "class": 'btn-default btn-xs',
dataPlacement: 'top', dataPlacement: 'top',
ngShow: 'job_template.summary_fields.user_capabilities.edit'
},
view: {
label: 'View',
ngClick: "editJobTemplate(job_template.id)",
awToolTip: 'View template',
"class": 'btn-default btn-xs',
dataPlacement: 'top',
ngShow: '!job_template.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
label: 'Delete', label: 'Delete',
@@ -98,6 +110,7 @@ export default
"class": 'btn-danger btn-xs', "class": 'btn-danger btn-xs',
awToolTip: 'Delete template', awToolTip: 'Delete template',
dataPlacement: 'top', dataPlacement: 'top',
ngShow: 'job_template.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -65,7 +65,8 @@ export default
ngClick: 'addProject()', ngClick: 'addProject()',
awToolTip: 'Create a new project', awToolTip: 'Create a new project',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: "canAdd"
}, },
refresh: { refresh: {
mode: 'all', mode: 'all',
@@ -86,30 +87,40 @@ export default
awToolTip: "{{ project.scm_update_tooltip }}", awToolTip: "{{ project.scm_update_tooltip }}",
dataTipWatch: "project.scm_update_tooltip", dataTipWatch: "project.scm_update_tooltip",
ngClass: "project.scm_type_class", ngClass: "project.scm_type_class",
dataPlacement: 'top' dataPlacement: 'top',
ngShow: "project.summary_fields.user_capabilities.start"
}, },
schedule: { schedule: {
mode: 'all', mode: 'all',
ngClick: "editSchedules(project.id)", ngClick: "editSchedules(project.id)",
awToolTip: "{{ project.scm_schedule_tooltip }}", awToolTip: "{{ project.scm_schedule_tooltip }}",
ngClass: "project.scm_type_class", ngClass: "project.scm_type_class",
dataPlacement: 'top' dataPlacement: 'top',
ngShow: "project.summary_fields.user_capabilities.schedule"
}, },
edit: { edit: {
ngClick: "editProject(project.id)", ngClick: "editProject(project.id)",
awToolTip: 'Edit the project', awToolTip: 'Edit the project',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: "project.summary_fields.user_capabilities.edit"
},
view: {
ngClick: "editProject(project.id)",
awToolTip: 'View the project',
dataPlacement: 'top',
ngShow: "!project.summary_fields.user_capabilities.edit",
icon: 'fa-eye',
}, },
"delete": { "delete": {
ngClick: "deleteProject(project.id, project.name)", ngClick: "deleteProject(project.id, project.name)",
awToolTip: 'Delete the project', awToolTip: 'Delete the project',
ngShow: "project.status !== 'updating' && project.status !== 'running' && project.status !== 'pending'", ngShow: "(project.status !== 'updating' && project.status !== 'running' && project.status !== 'pending') && project.summary_fields.user_capabilities.delete",
dataPlacement: 'top' dataPlacement: 'top'
}, },
cancel: { cancel: {
ngClick: "cancelUpdate(project.id, project.name)", ngClick: "cancelUpdate(project.id, project.name)",
awToolTip: 'Cancel the SCM update', awToolTip: 'Cancel the SCM update',
ngShow: "project.status == 'updating' || project.status == 'running' || project.status == 'pending'", ngShow: "(project.status == 'updating' || project.status == 'running' || project.status == 'pending') && project.summary_fields.user_capabilities.start",
dataPlacement: 'top' dataPlacement: 'top'
} }
} }

View File

@@ -78,13 +78,22 @@ export default
mode: "all", mode: "all",
ngClick: "editSchedule(schedule)", ngClick: "editSchedule(schedule)",
awToolTip: "Edit the schedule", awToolTip: "Edit the schedule",
dataPlacement: "top" dataPlacement: "top",
ngShow: 'schedule.summary_fields.user_capabilities.edit'
},
"view": {
mode: "all",
ngClick: "editSchedule(schedule)",
awToolTip: "View the schedule",
dataPlacement: "top",
ngShow: '!schedule.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
mode: 'all', mode: 'all',
ngClick: 'deleteSchedule(schedule.id)', ngClick: 'deleteSchedule(schedule.id)',
awToolTip: 'Delete the schedule', awToolTip: 'Delete the schedule',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'schedule.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -69,7 +69,8 @@ export default
ngClick: 'addSchedule()', ngClick: 'addSchedule()',
awToolTip: 'Add a new schedule', awToolTip: 'Add a new schedule',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: 'canAdd'
} }
}, },
@@ -79,14 +80,23 @@ export default
ngClick: "editSchedule(schedule.id)", ngClick: "editSchedule(schedule.id)",
icon: 'icon-edit', icon: 'icon-edit',
awToolTip: 'Edit schedule', awToolTip: 'Edit schedule',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'schedule.summary_fields.user_capabilities.edit'
},
view: {
label: 'View',
ngClick: "editSchedule(schedule.id)",
awToolTip: 'View schedule',
dataPlacement: 'top',
ngShow: '!schedule.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
label: 'Delete', label: 'Delete',
ngClick: "deleteSchedule(schedule.id)", ngClick: "deleteSchedule(schedule.id)",
icon: 'icon-trash', icon: 'icon-trash',
awToolTip: 'Delete schedule', awToolTip: 'Delete schedule',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'schedule.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -47,7 +47,8 @@ export default
ngClick: 'addTeam()', ngClick: 'addTeam()',
awToolTip: 'Create a new team', awToolTip: 'Create a new team',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: 'canAdd'
} }
}, },
@@ -61,16 +62,25 @@ export default
icon: 'icon-edit', icon: 'icon-edit',
"class": 'btn-xs btn-default', "class": 'btn-xs btn-default',
awToolTip: 'Edit team', awToolTip: 'Edit team',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'team.summary_fields.user_capabilities.edit'
},
view: {
label: 'View',
ngClick: "editTeam(team.id)",
"class": 'btn-xs btn-default',
awToolTip: 'View team',
dataPlacement: 'top',
ngShow: '!team.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
label: 'Delete', label: 'Delete',
ngClick: "deleteTeam(team.id, team.name)", ngClick: "deleteTeam(team.id, team.name)",
icon: 'icon-trash', icon: 'icon-trash',
"class": 'btn-xs btn-danger', "class": 'btn-xs btn-danger',
awToolTip: 'Delete team', awToolTip: 'Delete team',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'team.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -44,7 +44,8 @@ export default
basePaths: ['organizations', 'users'], // base path must be in list, or action not available basePaths: ['organizations', 'users'], // base path must be in list, or action not available
awToolTip: 'Create a new user', awToolTip: 'Create a new user',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: 'canAdd'
} }
}, },
@@ -58,7 +59,17 @@ export default
icon: 'icon-edit', icon: 'icon-edit',
"class": 'btn-xs btn-default', "class": 'btn-xs btn-default',
awToolTip: 'Edit user', awToolTip: 'Edit user',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'user.summary_fields.user_capabilities.edit'
},
view: {
label: 'View',
ngClick: "editUser(user.id)",
"class": 'btn-xs btn-default',
awToolTip: 'View user',
dataPlacement: 'top',
ngShow: '!user.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
@@ -67,7 +78,8 @@ export default
icon: 'icon-trash', icon: 'icon-trash',
"class": 'btn-xs btn-danger', "class": 'btn-xs btn-danger',
awToolTip: 'Delete user', awToolTip: 'Delete user',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'user.summary_fields.user_capabilities.delete'
} }
} }
}); });

View File

@@ -17,6 +17,7 @@
<div class="MgmtCards-actionItems"> <div class="MgmtCards-actionItems">
<button class="MgmtCards-actionItem List-actionButton" <button class="MgmtCards-actionItem List-actionButton"
ng-click='chooseRunJob(card.id, card.name)' ng-click='chooseRunJob(card.id, card.name)'
ng-show='current_user.is_superuser'
data-placement="top" aw-tool-tip="Launch Management Job" data-original-title="" title=""> data-placement="top" aw-tool-tip="Launch Management Job" data-original-title="" title="">
<i class="MgmtCards-actionItemIcon icon-launch"></i> <i class="MgmtCards-actionItemIcon icon-launch"></i>
</button> </button>
@@ -27,6 +28,7 @@
</button> </button>
<button class="MgmtCards-actionItem List-actionButton" <button class="MgmtCards-actionItem List-actionButton"
ng-click='goToNotifications(card, card.id)' ng-click='goToNotifications(card, card.id)'
ng-show='current_user.is_superuser'
data-placement="top" aw-tool-tip="Configure Notifications" data-original-title="" title="" ng-class="{'List-editButton--selected': activeCard === card.id && cardAction === 'notifications'}"> data-placement="top" aw-tool-tip="Configure Notifications" data-original-title="" title="" ng-class="{'List-editButton--selected': activeCard === card.id && cardAction === 'notifications'}">
<i class="MgmtCards-actionItemIcon fa fa-bell-o"></i> <i class="MgmtCards-actionItemIcon fa fa-bell-o"></i>
</button> </button>

View File

@@ -9,14 +9,23 @@ export default
'NotificationsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty', 'NotificationsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty',
'GenerateForm', 'SearchInit' , 'PaginateInit', 'LookUpInit', 'GenerateForm', 'SearchInit' , 'PaginateInit', 'LookUpInit',
'OrganizationList', '$scope', '$state', 'CreateSelect2', 'GetChoices', 'OrganizationList', '$scope', '$state', 'CreateSelect2', 'GetChoices',
'NotificationsTypeChange', 'ParseTypeChange', 'NotificationsTypeChange', 'ParseTypeChange', 'Alert',
function( function(
$rootScope, pagination, $compile, SchedulerInit, Rest, Wait, $rootScope, pagination, $compile, SchedulerInit, Rest, Wait,
NotificationsFormObject, ProcessErrors, GetBasePath, Empty, NotificationsFormObject, ProcessErrors, GetBasePath, Empty,
GenerateForm, SearchInit, PaginateInit, LookUpInit, GenerateForm, SearchInit, PaginateInit, LookUpInit,
OrganizationList, $scope, $state, CreateSelect2, GetChoices, OrganizationList, $scope, $state, CreateSelect2, GetChoices,
NotificationsTypeChange, ParseTypeChange NotificationsTypeChange, ParseTypeChange, Alert
) { ) {
Rest.setUrl(GetBasePath('projects'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a notification template.', 'alert-info');
}
});
var generator = GenerateForm, var generator = GenerateForm,
form = NotificationsFormObject, form = NotificationsFormObject,
url = GetBasePath('notification_templates'); url = GetBasePath('notification_templates');

View File

@@ -26,6 +26,13 @@ export default
url = GetBasePath('notification_templates'); url = GetBasePath('notification_templates');
$scope.notification_template = notification_template; $scope.notification_template = notification_template;
$scope.$watch('notification_template.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
generator.inject(form, { generator.inject(form, {
mode: 'edit' , mode: 'edit' ,
scope:$scope, scope:$scope,

View File

@@ -8,12 +8,12 @@ export default
[ '$rootScope','Wait', 'generateList', 'NotificationTemplatesList', [ '$rootScope','Wait', 'generateList', 'NotificationTemplatesList',
'GetBasePath' , 'SearchInit' , 'PaginateInit', 'Rest' , 'GetBasePath' , 'SearchInit' , 'PaginateInit', 'Rest' ,
'ProcessErrors', 'Prompt', '$state', 'GetChoices', 'Empty', 'Find', 'ProcessErrors', 'Prompt', '$state', 'GetChoices', 'Empty', 'Find',
'ngToast', '$compile', '$filter', 'ngToast', '$compile', '$filter', 'rbacUiControlService',
function( function(
$rootScope,Wait, GenerateList, NotificationTemplatesList, $rootScope,Wait, GenerateList, NotificationTemplatesList,
GetBasePath, SearchInit, PaginateInit, Rest, GetBasePath, SearchInit, PaginateInit, Rest,
ProcessErrors, Prompt, $state, GetChoices, Empty, Find, ngToast, ProcessErrors, Prompt, $state, GetChoices, Empty, Find, ngToast,
$compile, $filter) { $compile, $filter, rbacUiControlService) {
var scope = $rootScope.$new(), var scope = $rootScope.$new(),
defaultUrl = GetBasePath('notification_templates'), defaultUrl = GetBasePath('notification_templates'),
list = NotificationTemplatesList, list = NotificationTemplatesList,
@@ -24,6 +24,13 @@ export default
scope: scope scope: scope
}); });
scope.canAdd = false;
rbacUiControlService.canAdd("notification_templates")
.then(function(canAdd) {
scope.canAdd = canAdd;
});
scope.removePostRefresh = scope.$on('PostRefresh', function () { scope.removePostRefresh = scope.$on('PostRefresh', function () {
Wait('stop'); Wait('stop');
if (scope.notification_templates) { if (scope.notification_templates) {

View File

@@ -27,13 +27,15 @@ export default function() {
type: 'text', type: 'text',
addRequired: true, addRequired: true,
editRequired: true, editRequired: true,
capitalize: false capitalize: false,
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
description: { description: {
label: 'Description', label: 'Description',
type: 'text', type: 'text',
addRequired: false, addRequired: false,
editRequired: false editRequired: false,
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
organization: { organization: {
label: 'Organization', label: 'Organization',
@@ -44,7 +46,8 @@ export default function() {
awRequiredWhen: { awRequiredWhen: {
reqExpression: "organizationrequired", reqExpression: "organizationrequired",
init: "true" init: "true"
} },
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
notification_type: { notification_type: {
label: 'Type', label: 'Type',
@@ -54,13 +57,15 @@ export default function() {
class: 'NotificationsForm-typeSelect', class: 'NotificationsForm-typeSelect',
ngOptions: 'type.label for type in notification_type_options track by type.value', ngOptions: 'type.label for type in notification_type_options track by type.value',
ngChange: 'typeChange()', ngChange: 'typeChange()',
hasSubForm: true hasSubForm: true,
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
username: { username: {
label: 'Username', label: 'Username',
type: 'text', type: 'text',
ngShow: "notification_type.value == 'email' ", ngShow: "notification_type.value == 'email' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
host: { host: {
@@ -71,7 +76,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'email' ", ngShow: "notification_type.value == 'email' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
sender: { sender: {
label: 'Sender Email', label: 'Sender Email',
@@ -81,7 +87,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'email' ", ngShow: "notification_type.value == 'email' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
recipients: { recipients: {
label: 'Recipient List', label: 'Recipient List',
@@ -97,7 +104,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'email' ", ngShow: "notification_type.value == 'email' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
password: { password: {
labelBind: 'passwordLabel', labelBind: 'passwordLabel',
@@ -108,7 +116,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'email' || notification_type.value == 'irc' ", ngShow: "notification_type.value == 'email' || notification_type.value == 'irc' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
port: { port: {
labelBind: 'portLabel', labelBind: 'portLabel',
@@ -122,7 +131,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'email' || notification_type.value == 'irc'", ngShow: "notification_type.value == 'email' || notification_type.value == 'irc'",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
channels: { channels: {
label: 'Destination Channels', label: 'Destination Channels',
@@ -138,7 +148,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'slack'", ngShow: "notification_type.value == 'slack'",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
rooms: { rooms: {
label: 'Destination Channels', label: 'Destination Channels',
@@ -154,7 +165,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'hipchat'", ngShow: "notification_type.value == 'hipchat'",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
token: { token: {
labelBind: 'tokenLabel', labelBind: 'tokenLabel',
@@ -165,7 +177,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'slack' || notification_type.value == 'pagerduty' || notification_type.value == 'hipchat'", ngShow: "notification_type.value == 'slack' || notification_type.value == 'pagerduty' || notification_type.value == 'hipchat'",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
account_token: { account_token: {
label: 'Account Token', label: 'Account Token',
@@ -176,7 +189,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'twilio' ", ngShow: "notification_type.value == 'twilio' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
from_number: { from_number: {
label: 'Source Phone Number', label: 'Source Phone Number',
@@ -188,7 +202,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'twilio' ", ngShow: "notification_type.value == 'twilio' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
to_numbers: { to_numbers: {
label: 'Destination SMS Number', label: 'Destination SMS Number',
@@ -204,7 +219,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'twilio' ", ngShow: "notification_type.value == 'twilio' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
account_sid: { account_sid: {
label: 'Account SID', label: 'Account SID',
@@ -214,7 +230,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'twilio' ", ngShow: "notification_type.value == 'twilio' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
subdomain: { subdomain: {
label: 'Pagerduty subdomain', label: 'Pagerduty subdomain',
@@ -224,7 +241,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'pagerduty' ", ngShow: "notification_type.value == 'pagerduty' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
service_key: { service_key: {
label: 'API Service/Integration Key', label: 'API Service/Integration Key',
@@ -234,7 +252,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'pagerduty' ", ngShow: "notification_type.value == 'pagerduty' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
client_name: { client_name: {
label: 'Client Identifier', label: 'Client Identifier',
@@ -244,7 +263,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'pagerduty' ", ngShow: "notification_type.value == 'pagerduty' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
message_from: { message_from: {
label: 'Label to be shown with notification', label: 'Label to be shown with notification',
@@ -254,7 +274,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'hipchat' ", ngShow: "notification_type.value == 'hipchat' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
api_url: { api_url: {
label: 'API URL', label: 'API URL',
@@ -265,7 +286,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'hipchat' ", ngShow: "notification_type.value == 'hipchat' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
color: { color: {
label: 'Notification Color', label: 'Notification Color',
@@ -277,13 +299,15 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'hipchat' ", ngShow: "notification_type.value == 'hipchat' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
notify: { notify: {
label: 'Notify Channel', label: 'Notify Channel',
type: 'checkbox', type: 'checkbox',
ngShow: "notification_type.value == 'hipchat' ", ngShow: "notification_type.value == 'hipchat' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
url: { url: {
label: 'Target URL', label: 'Target URL',
@@ -293,7 +317,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'webhook' ", ngShow: "notification_type.value == 'webhook' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
headers: { headers: {
label: 'HTTP Headers', label: 'HTTP Headers',
@@ -313,7 +338,8 @@ export default function() {
'</pre></p>', '</pre></p>',
dataPlacement: 'right', dataPlacement: 'right',
ngShow: "notification_type.value == 'webhook' ", ngShow: "notification_type.value == 'webhook' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
server: { server: {
label: 'IRC Server Address', label: 'IRC Server Address',
@@ -323,7 +349,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'irc' ", ngShow: "notification_type.value == 'irc' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
nickname: { nickname: {
label: 'IRC Nick', label: 'IRC Nick',
@@ -333,7 +360,8 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'irc' ", ngShow: "notification_type.value == 'irc' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
targets: { targets: {
label: 'Destination Channels or Users', label: 'Destination Channels or Users',
@@ -349,13 +377,15 @@ export default function() {
init: "false" init: "false"
}, },
ngShow: "notification_type.value == 'irc' ", ngShow: "notification_type.value == 'irc' ",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
use_ssl: { use_ssl: {
label: 'SSL Connection', label: 'SSL Connection',
type: 'checkbox', type: 'checkbox',
ngShow: "notification_type.value == 'irc'", ngShow: "notification_type.value == 'irc'",
subForm: 'typeSubForm' subForm: 'typeSubForm',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
checkbox_group: { checkbox_group: {
label: 'Options', label: 'Options',
@@ -367,13 +397,15 @@ export default function() {
label: 'Use TLS', label: 'Use TLS',
type: 'checkbox', type: 'checkbox',
ngShow: "notification_type.value == 'email' ", ngShow: "notification_type.value == 'email' ",
labelClass: 'checkbox-options stack-inline' labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, { }, {
name: 'use_ssl', name: 'use_ssl',
label: 'Use SSL', label: 'Use SSL',
type: 'checkbox', type: 'checkbox',
ngShow: "notification_type.value == 'email'", ngShow: "notification_type.value == 'email'",
labelClass: 'checkbox-options stack-inline' labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}] }]
} }
}, },
@@ -381,9 +413,15 @@ export default function() {
buttons: { //for now always generates <button> tags buttons: { //for now always generates <button> tags
cancel: { cancel: {
ngClick: 'formCancel()', ngClick: 'formCancel()',
ngShow: '(notification_template.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
}, },
save: { save: {
ngClick: 'formSave()', //$scope.function to call on click, optional ngClick: 'formSave()',
ngShow: '(notification_template.summary_fields.user_capabilities.edit || canAdd)', //$scope.function to call on click, optional
ngDisabled: true //Disable when $pristine or $invalid, optional ngDisabled: true //Disable when $pristine or $invalid, optional
} }
} }

View File

@@ -49,7 +49,8 @@ export default function(){
ngClick: 'addNotification()', ngClick: 'addNotification()',
awToolTip: 'Create a new custom inventory', awToolTip: 'Create a new custom inventory',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD' buttonContent: '&#43; ADD',
ngShow: 'canAdd'
} }
}, },
@@ -62,7 +63,8 @@ export default function(){
label: 'Edit', label: 'Edit',
"class": 'btn-sm', "class": 'btn-sm',
awToolTip: 'Test notification', awToolTip: 'Test notification',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'notification_template.summary_fields.user_capabilities.edit'
}, },
edit: { edit: {
ngClick: "editNotification(notification_template.id)", ngClick: "editNotification(notification_template.id)",
@@ -70,7 +72,16 @@ export default function(){
label: 'Edit', label: 'Edit',
"class": 'btn-sm', "class": 'btn-sm',
awToolTip: 'Edit notification', awToolTip: 'Edit notification',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'notification_template.summary_fields.user_capabilities.edit'
},
view: {
ngClick: "editNotification(notification_template.id)",
label: 'View',
"class": 'btn-sm',
awToolTip: 'View notification',
dataPlacement: 'top',
ngShow: '!notification_template.summary_fields.user_capabilities.edit'
}, },
"delete": { "delete": {
ngClick: "deleteNotification(notification_template.id, notification_template.name)", ngClick: "deleteNotification(notification_template.id, notification_template.name)",
@@ -78,7 +89,8 @@ export default function(){
label: 'Delete', label: 'Delete',
"class": 'btn-sm', "class": 'btn-sm',
awToolTip: 'Delete notification', awToolTip: 'Delete notification',
dataPlacement: 'top' dataPlacement: 'top',
ngShow: 'notification_template.summary_fields.user_capabilities.delete'
} }
} }
}; };

View File

@@ -65,7 +65,8 @@ export default function(){
ngClick: 'addNotificationTemplate()', ngClick: 'addNotificationTemplate()',
awToolTip: 'Create a new notification template', awToolTip: 'Create a new notification template',
actionClass: 'btn List-buttonSubmit', actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD NOTIFICATION TEMPLATE' buttonContent: '&#43; ADD NOTIFICATION TEMPLATE',
ngShow: 'current_user.is_superuser || (current_user_admin_orgs && current_user_admin_orgs.length > 0)'
} }
} }

View File

@@ -15,13 +15,21 @@
*/ */
export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', 'GetChoices', export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', 'GetChoices',
'$state', '$state', '$rootScope',
function(Wait, GetBasePath, ProcessErrors, Rest, GetChoices, $state) { function(Wait, GetBasePath, ProcessErrors, Rest, GetChoices, $state, $rootScope) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
url = params.url, url = params.url,
id = params.id; id = params.id;
scope.current_user_admin_orgs = [];
Rest.setUrl($rootScope.current_user.related.admin_of_organizations);
Rest.get()
.success(function(data) {
scope.current_user_admin_orgs = data.results.map(i => i.name);
});
scope.addNotificationTemplate = function(){ scope.addNotificationTemplate = function(){
$state.go('notifications.add'); $state.go('notifications.add');
}; };

View File

@@ -12,6 +12,15 @@ export default ['$scope', '$rootScope', '$compile', '$location',
$stateParams, OrganizationForm, GenerateForm, Rest, Alert, ProcessErrors, $stateParams, OrganizationForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, ReturnToCaller, Wait, $state) { ClearScope, GetBasePath, ReturnToCaller, Wait, $state) {
Rest.setUrl(GetBasePath('organizations'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add an organization.', 'alert-info');
}
});
ClearScope(); ClearScope();
// Inject dynamic view // Inject dynamic view

View File

@@ -25,6 +25,12 @@ export default ['$scope', '$rootScope', '$compile', '$location',
id = $stateParams.organization_id, id = $stateParams.organization_id,
relatedSets = {}; relatedSets = {};
$scope.$watch('organization_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.$parent.activeMode = 'edit'; $scope.$parent.activeMode = 'edit';
$scope.$parent.activeCard = parseInt(id); $scope.$parent.activeCard = parseInt(id);

View File

@@ -8,15 +8,22 @@ export default ['$stateParams', '$scope', '$rootScope', '$location',
'$log', '$compile', 'Rest', 'PaginateInit', '$log', '$compile', 'Rest', 'PaginateInit',
'SearchInit', 'OrganizationList', 'Alert', 'Prompt', 'ClearScope', 'SearchInit', 'OrganizationList', 'Alert', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', 'ProcessErrors', 'GetBasePath', 'Wait',
'$state', 'generateList', 'Refresh', '$filter', '$state', 'generateList', 'Refresh', '$filter', 'rbacUiControlService',
function($stateParams, $scope, $rootScope, $location, function($stateParams, $scope, $rootScope, $location,
$log, $compile, Rest, PaginateInit, $log, $compile, Rest, PaginateInit,
SearchInit, OrganizationList, Alert, Prompt, ClearScope, SearchInit, OrganizationList, Alert, Prompt, ClearScope,
ProcessErrors, GetBasePath, Wait, ProcessErrors, GetBasePath, Wait,
$state, generateList, Refresh, $filter) { $state, generateList, Refresh, $filter, rbacUiControlService) {
ClearScope(); ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd("organizations")
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
var defaultUrl = GetBasePath('organizations'), var defaultUrl = GetBasePath('organizations'),
list = OrganizationList, list = OrganizationList,
pageSize = 24, pageSize = 24,
@@ -25,6 +32,7 @@ export default ['$stateParams', '$scope', '$rootScope', '$location',
var parseCardData = function(cards) { var parseCardData = function(cards) {
return cards.map(function(card) { return cards.map(function(card) {
var val = {}, url = '/#/organizations/' + card.id + '/'; var val = {}, url = '/#/organizations/' + card.id + '/';
val.user_capabilities = card.summary_fields.user_capabilities;
val.name = card.name; val.name = card.name;
val.id = card.id; val.id = card.id;
val.description = card.description || undefined; val.description = card.description || undefined;

View File

@@ -13,6 +13,7 @@
<div class="List-actions"> <div class="List-actions">
<button class="btn List-buttonSubmit" <button class="btn List-buttonSubmit"
aw-tool-tip="Create a new organization" aw-tool-tip="Create a new organization"
ng-show="canAdd"
ng-click="addOrganization()"> ng-click="addOrganization()">
+ ADD + ADD
</button> </button>
@@ -31,13 +32,23 @@
<div class="OrgCards-actionItems"> <div class="OrgCards-actionItems">
<button class="OrgCards-actionItem <button class="OrgCards-actionItem
List-actionButton" List-actionButton"
ng-show="card.user_capabilities.edit"
ng-class="{'List-editButton--selected': (activeCard === card.id || card.isActiveCard) && activeMode === 'edit' }" ng-class="{'List-editButton--selected': (activeCard === card.id || card.isActiveCard) && activeMode === 'edit' }"
ng-click="editOrganization(card.id)"> ng-click="editOrganization(card.id)">
<i class="OrgCards-actionItemIcon fa fa-pencil"> <i class="OrgCards-actionItemIcon fa fa-pencil">
</i> </i>
</button> </button>
<button class="OrgCards-actionItem
List-actionButton"
ng-show="!card.user_capabilities.edit"
ng-class="{'List-editButton--selected': (activeCard === card.id || card.isActiveCard) && activeMode === 'edit' }"
ng-click="editOrganization(card.id)">
<i class="OrgCards-actionItemIcon fa fa-search-plus">
</i>
</button>
<button class="OrgCards-actionItem List-actionButton <button class="OrgCards-actionItem List-actionButton
List-actionButton--delete" List-actionButton--delete"
ng-show="card.user_capabilities.delete"
ng-click="deleteOrganization(card.id, card.name)"> ng-click="deleteOrganization(card.id, card.name)">
<i class="OrgCards-actionItemIcon <i class="OrgCards-actionItemIcon
fa fa-trash-o"> fa fa-trash-o">

View File

@@ -32,13 +32,13 @@
</div> </div>
</div> </div>
<div class="SurveyMaker-content"> <div class="SurveyMaker-content">
<div class="SurveyMaker-questionPanel"> <div class="SurveyMaker-questionPanel" ng-show="(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">
<div id="survey_maker_question_form"></div> <div id="survey_maker_question_form"></div>
</div> </div>
<div class="SurveyMaker-separatorPanel"> <div class="SurveyMaker-separatorPanel" ng-show="(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">
<div class="SurveyMaker-contentSeparator"></div> <div class="SurveyMaker-contentSeparator"></div>
</div> </div>
<div class="SurveyMaker-previewPanel"> <div class="SurveyMaker-previewPanel" ng-class="{'SurveyMaker-previewPanel--viewOnly': !(job_template_obj.summary_fields.user_capabilities.edit || canAdd)}">
<div style="display: flex; flex-direction: column; width: 100%;"> <div style="display: flex; flex-direction: column; width: 100%;">
<div class="SurveyMaker-panelHeader">PREVIEW</div> <div class="SurveyMaker-panelHeader">PREVIEW</div>
<div class="SurveyMaker-panelBody"> <div class="SurveyMaker-panelBody">
@@ -56,13 +56,13 @@
<i>{{question.question_description}}</i> <i>{{question.question_description}}</i>
</div> </div>
<div class="SurveyMaker-previewInputRow"> <div class="SurveyMaker-previewInputRow">
<span dnd-handle class="SurveyMaker-reorderButton" data-placement="top" aw-tool-tip="Drag to reorder question" data-original-title="" title=""> <span dnd-handle class="SurveyMaker-reorderButton" data-placement="top" aw-tool-tip="Drag to reorder question" data-original-title="" title="" ng-show="(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">
<i class="fa fa-ellipsis-v"></i> <i class="fa fa-ellipsis-v"></i>
<span>&nbsp;</span> <span>&nbsp;</span>
<i class="fa fa-ellipsis-v"></i> <i class="fa fa-ellipsis-v"></i>
</span> </span>
<survey-question class="SurveyMaker-previewInput" preview="true" question="question" ng-required="question.required" ng-disabled=true></survey-question> <survey-question class="SurveyMaker-previewInput" preview="true" question="question" ng-required="question.required" ng-disabled=true></survey-question>
<div class="SurveyMaker-previewActions"> <div class="SurveyMaker-previewActions" ng-show="(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">
<button class="List-actionButton" data-placement="top" ng-class="{'SurveyMaker-previewActions--selected' : editQuestionIndex == $index}" ng-click="editQuestion($index)" aw-tool-tip="Edit question" data-original-title="" title=""> <button class="List-actionButton" data-placement="top" ng-class="{'SurveyMaker-previewActions--selected' : editQuestionIndex == $index}" ng-click="editQuestion($index)" aw-tool-tip="Edit question" data-original-title="" title="">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</button> </button>
@@ -80,9 +80,10 @@
</div> </div>
<div class="SurveyMaker-panelFooter"> <div class="SurveyMaker-panelFooter">
<div class="Form-buttons"> <div class="Form-buttons">
<button id="survey-delete-button" class="btn btn-sm SurveyMaker-deleteButton" ng-show="survey_exists" ng-click="showDeleteOverlay('survey')">DELETE SURVEY</button> <button id="survey-delete-button" class="btn btn-sm SurveyMaker-deleteButton" ng-show="survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || canAdd)" ng-click="showDeleteOverlay('survey')">DELETE SURVEY</button>
<button id="survey-close-button" class="btn btn-sm Form-buttonDefault" ng-click="closeSurvey('survey-modal-dialog')">CANCEL</button> <button id="survey-close-button" class="btn btn-sm Form-buttonDefault" ng-click="closeSurvey('survey-modal-dialog')" ng-show="(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">CANCEL</button>
<button id="survey-save-button" class="btn btn-sm Form-saveButton" ng-click="saveSurvey()" ng-disabled="survey_questions.length < 1 || !can_edit || editQuestionIndex !== null">SAVE</button> <button id="survey-close-button" class="btn btn-sm Form-buttonDefault" ng-click="closeSurvey('survey-modal-dialog')" ng-show="!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">CLOSE</button>
<button id="survey-save-button" class="btn btn-sm Form-saveButton" ng-click="saveSurvey()" ng-disabled="survey_questions.length < 1 || !can_edit || editQuestionIndex !== null" ng-show="(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">SAVE</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -14,11 +14,11 @@
export default [ export default [
'$scope', '$compile', '$location', '$stateParams', 'SchedulesList', 'Rest', '$scope', '$compile', '$location', '$stateParams', 'SchedulesList', 'Rest',
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'Wait', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'Wait',
'Find', 'LoadSchedulesScope', 'GetChoices', '$q', '$state', 'Find', 'LoadSchedulesScope', 'GetChoices', '$q', '$state', 'rbacUiControlService',
function ($scope, $compile, $location, $stateParams, function ($scope, $compile, $location, $stateParams,
SchedulesList, Rest, ProcessErrors, ReturnToCaller, ClearScope, SchedulesList, Rest, ProcessErrors, ReturnToCaller, ClearScope,
GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices, GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices,
$q, $state) { $q, $state, rbacUiControlService) {
var schedList = _.cloneDeep(SchedulesList); var schedList = _.cloneDeep(SchedulesList);
ClearScope(); ClearScope();
@@ -48,6 +48,14 @@ export default [
} }
$scope.removeParentLoaded = $scope.$on('ParentLoaded', function() { $scope.removeParentLoaded = $scope.$on('ParentLoaded', function() {
url += "schedules/"; url += "schedules/";
$scope.canAdd = false;
rbacUiControlService.canAdd(url)
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
schedList.well = true; schedList.well = true;
// include name of item in listTitle // include name of item in listTitle

View File

@@ -9,7 +9,6 @@ export default ['$compile', '$filter', '$state', '$stateParams', 'AddSchedule',
'Rest', 'ParamPass', 'Rest', 'ParamPass',
function($compile, $filter, $state, $stateParams, AddSchedule, Wait, $scope, function($compile, $filter, $state, $stateParams, AddSchedule, Wait, $scope,
$rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParamPass) { $rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParamPass) {
$scope.processSchedulerEndDt = function(){ $scope.processSchedulerEndDt = function(){
// set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight // set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight
var dt = new Date($scope.schedulerUTCTime); var dt = new Date($scope.schedulerUTCTime);

View File

@@ -865,6 +865,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += ">"; html += ">";
html += "<input type=\"checkbox\" ng-model=\"" + field.subCheckbox.variable + "\" "; html += "<input type=\"checkbox\" ng-model=\"" + field.subCheckbox.variable + "\" ";
html += (field.subCheckbox.ngChange) ? "ng-change=\"" + field.subCheckbox.ngChange + "\" " : ""; html += (field.subCheckbox.ngChange) ? "ng-change=\"" + field.subCheckbox.ngChange + "\" " : "";
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += "id=\"" + this.form.name + "_" + fld + "_ask_chbox\" "; html += "id=\"" + this.form.name + "_" + fld + "_ask_chbox\" ";
html += ">"; html += ">";
html += field.subCheckbox.text ? field.subCheckbox.text : ""; html += field.subCheckbox.text ? field.subCheckbox.text : "";
@@ -987,6 +988,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if (field.subCheckbox.ngDisabled) { if (field.subCheckbox.ngDisabled) {
html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'"; html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'";
} }
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += ">"; html += ">";
html += field.subCheckbox.text ? field.subCheckbox.text : ""; html += field.subCheckbox.text ? field.subCheckbox.text : "";
html += "</label>"; html += "</label>";
@@ -1084,6 +1086,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if (field.subCheckbox.ngDisabled) { if (field.subCheckbox.ngDisabled) {
html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'"; html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'";
} }
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += ">"; html += ">";
html += field.subCheckbox.text ? field.subCheckbox.text : ""; html += field.subCheckbox.text ? field.subCheckbox.text : "";
html += "</label>"; html += "</label>";
@@ -1151,6 +1154,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if (field.subCheckbox.ngDisabled) { if (field.subCheckbox.ngDisabled) {
html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'"; html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'";
} }
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += ">"; html += ">";
html += field.subCheckbox.text ? field.subCheckbox.text : ""; html += field.subCheckbox.text ? field.subCheckbox.text : "";
html += "</label>"; html += "</label>";
@@ -1199,6 +1203,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += (field.min || field.min === 0) ? this.attr(field, 'min') : ""; html += (field.min || field.min === 0) ? this.attr(field, 'min') : "";
html += (field.max) ? this.attr(field, 'max') : ""; html += (field.max) ? this.attr(field, 'max') : "";
html += (field.ngChange) ? this.attr(field, 'ngChange') : ""; html += (field.ngChange) ? this.attr(field, 'ngChange') : "";
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += (field.slider) ? "id=\"" + fld + "-number\"" : (field.id) ? this.attr(field, 'id') : ""; html += (field.slider) ? "id=\"" + fld + "-number\"" : (field.id) ? this.attr(field, 'id') : "";
html += (options.mode === 'edit' && field.editRequired) ? "required " : ""; html += (options.mode === 'edit' && field.editRequired) ? "required " : "";
html += (options.mode === 'add' && field.addRequired) ? "required " : ""; html += (options.mode === 'add' && field.addRequired) ? "required " : "";
@@ -1219,6 +1224,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "<input type=\"checkbox\" ng-model=\"" + html += "<input type=\"checkbox\" ng-model=\"" +
field.subCheckbox.variable + "\" "; field.subCheckbox.variable + "\" ";
html += (field.subCheckbox.ngChange) ? "ng-change=\"" + field.subCheckbox.ngChange + "\" " : ""; html += (field.subCheckbox.ngChange) ? "ng-change=\"" + field.subCheckbox.ngChange + "\" " : "";
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += "id=\"" + this.form.name + "_" + fld + "_ask_chbox\" "; html += "id=\"" + this.form.name + "_" + fld + "_ask_chbox\" ";
html += ">"; html += ">";
html += field.subCheckbox.text ? field.subCheckbox.text : ""; html += field.subCheckbox.text ? field.subCheckbox.text : "";
@@ -1422,6 +1428,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "<input type=\"checkbox\" ng-model=\"" + html += "<input type=\"checkbox\" ng-model=\"" +
field.subCheckbox.variable + "\" "; field.subCheckbox.variable + "\" ";
html += (field.subCheckbox.ngChange) ? "ng-change=\"" + field.subCheckbox.ngChange + "\" " : ""; html += (field.subCheckbox.ngChange) ? "ng-change=\"" + field.subCheckbox.ngChange + "\" " : "";
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += "id=\"" + this.form.name + "_" + fld + "_ask_chbox\" "; html += "id=\"" + this.form.name + "_" + fld + "_ask_chbox\" ";
html += ">"; html += ">";
html += field.subCheckbox.text ? field.subCheckbox.text : ""; html += field.subCheckbox.text ? field.subCheckbox.text : "";
@@ -1693,6 +1700,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
button.label = 'Cancel'; button.label = 'Cancel';
button['class'] = 'Form-cancelButton'; button['class'] = 'Form-cancelButton';
} }
if (btn === 'close') {
button.label = 'Close';
button['class'] = 'Form-cancelButton';
}
if (btn === 'launch') { if (btn === 'launch') {
button.label = 'Launch'; button.label = 'Launch';
button['class'] = 'Form-launchButton'; button['class'] = 'Form-launchButton';
@@ -1705,6 +1716,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
button.label = 'Edit Survey'; button.label = 'Edit Survey';
button['class'] = 'Form-surveyButton'; button['class'] = 'Form-surveyButton';
} }
if (btn === 'view_survey') {
button.label = 'View Survey';
button['class'] = 'Form-surveyButton';
}
// Build button HTML // Build button HTML
html += "<button type=\"button\" "; html += "<button type=\"button\" ";

View File

@@ -11,12 +11,14 @@ import lodashAsPromised from './lodash-as-promised';
import stringFilters from './string-filters/main'; import stringFilters from './string-filters/main';
import truncatedText from './truncated-text.directive'; import truncatedText from './truncated-text.directive';
import stateExtender from './stateExtender.provider'; import stateExtender from './stateExtender.provider';
import rbacUiControl from './rbacUiControl';
export default export default
angular.module('shared', [listGenerator.name, angular.module('shared', [listGenerator.name,
pagination.name, pagination.name,
stringFilters.name, stringFilters.name,
'ui.router' 'ui.router',
rbacUiControl.name
]) ])
.factory('lodashAsPromised', lodashAsPromised) .factory('lodashAsPromised', lodashAsPromised)
.directive('truncatedText', truncatedText) .directive('truncatedText', truncatedText)

View File

@@ -0,0 +1,32 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
angular.module('rbacUiControl', [])
.service('rbacUiControlService', ['$q', 'GetBasePath', 'Rest', 'Wait', function($q, GetBasePath, Rest, Wait){
this.canAdd = function(apiPath) {
var canAddVal = $q.defer();
if (apiPath.indexOf("api/v1") > -1) {
Rest.setUrl(apiPath);
} else {
Rest.setUrl(GetBasePath(apiPath));
}
Wait("start");
Rest.options()
.success(function(data) {
if (data.actions.POST) {
canAddVal.resolve(true);
} else {
canAddVal.reject(false);
}
Wait("stop");
});
return canAddVal.promise;
};
}]);

View File

@@ -9,13 +9,13 @@ module.exports = function(config) {
browsers: ['Chrome', 'Firefox'], browsers: ['Chrome', 'Firefox'],
coverageReporter: { coverageReporter: {
reporters: [ reporters: [
{ type: 'html', subdir: 'html' } { type: 'html', subdir: 'html' },
] ]
}, },
frameworks: [ frameworks: [
'jasmine', 'jasmine',
], ],
reporters: ['progress', 'coverage'], reporters: ['progress', 'coverage', 'junit'],
files: [ files: [
'./client/src/app.js', './client/src/app.js',
'./node_modules/angular-mocks/angular-mocks.js', './node_modules/angular-mocks/angular-mocks.js',
@@ -86,7 +86,9 @@ module.exports = function(config) {
} }
}, },
junitReporter: { junitReporter: {
outputFile: 'coverage/test-results.xml' outputDir: 'coverage',
outputFile: 'ui-unit-test-results.xml',
useBrowserName: false
} }
}); });
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,7 @@
"expose-loader": "^0.7.1", "expose-loader": "^0.7.1",
"grunt": "^1.0.1", "grunt": "^1.0.1",
"grunt-browser-sync": "^2.2.0", "grunt-browser-sync": "^2.2.0",
"grunt-cli": "^1.2.0",
"grunt-concurrent": "^2.3.0", "grunt-concurrent": "^2.3.0",
"grunt-contrib-clean": "^1.0.0", "grunt-contrib-clean": "^1.0.0",
"grunt-contrib-concat": "^1.0.1", "grunt-contrib-concat": "^1.0.1",
@@ -56,12 +57,15 @@
"karma-firefox-launcher": "^1.0.0", "karma-firefox-launcher": "^1.0.0",
"karma-html2js-preprocessor": "^1.0.0", "karma-html2js-preprocessor": "^1.0.0",
"karma-jasmine": "^1.0.2", "karma-jasmine": "^1.0.2",
"karma-junit-reporter": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.2",
"karma-sauce-launcher": "^1.0.0", "karma-sauce-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.8.0", "karma-webpack": "^1.8.0",
"less-plugin-autoprefix": "^1.4.2", "less-plugin-autoprefix": "^1.4.2",
"load-grunt-configs": "^1.0.0", "load-grunt-configs": "^1.0.0",
"load-grunt-tasks": "^3.5.0", "load-grunt-tasks": "^3.5.0",
"phantomjs-prebuilt": "^2.1.12",
"time-grunt": "^1.4.0", "time-grunt": "^1.4.0",
"webpack": "^1.13.1", "webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1" "webpack-dev-server": "^1.14.1"

View File

@@ -29,12 +29,34 @@ RUN yum install -y \
# Remove the 2 lines below and uncomment the 3 lines above to build # Remove the 2 lines below and uncomment the 3 lines above to build
# RPMs with the old JS build system. # RPMs with the old JS build system.
RUN curl --silent --location https://rpm.nodesource.com/setup_6.x | bash - RUN curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -
RUN yum install -y nodejs
RUN npm set progress=false RUN yum install -y nodejs
WORKDIR "/ansible-tower" WORKDIR "/ansible-tower"
ENV VENV_BASE="/venv" # Copy requirements files
COPY requirements/*.txt requirements/
# Copy __init__.py so the Makefile can retrieve `awx.__version__`
COPY awx/__init__.py awx/
# Copy Makefile
COPY Makefile .
# Install tower runtime virtualenvs
ENV SWIG_FEATURES="-cpperraswarn -includeall -I/usr/include/openssl"
RUN make requirements
# Install tower test requirements
ENV VENV_BASE=""
RUN make requirements_jenkins
# Build front-end deps
COPY awx/ui/package.json awx/ui/
RUN npm set progress=false
RUN make ui-deps-built
ENTRYPOINT ["/bin/bash", "-c"] ENTRYPOINT ["/bin/bash", "-c"]
CMD ["bash"] CMD ["bash"]

View File

@@ -6,6 +6,13 @@ $ docker-compose -f tools/docker-compose/unit-tests/docker-compose.yml run unit-
This will start the container, install the dependencies, and run the unit tests. This will start the container, install the dependencies, and run the unit tests.
To rebuild:
```shell
$ docker-compose -f tools/docker-compose/unit-tests/docker-compose.yml build
```
If you just want to pop into a shell and poke around, run: If you just want to pop into a shell and poke around, run:
```shell ```shell

View File

@@ -7,7 +7,7 @@ services:
dockerfile: tools/docker-compose/unit-tests/Dockerfile dockerfile: tools/docker-compose/unit-tests/Dockerfile
environment: environment:
SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl" SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl"
command: ["make requirements_test test"] TEST_DIRS: "awx/main/tests/unit"
command: ["make test"]
volumes: volumes:
- ../../../:/ansible-tower - ../../../:/ansible-tower