mirror of
https://github.com/ansible/awx.git
synced 2026-01-14 11:20:39 -03:30
Merge branch 'devel' of https://github.com/ansible/ansible-tower into wf_rbac_prompt
This commit is contained in:
commit
e6273ce46e
12
Makefile
12
Makefile
@ -298,12 +298,10 @@ requirements_tower_dev:
|
||||
# Install third-party requirements needed for running unittests in jenkins
|
||||
requirements_jenkins:
|
||||
if [ "$(VENV_BASE)" ]; then \
|
||||
. $(VENV_BASE)/tower/bin/activate; \
|
||||
$(VENV_BASE)/tower/bin/pip install -Ir requirements/requirements_jenkins.txt; \
|
||||
. $(VENV_BASE)/tower/bin/activate && pip install -Ir requirements/requirements_jenkins.txt; \
|
||||
else \
|
||||
pip install -Ir requirements/requirements_jenkins.txt; \
|
||||
fi && \
|
||||
$(NPM_BIN) install csslint
|
||||
fi
|
||||
|
||||
requirements: requirements_ansible requirements_tower
|
||||
|
||||
@ -317,8 +315,8 @@ develop:
|
||||
pip uninstall -y awx; \
|
||||
$(PYTHON) setup.py develop; \
|
||||
else \
|
||||
sudo pip uninstall -y awx; \
|
||||
sudo $(PYTHON) setup.py develop; \
|
||||
pip uninstall -y awx; \
|
||||
$(PYTHON) setup.py develop; \
|
||||
fi
|
||||
|
||||
version_file:
|
||||
@ -448,7 +446,7 @@ pylint: reports
|
||||
|
||||
check: flake8 pep8 # pyflakes pylint
|
||||
|
||||
TEST_DIRS=awx/main/tests
|
||||
TEST_DIRS ?= awx/main/tests
|
||||
# Run all API unit tests.
|
||||
test:
|
||||
@if [ "$(VENV_BASE)" ]; then \
|
||||
|
||||
@ -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;
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
@ -235,6 +235,13 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
|
||||
def get_queryset(self):
|
||||
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):
|
||||
opts = self.model._meta
|
||||
if 'username' in opts.get_all_field_names():
|
||||
|
||||
@ -49,6 +49,9 @@ class ModelAccessPermission(permissions.BasePermission):
|
||||
if not check_user_access(request.user, view.parent_model, 'read',
|
||||
parent_obj):
|
||||
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
|
||||
elif getattr(view, 'is_job_start', False):
|
||||
if not obj:
|
||||
@ -206,6 +209,8 @@ class ProjectUpdatePermission(ModelAccessPermission):
|
||||
|
||||
class UserPermission(ModelAccessPermission):
|
||||
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
|
||||
raise PermissionDenied()
|
||||
|
||||
@ -37,6 +37,7 @@ from polymorphic import PolymorphicModel
|
||||
# AWX
|
||||
from awx.main.constants import SCHEDULEABLE_PROVIDERS
|
||||
from awx.main.models import * # noqa
|
||||
from awx.main.access import get_user_capabilities
|
||||
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.conf import tower_settings
|
||||
@ -345,6 +346,19 @@ class BaseSerializer(serializers.ModelSerializer):
|
||||
}
|
||||
if len(roles) > 0:
|
||||
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
|
||||
|
||||
def get_created(self, obj):
|
||||
@ -553,6 +567,7 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class UnifiedJobSerializer(BaseSerializer):
|
||||
show_capabilities = ['start', 'delete']
|
||||
|
||||
result_stdout = serializers.SerializerMethodField()
|
||||
|
||||
@ -697,11 +712,12 @@ class UserSerializer(BaseSerializer):
|
||||
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')
|
||||
is_system_auditor = serializers.BooleanField(default=False)
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
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')
|
||||
|
||||
def to_representation(self, obj):
|
||||
@ -822,6 +838,7 @@ class UserSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class OrganizationSerializer(BaseSerializer):
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
@ -906,6 +923,7 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
|
||||
status = serializers.ChoiceField(choices=Project.PROJECT_STATUS_CHOICES, read_only=True)
|
||||
last_update_failed = serializers.BooleanField(read_only=True)
|
||||
last_updated = serializers.DateTimeField(read_only=True)
|
||||
show_capabilities = ['start', 'schedule', 'edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
@ -1014,6 +1032,7 @@ class BaseSerializerWithVariables(BaseSerializer):
|
||||
|
||||
|
||||
class InventorySerializer(BaseSerializerWithVariables):
|
||||
show_capabilities = ['edit', 'delete', 'adhoc']
|
||||
|
||||
class Meta:
|
||||
model = Inventory
|
||||
@ -1064,12 +1083,14 @@ class InventoryDetailSerializer(InventorySerializer):
|
||||
|
||||
|
||||
class InventoryScriptSerializer(InventorySerializer):
|
||||
show_capabilities = ['copy', 'edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
fields = ()
|
||||
|
||||
|
||||
class HostSerializer(BaseSerializerWithVariables):
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = Host
|
||||
@ -1180,6 +1201,7 @@ class HostSerializer(BaseSerializerWithVariables):
|
||||
|
||||
|
||||
class GroupSerializer(BaseSerializerWithVariables):
|
||||
show_capabilities = ['start', 'copy', 'schedule', 'edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
@ -1284,6 +1306,7 @@ class GroupVariableDataSerializer(BaseVariableDataSerializer):
|
||||
class CustomInventoryScriptSerializer(BaseSerializer):
|
||||
|
||||
script = serializers.CharField(trim_whitespace=False)
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = CustomInventoryScript
|
||||
@ -1454,6 +1477,7 @@ class InventoryUpdateCancelSerializer(InventoryUpdateSerializer):
|
||||
|
||||
|
||||
class TeamSerializer(BaseSerializer):
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
@ -1522,8 +1546,12 @@ class RoleSerializer(BaseSerializer):
|
||||
return ret
|
||||
|
||||
|
||||
class RoleSerializerWithParentAccess(RoleSerializer):
|
||||
show_capabilities = ['unattach']
|
||||
|
||||
|
||||
class ResourceAccessListElementSerializer(UserSerializer):
|
||||
show_capabilities = [] # Clear fields from UserSerializer parent class
|
||||
|
||||
def to_representation(self, user):
|
||||
'''
|
||||
@ -1539,18 +1567,25 @@ class ResourceAccessListElementSerializer(UserSerializer):
|
||||
ret = super(ResourceAccessListElementSerializer, self).to_representation(user)
|
||||
object_id = self.context['view'].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:
|
||||
ret['summary_fields'] = {}
|
||||
|
||||
def format_role_perm(role):
|
||||
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_type'] = role.content_type.name
|
||||
role_dict['related'] = reverse_gfk(role.content_object)
|
||||
except:
|
||||
pass
|
||||
role_dict['user_capabilities'] = {'unattach': requesting_user.can_access(
|
||||
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)}
|
||||
|
||||
def format_team_role_perm(team_role, permissive_role_ids):
|
||||
@ -1563,20 +1598,21 @@ class ResourceAccessListElementSerializer(UserSerializer):
|
||||
'team_id': team_role.object_id,
|
||||
'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_type'] = role.content_type.name
|
||||
role_dict['related'] = reverse_gfk(role.content_object)
|
||||
except:
|
||||
pass
|
||||
role_dict['user_capabilities'] = {'unattach': requesting_user.can_access(
|
||||
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)})
|
||||
return ret
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
@ -1621,6 +1657,7 @@ class ResourceAccessListElementSerializer(UserSerializer):
|
||||
|
||||
|
||||
class CredentialSerializer(BaseSerializer):
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = Credential
|
||||
@ -1825,6 +1862,7 @@ class JobOptionsSerializer(BaseSerializer):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -1860,21 +1898,6 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
|
||||
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):
|
||||
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)
|
||||
return d
|
||||
|
||||
@ -2561,6 +2584,7 @@ class JobLaunchSerializer(BaseSerializer):
|
||||
return attrs
|
||||
|
||||
class NotificationTemplateSerializer(BaseSerializer):
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = NotificationTemplate
|
||||
@ -2674,6 +2698,7 @@ class LabelSerializer(BaseSerializer):
|
||||
return res
|
||||
|
||||
class ScheduleSerializer(BaseSerializer):
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
model = Schedule
|
||||
|
||||
@ -854,7 +854,7 @@ class TeamUsersList(BaseUsersList):
|
||||
class TeamRolesList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
model = Role
|
||||
serializer_class = RoleSerializer
|
||||
serializer_class = RoleSerializerWithParentAccess
|
||||
metadata_class = RoleMetadata
|
||||
parent_model = Team
|
||||
relationship='member_role.children'
|
||||
@ -953,6 +953,7 @@ class ProjectList(ListCreateAPIView):
|
||||
|
||||
model = Project
|
||||
serializer_class = ProjectSerializer
|
||||
capabilities_prefetch = ['admin', 'update']
|
||||
|
||||
def get_queryset(self):
|
||||
projects_qs = Project.accessible_objects(self.request.user, 'read_role')
|
||||
@ -1151,6 +1152,7 @@ class UserList(ListCreateAPIView):
|
||||
|
||||
model = User
|
||||
serializer_class = UserSerializer
|
||||
capabilities_prefetch = ['admin']
|
||||
permission_classes = (UserPermission,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@ -1191,7 +1193,7 @@ class UserTeamsList(ListAPIView):
|
||||
class UserRolesList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
model = Role
|
||||
serializer_class = RoleSerializer
|
||||
serializer_class = RoleSerializerWithParentAccess
|
||||
metadata_class = RoleMetadata
|
||||
parent_model = User
|
||||
relationship='roles'
|
||||
@ -1511,6 +1513,7 @@ class InventoryList(ListCreateAPIView):
|
||||
|
||||
model = Inventory
|
||||
serializer_class = InventorySerializer
|
||||
capabilities_prefetch = ['admin', 'adhoc']
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Inventory.accessible_objects(self.request.user, 'read_role')
|
||||
@ -1742,6 +1745,7 @@ class GroupList(ListCreateAPIView):
|
||||
|
||||
model = Group
|
||||
serializer_class = GroupSerializer
|
||||
capabilities_prefetch = ['inventory.admin', 'inventory.adhoc', 'inventory.update']
|
||||
|
||||
'''
|
||||
Useful when you have a self-refering ManyToManyRelationship.
|
||||
@ -2210,6 +2214,10 @@ class JobTemplateList(ListCreateAPIView):
|
||||
model = JobTemplate
|
||||
serializer_class = JobTemplateSerializer
|
||||
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):
|
||||
ret = super(JobTemplateList, self).post(request, *args, **kwargs)
|
||||
@ -3680,6 +3688,7 @@ class NotificationTemplateTest(GenericAPIView):
|
||||
model = NotificationTemplate
|
||||
serializer_class = EmptySerializer
|
||||
new_in_300 = True
|
||||
is_job_start = True
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
|
||||
@ -116,6 +116,18 @@ def check_user_access(user, model_class, action, *args, **kwargs):
|
||||
return result
|
||||
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):
|
||||
'''
|
||||
check_superuser is a decorator that provides a simple short circuit
|
||||
@ -207,6 +219,78 @@ class BaseAccess(object):
|
||||
elif "features" not in validation_info:
|
||||
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):
|
||||
'''
|
||||
@ -526,6 +610,12 @@ class GroupAccess(BaseAccess):
|
||||
"active_jobs": active_jobs})
|
||||
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):
|
||||
'''
|
||||
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
|
||||
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
|
||||
def can_delete(self, obj):
|
||||
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
|
||||
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
|
||||
def can_delete(self, obj):
|
||||
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.
|
||||
'''
|
||||
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
|
||||
reference_obj = data.pop('reference_obj', None)
|
||||
@ -1132,6 +1237,10 @@ class SystemJobAccess(BaseAccess):
|
||||
'''
|
||||
model = SystemJob
|
||||
|
||||
def can_start(self, obj):
|
||||
return False # no relaunching of system jobs
|
||||
|
||||
# TODO:
|
||||
class WorkflowJobTemplateNodeAccess(BaseAccess):
|
||||
'''
|
||||
I can see/use a WorkflowJobTemplateNode if I have read permission
|
||||
@ -1762,6 +1871,12 @@ class NotificationTemplateAccess(BaseAccess):
|
||||
def can_delete(self, obj):
|
||||
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):
|
||||
'''
|
||||
I can see/use a notification if I have permission to
|
||||
@ -1970,8 +2085,13 @@ class RoleAccess(BaseAccess):
|
||||
|
||||
@check_superuser
|
||||
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 check_user_access(self.user, sub_obj.__class__, 'read', sub_obj):
|
||||
if not skip_sub_obj_read_check and relationship in ['members', 'member_role.parents', 'parents']:
|
||||
# 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
|
||||
|
||||
if isinstance(obj.content_object, ResourceMixin) and \
|
||||
|
||||
@ -47,3 +47,4 @@ class Command(BaseCommand):
|
||||
inventory=i,
|
||||
credential=c)
|
||||
print('Default organization added.')
|
||||
print('Demo Credential, Inventory, and Job Template added.')
|
||||
|
||||
@ -340,6 +340,10 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
||||
errors.append("'%s' value missing" % survey_element['variable'])
|
||||
elif survey_element['type'] in ["textarea", "text", "password"]:
|
||||
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']):
|
||||
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']))
|
||||
@ -348,6 +352,10 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
||||
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
|
||||
elif survey_element['type'] == 'integer':
|
||||
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 \
|
||||
data[survey_element['variable']] < int(survey_element['min']):
|
||||
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']):
|
||||
errors.append("'%s' value %s is too large (must be no more than %s)." %
|
||||
(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':
|
||||
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']):
|
||||
errors.append("'%s' value %s is too small (must be at least %s)." %
|
||||
(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']):
|
||||
errors.append("'%s' value %s is too large (must be no more than %s)." %
|
||||
(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':
|
||||
if survey_element['variable'] in data:
|
||||
if type(data[survey_element['variable']]) != list:
|
||||
|
||||
@ -177,7 +177,6 @@ class WorkflowJobNode(WorkflowNodeBase):
|
||||
# TODO: merge artifacts, add ancestor_artifacts to kwargs
|
||||
if extra_vars:
|
||||
data['extra_vars'] = extra_vars
|
||||
print ' job KV data: ' + str(data)
|
||||
return data
|
||||
|
||||
class WorkflowJobOptions(BaseModel):
|
||||
|
||||
@ -654,7 +654,8 @@ class BaseTask(Task):
|
||||
if status == 'canceled':
|
||||
raise Exception("Task %s(pk:%s) was canceled (rc=%s)" % (str(self.model.__class__), str(pk), str(rc)))
|
||||
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'):
|
||||
self.signal_finished(pk)
|
||||
|
||||
|
||||
@ -3,12 +3,11 @@ import mock
|
||||
|
||||
# AWX
|
||||
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.migrations import _save_password_keys as save_password_keys
|
||||
|
||||
# Django
|
||||
from django.test.client import RequestFactory
|
||||
from django.core.urlresolvers import reverse
|
||||
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)
|
||||
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
|
||||
@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)
|
||||
assert get_response.status_code == 200
|
||||
assert not get_response.data['summary_fields']['can_copy']
|
||||
|
||||
post_data = get_response.data
|
||||
post_data['name'] = '%s @ 12:19:47 pm' % post_data['name']
|
||||
|
||||
323
awx/main/tests/functional/api/test_rbac_displays.py
Normal file
323
awx/main/tests/functional/api/test_rbac_displays.py
Normal 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']
|
||||
|
||||
@ -6,9 +6,13 @@ import mock
|
||||
from awx.api.serializers import (
|
||||
JobTemplateSerializer,
|
||||
)
|
||||
from awx.api.views import JobTemplateDetail
|
||||
from awx.main.models import (
|
||||
Role,
|
||||
User,
|
||||
Job,
|
||||
)
|
||||
from rest_framework.test import APIRequestFactory
|
||||
|
||||
#DRF
|
||||
from rest_framework import serializers
|
||||
@ -77,21 +81,33 @@ class TestJobTemplateSerializerGetSummaryFields():
|
||||
summary = get_summary_fields_mock_and_run(JobTemplateSerializer, job_template)
|
||||
assert 'survey' not in summary
|
||||
|
||||
@pytest.mark.skip(reason="RBAC needs to land")
|
||||
def test_can_copy_true(self, mocker, job_template):
|
||||
pass
|
||||
def test_copy_edit_standard(self, mocker, job_template_factory):
|
||||
"""Verify that the exact output of the access.py methods
|
||||
are put into the serializer user_capabilities"""
|
||||
|
||||
@pytest.mark.skip(reason="RBAC needs to land")
|
||||
def test_can_copy_false(self, mocker, job_template):
|
||||
pass
|
||||
jt_obj = job_template_factory('testJT', project='proj1', persisted=False).job_template
|
||||
jt_obj.id = 5
|
||||
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")
|
||||
def test_can_edit_true(self, mocker, job_template):
|
||||
pass
|
||||
with mocker.patch("awx.main.models.rbac.Role.get_description", return_value='Can eat pie'):
|
||||
with mocker.patch("awx.main.access.JobTemplateAccess.can_change", return_value='foobar'):
|
||||
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")
|
||||
def test_can_edit_false(self, mocker, job_template):
|
||||
pass
|
||||
assert response['user_capabilities']['copy'] == 'foo'
|
||||
assert response['user_capabilities']['edit'] == 'foobar'
|
||||
|
||||
class TestJobTemplateSerializerValidation(object):
|
||||
|
||||
|
||||
@ -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
|
||||
extra_vars - used when creating a new job"""
|
||||
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."]
|
||||
|
||||
@ -134,3 +134,28 @@ class TestWorkflowAccessMethods:
|
||||
with mock.patch('awx.main.access.get_object_or_400', mock_get_object):
|
||||
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'
|
||||
}
|
||||
|
||||
|
||||
@ -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, 'run_pexpect', return_value=('successful', 0))
|
||||
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)
|
||||
assert run_job.update_model.call_count == 3
|
||||
|
||||
@ -33,7 +33,7 @@ logger = logging.getLogger('awx.main.utils')
|
||||
|
||||
__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_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',
|
||||
'_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided',
|
||||
'get_current_apps', 'set_current_apps']
|
||||
@ -409,6 +409,72 @@ def get_model_for_type(type):
|
||||
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():
|
||||
'''
|
||||
Measure system memory and use it as a baseline for determining the system's capacity
|
||||
|
||||
1
awx/ui/.npmrc
Normal file
1
awx/ui/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
progress=false
|
||||
@ -1,13 +1,13 @@
|
||||
<div class="RoleList-tagContainer"
|
||||
ng-repeat="entry in access_list">
|
||||
<div class="RoleList-deleteContainer"
|
||||
ng-if="entry.explicit"
|
||||
ng-if="entry.explicit && entry.user_capabilities.unattach"
|
||||
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>
|
||||
</div>
|
||||
<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}"
|
||||
aw-tool-tip='{{entry.team_name | sanitize}}' aw-tip-placement='bottom'>
|
||||
<span class="RoleList-name">{{ entry.name }}</span>
|
||||
|
||||
@ -839,6 +839,7 @@ var tower = angular.module('Tower', [
|
||||
// If browser refresh, set the user_is_superuser value
|
||||
$rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser');
|
||||
$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)
|
||||
if (!_.contains($location.$$url, '/login')) {
|
||||
ConfigService.getConfig().then(function() {
|
||||
|
||||
@ -14,9 +14,14 @@
|
||||
export function CredentialsList($scope, $rootScope, $location, $log,
|
||||
$stateParams, Rest, Alert, CredentialList, GenerateList, Prompt, SearchInit,
|
||||
PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
|
||||
SelectionInit, GetChoices, Wait, $state, $filter) {
|
||||
SelectionInit, GetChoices, Wait, $state, $filter, rbacUiControlService) {
|
||||
ClearScope();
|
||||
|
||||
rbacUiControlService.canAdd('credentials')
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
Wait('start');
|
||||
|
||||
var list = CredentialList,
|
||||
@ -129,7 +134,7 @@ CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log',
|
||||
'$stateParams', 'Rest', 'Alert', 'CredentialList', 'generateList', 'Prompt',
|
||||
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
|
||||
'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,
|
||||
LookUpInit, OrganizationList, GetBasePath, GetChoices, Empty, KindChange,
|
||||
OwnerChange, FormSave, $state, CreateSelect2) {
|
||||
|
||||
ClearScope();
|
||||
|
||||
// Inject dynamic view
|
||||
@ -337,6 +341,7 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
|
||||
}
|
||||
|
||||
ClearScope();
|
||||
|
||||
var defaultUrl = GetBasePath('credentials'),
|
||||
generator = GenerateForm,
|
||||
form = CredentialForm,
|
||||
@ -344,10 +349,17 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
|
||||
master = {},
|
||||
id = $stateParams.credential_id,
|
||||
relatedSets = {};
|
||||
|
||||
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
|
||||
generator.reset();
|
||||
$scope.id = id;
|
||||
|
||||
$scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) {
|
||||
if (val === false) {
|
||||
$scope.canAdd = false;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.canShareCredential = false;
|
||||
|
||||
if ($rootScope.current_user.is_superuser) {
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
export function JobsListController ($rootScope, $log, $scope, $compile, $stateParams,
|
||||
ClearScope, LoadSchedulesScope,
|
||||
LoadJobsScope, AllJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait) {
|
||||
LoadJobsScope, AllJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait, $state) {
|
||||
|
||||
ClearScope();
|
||||
|
||||
@ -61,6 +61,11 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
|
||||
}
|
||||
}
|
||||
jobs_scope = $scope.$new(true);
|
||||
|
||||
jobs_scope.viewJob = function (id) {
|
||||
$state.transitionTo('jobDetail', {id: id});
|
||||
};
|
||||
|
||||
jobs_scope.showJobType = true;
|
||||
LoadJobsScope({
|
||||
parent_scope: $scope,
|
||||
@ -153,4 +158,4 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
|
||||
|
||||
JobsListController.$inject = ['$rootScope', '$log', '$scope', '$compile', '$stateParams',
|
||||
'ClearScope', 'LoadSchedulesScope', 'LoadJobsScope',
|
||||
'AllJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait'];
|
||||
'AllJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', '$state'];
|
||||
|
||||
@ -15,10 +15,16 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
|
||||
Rest, Alert, ProjectList, GenerateList, Prompt, SearchInit,
|
||||
PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
|
||||
SelectionInit, ProjectUpdate, Refresh, Wait, GetChoices, Empty,
|
||||
Find, GetProjectIcon, GetProjectToolTip, $filter, $state) {
|
||||
|
||||
Find, GetProjectIcon, GetProjectToolTip, $filter, $state, rbacUiControlService) {
|
||||
ClearScope();
|
||||
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd('projects')
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
Wait('start');
|
||||
|
||||
var list = ProjectList,
|
||||
@ -369,7 +375,7 @@ ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log',
|
||||
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
|
||||
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'ProjectUpdate',
|
||||
'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,
|
||||
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();
|
||||
|
||||
// Inject dynamic view
|
||||
@ -559,6 +574,12 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
|
||||
|
||||
ClearScope('htmlTemplate');
|
||||
|
||||
$scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) {
|
||||
if (val === false) {
|
||||
$scope.canAdd = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Inject dynamic view
|
||||
var form = ProjectsForm(),
|
||||
generator = GenerateForm,
|
||||
|
||||
@ -14,10 +14,16 @@
|
||||
export function TeamsList($scope, $rootScope, $location, $log, $stateParams,
|
||||
Rest, Alert, TeamList, GenerateList, Prompt, SearchInit, PaginateInit,
|
||||
ReturnToCaller, ClearScope, ProcessErrors, SetTeamListeners, GetBasePath,
|
||||
SelectionInit, Wait, $state, Refresh, $filter) {
|
||||
|
||||
SelectionInit, Wait, $state, Refresh, $filter, rbacUiControlService) {
|
||||
ClearScope();
|
||||
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd('teams')
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
var list = TeamList,
|
||||
defaultUrl = GetBasePath('teams'),
|
||||
generator = GenerateList,
|
||||
@ -126,7 +132,7 @@ TeamsList.$inject = ['$scope', '$rootScope', '$location', '$log',
|
||||
'$stateParams', 'Rest', 'Alert', 'TeamList', 'generateList', 'Prompt',
|
||||
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
|
||||
'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
|
||||
//$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
|
||||
var defaultUrl = GetBasePath('teams'),
|
||||
form = TeamForm,
|
||||
@ -208,6 +223,12 @@ export function TeamsEdit($scope, $rootScope, $location,
|
||||
|
||||
$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.reset();
|
||||
|
||||
@ -34,10 +34,16 @@ function user_type_sync($scope) {
|
||||
export function UsersList($scope, $rootScope, $location, $log, $stateParams,
|
||||
Rest, Alert, UserList, GenerateList, Prompt, SearchInit, PaginateInit,
|
||||
ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit,
|
||||
Wait, $state, Refresh, $filter) {
|
||||
|
||||
Wait, $state, Refresh, $filter, rbacUiControlService) {
|
||||
ClearScope();
|
||||
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd('users')
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
var list = UserList,
|
||||
defaultUrl = GetBasePath('users'),
|
||||
generator = GenerateList,
|
||||
@ -136,7 +142,7 @@ UsersList.$inject = ['$scope', '$rootScope', '$location', '$log',
|
||||
'$stateParams', 'Rest', 'Alert', 'UserList', 'generateList', 'Prompt',
|
||||
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
|
||||
'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,
|
||||
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();
|
||||
|
||||
// Inject dynamic view
|
||||
@ -279,6 +294,12 @@ export function UsersEdit($scope, $rootScope, $location,
|
||||
$scope.user_type = user_type_options[0];
|
||||
$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){
|
||||
_(data)
|
||||
.pick(function(value, key){
|
||||
|
||||
@ -32,13 +32,15 @@ export default
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
autocomplete: false
|
||||
autocomplete: false,
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
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>",
|
||||
dataTitle: 'Organization ',
|
||||
dataPlacement: 'bottom',
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
kind: {
|
||||
label: 'Type',
|
||||
@ -83,7 +86,8 @@ export default
|
||||
dataTitle: 'Type',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
hasSubForm: true
|
||||
hasSubForm: true,
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
access_key: {
|
||||
label: 'Access Key',
|
||||
@ -96,12 +100,13 @@ export default
|
||||
autocomplete: false,
|
||||
apiField: 'username',
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
secret_key: {
|
||||
label: 'Secret Key',
|
||||
type: 'sensitive',
|
||||
ngShow: "kind.value == 'aws'",
|
||||
ngDisabled: "secret_key_ask",
|
||||
ngDisabled: "secret_key_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
|
||||
awRequiredWhen: {
|
||||
reqExpression: "aws_required",
|
||||
init: false
|
||||
@ -123,7 +128,8 @@ export default
|
||||
dataTitle: 'STS Token',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"host": {
|
||||
labelBind: 'hostLabel',
|
||||
@ -139,7 +145,8 @@ export default
|
||||
reqExpression: 'host_required',
|
||||
init: false
|
||||
},
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"subscription": {
|
||||
label: "Subscription ID",
|
||||
@ -156,7 +163,8 @@ export default
|
||||
dataTitle: 'Subscription ID',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"username": {
|
||||
labelBind: 'usernameLabel',
|
||||
@ -168,7 +176,8 @@ export default
|
||||
init: false
|
||||
},
|
||||
autocomplete: false,
|
||||
subForm: "credentialSubForm"
|
||||
subForm: "credentialSubForm",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"email_address": {
|
||||
labelBind: 'usernameLabel',
|
||||
@ -183,7 +192,8 @@ export default
|
||||
dataTitle: 'Email',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"api_key": {
|
||||
label: 'API Key',
|
||||
@ -196,7 +206,8 @@ export default
|
||||
autocomplete: false,
|
||||
hasShowInputButton: true,
|
||||
clear: false,
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"password": {
|
||||
labelBind: 'passwordLabel',
|
||||
@ -209,13 +220,14 @@ export default
|
||||
reqExpression: "password_required",
|
||||
init: false
|
||||
},
|
||||
subForm: "credentialSubForm"
|
||||
subForm: "credentialSubForm",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"ssh_password": {
|
||||
label: 'Password',
|
||||
type: 'sensitive',
|
||||
ngShow: "kind.value == 'ssh'",
|
||||
ngDisabled: "ssh_password_ask",
|
||||
ngDisabled: "ssh_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
subCheckbox: {
|
||||
@ -247,7 +259,8 @@ export default
|
||||
dataTitle: 'Private Key',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
subForm: "credentialSubForm"
|
||||
subForm: "credentialSubForm",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"ssh_key_unlock": {
|
||||
label: 'Private Key Passphrase',
|
||||
@ -255,7 +268,7 @@ export default
|
||||
ngShow: "kind.value == 'ssh' || kind.value == 'scm'",
|
||||
addRequired: 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: {
|
||||
variable: 'ssh_key_unlock_ask',
|
||||
ngShow: "kind.value == 'ssh'",
|
||||
@ -278,7 +291,8 @@ export default
|
||||
"<code>sudo | su | pbrun | pfexec | runas</code> <br>(defaults to <code>sudo</code>)</p>",
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"become_username": {
|
||||
labelBind: 'becomeUsernameLabel',
|
||||
@ -287,13 +301,14 @@ export default
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
autocomplete: false,
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"become_password": {
|
||||
labelBind: 'becomePasswordLabel',
|
||||
type: 'sensitive',
|
||||
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,
|
||||
editRequired: false,
|
||||
subCheckbox: {
|
||||
@ -309,7 +324,8 @@ export default
|
||||
type: 'text',
|
||||
label: 'Client ID',
|
||||
subForm: 'credentialSubForm',
|
||||
ngShow: "kind.value === 'azure_rm'"
|
||||
ngShow: "kind.value === 'azure_rm'",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
secret:{
|
||||
type: 'sensitive',
|
||||
@ -317,20 +333,23 @@ export default
|
||||
autocomplete: false,
|
||||
label: 'Client Secret',
|
||||
subForm: 'credentialSubForm',
|
||||
ngShow: "kind.value === 'azure_rm'"
|
||||
ngShow: "kind.value === 'azure_rm'",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
tenant: {
|
||||
type: 'text',
|
||||
label: 'Tenant ID',
|
||||
subForm: 'credentialSubForm',
|
||||
ngShow: "kind.value === 'azure_rm'"
|
||||
ngShow: "kind.value === 'azure_rm'",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
authorize: {
|
||||
label: 'Authorize',
|
||||
type: 'checkbox',
|
||||
ngChange: "toggleCallback('host_config_key')",
|
||||
subForm: 'credentialSubForm',
|
||||
ngShow: "kind.value === 'net'"
|
||||
ngShow: "kind.value === 'net'",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
authorize_password: {
|
||||
label: 'Authorize Password',
|
||||
@ -339,6 +358,7 @@ export default
|
||||
autocomplete: false,
|
||||
subForm: 'credentialSubForm',
|
||||
ngShow: "authorize && authorize !== 'false'",
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"project": {
|
||||
labelBind: 'projectLabel',
|
||||
@ -355,7 +375,8 @@ export default
|
||||
reqExpression: 'project_required',
|
||||
init: false
|
||||
},
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"domain": {
|
||||
labelBind: 'domainLabel',
|
||||
@ -371,13 +392,14 @@ export default
|
||||
dataContainer: "body",
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
subForm: 'credentialSubForm'
|
||||
subForm: 'credentialSubForm',
|
||||
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
"vault_password": {
|
||||
label: "Vault Password",
|
||||
type: 'sensitive',
|
||||
ngShow: "kind.value == 'ssh'",
|
||||
ngDisabled: "vault_password_ask",
|
||||
ngDisabled: "vault_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
subCheckbox: {
|
||||
@ -394,11 +416,17 @@ export default
|
||||
buttons: {
|
||||
cancel: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
close: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
save: {
|
||||
label: 'Save',
|
||||
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',
|
||||
awToolTip: 'Add a permission',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -26,14 +26,16 @@ export default
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
tab: 'properties'
|
||||
tab: 'properties',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
tab: 'properties'
|
||||
tab: 'properties',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
variables: {
|
||||
label: 'Variables',
|
||||
@ -65,7 +67,8 @@ export default
|
||||
ngChange: 'sourceChange(source)',
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
ngModel: 'source'
|
||||
ngModel: 'source',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
credential: {
|
||||
label: 'Cloud Credential',
|
||||
@ -77,7 +80,8 @@ export default
|
||||
awRequiredWhen: {
|
||||
reqExpression: "cloudCredentialRequired",
|
||||
init: "false"
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
source_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, " +
|
||||
"or choose <em>All</em> to include all regions. Tower will only be updated with Hosts associated with the selected regions." +
|
||||
"</p>",
|
||||
dataContainer: 'body'
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
instance_filters: {
|
||||
label: 'Instance Filters',
|
||||
@ -112,7 +117,8 @@ export default
|
||||
"<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> " +
|
||||
"for a complete list of supported filters.</p>",
|
||||
dataContainer: 'body'
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
group_by: {
|
||||
label: 'Only Group By',
|
||||
@ -137,7 +143,8 @@ export default
|
||||
"<li>VPC ID: <strong>vpcs » vpc-5ca1ab1e</strong></li>" +
|
||||
"<li>Tag None: <strong>tags » tag_none</strong></li>" +
|
||||
"</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: {
|
||||
label : "Custom Inventory Script",
|
||||
@ -149,6 +156,7 @@ export default
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
ngRequired: "source && source.value === 'custom'",
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
},
|
||||
custom_variables: {
|
||||
id: 'custom_variables',
|
||||
@ -269,7 +277,8 @@ export default
|
||||
dataTitle: 'Overwrite',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options'
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}, {
|
||||
name: 'overwrite_vars',
|
||||
label: 'Overwrite Variables',
|
||||
@ -283,7 +292,8 @@ export default
|
||||
dataTitle: 'Overwrite Variables',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options'
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}, {
|
||||
name: 'update_on_launch',
|
||||
label: 'Update on Launch',
|
||||
@ -296,7 +306,8 @@ export default
|
||||
dataTitle: 'Update on Launch',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options'
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}]
|
||||
},
|
||||
update_cache_timeout: {
|
||||
@ -321,11 +332,17 @@ export default
|
||||
|
||||
buttons: {
|
||||
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: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true
|
||||
ngDisabled: true,
|
||||
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -46,13 +46,15 @@ export default
|
||||
"</blockquote>",
|
||||
dataTitle: 'Host Name',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: 'body'
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
variables: {
|
||||
label: 'Variables',
|
||||
@ -83,10 +85,16 @@ export default
|
||||
buttons: {
|
||||
cancel: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
close: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
save: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true
|
||||
ngDisabled: true,
|
||||
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -26,14 +26,16 @@ export default
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: false
|
||||
capitalize: false,
|
||||
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
inventory_description: {
|
||||
realName: 'description',
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
label: 'Organization',
|
||||
@ -44,7 +46,8 @@ export default
|
||||
awRequiredWhen: {
|
||||
reqExpression: "organizationrequired",
|
||||
init: "true"
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
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>',
|
||||
dataTitle: 'Inventory Variables',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: 'body'
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
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: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true
|
||||
ngDisabled: true,
|
||||
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
@ -94,7 +104,8 @@ export default
|
||||
label: 'Add',
|
||||
awToolTip: 'Add a permission',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -27,14 +27,16 @@ export default
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
column: 1
|
||||
column: 1,
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
column: 1
|
||||
column: 1,
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
job_type: {
|
||||
label: 'Job Type',
|
||||
@ -56,7 +58,8 @@ export default
|
||||
variable: 'ask_job_type_on_launch',
|
||||
ngShow: "!job_type.value || job_type.value !== 'scan'",
|
||||
text: 'Prompt on launch'
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
inventory: {
|
||||
label: 'Inventory',
|
||||
@ -78,7 +81,8 @@ export default
|
||||
variable: 'ask_inventory_on_launch',
|
||||
ngShow: "!job_type.value || job_type.value !== 'scan'",
|
||||
text: 'Prompt on launch'
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
project: {
|
||||
label: 'Project',
|
||||
@ -100,12 +104,13 @@ export default
|
||||
dataTitle: 'Project',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
playbook: {
|
||||
label: 'Playbook',
|
||||
type:'select',
|
||||
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',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "playbookrequired",
|
||||
@ -138,7 +143,8 @@ export default
|
||||
subCheckbox: {
|
||||
variable: 'ask_credential_on_launch',
|
||||
text: 'Prompt on launch'
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
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>",
|
||||
dataTitle: 'Cloud Credential',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
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>",
|
||||
dataTitle: 'Network Credential',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
forks: {
|
||||
label: 'Forks',
|
||||
@ -186,7 +194,8 @@ export default
|
||||
' target=\"_blank\">ansible configuration file</a>.</p>',
|
||||
dataTitle: 'Forks',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
|
||||
},
|
||||
limit: {
|
||||
label: 'Limit',
|
||||
@ -203,7 +212,8 @@ export default
|
||||
subCheckbox: {
|
||||
variable: 'ask_limit_on_launch',
|
||||
text: 'Prompt on launch'
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
verbosity: {
|
||||
label: 'Verbosity',
|
||||
@ -216,7 +226,8 @@ export default
|
||||
awPopOver: "<p>Control the level of output ansible will produce as the playbook executes.</p>",
|
||||
dataTitle: 'Verbosity',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
job_tags: {
|
||||
label: 'Job Tags',
|
||||
@ -235,7 +246,8 @@ export default
|
||||
subCheckbox: {
|
||||
variable: 'ask_tags_on_launch',
|
||||
text: 'Prompt on launch'
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
skip_tags: {
|
||||
label: 'Skip Tags',
|
||||
@ -254,7 +266,8 @@ export default
|
||||
subCheckbox: {
|
||||
variable: 'ask_skip_tags_on_launch',
|
||||
text: 'Prompt on launch'
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
checkbox_group: {
|
||||
label: 'Options',
|
||||
@ -270,7 +283,8 @@ export default
|
||||
dataPlacement: 'right',
|
||||
dataTitle: 'Become Privilege Escalation',
|
||||
dataContainer: "body",
|
||||
labelClass: 'stack-inline'
|
||||
labelClass: 'stack-inline',
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}, {
|
||||
name: 'allow_callbacks',
|
||||
label: 'Allow Provisioning Callbacks',
|
||||
@ -284,7 +298,8 @@ export default
|
||||
dataPlacement: 'right',
|
||||
dataTitle: 'Allow Provisioning Callbacks',
|
||||
dataContainer: "body",
|
||||
labelClass: 'stack-inline'
|
||||
labelClass: 'stack-inline',
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}]
|
||||
},
|
||||
callback_url: {
|
||||
@ -299,7 +314,8 @@ export default
|
||||
awPopOverWatch: "callback_help",
|
||||
dataPlacement: 'top',
|
||||
dataTitle: 'Provisioning Callback URL',
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
host_config_key: {
|
||||
label: 'Host Config Key',
|
||||
@ -312,7 +328,8 @@ export default
|
||||
awPopOverWatch: "callback_help",
|
||||
dataPlacement: 'right',
|
||||
dataTitle: "Host Config Key",
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
labels: {
|
||||
label: 'Labels',
|
||||
@ -325,7 +342,8 @@ export default
|
||||
dataTitle: 'Labels',
|
||||
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>",
|
||||
dataContainer: 'body'
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
variables: {
|
||||
label: 'Extra Variables',
|
||||
@ -348,14 +366,15 @@ export default
|
||||
subCheckbox: {
|
||||
variable: 'ask_variables_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
|
||||
add_survey: {
|
||||
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',
|
||||
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'
|
||||
@ -363,14 +382,25 @@ export default
|
||||
edit_survey: {
|
||||
ngClick: 'editSurvey()',
|
||||
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: {
|
||||
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: {
|
||||
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',
|
||||
awToolTip: 'Add a permission',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -25,23 +25,31 @@ export default
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: false
|
||||
capitalize: false,
|
||||
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
buttons: { //for now always generates <button> tags
|
||||
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: {
|
||||
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',
|
||||
awToolTip: 'Add a permission',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -30,13 +30,15 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: false
|
||||
capitalize: false,
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
label: 'Organization',
|
||||
@ -50,7 +52,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
},
|
||||
dataTitle: 'Organization',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right'
|
||||
dataPlacement: 'right',
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
scm_type: {
|
||||
label: 'SCM Type',
|
||||
@ -61,6 +64,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
hasSubForm: true,
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
missing_path_alert: {
|
||||
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>',
|
||||
dataTitle: 'Project Base Path',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right'
|
||||
dataPlacement: 'right',
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
local_path: {
|
||||
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>',
|
||||
dataTitle: 'Project Path',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right'
|
||||
dataPlacement: 'right',
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
scm_url: {
|
||||
label: 'SCM URL',
|
||||
@ -115,7 +121,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
awPopOver: "set in controllers/projects",
|
||||
dataTitle: 'SCM URL',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right'
|
||||
dataPlacement: 'right',
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
scm_branch: {
|
||||
labelBind: "scmBranchLabel",
|
||||
@ -123,7 +130,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
ngShow: "scm_type && scm_type.value !== 'manual'",
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
subForm: 'sourceSubForm'
|
||||
subForm: 'sourceSubForm',
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
credential: {
|
||||
label: 'SCM Credential',
|
||||
@ -134,7 +142,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
ngClick: 'lookUpCredential()',
|
||||
addRequired: false,
|
||||
editRequired: false,
|
||||
subForm: 'sourceSubForm'
|
||||
subForm: 'sourceSubForm',
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
checkbox_group: {
|
||||
label: 'SCM Update Options',
|
||||
@ -151,7 +160,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
dataTitle: 'SCM Clean',
|
||||
dataContainer: 'body',
|
||||
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',
|
||||
label: 'Delete on Update',
|
||||
@ -163,7 +173,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
dataTitle: 'SCM Delete',
|
||||
dataContainer: 'body',
|
||||
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',
|
||||
label: 'Update on Launch',
|
||||
@ -174,7 +185,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
dataTitle: 'SCM Update',
|
||||
dataContainer: 'body',
|
||||
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: {
|
||||
@ -193,17 +205,24 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
'and a new project update will be performed.</p>',
|
||||
dataTitle: 'Cache Timeout',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body"
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
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: {
|
||||
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',
|
||||
awToolTip: 'Add a permission',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -25,13 +25,15 @@ export default
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: false
|
||||
capitalize: false,
|
||||
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
label: 'Organization',
|
||||
@ -44,17 +46,24 @@ export default
|
||||
awRequiredWhen: {
|
||||
reqExpression: "orgrequired",
|
||||
init: true
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
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: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true
|
||||
ngDisabled: true,
|
||||
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
@ -75,7 +84,8 @@ export default
|
||||
label: 'Add',
|
||||
awToolTip: 'Add user to team',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
@ -132,7 +142,8 @@ export default
|
||||
'class': "List-actionButton--delete",
|
||||
iconClass: 'fa fa-times',
|
||||
awToolTip: 'Dissasociate permission from team',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'permission.summary_fields.user_capabilities.unattach'
|
||||
}
|
||||
},
|
||||
hideOnSuperuser: true
|
||||
|
||||
@ -26,21 +26,24 @@ export default
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: true
|
||||
capitalize: true,
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
last_name: {
|
||||
label: 'Last Name',
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: true
|
||||
capitalize: true,
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
email: {
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
autocomplete: false
|
||||
autocomplete: false,
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
username: {
|
||||
label: 'Username',
|
||||
@ -49,7 +52,8 @@ export default
|
||||
reqExpression: "not_ldap_user && external_account === null",
|
||||
init: true
|
||||
},
|
||||
autocomplete: false
|
||||
autocomplete: false,
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
label: 'Organization',
|
||||
@ -63,7 +67,8 @@ export default
|
||||
awRequiredWhen: {
|
||||
reqExpression: "orgrequired",
|
||||
init: true
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
password: {
|
||||
label: 'Password',
|
||||
@ -74,7 +79,8 @@ export default
|
||||
editRequired: false,
|
||||
ngChange: "clearPWConfirm('password_confirm')",
|
||||
autocomplete: false,
|
||||
chkPass: true
|
||||
chkPass: true,
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
password_confirm: {
|
||||
label: 'Confirm Password',
|
||||
@ -85,7 +91,8 @@ export default
|
||||
editRequired: false,
|
||||
awPassMatch: true,
|
||||
associated: 'password',
|
||||
autocomplete: false
|
||||
autocomplete: false,
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
user_type: {
|
||||
label: 'User Type',
|
||||
@ -94,16 +101,23 @@ export default
|
||||
disableChooseOption: true,
|
||||
ngModel: 'user_type',
|
||||
ngShow: 'current_user["is_superuser"]',
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
},
|
||||
|
||||
buttons: {
|
||||
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: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true
|
||||
ngDisabled: true,
|
||||
ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
@ -186,7 +200,8 @@ export default
|
||||
label: 'Remove',
|
||||
ngClick: 'deletePermissionFromUser(user_id, username, permission.name, permission.summary_fields.resource_name, permission.related.users)',
|
||||
iconClass: 'fa fa-times',
|
||||
awToolTip: 'Dissasociate permission from user'
|
||||
awToolTip: 'Dissasociate permission from user',
|
||||
ngShow: 'permission.summary_fields.user_capabilities.unattach'
|
||||
}
|
||||
},
|
||||
hideOnSuperuser: true
|
||||
|
||||
@ -46,7 +46,7 @@ export default
|
||||
function createField(onChange, onReady, fld) {
|
||||
//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'].showTextArea({
|
||||
scope: scope,
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name helpers.function:ProjectPath
|
||||
@ -47,11 +47,13 @@ export default
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
var opts = [], i;
|
||||
for (i = 0; i < data.project_local_paths.length; i++) {
|
||||
opts.push({
|
||||
label: data.project_local_paths[i],
|
||||
value: data.project_local_paths[i]
|
||||
});
|
||||
if (data.project_local_paths) {
|
||||
for (i = 0; i < data.project_local_paths.length; i++) {
|
||||
opts.push({
|
||||
label: data.project_local_paths[i],
|
||||
value: data.project_local_paths[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
if (scope.local_path) {
|
||||
// List only includes paths not assigned to projects, so add the
|
||||
|
||||
@ -16,6 +16,15 @@ function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
|
||||
PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
|
||||
$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();
|
||||
|
||||
// Inject dynamic view
|
||||
|
||||
@ -17,7 +17,6 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
|
||||
ParseVariableString, RelatedSearchInit, RelatedPaginateInit,
|
||||
Prompt, InitiatePlaybookRun, CreateDialog, deleteJobTemplate, $state,
|
||||
$filter) {
|
||||
|
||||
ClearScope();
|
||||
|
||||
// Inject dynamic view
|
||||
@ -32,6 +31,13 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
|
||||
form.formLabelSize = null;
|
||||
form.formFieldSize = null;
|
||||
$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.reset();
|
||||
|
||||
@ -14,7 +14,14 @@ function InventoriesList($scope, $rootScope, $location, $log,
|
||||
$stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList,
|
||||
generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller,
|
||||
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,
|
||||
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',
|
||||
'$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList',
|
||||
'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
|
||||
'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', InventoriesList];
|
||||
'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'rbacUiControlService', InventoriesList];
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
|
||||
export default
|
||||
['$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,
|
||||
GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions){
|
||||
GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, rbacUiControlService){
|
||||
var generator = GenerateForm,
|
||||
form = GroupForm();
|
||||
|
||||
@ -16,6 +16,11 @@
|
||||
CredentialList = _.cloneDeep(CredentialList);
|
||||
CredentialList.fields.kind.noSearch = true;
|
||||
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
$scope.formCancel = function(){
|
||||
$state.go('^');
|
||||
};
|
||||
|
||||
@ -18,6 +18,12 @@
|
||||
CredentialList = _.cloneDeep(CredentialList);
|
||||
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(){
|
||||
$state.go('^');
|
||||
};
|
||||
@ -256,6 +262,7 @@
|
||||
$scope.credential_name = inventorySourceData.summary_fields.credential.name;
|
||||
}
|
||||
$scope = angular.extend($scope, groupData);
|
||||
$scope.group_obj = groupData;
|
||||
|
||||
// instantiate lookup fields
|
||||
if (inventorySourceData.source !== 'custom'){
|
||||
|
||||
@ -5,13 +5,22 @@
|
||||
*************************************************/
|
||||
export default
|
||||
['$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,
|
||||
InventoryManageService, groupsUrl, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg){
|
||||
InventoryManageService, groupsUrl, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg, Rest, GetBasePath, rbacUiControlService){
|
||||
var list = InventoryGroups,
|
||||
view = generateList,
|
||||
pageSize = 20;
|
||||
$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.
|
||||
// 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
|
||||
|
||||
@ -5,10 +5,18 @@
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService',
|
||||
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService){
|
||||
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'rbacUiControlService', 'GetBasePath',
|
||||
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, rbacUiControlService, GetBasePath){
|
||||
var generator = GenerateForm,
|
||||
form = HostForm;
|
||||
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/hosts")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.formCancel = function(){
|
||||
$state.go('^');
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host){
|
||||
var generator = GenerateForm,
|
||||
form = HostForm;
|
||||
|
||||
$scope.$watch('host.summary_fields.user_capabilities.edit', function(val) {
|
||||
if (val === false) {
|
||||
$scope.canAdd = false;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.formCancel = function(){
|
||||
$state.go('^');
|
||||
|
||||
@ -5,12 +5,23 @@
|
||||
*************************************************/
|
||||
export default
|
||||
['$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,
|
||||
hostsUrl, SearchInit, PaginateInit, SetStatus, Prompt, Wait, inventoryData, $filter){
|
||||
hostsUrl, SearchInit, PaginateInit, SetStatus, Prompt, Wait, inventoryData, $filter, Rest, GetBasePath, rbacUiControlService){
|
||||
|
||||
var list = InventoryHosts,
|
||||
view = generateList,
|
||||
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.
|
||||
// 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
|
||||
|
||||
@ -3,21 +3,26 @@
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
export default
|
||||
['$scope', '$state', function($scope, $state){
|
||||
$scope.groupsSelected = false;
|
||||
$scope.hostsSelected = false;
|
||||
$scope.hostsSelectedItems = [];
|
||||
$scope.groupsSelectedItems = [];
|
||||
$scope.setAdhocPattern = function(){
|
||||
var pattern = _($scope.groupsSelectedItems)
|
||||
.concat($scope.hostsSelectedItems)
|
||||
.map(function(item){
|
||||
return item.name;
|
||||
}).value().join(':');
|
||||
$state.go('inventoryManage.adhoc', {pattern: pattern});
|
||||
};
|
||||
export default
|
||||
['$scope', '$state', 'inventoryData', function($scope, $state, inventoryData){
|
||||
$scope.groupsSelected = false;
|
||||
$scope.hostsSelected = false;
|
||||
$scope.hostsSelectedItems = [];
|
||||
$scope.groupsSelectedItems = [];
|
||||
|
||||
$scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc;
|
||||
|
||||
$scope.setAdhocPattern = function(){
|
||||
var pattern = _($scope.groupsSelectedItems)
|
||||
.concat($scope.hostsSelectedItems)
|
||||
.map(function(item){
|
||||
return item.name;
|
||||
}).value().join(':');
|
||||
|
||||
$state.go('inventoryManage.adhoc', {pattern: pattern});
|
||||
};
|
||||
|
||||
$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.";
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
||||
@ -8,20 +8,27 @@ export default
|
||||
[ '$rootScope', 'pagination', '$compile','SchedulerInit', 'Rest', 'Wait',
|
||||
'inventoryScriptsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty',
|
||||
'GenerateForm', 'SearchInit' , 'PaginateInit',
|
||||
'LookUpInit', 'OrganizationList', '$scope', '$state',
|
||||
'LookUpInit', 'OrganizationList', '$scope', '$state', 'Alert',
|
||||
function(
|
||||
$rootScope, pagination, $compile, SchedulerInit, Rest, Wait,
|
||||
inventoryScriptsFormObject, ProcessErrors, GetBasePath, Empty,
|
||||
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,
|
||||
generator = GenerateForm,
|
||||
form = inventoryScriptsFormObject,
|
||||
url = GetBasePath('inventory_scripts');
|
||||
|
||||
$scope.canEdit = true;
|
||||
|
||||
generator.inject(form, {
|
||||
mode: 'add' ,
|
||||
scope:scope,
|
||||
|
||||
@ -17,6 +17,7 @@ export default
|
||||
LookUpInit, OrganizationList, inventory_script,
|
||||
$scope, $state
|
||||
) {
|
||||
|
||||
var generator = GenerateForm,
|
||||
id = inventory_script.id,
|
||||
form = inventoryScriptsFormObject,
|
||||
@ -24,6 +25,13 @@ export default
|
||||
url = GetBasePath('inventory_scripts');
|
||||
|
||||
$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, {
|
||||
mode: 'edit' ,
|
||||
scope:$scope,
|
||||
|
||||
@ -24,13 +24,15 @@ export default function() {
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: false
|
||||
capitalize: false,
|
||||
ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
label: 'Organization',
|
||||
@ -41,7 +43,8 @@ export default function() {
|
||||
},
|
||||
sourceModel: 'organization',
|
||||
sourceField: 'name',
|
||||
ngClick: 'lookUpOrganization()'
|
||||
ngClick: 'lookUpOrganization()',
|
||||
ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
script: {
|
||||
label: 'Custom Script',
|
||||
@ -51,7 +54,7 @@ export default function() {
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
awDropFile: true,
|
||||
ngDisabled: '!canEdit',
|
||||
ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
rows: 10,
|
||||
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>",
|
||||
@ -64,10 +67,16 @@ export default function() {
|
||||
buttons: { //for now always generates <button> tags
|
||||
cancel: {
|
||||
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: {
|
||||
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)'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -42,7 +42,8 @@ export default function(){
|
||||
ngClick: 'addCustomInv()',
|
||||
awToolTip: 'Create a new custom inventory',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -56,7 +57,16 @@ export default function(){
|
||||
label: 'Edit',
|
||||
"class": 'btn-sm',
|
||||
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": {
|
||||
ngClick: "deleteCustomInv(inventory_script.id, inventory_script.name)",
|
||||
@ -64,7 +74,8 @@ export default function(){
|
||||
label: 'Delete',
|
||||
"class": 'btn-sm',
|
||||
awToolTip: 'Delete inventory script',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'inventory_script.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -7,17 +7,24 @@
|
||||
export default
|
||||
[ '$rootScope','Wait', 'generateList', 'inventoryScriptsListObject',
|
||||
'GetBasePath' , 'SearchInit' , 'PaginateInit', 'Rest' , 'ProcessErrors',
|
||||
'Prompt', '$state', '$filter',
|
||||
'Prompt', '$state', '$filter', 'rbacUiControlService',
|
||||
function(
|
||||
$rootScope,Wait, GenerateList, inventoryScriptsListObject,
|
||||
GetBasePath, SearchInit, PaginateInit,
|
||||
Rest, ProcessErrors, Prompt, $state, $filter
|
||||
Rest, ProcessErrors, Prompt, $state, $filter, rbacUiControlService
|
||||
) {
|
||||
var scope = $rootScope.$new(),
|
||||
defaultUrl = GetBasePath('inventory_scripts'),
|
||||
list = inventoryScriptsListObject,
|
||||
view = GenerateList;
|
||||
|
||||
scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd("inventory_scripts")
|
||||
.then(function(canAdd) {
|
||||
scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
view.inject( list, {
|
||||
mode: 'edit',
|
||||
scope: scope
|
||||
|
||||
@ -21,6 +21,15 @@
|
||||
$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();
|
||||
// Inject dynamic view
|
||||
var defaultUrl = GetBasePath('job_templates'),
|
||||
|
||||
@ -36,6 +36,12 @@ export default
|
||||
|
||||
ClearScope();
|
||||
|
||||
$scope.$watch('job_template_obj.summary_fields.user_capabilities.edit', function(val) {
|
||||
if (val === false) {
|
||||
$scope.canAdd = false;
|
||||
}
|
||||
});
|
||||
|
||||
var defaultUrl = GetBasePath('job_templates'),
|
||||
generator = GenerateForm,
|
||||
form = JobTemplateForm(),
|
||||
|
||||
@ -10,18 +10,24 @@ export default
|
||||
'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
|
||||
'ProcessErrors', 'GetBasePath', 'JobTemplateForm', 'CredentialList',
|
||||
'LookUpInit', 'InitiatePlaybookRun', 'Wait', '$compile',
|
||||
'$state', '$filter',
|
||||
'$state', '$filter', 'rbacUiControlService',
|
||||
|
||||
function(
|
||||
$scope, $rootScope, $location, $log,
|
||||
$stateParams, Rest, Alert, JobTemplateList, GenerateList, Prompt,
|
||||
SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors,
|
||||
GetBasePath, JobTemplateForm, CredentialList, LookUpInit, InitiatePlaybookRun,
|
||||
Wait, $compile, $state, $filter
|
||||
Wait, $compile, $state, $filter, rbacUiControlService
|
||||
) {
|
||||
|
||||
ClearScope();
|
||||
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd("job_templates")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
var list = JobTemplateList,
|
||||
defaultUrl = GetBasePath('job_templates'),
|
||||
view = GenerateList,
|
||||
|
||||
@ -70,6 +70,12 @@
|
||||
flex: 0 0 637px;
|
||||
max-width: 637px;
|
||||
}
|
||||
|
||||
.SurveyMaker-previewPanel--viewOnly {
|
||||
flex: 0 0 1155px;
|
||||
max-width: 1155px;
|
||||
}
|
||||
|
||||
.SurveyMaker-separatorPanel {
|
||||
display: flex;
|
||||
flex: 0 0 51px;
|
||||
|
||||
@ -94,28 +94,33 @@ export default
|
||||
fieldActions: {
|
||||
|
||||
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: {
|
||||
icon: 'icon-rocket',
|
||||
mode: 'all',
|
||||
ngClick: 'relaunchJob($event, all_job.id)',
|
||||
awToolTip: 'Relaunch using the same parameters',
|
||||
dataPlacement: 'top',
|
||||
ngHide: "all_job.type == 'system_job' "
|
||||
ngShow: "!(all_job.type == 'system_job') && all_job.summary_fields.user_capabilities.start"
|
||||
},
|
||||
cancel: {
|
||||
mode: 'all',
|
||||
ngClick: 'deleteJob(all_job.id)',
|
||||
awToolTip: 'Cancel the job',
|
||||
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": {
|
||||
mode: 'all',
|
||||
ngClick: 'deleteJob(all_job.id)',
|
||||
awToolTip: 'Delete the job',
|
||||
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"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -100,13 +100,14 @@ export default
|
||||
ngClick: 'relaunchJob($event, completed_job.id)',
|
||||
awToolTip: 'Relaunch using the same parameters',
|
||||
dataPlacement: 'top',
|
||||
ngHide: "completed_job.type == 'system_job' "
|
||||
ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start"
|
||||
},
|
||||
"delete": {
|
||||
mode: 'all',
|
||||
ngClick: 'deleteJob(completed_job.id)',
|
||||
awToolTip: 'Delete the job',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'completed_job.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,7 +72,17 @@ export default
|
||||
label: 'Edit',
|
||||
"class": 'btn-sm',
|
||||
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": {
|
||||
@ -81,7 +91,8 @@ export default
|
||||
label: 'Delete',
|
||||
"class": 'btn-sm',
|
||||
awToolTip: 'Delete credential',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'credential.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -91,7 +91,8 @@ export default
|
||||
ngClick: 'addInventory()',
|
||||
awToolTip: 'Create a new inventory',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -103,13 +104,22 @@ export default
|
||||
label: 'Edit',
|
||||
ngClick: 'editInventory(inventory.id)',
|
||||
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": {
|
||||
label: 'Delete',
|
||||
ngClick: "deleteInventory(inventory.id, inventory.name)",
|
||||
awToolTip: 'Delete inventory',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'inventory.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -143,7 +143,8 @@ export default
|
||||
actionClass: 'btn List-buttonDefault',
|
||||
buttonContent: 'RUN COMMANDS',
|
||||
showTipWhenDisabled: true,
|
||||
tooltipInnerClass: "Tooltip-wide"
|
||||
tooltipInnerClass: "Tooltip-wide",
|
||||
ngShow: 'canAdhoc'
|
||||
// TODO: set up a tip watcher and change text based on when
|
||||
// things are selected/not selected. This is started and
|
||||
// commented out in the inventory controller within the watchers.
|
||||
@ -155,7 +156,8 @@ export default
|
||||
ngClick: "createGroup()",
|
||||
awToolTip: "Create a new group",
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD GROUP'
|
||||
buttonContent: '+ ADD GROUP',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -169,8 +171,8 @@ export default
|
||||
ngClick: 'updateGroup(group)',
|
||||
awToolTip: "{{ group.launch_tooltip }}",
|
||||
dataTipWatch: "group.launch_tooltip",
|
||||
ngShow: "group.status !== 'running' && group.status " +
|
||||
"!== 'pending' && group.status !== 'updating'",
|
||||
ngShow: "(group.status !== 'running' && group.status " +
|
||||
"!== 'pending' && group.status !== 'updating') && group.summary_fields.user_capabilities.start",
|
||||
ngClass: "group.launch_class",
|
||||
dataPlacement: "top",
|
||||
},
|
||||
@ -180,8 +182,8 @@ export default
|
||||
ngClick: "cancelUpdate(group.id)",
|
||||
awToolTip: "Cancel sync process",
|
||||
'class': 'red-txt',
|
||||
ngShow: "group.status == 'running' || group.status == 'pending' " +
|
||||
"|| group.status == 'updating'",
|
||||
ngShow: "(group.status == 'running' || group.status == 'pending' " +
|
||||
"|| group.status == 'updating') && group.summary_fields.user_capabilities.start",
|
||||
dataPlacement: "top",
|
||||
iconClass: "fa fa-minus-circle"
|
||||
},
|
||||
@ -189,7 +191,7 @@ export default
|
||||
mode: 'all',
|
||||
ngClick: "copyMoveGroup(group.id)",
|
||||
awToolTip: 'Copy or move group',
|
||||
ngShow: "group.id > 0",
|
||||
ngShow: "group.id > 0 && group.summary_fields.user_capabilities.copy",
|
||||
dataPlacement: "top"
|
||||
},
|
||||
schedule: {
|
||||
@ -198,21 +200,31 @@ export default
|
||||
awToolTip: "{{ group.group_schedule_tooltip }}",
|
||||
ngClass: "group.scm_type_class",
|
||||
dataPlacement: 'top',
|
||||
ngHide: "group.summary_fields.inventory_source.source === ''"
|
||||
ngShow: "!(group.summary_fields.inventory_source.source === '') && group.summary_fields.user_capabilities.schedule"
|
||||
},
|
||||
edit: {
|
||||
//label: 'Edit',
|
||||
mode: 'all',
|
||||
ngClick: "editGroup(group.id)",
|
||||
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": {
|
||||
//label: 'Delete',
|
||||
mode: 'all',
|
||||
ngClick: "deleteGroup(group)",
|
||||
awToolTip: 'Delete group',
|
||||
dataPlacement: "top"
|
||||
dataPlacement: "top",
|
||||
ngShow: "group.summary_fields.user_capabilities.delete"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -78,21 +78,31 @@ export default
|
||||
mode: 'all',
|
||||
ngClick: "copyMoveHost(host.id)",
|
||||
awToolTip: 'Copy or move host to another group',
|
||||
dataPlacement: "top"
|
||||
dataPlacement: "top",
|
||||
ngShow: 'host.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
edit: {
|
||||
//label: 'Edit',
|
||||
ngClick: "editHost(host.id)",
|
||||
icon: 'icon-edit',
|
||||
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": {
|
||||
//label: 'Delete',
|
||||
ngClick: "deleteHost(host.id, host.name)",
|
||||
icon: 'icon-trash',
|
||||
awToolTip: 'Delete host',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'host.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
},
|
||||
|
||||
@ -122,7 +132,8 @@ export default
|
||||
ngClick: "createHost()",
|
||||
awToolTip: "Create a new host",
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD HOST'
|
||||
buttonContent: '+ ADD HOST',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,8 @@ export default
|
||||
basePaths: ['job_templates'],
|
||||
awToolTip: 'Create a new template',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -68,7 +69,8 @@ export default
|
||||
mode: 'all',
|
||||
ngClick: 'submitJob(job_template.id)',
|
||||
awToolTip: 'Start a job using this template',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'job_template.summary_fields.user_capabilities.start'
|
||||
},
|
||||
schedule: {
|
||||
label: 'Schedule',
|
||||
@ -76,6 +78,7 @@ export default
|
||||
ngClick: 'scheduleJob(job_template.id)',
|
||||
awToolTip: 'Schedule future job template runs',
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'job_template.summary_fields.user_capabilities.schedule'
|
||||
},
|
||||
copy: {
|
||||
label: 'Copy',
|
||||
@ -83,7 +86,7 @@ export default
|
||||
"class": 'btn-danger btn-xs',
|
||||
awToolTip: 'Copy template',
|
||||
dataPlacement: 'top',
|
||||
ngHide: 'job_template.summary_fields.can_copy===false'
|
||||
ngShow: 'job_template.summary_fields.user_capabilities.copy'
|
||||
},
|
||||
edit: {
|
||||
label: 'Edit',
|
||||
@ -91,6 +94,15 @@ export default
|
||||
awToolTip: 'Edit template',
|
||||
"class": 'btn-default btn-xs',
|
||||
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": {
|
||||
label: 'Delete',
|
||||
@ -98,6 +110,7 @@ export default
|
||||
"class": 'btn-danger btn-xs',
|
||||
awToolTip: 'Delete template',
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'job_template.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -65,7 +65,8 @@ export default
|
||||
ngClick: 'addProject()',
|
||||
awToolTip: 'Create a new project',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: "canAdd"
|
||||
},
|
||||
refresh: {
|
||||
mode: 'all',
|
||||
@ -86,30 +87,40 @@ export default
|
||||
awToolTip: "{{ project.scm_update_tooltip }}",
|
||||
dataTipWatch: "project.scm_update_tooltip",
|
||||
ngClass: "project.scm_type_class",
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: "project.summary_fields.user_capabilities.start"
|
||||
},
|
||||
schedule: {
|
||||
mode: 'all',
|
||||
ngClick: "editSchedules(project.id)",
|
||||
awToolTip: "{{ project.scm_schedule_tooltip }}",
|
||||
ngClass: "project.scm_type_class",
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: "project.summary_fields.user_capabilities.schedule"
|
||||
},
|
||||
edit: {
|
||||
ngClick: "editProject(project.id)",
|
||||
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": {
|
||||
ngClick: "deleteProject(project.id, project.name)",
|
||||
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'
|
||||
},
|
||||
cancel: {
|
||||
ngClick: "cancelUpdate(project.id, project.name)",
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,13 +78,22 @@ export default
|
||||
mode: "all",
|
||||
ngClick: "editSchedule(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": {
|
||||
mode: 'all',
|
||||
ngClick: 'deleteSchedule(schedule.id)',
|
||||
awToolTip: 'Delete the schedule',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'schedule.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -69,7 +69,8 @@ export default
|
||||
ngClick: 'addSchedule()',
|
||||
awToolTip: 'Add a new schedule',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -79,14 +80,23 @@ export default
|
||||
ngClick: "editSchedule(schedule.id)",
|
||||
icon: 'icon-edit',
|
||||
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": {
|
||||
label: 'Delete',
|
||||
ngClick: "deleteSchedule(schedule.id)",
|
||||
icon: 'icon-trash',
|
||||
awToolTip: 'Delete schedule',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'schedule.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -47,7 +47,8 @@ export default
|
||||
ngClick: 'addTeam()',
|
||||
awToolTip: 'Create a new team',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -61,16 +62,25 @@ export default
|
||||
icon: 'icon-edit',
|
||||
"class": 'btn-xs btn-default',
|
||||
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": {
|
||||
label: 'Delete',
|
||||
ngClick: "deleteTeam(team.id, team.name)",
|
||||
icon: 'icon-trash',
|
||||
"class": 'btn-xs btn-danger',
|
||||
awToolTip: 'Delete team',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'team.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -44,7 +44,8 @@ export default
|
||||
basePaths: ['organizations', 'users'], // base path must be in list, or action not available
|
||||
awToolTip: 'Create a new user',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -58,7 +59,17 @@ export default
|
||||
icon: 'icon-edit',
|
||||
"class": 'btn-xs btn-default',
|
||||
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": {
|
||||
@ -67,7 +78,8 @@ export default
|
||||
icon: 'icon-trash',
|
||||
"class": 'btn-xs btn-danger',
|
||||
awToolTip: 'Delete user',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'user.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
<div class="MgmtCards-actionItems">
|
||||
<button class="MgmtCards-actionItem List-actionButton"
|
||||
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="">
|
||||
<i class="MgmtCards-actionItemIcon icon-launch"></i>
|
||||
</button>
|
||||
@ -27,6 +28,7 @@
|
||||
</button>
|
||||
<button class="MgmtCards-actionItem List-actionButton"
|
||||
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'}">
|
||||
<i class="MgmtCards-actionItemIcon fa fa-bell-o"></i>
|
||||
</button>
|
||||
|
||||
@ -9,14 +9,23 @@ export default
|
||||
'NotificationsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty',
|
||||
'GenerateForm', 'SearchInit' , 'PaginateInit', 'LookUpInit',
|
||||
'OrganizationList', '$scope', '$state', 'CreateSelect2', 'GetChoices',
|
||||
'NotificationsTypeChange', 'ParseTypeChange',
|
||||
'NotificationsTypeChange', 'ParseTypeChange', 'Alert',
|
||||
function(
|
||||
$rootScope, pagination, $compile, SchedulerInit, Rest, Wait,
|
||||
NotificationsFormObject, ProcessErrors, GetBasePath, Empty,
|
||||
GenerateForm, SearchInit, PaginateInit, LookUpInit,
|
||||
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,
|
||||
form = NotificationsFormObject,
|
||||
url = GetBasePath('notification_templates');
|
||||
|
||||
@ -26,6 +26,13 @@ export default
|
||||
url = GetBasePath('notification_templates');
|
||||
|
||||
$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, {
|
||||
mode: 'edit' ,
|
||||
scope:$scope,
|
||||
|
||||
@ -8,12 +8,12 @@ export default
|
||||
[ '$rootScope','Wait', 'generateList', 'NotificationTemplatesList',
|
||||
'GetBasePath' , 'SearchInit' , 'PaginateInit', 'Rest' ,
|
||||
'ProcessErrors', 'Prompt', '$state', 'GetChoices', 'Empty', 'Find',
|
||||
'ngToast', '$compile', '$filter',
|
||||
'ngToast', '$compile', '$filter', 'rbacUiControlService',
|
||||
function(
|
||||
$rootScope,Wait, GenerateList, NotificationTemplatesList,
|
||||
GetBasePath, SearchInit, PaginateInit, Rest,
|
||||
ProcessErrors, Prompt, $state, GetChoices, Empty, Find, ngToast,
|
||||
$compile, $filter) {
|
||||
$compile, $filter, rbacUiControlService) {
|
||||
var scope = $rootScope.$new(),
|
||||
defaultUrl = GetBasePath('notification_templates'),
|
||||
list = NotificationTemplatesList,
|
||||
@ -24,6 +24,13 @@ export default
|
||||
scope: scope
|
||||
});
|
||||
|
||||
scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd("notification_templates")
|
||||
.then(function(canAdd) {
|
||||
scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
scope.removePostRefresh = scope.$on('PostRefresh', function () {
|
||||
Wait('stop');
|
||||
if (scope.notification_templates) {
|
||||
|
||||
@ -27,13 +27,15 @@ export default function() {
|
||||
type: 'text',
|
||||
addRequired: true,
|
||||
editRequired: true,
|
||||
capitalize: false
|
||||
capitalize: false,
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
addRequired: false,
|
||||
editRequired: false
|
||||
editRequired: false,
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
label: 'Organization',
|
||||
@ -44,7 +46,8 @@ export default function() {
|
||||
awRequiredWhen: {
|
||||
reqExpression: "organizationrequired",
|
||||
init: "true"
|
||||
}
|
||||
},
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
notification_type: {
|
||||
label: 'Type',
|
||||
@ -54,13 +57,15 @@ export default function() {
|
||||
class: 'NotificationsForm-typeSelect',
|
||||
ngOptions: 'type.label for type in notification_type_options track by type.value',
|
||||
ngChange: 'typeChange()',
|
||||
hasSubForm: true
|
||||
hasSubForm: true,
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
username: {
|
||||
label: 'Username',
|
||||
type: 'text',
|
||||
ngShow: "notification_type.value == 'email' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
|
||||
host: {
|
||||
@ -71,7 +76,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'email' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
sender: {
|
||||
label: 'Sender Email',
|
||||
@ -81,7 +87,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'email' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
recipients: {
|
||||
label: 'Recipient List',
|
||||
@ -97,7 +104,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'email' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
password: {
|
||||
labelBind: 'passwordLabel',
|
||||
@ -108,7 +116,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'email' || notification_type.value == 'irc' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
port: {
|
||||
labelBind: 'portLabel',
|
||||
@ -122,7 +131,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'email' || notification_type.value == 'irc'",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
channels: {
|
||||
label: 'Destination Channels',
|
||||
@ -138,7 +148,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'slack'",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
rooms: {
|
||||
label: 'Destination Channels',
|
||||
@ -154,7 +165,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'hipchat'",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
token: {
|
||||
labelBind: 'tokenLabel',
|
||||
@ -165,7 +177,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
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: {
|
||||
label: 'Account Token',
|
||||
@ -176,7 +189,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'twilio' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
from_number: {
|
||||
label: 'Source Phone Number',
|
||||
@ -188,7 +202,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'twilio' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
to_numbers: {
|
||||
label: 'Destination SMS Number',
|
||||
@ -204,7 +219,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'twilio' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
account_sid: {
|
||||
label: 'Account SID',
|
||||
@ -214,7 +230,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'twilio' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
subdomain: {
|
||||
label: 'Pagerduty subdomain',
|
||||
@ -224,7 +241,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'pagerduty' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
service_key: {
|
||||
label: 'API Service/Integration Key',
|
||||
@ -234,7 +252,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'pagerduty' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
client_name: {
|
||||
label: 'Client Identifier',
|
||||
@ -244,7 +263,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'pagerduty' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
message_from: {
|
||||
label: 'Label to be shown with notification',
|
||||
@ -254,7 +274,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'hipchat' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
api_url: {
|
||||
label: 'API URL',
|
||||
@ -265,7 +286,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'hipchat' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
color: {
|
||||
label: 'Notification Color',
|
||||
@ -277,13 +299,15 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'hipchat' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
notify: {
|
||||
label: 'Notify Channel',
|
||||
type: 'checkbox',
|
||||
ngShow: "notification_type.value == 'hipchat' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
url: {
|
||||
label: 'Target URL',
|
||||
@ -293,7 +317,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'webhook' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
headers: {
|
||||
label: 'HTTP Headers',
|
||||
@ -313,7 +338,8 @@ export default function() {
|
||||
'</pre></p>',
|
||||
dataPlacement: 'right',
|
||||
ngShow: "notification_type.value == 'webhook' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
server: {
|
||||
label: 'IRC Server Address',
|
||||
@ -323,7 +349,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'irc' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
nickname: {
|
||||
label: 'IRC Nick',
|
||||
@ -333,7 +360,8 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'irc' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
targets: {
|
||||
label: 'Destination Channels or Users',
|
||||
@ -349,13 +377,15 @@ export default function() {
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'irc' ",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
use_ssl: {
|
||||
label: 'SSL Connection',
|
||||
type: 'checkbox',
|
||||
ngShow: "notification_type.value == 'irc'",
|
||||
subForm: 'typeSubForm'
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
checkbox_group: {
|
||||
label: 'Options',
|
||||
@ -367,13 +397,15 @@ export default function() {
|
||||
label: 'Use TLS',
|
||||
type: 'checkbox',
|
||||
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',
|
||||
label: 'Use SSL',
|
||||
type: 'checkbox',
|
||||
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
|
||||
cancel: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
close: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,8 @@ export default function(){
|
||||
ngClick: 'addNotification()',
|
||||
awToolTip: 'Create a new custom inventory',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD'
|
||||
buttonContent: '+ ADD',
|
||||
ngShow: 'canAdd'
|
||||
}
|
||||
},
|
||||
|
||||
@ -62,7 +63,8 @@ export default function(){
|
||||
label: 'Edit',
|
||||
"class": 'btn-sm',
|
||||
awToolTip: 'Test notification',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'notification_template.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
edit: {
|
||||
ngClick: "editNotification(notification_template.id)",
|
||||
@ -70,7 +72,16 @@ export default function(){
|
||||
label: 'Edit',
|
||||
"class": 'btn-sm',
|
||||
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": {
|
||||
ngClick: "deleteNotification(notification_template.id, notification_template.name)",
|
||||
@ -78,7 +89,8 @@ export default function(){
|
||||
label: 'Delete',
|
||||
"class": 'btn-sm',
|
||||
awToolTip: 'Delete notification',
|
||||
dataPlacement: 'top'
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'notification_template.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -65,7 +65,8 @@ export default function(){
|
||||
ngClick: 'addNotificationTemplate()',
|
||||
awToolTip: 'Create a new notification template',
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD NOTIFICATION TEMPLATE'
|
||||
buttonContent: '+ ADD NOTIFICATION TEMPLATE',
|
||||
ngShow: 'current_user.is_superuser || (current_user_admin_orgs && current_user_admin_orgs.length > 0)'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,13 +15,21 @@
|
||||
*/
|
||||
|
||||
export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', 'GetChoices',
|
||||
'$state',
|
||||
function(Wait, GetBasePath, ProcessErrors, Rest, GetChoices, $state) {
|
||||
'$state', '$rootScope',
|
||||
function(Wait, GetBasePath, ProcessErrors, Rest, GetChoices, $state, $rootScope) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
url = params.url,
|
||||
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(){
|
||||
$state.go('notifications.add');
|
||||
};
|
||||
|
||||
@ -12,6 +12,15 @@ export default ['$scope', '$rootScope', '$compile', '$location',
|
||||
$stateParams, OrganizationForm, GenerateForm, Rest, Alert, ProcessErrors,
|
||||
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();
|
||||
|
||||
// Inject dynamic view
|
||||
|
||||
@ -25,6 +25,12 @@ export default ['$scope', '$rootScope', '$compile', '$location',
|
||||
id = $stateParams.organization_id,
|
||||
relatedSets = {};
|
||||
|
||||
$scope.$watch('organization_obj.summary_fields.user_capabilities.edit', function(val) {
|
||||
if (val === false) {
|
||||
$scope.canAdd = false;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$parent.activeMode = 'edit';
|
||||
|
||||
$scope.$parent.activeCard = parseInt(id);
|
||||
|
||||
@ -8,15 +8,22 @@ export default ['$stateParams', '$scope', '$rootScope', '$location',
|
||||
'$log', '$compile', 'Rest', 'PaginateInit',
|
||||
'SearchInit', 'OrganizationList', 'Alert', 'Prompt', 'ClearScope',
|
||||
'ProcessErrors', 'GetBasePath', 'Wait',
|
||||
'$state', 'generateList', 'Refresh', '$filter',
|
||||
'$state', 'generateList', 'Refresh', '$filter', 'rbacUiControlService',
|
||||
function($stateParams, $scope, $rootScope, $location,
|
||||
$log, $compile, Rest, PaginateInit,
|
||||
SearchInit, OrganizationList, Alert, Prompt, ClearScope,
|
||||
ProcessErrors, GetBasePath, Wait,
|
||||
$state, generateList, Refresh, $filter) {
|
||||
$state, generateList, Refresh, $filter, rbacUiControlService) {
|
||||
|
||||
ClearScope();
|
||||
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd("organizations")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
var defaultUrl = GetBasePath('organizations'),
|
||||
list = OrganizationList,
|
||||
pageSize = 24,
|
||||
@ -25,6 +32,7 @@ export default ['$stateParams', '$scope', '$rootScope', '$location',
|
||||
var parseCardData = function(cards) {
|
||||
return cards.map(function(card) {
|
||||
var val = {}, url = '/#/organizations/' + card.id + '/';
|
||||
val.user_capabilities = card.summary_fields.user_capabilities;
|
||||
val.name = card.name;
|
||||
val.id = card.id;
|
||||
val.description = card.description || undefined;
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
<div class="List-actions">
|
||||
<button class="btn List-buttonSubmit"
|
||||
aw-tool-tip="Create a new organization"
|
||||
ng-show="canAdd"
|
||||
ng-click="addOrganization()">
|
||||
+ ADD
|
||||
</button>
|
||||
@ -31,13 +32,23 @@
|
||||
<div class="OrgCards-actionItems">
|
||||
<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-pencil">
|
||||
</i>
|
||||
</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
|
||||
List-actionButton--delete"
|
||||
ng-show="card.user_capabilities.delete"
|
||||
ng-click="deleteOrganization(card.id, card.name)">
|
||||
<i class="OrgCards-actionItemIcon
|
||||
fa fa-trash-o">
|
||||
|
||||
@ -32,13 +32,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
<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 class="SurveyMaker-panelHeader">PREVIEW</div>
|
||||
<div class="SurveyMaker-panelBody">
|
||||
@ -56,13 +56,13 @@
|
||||
<i>{{question.question_description}}</i>
|
||||
</div>
|
||||
<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>
|
||||
<span> </span>
|
||||
<i class="fa fa-ellipsis-v"></i>
|
||||
</span>
|
||||
<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="">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
@ -80,9 +80,10 @@
|
||||
</div>
|
||||
<div class="SurveyMaker-panelFooter">
|
||||
<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-close-button" class="btn btn-sm Form-buttonDefault" ng-click="closeSurvey('survey-modal-dialog')">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-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')" ng-show="(job_template_obj.summary_fields.user_capabilities.edit || canAdd)">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)">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>
|
||||
|
||||
@ -14,11 +14,11 @@
|
||||
export default [
|
||||
'$scope', '$compile', '$location', '$stateParams', 'SchedulesList', 'Rest',
|
||||
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'Wait',
|
||||
'Find', 'LoadSchedulesScope', 'GetChoices', '$q', '$state',
|
||||
'Find', 'LoadSchedulesScope', 'GetChoices', '$q', '$state', 'rbacUiControlService',
|
||||
function ($scope, $compile, $location, $stateParams,
|
||||
SchedulesList, Rest, ProcessErrors, ReturnToCaller, ClearScope,
|
||||
GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices,
|
||||
$q, $state) {
|
||||
$q, $state, rbacUiControlService) {
|
||||
var schedList = _.cloneDeep(SchedulesList);
|
||||
|
||||
ClearScope();
|
||||
@ -48,6 +48,14 @@ export default [
|
||||
}
|
||||
$scope.removeParentLoaded = $scope.$on('ParentLoaded', function() {
|
||||
url += "schedules/";
|
||||
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd(url)
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
schedList.well = true;
|
||||
|
||||
// include name of item in listTitle
|
||||
|
||||
@ -9,7 +9,6 @@ export default ['$compile', '$filter', '$state', '$stateParams', 'AddSchedule',
|
||||
'Rest', 'ParamPass',
|
||||
function($compile, $filter, $state, $stateParams, AddSchedule, Wait, $scope,
|
||||
$rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParamPass) {
|
||||
|
||||
$scope.processSchedulerEndDt = function(){
|
||||
// set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight
|
||||
var dt = new Date($scope.schedulerUTCTime);
|
||||
|
||||
@ -865,6 +865,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
html += ">";
|
||||
html += "<input type=\"checkbox\" ng-model=\"" + field.subCheckbox.variable + "\" ";
|
||||
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 += ">";
|
||||
html += field.subCheckbox.text ? field.subCheckbox.text : "";
|
||||
@ -987,6 +988,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
if (field.subCheckbox.ngDisabled) {
|
||||
html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'";
|
||||
}
|
||||
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
|
||||
html += ">";
|
||||
html += field.subCheckbox.text ? field.subCheckbox.text : "";
|
||||
html += "</label>";
|
||||
@ -1084,6 +1086,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
if (field.subCheckbox.ngDisabled) {
|
||||
html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'";
|
||||
}
|
||||
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
|
||||
html += ">";
|
||||
html += field.subCheckbox.text ? field.subCheckbox.text : "";
|
||||
html += "</label>";
|
||||
@ -1151,6 +1154,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
if (field.subCheckbox.ngDisabled) {
|
||||
html += "ng-disabled='" + field.subCheckbox.ngDisabled + "'";
|
||||
}
|
||||
html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
|
||||
html += ">";
|
||||
html += field.subCheckbox.text ? field.subCheckbox.text : "";
|
||||
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.max) ? this.attr(field, 'max') : "";
|
||||
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 += (options.mode === 'edit' && field.editRequired) ? "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=\"" +
|
||||
field.subCheckbox.variable + "\" ";
|
||||
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 += ">";
|
||||
html += field.subCheckbox.text ? field.subCheckbox.text : "";
|
||||
@ -1422,6 +1428,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
html += "<input type=\"checkbox\" ng-model=\"" +
|
||||
field.subCheckbox.variable + "\" ";
|
||||
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 += ">";
|
||||
html += field.subCheckbox.text ? field.subCheckbox.text : "";
|
||||
@ -1693,6 +1700,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
button.label = 'Cancel';
|
||||
button['class'] = 'Form-cancelButton';
|
||||
}
|
||||
if (btn === 'close') {
|
||||
button.label = 'Close';
|
||||
button['class'] = 'Form-cancelButton';
|
||||
}
|
||||
if (btn === 'launch') {
|
||||
button.label = 'Launch';
|
||||
button['class'] = 'Form-launchButton';
|
||||
@ -1705,6 +1716,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
button.label = 'Edit Survey';
|
||||
button['class'] = 'Form-surveyButton';
|
||||
}
|
||||
if (btn === 'view_survey') {
|
||||
button.label = 'View Survey';
|
||||
button['class'] = 'Form-surveyButton';
|
||||
}
|
||||
|
||||
// Build button HTML
|
||||
html += "<button type=\"button\" ";
|
||||
|
||||
@ -11,12 +11,14 @@ import lodashAsPromised from './lodash-as-promised';
|
||||
import stringFilters from './string-filters/main';
|
||||
import truncatedText from './truncated-text.directive';
|
||||
import stateExtender from './stateExtender.provider';
|
||||
import rbacUiControl from './rbacUiControl';
|
||||
|
||||
export default
|
||||
angular.module('shared', [listGenerator.name,
|
||||
pagination.name,
|
||||
stringFilters.name,
|
||||
'ui.router'
|
||||
'ui.router',
|
||||
rbacUiControl.name
|
||||
])
|
||||
.factory('lodashAsPromised', lodashAsPromised)
|
||||
.directive('truncatedText', truncatedText)
|
||||
|
||||
32
awx/ui/client/src/shared/rbacUiControl.js
Normal file
32
awx/ui/client/src/shared/rbacUiControl.js
Normal 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;
|
||||
};
|
||||
}]);
|
||||
@ -9,13 +9,13 @@ module.exports = function(config) {
|
||||
browsers: ['Chrome', 'Firefox'],
|
||||
coverageReporter: {
|
||||
reporters: [
|
||||
{ type: 'html', subdir: 'html' }
|
||||
{ type: 'html', subdir: 'html' },
|
||||
]
|
||||
},
|
||||
frameworks: [
|
||||
'jasmine',
|
||||
],
|
||||
reporters: ['progress', 'coverage'],
|
||||
reporters: ['progress', 'coverage', 'junit'],
|
||||
files: [
|
||||
'./client/src/app.js',
|
||||
'./node_modules/angular-mocks/angular-mocks.js',
|
||||
@ -86,7 +86,9 @@ module.exports = function(config) {
|
||||
}
|
||||
},
|
||||
junitReporter: {
|
||||
outputFile: 'coverage/test-results.xml'
|
||||
outputDir: 'coverage',
|
||||
outputFile: 'ui-unit-test-results.xml',
|
||||
useBrowserName: false
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
618
awx/ui/npm-shrinkwrap.json
generated
618
awx/ui/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,7 @@
|
||||
"expose-loader": "^0.7.1",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-browser-sync": "^2.2.0",
|
||||
"grunt-cli": "^1.2.0",
|
||||
"grunt-concurrent": "^2.3.0",
|
||||
"grunt-contrib-clean": "^1.0.0",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
@ -56,12 +57,15 @@
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-html2js-preprocessor": "^1.0.0",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-junit-reporter": "^1.1.0",
|
||||
"karma-phantomjs-launcher": "^1.0.2",
|
||||
"karma-sauce-launcher": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^1.8.0",
|
||||
"less-plugin-autoprefix": "^1.4.2",
|
||||
"load-grunt-configs": "^1.0.0",
|
||||
"load-grunt-tasks": "^3.5.0",
|
||||
"phantomjs-prebuilt": "^2.1.12",
|
||||
"time-grunt": "^1.4.0",
|
||||
"webpack": "^1.13.1",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
|
||||
@ -29,12 +29,34 @@ RUN yum install -y \
|
||||
# Remove the 2 lines below and uncomment the 3 lines above to build
|
||||
# RPMs with the old JS build system.
|
||||
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"
|
||||
|
||||
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"]
|
||||
CMD ["bash"]
|
||||
|
||||
@ -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.
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
|
||||
@ -7,7 +7,7 @@ services:
|
||||
dockerfile: tools/docker-compose/unit-tests/Dockerfile
|
||||
environment:
|
||||
SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl"
|
||||
command: ["make requirements_test test"]
|
||||
|
||||
TEST_DIRS: "awx/main/tests/unit"
|
||||
command: ["make test"]
|
||||
volumes:
|
||||
- ../../../:/ansible-tower
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user