Merge branch 'devel' into 3201-labels-pagination

This commit is contained in:
Michael Abashian 2017-06-27 11:59:12 -04:00 committed by GitHub
commit 49cf8f1e75
97 changed files with 1469 additions and 924 deletions

View File

@ -353,6 +353,7 @@ init:
if [ "$(EXTRA_GROUP_QUEUES)" == "thepentagon" ]; then \
tower-manage register_instance --hostname=isolated; \
tower-manage register_queue --queuename='thepentagon' --hostnames=isolated --controller=tower; \
tower-manage generate_isolated_key | ssh -o "StrictHostKeyChecking no" root@isolated 'cat > /root/.ssh/authorized_keys'; \
elif [ "$(EXTRA_GROUP_QUEUES)" != "" ]; then \
tower-manage register_queue --queuename=$(EXTRA_GROUP_QUEUES) --hostnames=$(COMPOSE_HOST); \
fi;

View File

@ -155,6 +155,47 @@ register(
category_slug='jobs',
)
register(
'AWX_ISOLATED_CHECK_INTERVAL',
field_class=fields.IntegerField,
label=_('Isolated status check interval'),
help_text=_('The number of seconds to sleep between status checks for jobs running on isolated instances.'), # noqa
category=_('Jobs'),
category_slug='jobs',
)
register(
'AWX_ISOLATED_LAUNCH_TIMEOUT',
field_class=fields.IntegerField,
label=_('Isolated launch timeout'),
help_text=_('The timeout (in seconds) for launching jobs on isolated instances. This includes the time needed to copy source control files (playbooks) to the isolated instance.'),
category=_('Jobs'),
category_slug='jobs',
)
register(
'AWX_ISOLATED_PRIVATE_KEY',
field_class=fields.CharField,
default='',
allow_blank=True,
encrypted=True,
label=_('The RSA private key for SSH traffic to isolated instances'),
help_text=_('The RSA private key for SSH traffic to isolated instances'), # noqa
category=_('Jobs'),
category_slug='jobs',
)
register(
'AWX_ISOLATED_PUBLIC_KEY',
field_class=fields.CharField,
default='',
allow_blank=True,
label=_('The RSA public key for SSH traffic to isolated instances'),
help_text=_('The RSA public key for SSH traffic to isolated instances'), # noqa
category=_('Jobs'),
category_slug='jobs',
)
register(
'STDOUT_MAX_BYTES_DISPLAY',
field_class=fields.IntegerField,

View File

@ -5,7 +5,9 @@ import StringIO
import json
import os
import re
import shutil
import stat
import tempfile
import time
import logging
@ -141,7 +143,7 @@ class IsolatedManager(object):
args.append('-%s' % ('v' * min(5, self.instance.verbosity)))
buff = StringIO.StringIO()
logger.debug('Starting job on isolated host with `run_isolated.yml` playbook.')
status, rc = run.run_pexpect(
status, rc = IsolatedManager.run_pexpect(
args, self.awx_playbook_path(), self.env, buff,
expect_passwords={
re.compile(r'Secret:\s*?$', re.M): base64.b64encode(json.dumps(secrets))
@ -154,6 +156,22 @@ class IsolatedManager(object):
self.stdout_handle.write(buff.getvalue())
return status, rc
@classmethod
def run_pexpect(cls, pexpect_args, *args, **kw):
isolated_ssh_path = None
try:
if getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None):
isolated_ssh_path = tempfile.mkdtemp(prefix='ansible_tower_isolated')
os.chmod(isolated_ssh_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
isolated_key = os.path.join(isolated_ssh_path, '.isolated')
ssh_sock = os.path.join(isolated_ssh_path, '.isolated_ssh_auth.sock')
run.open_fifo_write(isolated_key, settings.AWX_ISOLATED_PRIVATE_KEY)
pexpect_args = run.wrap_args_with_ssh_agent(pexpect_args, isolated_key, ssh_sock)
return run.run_pexpect(pexpect_args, *args, **kw)
finally:
if isolated_ssh_path:
shutil.rmtree(isolated_ssh_path)
def build_isolated_job_data(self):
'''
Write the playbook and metadata into a collection of files on the local
@ -251,7 +269,7 @@ class IsolatedManager(object):
buff = cStringIO.StringIO()
logger.debug('Checking job on isolated host with `check_isolated.yml` playbook.')
status, rc = run.run_pexpect(
status, rc = IsolatedManager.run_pexpect(
args, self.awx_playbook_path(), self.env, buff,
cancelled_callback=self.cancelled_callback,
idle_timeout=remaining,
@ -302,7 +320,7 @@ class IsolatedManager(object):
json.dumps(extra_vars)]
logger.debug('Cleaning up job on isolated host with `clean_isolated.yml` playbook.')
buff = cStringIO.StringIO()
status, rc = run.run_pexpect(
status, rc = IsolatedManager.run_pexpect(
args, self.awx_playbook_path(), self.env, buff,
idle_timeout=60, job_timeout=60,
pexpect_timeout=5
@ -333,7 +351,7 @@ class IsolatedManager(object):
env['ANSIBLE_STDOUT_CALLBACK'] = 'json'
buff = cStringIO.StringIO()
status, rc = run.run_pexpect(
status, rc = IsolatedManager.run_pexpect(
args, cls.awx_playbook_path(), env, buff,
idle_timeout=60, job_timeout=60,
pexpect_timeout=5
@ -357,7 +375,7 @@ class IsolatedManager(object):
continue
if 'capacity' in task_result:
instance.capacity = int(task_result['capacity'])
instance.save(update_fields=['capacity'])
instance.save(update_fields=['capacity', 'modified'])
else:
logger.warning('Could not update capacity of {}, msg={}'.format(
instance.hostname, task_result.get('msg', 'unknown failure')))

View File

@ -0,0 +1,45 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved
import datetime
import sys
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from django.conf import settings
from django.core.management.base import BaseCommand
from awx.conf.models import Setting
class Command(BaseCommand):
"""Generate and store a randomized RSA key for SSH traffic to isolated instances"""
help = 'Generates and stores a randomized RSA key for SSH traffic to isolated instances'
def handle(self, *args, **kwargs):
if getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', False):
print settings.AWX_ISOLATED_PUBLIC_KEY
sys.exit(1)
key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
backend=default_backend()
)
Setting.objects.create(
key='AWX_ISOLATED_PRIVATE_KEY',
value=key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
).save()
pemfile = Setting.objects.create(
key='AWX_ISOLATED_PUBLIC_KEY',
value=key.public_key().public_bytes(
encoding=serialization.Encoding.OpenSSH,
format=serialization.PublicFormat.OpenSSH
) + " generated-by-awx@%s" % datetime.datetime.utcnow().isoformat()
)
pemfile.save()
print pemfile.value

View File

@ -609,7 +609,8 @@ AWX_ISOLATED_CHECK_INTERVAL = 30
# The timeout (in seconds) for launching jobs on isolated nodes
AWX_ISOLATED_LAUNCH_TIMEOUT = 600
# The time between the background isolated heartbeat status check
# The time (in seconds) between the periodic isolated heartbeat status check
AWX_ISOLATED_PERIODIC_CHECK = 600
# Enable Pendo on the UI, possible values are 'off', 'anonymous', and 'detailed'

View File

@ -7,8 +7,11 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
# Django REST Framework
from rest_framework import serializers
# Tower
from awx.conf import register
from awx.conf import register, register_validate
from awx.sso import fields
from awx.main.validators import validate_private_key, validate_certificate
from awx.sso.validators import * # noqa
@ -1083,3 +1086,23 @@ register(
placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
feature_required='enterprise_auth',
)
def tacacs_validate(serializer, attrs):
if not serializer.instance:
return attrs
errors = []
host = serializer.instance.TACACSPLUS_HOST
if 'TACACSPLUS_HOST' in attrs:
host = attrs['TACACSPLUS_HOST']
secret = serializer.instance.TACACSPLUS_SECRET
if 'TACACSPLUS_SECRET' in attrs:
secret = attrs['TACACSPLUS_SECRET']
if bool(host) ^ bool(secret):
errors.append('TACACSPLUS_HOST and TACACSPLUS_SECRET can only be both empty or both populated.')
if errors:
raise serializers.ValidationError(_('\n'.join(errors)))
return attrs
register_validate('tacacsplus', tacacs_validate)

View File

@ -6,7 +6,6 @@ function AddCredentialsController (models, $state) {
let me = models.me;
let credential = models.credential;
let credentialType = models.credentialType;
let organization = models.organization;
vm.panelTitle = 'NEW CREDENTIAL';
@ -23,22 +22,18 @@ function AddCredentialsController (models, $state) {
omit: ['user', 'team', 'inputs']
});
vm.form.organization._placeholder = DEFAULT_ORGANIZATION_PLACEHOLDER;
vm.form.organization._data = organization.get('results');
vm.form.organization._format = 'objects';
vm.form.organization._exp = 'org as org.name for org in state._data';
vm.form.organization._display = 'name';
vm.form.organization._key = 'id';
vm.form.organization._resource = 'organization';
vm.form.organization._route = 'credentials.add.organization';
vm.form.credential_type._data = credentialType.get('results');
vm.form.credential_type._placeholder = 'SELECT A TYPE';
vm.form.credential_type._format = 'grouped-object';
vm.form.credential_type._display = 'name';
vm.form.credential_type._key = 'id';
vm.form.credential_type._exp = 'type as type.name group by type.kind for type in state._data';
vm.form.credential_type._resource = 'credential_type';
vm.form.credential_type._route = 'credentials.add.credentialType';
vm.form.inputs = {
_get: credentialType.mergeInputProperties,
_get: id => {
let type = credentialType.getById(id);
return credentialType.mergeInputProperties(type);
},
_source: vm.form.credential_type,
_reference: 'vm.form.inputs',
_key: 'inputs'

View File

@ -1,4 +1,4 @@
<at-panel ng-if="$state.current.name === 'credentials.add' || $state.current.name === 'credentials.edit'">
<at-panel ng-if="!$state.current.name.includes('permissions')">
<at-panel-heading>{{ vm.panelTitle }}</at-panel-heading>
<at-tab-group>
@ -10,27 +10,26 @@
<at-form state="vm.form">
<at-input-text col="4" tab="1" state="vm.form.name"></at-input-text>
<at-input-text col="4" tab="2" state="vm.form.description"></at-input-text>
<at-input-select col="4" tab="3" state="vm.form.organization"></at-input-select>
<at-input-lookup col="4" tab="3" state="vm.form.organization"></at-input-lookup>
<at-divider></at-divider>
<at-input-select col="4" tab="4" state="vm.form.credential_type"></at-input-select>
<at-input-lookup col="4" tab="4" state="vm.form.credential_type"></at-input-lookup>
<at-input-group col="4" tab="4" state="vm.form.inputs">
<at-input-group col="4" tab="5" state="vm.form.inputs">
Type Details
</at-input-group>
<at-action-group col="12" pos="right">
<at-form-action type="cancel"></at-form-action>
<at-form-action type="cancel" to="credentials"></at-form-action>
<at-form-action type="save"></at-form-action>
</at-action-group>
</at-form>
</at-panel-body>
</at-panel>
<at-panel ng-if="$state.current.name === 'credentials.edit.permissions' ||
$state.current.name === 'credentials.edit.permissions.add'">
<at-panel-heading>Credentials Permissions</at-panel-heading>
<at-panel ng-if="$state.current.name.includes('permissions')">
<at-panel-heading>CREDENTIALS PERMISSIONS</at-panel-heading>
<at-tab-group>
<at-tab state="vm.tab.details">Details</at-tab>
@ -42,5 +41,5 @@
</at-panel-body>
</at-panel>
<div ng-if="$state.current.name === 'credentials.edit.permissions.add'" ui-view="modal"></div>
<div ng-if="$state.current.name.includes('permissions.add')" ui-view="modal"></div>

View File

@ -6,7 +6,7 @@ function EditCredentialsController (models, $state, $scope) {
let me = models.me;
let credential = models.credential;
let credentialType = models.credentialType;
let organization = models.organization;
let selectedCredentialType = credentialType.getById(credential.get('credential_type'));
vm.tab = {
details: {
@ -21,9 +21,9 @@ function EditCredentialsController (models, $state, $scope) {
};
$scope.$watch('$state.current.name', (value) => {
if (value === 'credentials.edit') {
if (/credentials.edit($|\.organization$)/.test(value)) {
vm.tab.details._active = true;
vm.tab.details._permissions = false;
vm.tab.permissions._active = false;
} else {
vm.tab.permissions._active = true;
vm.tab.details._active = false;
@ -39,23 +39,19 @@ function EditCredentialsController (models, $state, $scope) {
omit: ['user', 'team', 'inputs']
});
vm.form.organization._placeholder = DEFAULT_ORGANIZATION_PLACEHOLDER;
vm.form.organization._data = organization.get('results');
vm.form.organization._format = 'objects';
vm.form.organization._exp = 'org as org.name for org in state._data';
vm.form.organization._display = 'name';
vm.form.organization._key = 'id';
vm.form.organization._value = organization.getById(credential.get('organization'));
vm.form.credential_type._data = credentialType.get('results');
vm.form.credential_type._format = 'grouped-object';
vm.form.credential_type._display = 'name';
vm.form.credential_type._key = 'id';
vm.form.credential_type._exp = 'type as type.name group by type.kind for type in state._data';
vm.form.credential_type._value = credentialType.getById(credential.get('credential_type'));
vm.form.organization._resource = 'organization';
vm.form.organization._route = 'credentials.edit.organization';
vm.form.organization._value = credential.get('summary_fields.organization.id');
vm.form.organization._displayValue = credential.get('summary_fields.organization.name');
vm.form.credential_type._resource = 'credential_type';
vm.form.credential_type._route = 'credentials.edit.credentialType';
vm.form.credential_type._value = selectedCredentialType.id;
vm.form.credential_type._displayValue = selectedCredentialType.name;
vm.form.inputs = {
_get (type) {
_get (id) {
let type = credentialType.getById(id);
let inputs = credentialType.mergeInputProperties(type);
if (type.id === credential.get('credential_type')) {
@ -77,7 +73,7 @@ function EditCredentialsController (models, $state, $scope) {
};
vm.form.onSaveSuccess = res => {
$state.go('credentials', { reload: true });
$state.go('credentials.edit', { credential_id: credential.get('id') }, { reload: true });
};
}

View File

@ -1,18 +1,14 @@
import PermissionsList from '../../src/access/permissions-list.controller';
import CredentialForm from '../../src/credentials/credentials.form';
import CredentialList from '../../src/credentials/credentials.list';
import ListController from '../../src/credentials/list/credentials-list.controller';
import LegacyCredentials from './legacy.credentials';
import AddController from './add-credentials.controller.js';
import EditController from './edit-credentials.controller.js';
import { N_ } from '../../src/i18n';
function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, Organization) {
function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType) {
let id = $stateParams.credential_id;
let promises = {
me: new Me('get'),
credentialType: new CredentialType('get'),
organization: new Organization('get')
credentialType: new CredentialType('get')
};
if (id) {
@ -29,46 +25,13 @@ CredentialsResolve.$inject = [
'$stateParams',
'MeModel',
'CredentialModel',
'CredentialTypeModel',
'OrganizationModel'
'CredentialTypeModel'
];
function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceProvider) {
let pathService = pathServiceProvider.$get();
function CredentialsConfig ($stateExtenderProvider, legacyProvider, pathProvider) {
let path = pathProvider.$get();
let stateExtender = $stateExtenderProvider.$get();
stateExtender.addState({
name: 'credentials',
route: '/credentials',
ncyBreadcrumb: {
label: N_('CREDENTIALS')
},
views: {
'@': {
templateUrl: pathService.getViewPath('credentials/index')
},
'list@credentials': {
templateProvider: function(CredentialList, generateList) {
let html = generateList.build({
list: CredentialList,
mode: 'edit'
});
return html;
},
controller: ListController
}
},
searchPrefix: 'credential',
resolve: {
Dataset: ['CredentialList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
}
});
let legacy = legacyProvider.$get();
stateExtender.addState({
name: 'credentials.add',
@ -78,7 +41,7 @@ function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceP
},
views: {
'add@credentials': {
templateUrl: pathService.getViewPath('credentials/add-edit-credentials'),
templateUrl: path.getViewPath('credentials/add-edit-credentials'),
controller: AddController,
controllerAs: 'vm'
}
@ -96,7 +59,7 @@ function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceP
},
views: {
'edit@credentials': {
templateUrl: pathService.getViewPath('credentials/add-edit-credentials'),
templateUrl: path.getViewPath('credentials/add-edit-credentials'),
controller: EditController,
controllerAs: 'vm'
}
@ -106,178 +69,24 @@ function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceP
}
});
stateExtender.addState({
name: "credentials.edit.permissions",
url: "/permissions?{permission_search:queryset}",
resolve: {
ListDefinition: () => {
return {
name: 'permissions',
disabled: '(organization === undefined ? true : false)',
// Do not transition the state if organization is undefined
ngClick: `(organization === undefined ? true : false)||$state.go('credentials.edit.permissions')`,
awToolTip: '{{permissionsTooltip}}',
dataTipWatch: 'permissionsTooltip',
awToolTipTabEnabledInEditMode: true,
dataPlacement: 'right',
basePath: 'api/v2/credentials/{{$stateParams.id}}/access_list/',
search: {
order_by: 'username'
},
type: 'collection',
title: N_('Permissions'),
iterator: 'permission',
index: false,
open: false,
actions: {
add: {
ngClick: "$state.go('.add')",
label: 'Add',
awToolTip: N_('Add a permission'),
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ' + N_('ADD'),
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
fields: {
username: {
key: true,
label: N_('User'),
linkBase: 'users',
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
},
role: {
label: N_('Role'),
type: 'role',
nosort: true,
class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4'
},
team_roles: {
label: N_('Team Roles'),
type: 'team_roles',
nosort: true,
class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4'
}
}
};
},
Dataset: ['QuerySet', '$stateParams', (qs, $stateParams) => {
let id = $stateParams.credential_id;
let path = `api/v2/credentials/${id}/access_list/`;
return qs.search(path, $stateParams[`permission_search`]);
}
]
},
params: {
permission_search: {
value: {
page_size: "20",
order_by: "username"
},
dynamic:true,
squash:""
}
},
ncyBreadcrumb: {
parent: "credentials.edit",
label: "PERMISSIONS"
},
views: {
'related': {
templateProvider: function(CredentialForm, GenerateForm) {
let html = GenerateForm.buildCollection({
mode: 'edit',
related: `permissions`,
form: typeof(CredentialForm) === 'function' ?
CredentialForm() : CredentialForm
});
return html;
},
controller: 'PermissionsList'
}
}
});
stateExtender.addState({
name: 'credentials.edit.permissions.add',
url: '/add-permissions',
resolve: {
usersDataset: [
'addPermissionsUsersList',
'QuerySet',
'$stateParams',
'GetBasePath',
(list, qs, $stateParams, GetBasePath) => {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams.user_search);
}
],
teamsDataset: [
'addPermissionsTeamsList',
'QuerySet',
'$stateParams',
'GetBasePath',
(list, qs, $stateParams, GetBasePath) => {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams.team_search);
}
],
resourceData: ['CredentialModel', '$stateParams', (Credential, $stateParams) => {
return new Credential('get', $stateParams.credential_id)
.then(credential => ({ data: credential.get() }));
}],
},
params: {
user_search: {
value: {
order_by: 'username',
page_size: 5
},
dynamic: true
},
team_search: {
value: {
order_by: 'name',
page_size: 5
},
dynamic: true
}
},
ncyBreadcrumb: {
skip: true
},
views: {
'modal@credentials.edit': {
template: `
<add-rbac-resource
users-dataset="$resolve.usersDataset"
teams-dataset="$resolve.teamsDataset"
selected="allSelected"
resource-data="$resolve.resourceData"
title="Add Users / Teams">
</add-rbac-resource>`
}
},
onExit: $state => {
if ($state.transition) {
$('#add-permissions-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
});
stateExtender.addState(legacy.getStateConfiguration('list'));
stateExtender.addState(legacy.getStateConfiguration('edit-permissions'));
stateExtender.addState(legacy.getStateConfiguration('add-permissions'));
stateExtender.addState(legacy.getStateConfiguration('add-organization'));
stateExtender.addState(legacy.getStateConfiguration('edit-organization'));
stateExtender.addState(legacy.getStateConfiguration('add-credential-type'));
stateExtender.addState(legacy.getStateConfiguration('edit-credential-type'));
}
CredentialsConfig.$inject = [
'$stateProvider',
'$stateExtenderProvider',
'PathServiceProvider'
'$stateExtenderProvider',
'LegacyCredentialsServiceProvider',
'PathServiceProvider'
];
angular
.module('at.features.credentials', [])
.config(CredentialsConfig)
.controller('AddController', AddController)
.controller('EditController', EditController);
.controller('EditController', EditController)
.service('LegacyCredentialsService', LegacyCredentials);

View File

@ -0,0 +1,348 @@
import PermissionsList from '../../src/access/permissions-list.controller';
import CredentialForm from '../../src/credentials/credentials.form';
import CredentialList from '../../src/credentials/credentials.list';
import OrganizationList from '../../src/organizations/organizations.list';
import ListController from '../../src/credentials/list/credentials-list.controller';
import { N_ } from '../../src/i18n';
function LegacyCredentialsService (pathService) {
this.list = {
name: 'credentials',
route: '/credentials',
ncyBreadcrumb: {
label: N_('CREDENTIALS')
},
views: {
'@': {
templateUrl: pathService.getViewPath('credentials/index')
},
'list@credentials': {
templateProvider: function(CredentialList, generateList) {
let html = generateList.build({
list: CredentialList,
mode: 'edit'
});
return html;
},
controller: ListController
}
},
searchPrefix: 'credential',
resolve: {
Dataset: ['CredentialList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
}
};
this.editPermissions = {
name: 'credentials.edit.permissions',
url: '/permissions?{permission_search:queryset}',
resolve: {
ListDefinition: () => {
return {
name: 'permissions',
disabled: 'organization === undefined',
ngClick: `organization === undefined || $state.go('credentials.edit.permissions')`,
awToolTip: '{{permissionsTooltip}}',
dataTipWatch: 'permissionsTooltip',
awToolTipTabEnabledInEditMode: true,
dataPlacement: 'right',
basePath: 'api/v2/credentials/{{$stateParams.id}}/access_list/',
search: {
order_by: 'username'
},
type: 'collection',
title: N_('Permissions'),
iterator: 'permission',
index: false,
open: false,
actions: {
add: {
ngClick: `$state.go('.add')`,
label: 'Add',
awToolTip: N_('Add a permission'),
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ' + N_('ADD'),
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
fields: {
username: {
key: true,
label: N_('User'),
linkBase: 'users',
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
},
role: {
label: N_('Role'),
type: 'role',
nosort: true,
class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4'
},
team_roles: {
label: N_('Team Roles'),
type: 'team_roles',
nosort: true,
class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4'
}
}
};
},
Dataset: ['QuerySet', '$stateParams', (qs, $stateParams) => {
let id = $stateParams.credential_id;
let path = `api/v2/credentials/${id}/access_list/`;
return qs.search(path, $stateParams[`permission_search`]);
}
]
},
params: {
permission_search: {
value: {
page_size: '20',
order_by: 'username'
},
dynamic:true,
squash:''
}
},
ncyBreadcrumb: {
parent: 'credentials.edit',
label: 'PERMISSIONS'
},
views: {
'related': {
templateProvider: function(CredentialForm, GenerateForm) {
let html = GenerateForm.buildCollection({
mode: 'edit',
related: `permissions`,
form: typeof(CredentialForm) === 'function' ?
CredentialForm() : CredentialForm
});
return html;
},
controller: 'PermissionsList'
}
}
};
this.addPermissions = {
name: 'credentials.edit.permissions.add',
url: '/add-permissions',
resolve: {
usersDataset: [
'addPermissionsUsersList',
'QuerySet',
'$stateParams',
'GetBasePath',
(list, qs, $stateParams, GetBasePath) => {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams.user_search);
}
],
teamsDataset: [
'addPermissionsTeamsList',
'QuerySet',
'$stateParams',
'GetBasePath',
(list, qs, $stateParams, GetBasePath) => {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams.team_search);
}
],
resourceData: ['CredentialModel', '$stateParams', (Credential, $stateParams) => {
return new Credential('get', $stateParams.credential_id)
.then(credential => ({ data: credential.get() }));
}],
},
params: {
user_search: {
value: {
order_by: 'username',
page_size: 5
},
dynamic: true
},
team_search: {
value: {
order_by: 'name',
page_size: 5
},
dynamic: true
}
},
ncyBreadcrumb: {
skip: true
},
views: {
'modal@credentials.edit': {
template: `
<add-rbac-resource
users-dataset='$resolve.usersDataset'
teams-dataset='$resolve.teamsDataset'
selected='allSelected'
resource-data='$resolve.resourceData'
title='Add Users / Teams'>
</add-rbac-resource>`
}
},
onExit: $state => {
if ($state.transition) {
$('#add-permissions-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
};
this.lookupTemplateProvider = (ListDefinition, generateList) => {
let html = generateList.build({
mode: 'lookup',
list: ListDefinition,
input_type: 'radio'
});
return `<lookup-modal>${html}</lookup-modal>`;
};
this.organization = {
url: '/organization?selected',
searchPrefix: 'organization',
params: {
organization_search: {
value: {
page_size: 5,
order_by: 'name',
role_level: 'admin_role'
},
dynamic: true,
squash: ''
}
},
data: {
basePath: 'organizations',
formChildState: true
},
ncyBreadcrumb: {
skip: true
},
views: {},
resolve: {
ListDefinition: ['OrganizationList', list => {
return list;
}],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath',
(list, qs, $stateParams, GetBasePath) => {
return qs.search(
GetBasePath('organizations'),
$stateParams[`${list.iterator}_search`]
);
}
]
},
onExit: function($state) {
if ($state.transition) {
$('#form-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
};
this.credentialType = {
url: '/credential_type?selected',
searchPrefix: 'credential_type',
params: {
credential_type_search: {
value: {
page_size: 5,
order_by: 'name'
},
dynamic: true,
squash: ''
}
},
data: {
basePath: 'credential_types',
formChildState: true
},
ncyBreadcrumb: {
skip: true
},
views: {},
resolve: {
ListDefinition: ['CredentialTypesList', list => {
return list;
}],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath',
(list, qs, $stateParams, GetBasePath) => {
return qs.search(
GetBasePath('credential_types'),
$stateParams[`${list.iterator}_search`]
);
}
]
},
onExit: function($state) {
if ($state.transition) {
$('#form-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
};
this.getStateConfiguration = (name) => {
switch (name) {
case 'list':
return this.list;
case 'edit-permissions':
return this.editPermissions;
case 'add-permissions':
return this.addPermissions;
case 'add-organization':
this.organization.name = 'credentials.add.organization';
this.organization.views['organization@credentials.add'] = {
templateProvider: this.lookupTemplateProvider
};
return this.organization;
case 'edit-organization':
this.organization.name = 'credentials.edit.organization';
this.organization.views['organization@credentials.edit'] = {
templateProvider: this.lookupTemplateProvider
};
return this.organization;
case 'add-credential-type':
this.credentialType.name = 'credentials.add.credentialType';
this.credentialType.views['credential_type@credentials.add'] = {
templateProvider: this.lookupTemplateProvider
};
return this.credentialType;
case 'edit-credential-type':
this.credentialType.name = 'credentials.edit.credentialType';
this.credentialType.views['credential_type@credentials.edit'] = {
templateProvider: this.lookupTemplateProvider
};
return this.credentialType;
default:
throw new Error(`Legacy state configuration for ${name} does not exist`);
};
};
}
LegacyCredentialsService.$inject = [
'PathService'
];
export default LegacyCredentialsService;

View File

@ -80,6 +80,28 @@ table, tbody {
padding-left: 10px;
}
.List-tableRow--disabled {
.List-tableCell, .List-tableCell * {
color: @b7grey;
cursor: not-allowed;
}
}
.List-tableRow--disabled {
.List-actionButton:hover {
color: @list-action-icon;
background-color: @list-actn-bg !important;
}
}
.List-tableRow--disabled {
.List-actionButtonCell * {
color: @default-err;
font-size: 11px;
text-transform: uppercase;
}
}
.List-tableCell {
padding: 7px 15px;
border-top:0px!important;

View File

@ -1,7 +1,7 @@
@import 'action/_index';
@import 'input/_index';
@import 'panel/_index';
@import 'modal/_index';
@import 'panel/_index';
@import 'popover/_index';
@import 'tabs/_index';
@import 'utility/_index';

View File

@ -1,7 +1,7 @@
.at-ActionGroup {
margin-top: @at-space-6x;
margin-top: @at-margin-panel;
button:last-child {
margin-left: @at-space-5x;
margin-left: @at-margin-panel-inset;
}
}

View File

@ -38,14 +38,14 @@ function atFormActionController ($state) {
vm.setCancelDefaults = () => {
scope.text = 'CANCEL';
scope.fill = 'Hollow';
scope.color = 'white';
scope.action = () => $state.go('^');
scope.color = 'default';
scope.action = () => $state.go(scope.to || '^');
};
vm.setSaveDefaults = () => {
scope.text = 'SAVE';
scope.fill = '';
scope.color = 'green';
scope.color = 'success';
scope.action = () => form.submit();
};
}
@ -64,7 +64,8 @@ function atFormAction (pathService) {
link,
scope: {
state: '=',
type: '@'
type: '@',
to: '@'
}
};
}

View File

@ -2,6 +2,9 @@ function atFormLink (scope, el, attrs, controllers) {
let formController = controllers[0];
let form = el[0];
scope.ns = 'form';
scope[scope.ns] = { modal: {} };
formController.init(scope, form);
}
@ -9,10 +12,10 @@ function AtFormController (eventService) {
let vm = this || {};
let scope;
let modal;
let form;
vm.components = [];
vm.modal = {};
vm.state = {
isValid: false,
disabled: false,
@ -22,6 +25,7 @@ function AtFormController (eventService) {
vm.init = (_scope_, _form_) => {
scope = _scope_;
form = _form_;
modal = scope[scope.ns].modal;
vm.setListeners();
};
@ -102,7 +106,7 @@ function AtFormController (eventService) {
message = err.data;
}
vm.modal.show('Unable to Submit', `Unexpected Error: ${message}`);
modal.show('Unable to Submit', `Unexpected Error: ${message}`);
}
};
@ -110,7 +114,7 @@ function AtFormController (eventService) {
let title = 'Unable to Submit';
let message = 'Unexpected server error. View the console for more information';
vm.modal.show(title, message);
modal.show(title, message);
return true;
};

View File

@ -5,5 +5,5 @@
</div>
</form>
<at-modal state="vm.modal"></at-modal>
<at-modal></at-modal>
</div>

View File

@ -7,8 +7,6 @@ import inputGroup from './input/group.directive';
import inputLabel from './input/label.directive';
import inputLookup from './input/lookup.directive';
import inputMessage from './input/message.directive';
import inputNumber from './input/number.directive';
import inputSelect from './input/select.directive';
import inputSecret from './input/secret.directive';
import inputText from './input/text.directive';
import inputTextarea from './input/textarea.directive';
@ -34,9 +32,7 @@ angular
.directive('atInputLabel', inputLabel)
.directive('atInputLookup', inputLookup)
.directive('atInputMessage', inputMessage)
.directive('atInputNumber', inputNumber)
.directive('atInputSecret', inputSecret)
.directive('atInputSelect', inputSelect)
.directive('atInputText', inputText)
.directive('atInputTextarea', inputTextarea)
.directive('atInputTextareaSecret', inputTextareaSecret)

View File

@ -1,17 +1,21 @@
.at-Input {
.at-mixin-Placeholder(@at-gray-dark-3x);
.at-mixin-Placeholder(@at-color-input-placeholder);
height: @at-input-height;
background: @at-white;
height: @at-height-input;
background: @at-color-input-background;
border-radius: @at-border-radius;
color: @at-gray-dark-5x;
color: @at-color-input-text;
&, &:active {
border-color: @at-gray-dark-2x;
border-color: @at-color-input-border;
}
&:focus {
border-color: @at-blue;
border-color: @at-color-input-focus;
}
&[disabled] {
background: @at-color-input-disabled;
}
}
@ -21,43 +25,43 @@
& > label {
& > input[type=checkbox] {
height: @at-input-height;
height: @at-height-input;
margin: 0;
padding: 0;
}
& > p {
margin: 0;
padding: 0 0 0 @at-space-6x;
padding: 0 0 0 @at-padding-panel;
line-height: @at-line-height-tall;
}
}
}
.at-InputContainer {
margin-top: @at-space-6x;
margin-top: @at-margin-panel;
}
.at-Input-button {
min-width: @at-input-button-width;
display: block;
height: @at-input-height;
height: @at-height-button;
line-height: 1;
&, &:active, &:hover, &:focus {
color: @at-gray-dark-3x;
border-color: @at-gray-dark-2x;
background-color: @at-white;
color: @at-color-button-text-default;
border-color: @at-color-input-border;
background-color: @at-color-default;
cursor: pointer;
}
}
.at-Input--focus {
border-color: @at-blue;
border-color: @at-color-input-focus;
}
.at-Input--rejected {
&, &:focus {
border-color: @at-red;
border-color: @at-color-input-error;
}
}
@ -66,7 +70,6 @@
height: 100%;
width: 100%;
left: 0;
right: @at-input-button-width;
z-index: -2;
opacity: 0;
}
@ -77,15 +80,15 @@
.at-InputGroup {
padding: 0;
margin: @at-space-6x 0 0 0;
margin: @at-margin-panel 0 0 0;
}
.at-InputGroup-border {
position: absolute;
width: @at-inset-width;
width: 5px;
height: 100%;
background: @at-gray-dark;
left: -@at-inset-width;
background: @at-color-panel-border;
left: -5px;
}
.at-InputGroup-button {
@ -93,19 +96,22 @@
& > button {
height: 100%;
border-right: none;
color: @at-color-button-text-default;
min-width: @at-input-button-width;
}
}
.at-InputGroup-title {
.at-mixin-Heading(@at-font-size-2x);
margin: 0 0 0 @at-space-5x;
.at-mixin-Heading(@at-font-size-panel-inset-heading);
margin: 0 0 0 @at-margin-panel-inset;
}
.at-InputGroup-divider {
clear: both;
margin: 0;
padding: 0;
height: @at-space-6x;
height: @at-height-divider;
}
.at-InputLabel {
@ -114,17 +120,17 @@
}
.at-InputLabel-name {
color: @at-gray-dark-4x;
font-size: @at-font-size-2x;
font-weight: @at-font-weight;
color: @at-color-form-label;
font-size: @at-font-size-form-label;
font-weight: @at-font-weight-body;
text-transform: uppercase;
}
.at-InputLabel-hint {
margin-left: @at-space-4x;
color: @at-gray-dark-3x;
font-size: @at-font-size;
font-weight: @at-font-weight;
margin-left: @at-margin-form-label-hint;
color: @at-color-input-hint;
font-size: @at-font-size-help-text;
font-weight: @at-font-weight-body;
line-height: @at-line-height-short;
}
@ -137,15 +143,15 @@
margin-bottom: 0;
& > input[type=checkbox] {
margin: 0 @at-space 0 0;
margin: 0 3px 0 0;
position: relative;
top: @at-space;
top: 3px
}
& > p {
font-size: @at-font-size;
color: @at-gray-dark-4x;
font-weight: @at-font-weight;
font-size: @at-font-size-help-text;
color: @at-color-form-label;
font-weight: @at-font-weight-body;
display: inline;
margin: 0;
padding: 0;
@ -153,16 +159,16 @@
}
.at-InputMessage--rejected {
font-size: @at-font-size;
color: @at-red;
margin: @at-space-3x 0 0 0;
font-size: @at-font-size-help-text;
color: @at-color-error;
margin: @at-margin-input-message 0 0 0;
padding: 0;
}
.at-InputLabel-required {
color: @at-red;
font-weight: @at-font-weight-2x;
font-size: @at-font-size-2x;
color: @at-color-error;
font-weight: @at-font-weight-heading;
font-size: @at-font-size-form-label;
margin: 0;
}
@ -171,13 +177,13 @@
width: 100%;
& > i {
font-size: @at-font-size;
font-size: @at-font-size-button;
position: absolute;
z-index: 3;
pointer-events: none;
top: @at-space-4x;
right: @at-space-4x;
color: @at-gray-dark-2x;
top: @at-height-input / 3;
right: @at-height-input / 3;
color: @at-color-input-icon;
}
}
@ -188,7 +194,7 @@
}
.at-InputSelect-select {
height: @at-input-height;
height: @at-height-input;
cursor: pointer;
position: absolute;
z-index: 1;

View File

@ -49,7 +49,6 @@ function AtInputGroupController ($scope, $compile) {
vm.insert(group);
state._group = group;
vm.compile(group);
};
vm.createComponentConfigs = inputs => {
@ -138,20 +137,22 @@ function AtInputGroupController ($scope, $compile) {
vm.createComponent = (input, index) => {
let tabindex = Number(scope.tab) + index;
let col = input._expand ? 12 : scope.col;
return angular.element(
let component = angular.element(
`<${input._component} col="${col}" tab="${tabindex}"
state="${state._reference}._group[${index}]">
</${input._component}>`
);
$compile(component)(scope.$parent)
return component;
};
vm.createDivider = () => {
return angular.element('<at-divider></at-divider>');
};
let divider = angular.element('<at-divider></at-divider>');
$compile(divider[0])(scope.$parent);
vm.compile = group => {
group.forEach(component => $compile(component._element[0])(scope.$parent));
return divider;
};
vm.clear = () => {

View File

@ -9,43 +9,46 @@ function atInputLookupLink (scope, element, attrs, controllers) {
inputController.init(scope, element, formController);
}
function AtInputLookupController (baseInputController) {
function AtInputLookupController (baseInputController, $state, $stateParams) {
let vm = this || {};
vm.lookup = {};
let scope;
vm.init = (scope, element, form) => {
baseInputController.call(vm, 'input', scope, element, form);
vm.init = (_scope_, element, form) => {
baseInputController.call(vm, 'input', _scope_, element, form);
vm.lookup.modal = {
title: 'Select Organization',
buttons: [
{
type: 'cancel'
},
{
type: 'select'
}
]
};
scope = _scope_;
vm.lookup.search = {
placeholder: 'test'
};
vm.lookup.table = {
};
scope.$watch(scope.state._resource, vm.watchResource);
vm.check();
};
vm.watchResource = () => {
if (scope[scope.state._resource]) {
scope.state._value = scope[scope.state._resource];
scope.state._displayValue = scope[`${scope.state._resource}_name`];
vm.check();
}
};
vm.search = () => {
vm.modal.show('test');
let params = {};
if (scope.state._value) {
params.selected = scope.state._value;
}
$state.go(scope.state._route, params);
};
}
AtInputLookupController.$inject = ['BaseInputController'];
AtInputLookupController.$inject = [
'BaseInputController',
'$state',
'$stateParams'
];
function atInputLookup (pathService) {
return {

View File

@ -4,7 +4,7 @@
<div class="input-group">
<span class="input-group-btn">
<button class="btn at-ButtonHollow--white at-Input-button"
<button class="btn at-ButtonHollow--default at-Input-button"
ng-disabled="state._disabled || form.disabled"
ng-click="vm.search()">
<i class="fa fa-search"></i>
@ -13,7 +13,7 @@
<input type="text"
class="form-control at-Input"
ng-class="{ 'at-Input--rejected': state.rejected }"
ng-model="state._value"
ng-model="state._displayValue"
ng-attr-tabindex="{{ tab || undefined }}"
ng-attr-placeholder="{{::state._placeholder || undefined }}"
ng-change="vm.check()"
@ -23,8 +23,5 @@
<at-input-message></at-input-message>
</div>
<at-modal state="vm.lookup">
<at-search></at-search>
<at-table></at-table>
</at-modal>
<div ui-view="{{ state._resource }}"></div>
</div>

View File

@ -1,54 +0,0 @@
const DEFAULT_STEP = '1';
const DEFAULT_MIN = '0';
const DEFAULT_MAX = '1000000000';
const DEFAULT_PLACEHOLDER = '';
function atInputNumberLink (scope, element, attrs, controllers) {
let formController = controllers[0];
let inputController = controllers[1];
if (scope.tab === '1') {
element.find('input')[0].focus();
}
inputController.init(scope, element, formController);
}
function AtInputNumberController (baseInputController) {
let vm = this || {};
vm.init = (scope, element, form) => {
baseInputController.call(vm, 'input', scope, element, form);
scope.state._step = scope.state._step || DEFAULT_STEP;
scope.state._min = scope.state._min || DEFAULT_MIN;
scope.state._max = scope.state._max || DEFAULT_MAX;
scope.state._placeholder = scope.state._placeholder || DEFAULT_PLACEHOLDER;
vm.check();
};
}
AtInputNumberController.$inject = ['BaseInputController'];
function atInputNumber (pathService) {
return {
restrict: 'E',
transclude: true,
replace: true,
require: ['^^atForm', 'atInputNumber'],
templateUrl: pathService.getPartialPath('components/input/number'),
controller: AtInputNumberController,
controllerAs: 'vm',
link: atInputNumberLink,
scope: {
state: '=',
col: '@',
tab: '@'
}
};
}
atInputNumber.$inject = ['PathService'];
export default atInputNumber;

View File

@ -1,19 +0,0 @@
<div class="col-sm-{{::col}} at-InputContainer">
<div class="form-group at-u-flat">
<at-input-label></at-input-label>
<input type="number"
class="form-control at-Input"
ng-class="{ 'at-Input--rejected': state.rejected }"
ng-model="state._value"
ng-attr-min="state._min"
ng-attr-max="state._max"
ng-attr-step="state._step"
ng-attr-tabindex="{{ tab || undefined }}"
ng-attr-placeholder="{{::state._placeholder || undefined }}"
ng-change="vm.check()"
ng-disabled="state._disabled || form.disabled" />
<at-input-message></at-input-message>
</div>
</div>

View File

@ -1,10 +1,28 @@
.at-Modal-body {
font-size: @at-font-size;
padding: 0;
}
.at-Modal-dismiss {
.at-mixin-ButtonIcon();
font-size: @at-font-size-modal-dismiss;
color: @at-color-icon-dismiss;
text-align: right;
}
.at-Modal-heading {
margin: 0;
overflow: visible;
& > .at-Modal-dismiss {
margin: 0;
}
}
.at-Modal-title {
margin: 0;
padding: 0;
.at-mixin-Heading(@at-font-size-3x);
.at-mixin-Heading(@at-font-size-modal-heading);
}
.at-Modal-body {
font-size: @at-font-size;
}

View File

@ -1,45 +1,74 @@
const DEFAULT_ANIMATION_DURATION = 150;
function atModalLink (scope, el, attr, controllers) {
function atModalLink (scope, el, attrs, controllers) {
let modalController = controllers[0];
let container = el[0];
let property = `scope.${scope.ns}.modal`;
modalController.init(scope, container);
let done = scope.$watch(property, () => {
modalController.init(scope, el);
done();
});
}
function AtModalController () {
function AtModalController (eventService) {
let vm = this;
let scope;
let container;
let overlay;
let modal;
let listeners;
vm.init = (_scope_, _container_) => {
scope = _scope_;
container = _container_;
vm.init = (scope, el) => {
overlay = el[0];
modal = el.find('.at-Modal-window')[0];
scope.state.show = vm.show;
scope.state.hide = vm.hide;
vm.modal = scope[scope.ns].modal;
vm.modal.show = vm.show;
vm.modal.hide = vm.hide;
};
vm.show = (title, message) => {
scope.title = title;
scope.message = message;
vm.modal.title = title;
vm.modal.message = message;
container.style.display = 'block';
container.style.opacity = 1;
event.stopPropagation();
listeners = eventService.addListeners([
[window, 'click', vm.clickToHide]
]);
overlay.style.display = 'block';
overlay.style.opacity = 1;
};
vm.hide = () => {
container.style.opacity = 0;
overlay.style.opacity = 0;
setTimeout(() => {
container.style.display = 'none';
scope.message = '';
scope.title = '';
}, DEFAULT_ANIMATION_DURATION);
eventService.remove(listeners);
setTimeout(() => overlay.style.display = 'none', DEFAULT_ANIMATION_DURATION);
};
vm.clickToHide = event => {
if (vm.clickIsOutsideModal(event)) {
vm.hide();
}
};
vm.clickIsOutsideModal = e => {
let m = modal.getBoundingClientRect();
let cx = e.clientX;
let cy = e.clientY;
if (cx < m.left || cx > m.right || cy > m.bottom || cy < m.top) {
return true;
}
return false;
};
}
AtModalController.$inject = ['EventService'];
function atModal (pathService) {
return {
restrict: 'E',
@ -50,9 +79,7 @@ function atModal (pathService) {
controller: AtModalController,
controllerAs: 'vm',
link: atModalLink,
scope: {
state: '='
}
scope: true
};
}

View File

@ -1,20 +1,31 @@
<div class="modal at-Modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" ng-click="vm.hide()">
<i class="fa fa-times"></i>
</button>
<h4 class="modal-title at-Modal-title">{{ title }}</h4>
<div class="modal-content at-Modal-window">
<div class="row">
<div class="col-xs-10">
<div class="at-Modal-heading">
<h4 class="modal-title at-Modal-title">{{ vm.modal.title }}</h4>
</div>
</div>
<div class="col-xs-2">
<div class="at-Modal-dismiss">
<i class="fa fa-lg fa-times-circle" ng-click="vm.hide()"></i>
</div>
</div>
</div>
<div class="modal-body at-Modal-body">
<p ng-show="message">{{ message }}</p>
<ng-transclude></ng-transclude>
</div>
<div class="modal-footer">
<button type="button" class="btn at-ButtonHollow--white" ng-click="vm.hide()">
OK
</button>
<ng-transclude></ng-transclude>
<div ng-show="vm.modal.message">
<div class="modal-body at-Modal-body">
<p>{{ vm.modal.message }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn at-ButtonHollow--default"
ng-click="vm.hide()">
OK
</button>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
.at-Panel {
margin: @at-space-6x 0 0 0;
padding: @at-space-6x;
border-color: @at-gray-dark;
margin: @at-margin-panel 0 0 0;
padding: @at-padding-panel;
border-color: @at-color-panel-border;
}
.at-Panel-heading {
@ -11,6 +11,7 @@
.at-Panel-dismiss {
.at-mixin-ButtonIcon();
color: @at-color-icon-dismiss;
text-align: right;
}
@ -20,6 +21,6 @@
}
.at-Panel-headingTitle {
.at-mixin-Heading(@at-font-size-3x);
.at-mixin-Heading(@at-font-size-panel-heading);
text-transform: none;
}

View File

@ -1,5 +1,5 @@
.at-Popover {
padding: 0 0 0 @at-space-3x;
padding: 0 0 0 5px;
}
.at-Popover--inline {
@ -8,7 +8,8 @@
.at-Popover-icon {
.at-mixin-ButtonIcon();
font-size: @at-font-size-4x;
color: @at-color-icon-popover;
font-size: @at-font-size-icon;
padding: 0;
margin: 0;
}
@ -17,35 +18,35 @@
visibility: hidden;
opacity: 0;
color: @at-white;
background-color: @at-gray-dark-4x;
max-width: @at-popover-width;
padding: @at-space-4x;
background-color: @at-color-body-background-dark;
max-width: @at-popover-maxwidth;
padding: @at-padding-popover;
height: auto;
position: fixed;
z-index: 2000;
margin: 0 0 0 @at-space-6x;
margin: 0 0 0 18px;
border-radius: @at-border-radius;
box-shadow: 0 5px 10px rgba(0,0,0, 0.2);
transition: opacity .15s linear;
font-weight: @at-font-weight
font-weight: @at-font-weight-body;
}
.at-Popover-arrow {
color: @at-gray-dark-4x;
color: @at-color-body-background-dark;
position: fixed;
z-index: 1999;
padding: 0;
margin: @at-space-4x 0 0 @at-space;
margin: 8px 0 0 3px;
}
.at-Popover-title {
.at-mixin-Heading(@at-font-size);
color: @at-white;
margin-bottom: @at-space-4x;
.at-mixin-Heading(@at-font-size-body);
color: @at-color-body-text-dark;
margin-bottom: @at-margin-popover;
}
.at-Popover-text {
margin: 0;
padding: 0;
font-size: @at-font-size;
font-size: @at-font-size-body;
}

View File

@ -1,26 +1,27 @@
.at-TabGroup {
margin-top: @at-space-6x;
margin-top: @at-margin-panel;
}
.at-Tab {
margin: 0 @at-space-5x 0 0;
font-size: @at-font-size;
margin: 0 @at-margin-item-column 0 0;
font-size: @at-font-size-body;
line-height: 1;
}
.at-Tab--active {
&, &:hover, &:active, &:focus {
color: @at-white;
background-color: @at-gray-dark-3x;
border-color: @at-gray-dark-3x;
color: @at-color-tab-text-default-active;
background-color: @at-color-tab-default-active;
border-color: @at-color-tab-border-default-active;
cursor: default;
}
}
.at-Tab--disabled {
&, &:hover, &:active, &:focus {
background-color: @at-white;
color: @at-gray-dark-2x;
border-color: @at-gray-dark-2x;
background-color: @at-color-tab-default-disabled;
color: @at-color-tab-text-default-disabled;
border-color: @at-color-tab-border-default-disabled;
opacity: 0.65;
cursor: not-allowed;
}

View File

@ -1,4 +1,4 @@
<button class="btn at-ButtonHollow--white at-Tab"
<button class="btn at-ButtonHollow--default at-Tab"
ng-attr-disabled="{{ state._disabled || undefined }}"
ng-class="{ 'at-Tab--active': state._active, 'at-Tab--disabled': state._disabled }"
ng-click="vm.go()">

View File

@ -77,25 +77,24 @@ function httpOptions (resource) {
});
}
function get (method, keys) {
let model;
function options (keys) {
return this.find('options', keys);
}
if (keys) {
model = this.model[method.toUpperCase()];
} else {
model = this.model.GET;
keys = method;
}
function get (keys) {
return this.find('get', keys);
}
function find (method, keys) {
let value = this.model[method.toUpperCase()];
if (!keys) {
return model;
return value;
}
keys = keys.split('.');
let value = model;
try {
keys = keys.split('.');
keys.forEach(key => {
let bracketIndex = key.indexOf('[');
let hasArray = bracketIndex !== -1;
@ -137,6 +136,8 @@ function getById (id) {
function BaseModel (path) {
this.model = {};
this.get = get;
this.options = options;
this.find = find;
this.normalizePath = normalizePath;
this.getById = getById;
this.request = request;

View File

@ -3,7 +3,7 @@ const ENCRYPTED_VALUE = '$encrypted$';
let BaseModel;
function createFormSchema (method, config) {
let schema = Object.assign({}, this.get('options', `actions.${method.toUpperCase()}`));
let schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`));
if (config && config.omit) {
config.omit.forEach(key => {

View File

@ -0,0 +1,68 @@
/**
* All base variables used. These should only be referenced by the contextual variables defined
* in the _contextual-variables.less file. In development, unless you are intentionally making a
* fundamental change, these variables should not be modified, removed, or added to.
*
* These variables should not be used directly in development of components or features. If an
* alias doesn't exist for the context you're working within, check with UX to create a new alias
* or to define a more applicable alias.
*
* The goal is for UX to define the contexts for the contextual variables, so it's easy to make
* modifications like, "Change heading text to be smaller" or "Make all warnings a lighter shade
* of orange"
*
* 1. Colors
* 2. Typography
* 3. Layout
*
*/
// 1. Colors --------------------------------------------------------------------------------------
@at-gray-light-3x: #fcfcfc;
@at-gray-light-2x: #f2f2f2;
@at-gray-light: #ebebeb;
@at-gray: #e1e1e1;
@at-gray-dark: #d7d7d7;
@at-gray-dark-2x: #b7b7b7;
@at-gray-dark-3x: #A9A9A9;
@at-gray-dark-4x: #848992;
@at-gray-dark-5x: #707070;
@at-gray-dark-6x: #161b1f;
@at-white: #ffffff;
@at-white-hover: #f2f2f2;
@at-blue: #337ab7;
@at-blue-hover: #286090;
@at-green: #5cb85c;
@at-green-hover: #449D44;
@at-orange: #f0ad4e;
@at-orange-hover: #ec971f;
@at-red: #d9534f;
@at-red-hover: #c9302c;
@at-red-bright: #ff0000;
@at-red-bright-hover: #d81f1f;
// 2. Typography ----------------------------------------------------------------------------------
@at-font-size: 12px;
@at-font-size-2x: 13px;
@at-font-size-3x: 14px;
@at-font-size-4x: 16px;
@at-font-size-5x: 20px;
@at-font-weight: 400;
@at-font-weight-2x: 700;
// 3. Layout --------------------------------------------------------------------------------------
@at-space: 5px;
@at-space-2x: 10px;
@at-space-3x: 15px;
@at-space-4x: 20px;

View File

@ -1,38 +0,0 @@
/**
* For styles that are used in more than one place throughout the application.
*
* 1. Buttons
*
*/
// 1. Buttons -------------------------------------------------------------------------------------
.at-Button--green {
.at-mixin-Button();
.at-mixin-ButtonColor('at-green', 'at-white');
&[disabled] {
background: @at-gray-dark;
}
}
.at-Button--blue {
.at-mixin-Button();
.at-mixin-ButtonColor('at-blue', 'at-white');
}
.at-Button--red {
.at-mixin-Button();
.at-mixin-ButtonColor('at-red', 'at-white');
}
.at-ButtonHollow--white {
.at-mixin-Button();
.at-mixin-ButtonHollow('at-gray-dark-3x', 'at-gray-dark-2x');
border-color: @at-gray-dark;
}
.at-ButtonIcon {
padding: @at-space-2x @at-space-4x;
font-size: @at-font-size-3x;
}

View File

@ -0,0 +1,139 @@
/**
* All variables used in the UI. Use these variables directly during the development of components
* and features. Be sure the context of the variable name applies to the work that's being done.
* For example, it wouldn't make sense to use `@at-input-height` to describe the height of a
* button. Either add an alias if it makes sense to use the same base variable, or add a new
* base variable to reference.
*
* Keep in mind the goal is to be able to modify an item by referencing its context instead of
* an arbitrary variable name. For example, tt should be a simple change when an ask comes in to
* "increase the height of inputs"
*
* 1. Colors
* 2. Typography
* 3. Layout
* 4. Buttons
* 5. Misc
*
*/
// 1. Colors --------------------------------------------------------------------------------------
@at-color-default: @at-white;
@at-color-default-hover: @at-white-hover;
@at-color-unreachable: @at-red-bright;
@at-color-unreachable-hover: @at-red-bright-hover;
@at-color-error: @at-red;
@at-color-error-hover: @at-red-hover;
@at-color-warning: @at-orange;
@at-color-warning-hover: @at-orange-hover;
@at-color-info: @at-blue;
@at-color-info-hover: @at-blue-hover;
@at-color-success: @at-green;
@at-color-success-hover: @at-green-hover;
@at-color-disabled: @at-gray-dark;
@at-color-body-background-dark: @at-gray-dark-5x;
@at-color-body-text-dark: @at-white;
@at-color-body-background: @at-gray-light-3x;
@at-color-body-text: @at-gray-dark-5x;
@at-color-button-border-default: @at-gray-dark-2x;
@at-color-button-text-default: @at-gray-dark-5x;
@at-color-tab-default-active: @at-gray-dark-2x;
@at-color-tab-border-default-active: @at-gray-dark-2x;
@at-color-tab-text-default-active: @at-white;
@at-color-tab-default-disabled: @at-white;
@at-color-tab-border-default-disabled: @at-gray-dark-2x;
@at-color-tab-text-default-disabled: @at-gray-dark-5x;
@at-color-form-label: @at-gray-dark-5x;
@at-color-input-border: @at-gray-dark-2x;
@at-color-input-error: @at-color-error;
@at-color-input-focus: @at-color-info;
@at-color-input-hint: @at-gray-dark-4x;
@at-color-input-icon: @at-gray-dark-2x;
@at-color-input-placeholder: @at-gray-dark-4x;
@at-color-input-text: @at-gray-dark-6x;
@at-color-input-background: @at-gray-light-3x;
@at-color-input-disabled: @at-gray-light-2x;
@at-color-icon-dismiss: @at-gray-dark;
@at-color-icon-popover: @at-gray-dark-3x;
@at-color-icon-hover: @at-gray-dark-5x;
@at-color-panel-heading: @at-gray-dark-5x;
@at-color-panel-border: @at-gray-dark;
@at-color-search-key-active: @at-blue;
@at-color-table-header-background: @at-gray-light;
@at-color-line-separator: @at-gray;
// 2. Typography ----------------------------------------------------------------------------------
@at-font-size-body: @at-font-size-3x;
@at-font-size-button: @at-font-size;
@at-font-size-breadcrumb: @at-font-size-3x;
@at-font-size-form-label: @at-font-size-2x;
@at-font-size-help-text: @at-font-size;
@at-font-size-icon: @at-font-size-4x;
@at-font-size-input: @at-font-size-3x;
@at-font-size-panel-heading: @at-font-size-3x;
@at-font-size-panel-inset-heading: @at-font-size-2x;
@at-font-size-modal-heading: @at-font-size-3x;
@at-font-size-modal-dismiss: @at-font-size-3x;
@at-font-size-navigation: @at-font-size-3x;
@at-font-size-table-heading: @at-font-size-3x;
@at-font-size-menu-icon: @at-font-size-5x;
@at-font-weight-body: @at-font-weight;
@at-font-weight-heading: @at-font-weight-2x;
// 3. Layout --------------------------------------------------------------------------------------
@at-padding-button-horizontal: @at-space-2x;
@at-padding-button-vertical: @at-space;
@at-padding-inset: @at-space-3x;
@at-padding-panel: @at-space-4x;
@at-padding-popover: @at-space-2x;
@at-padding-well: @at-space-2x;
@at-margin-input-message: @at-space;
@at-margin-item-column: @at-space-3x;
@at-margin-panel: @at-space-4x;
@at-margin-panel-inset: @at-space-3x;
@at-margin-popover: @at-space-2x;
@at-margin-tag: @at-space-2x;
@at-margin-form-label: @at-space;
@at-margin-form-label-hint: @at-space-2x;
@at-margin-top-search-key: @at-space-2x;
@at-height-divider: @at-margin-panel;
@at-height-input: 30px;
@at-height-button: 30px;
@at-height-tab: 30px;
// 4. Transitions ---------------------------------------------------------------------------------
@at-transition-icon-button: 0.2s;
// 5. Misc ----------------------------------------------------------------------------------------
@at-border-radius: 5px;
@at-popover-maxwidth: 320px;
@at-line-height-short: 0.9;
@at-line-height-tall: 2;
@at-line-height: 24px;
@at-input-button-width: 72px;

View File

@ -0,0 +1,45 @@
/**
* For styles that are used in more than one place throughout the application.
*
* 1. Buttons
*
*/
// 1. Buttons -------------------------------------------------------------------------------------
.at-Button--success {
.at-mixin-Button();
.at-mixin-ButtonColor('at-color-success', 'at-color-default');
&[disabled] {
background: @at-color-disabled;
}
}
.at-Button--info {
.at-mixin-Button();
.at-mixin-ButtonColor('at-color-info', 'at-color-default');
}
.at-Button--error {
.at-mixin-Button();
.at-mixin-ButtonColor('at-color-error', 'at-color-default');
}
.at-ButtonHollow--default {
.at-mixin-Button();
.at-mixin-ButtonHollow(
'at-color-default',
'at-color-button-border-default',
'at-color-button-text-default'
);
}
.at-ButtonIcon {
padding: 4px @at-padding-button-horizontal;
font-size: @at-font-size-body;
}
.at-Button--expand {
width: 100%;
}

View File

@ -11,9 +11,9 @@
}
.at-mixin-Heading (@size) {
color: @at-gray-dark-4x;
color: @at-color-body-text;
font-size: @size;
font-weight: @at-font-weight-2x;
font-weight: @at-font-weight-heading;
line-height: @at-line-height-short;
text-transform: uppercase;
margin: 0;
@ -21,12 +21,13 @@
}
.at-mixin-Button () {
height: @at-input-height;
padding: @at-space-2x @at-space-4x;
font-size: @at-font-size;
height: @at-height-input;
padding: @at-padding-button-vertical @at-padding-button-horizontal;
font-size: @at-font-size-body;
line-height: 1;
}
.at-mixin-ButtonColor (@background, @color, @hover: '@{background}--hover') {
.at-mixin-ButtonColor (@background, @color, @hover: '@{background}-hover') {
background-color: @@background;
&, &:hover, &:focus {
@ -42,21 +43,23 @@
}
}
.at-mixin-ButtonHollow (@color, @accent) {
background-color: @at-white;
color: @@color;
border-color: @@color;
.at-mixin-ButtonHollow (@bg, @border, @text) {
@hover: '@{bg}-hover';
background-color: @@bg;
color: @@text;
border-color: @@border;
&:hover, &:active {
color: @@color;
background-color: @at-white--hover;
color: @@text;
background-color: @@hover;
box-shadow: none;
}
&:focus {
color: @at-white;
background-color: @@accent;
border-color: @@accent;
color: @@text;
background-color: @@hover;
border-color: @@border;
cursor: default;
}
@ -67,14 +70,14 @@
.at-mixin-ButtonIcon () {
line-height: @at-line-height-short;
color: @at-gray-dark-2x;
& > i {
cursor: pointer;
transition: color @at-transition-icon-button;
}
& > i:hover {
color: @at-gray-dark-3x;
color: @at-color-icon-hover
}
}

View File

@ -1,72 +0,0 @@
/**
* All variables used in the UI.
*
* 1. Colors
* 2. Typography
* 3. Layout
* 4. Input
* 5. Misc
*/
// 1. Colors --------------------------------------------------------------------------------------
@at-gray-light-5x: #fcfcfc;
@at-gray-light-4x: #fafafa;
@at-gray-light-3x: #f6f6f6;
@at-gray-light-2x: #f2f2f2;
@at-gray-light: #ebebeb;
@at-gray: #e1e1e1;
@at-gray-dark: #d7d7d7;
@at-gray-dark-2x: #b7b7b7;
@at-gray-dark-3x: #848992;
@at-gray-dark-4x: #707070;
@at-gray-dark-5x: #161b1f;
@at-white: #ffffff;
@at-white--hover: #f2f2f2;
@at-blue: #337ab7;
@at-blue--hover: #286090;
@at-green: #5cb85c;
@at-green--hover: #449D44;
@at-yellow: #f0ad4e;
@at-yellow--hover: #ec971f;
@at-red: #d9534f;
@at-red--hover: #c9302c;
@at-redAlert: #ff0000;
@at-redAlert--hover: #d81f1f;
// 2. Typography ----------------------------------------------------------------------------------
@at-font-size: 12px;
@at-font-size-2x: 13px;
@at-font-size-3x: 14px;
@at-font-size-4x: 16px;
@at-font-weight: 400;
@at-font-weight-2x: 700;
@at-font-weight-3x: 900;
@at-line-height-short: 0.9;
@at-line-height-tall: 2;
@at-line-height: 24px;
// 3. Layout --------------------------------------------------------------------------------------
@at-space: 3px;
@at-space-2x: 4px;
@at-space-3x: 5px;
@at-space-4x: 10px;
@at-space-5x: 15px;
@at-space-6x: 20px;
// 4. Input ---------------------------------------------------------------------------------------
@at-input-button-width: 72px;
@at-input-height: 30px;
// 5. Misc ----------------------------------------------------------------------------------------
@at-border-radius: 5px;
@at-popover-width: 320px;
@at-inset-width: 5px;

View File

@ -1,8 +1,9 @@
// App-wide styles
@import '_variables';
@import '_base-variables';
@import '_contextual-variables';
@import '_mixins';
@import '_utility';
@import '_common';
@import '_global';
// Aggregated component and feature specific styles
@import '../components/_index';

View File

@ -203,12 +203,12 @@ var tower = angular.module('Tower', [
])
.run(['$stateExtender', '$q', '$compile', '$cookies', '$rootScope', '$log', '$stateParams',
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'FeaturesService', '$filter', 'SocketService',
function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait,
LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
$filter, SocketService) {
@ -305,6 +305,28 @@ var tower = angular.module('Tower', [
window.clearInterval($rootScope.jobStdOutInterval);
}
$rootScope.flashMessage = null;
$('#form-modal2 .modal-body').empty();
$('.tooltip').each(function() {
$(this).remove();
});
$('.popover').each(function() {
$(this).remove();
});
$('.ui-dialog-content').each(function() {
$(this).dialog('close');
});
try {
$('#help-modal').dialog('close');
} catch (e) {
// ignore
}
// On each navigation request, check that the user is logged in
if (!/^\/(login|logout)/.test($location.path())) {
// capture most recent URL, excluding login/logout

View File

@ -5,7 +5,7 @@
*************************************************/
export default [
'$scope', '$rootScope', '$state', '$stateParams', '$timeout', '$q', 'Alert', 'ClearScope',
'$scope', '$rootScope', '$state', '$stateParams', '$timeout', '$q', 'Alert',
'ConfigurationService', 'ConfigurationUtils', 'CreateDialog', 'CreateSelect2', 'i18n', 'ParseTypeChange', 'ProcessErrors', 'Store',
'Wait', 'configDataResolve', 'ToJSON',
//Form definitions
@ -24,7 +24,7 @@ export default [
'ConfigurationJobsForm',
'ConfigurationUiForm',
function(
$scope, $rootScope, $state, $stateParams, $timeout, $q, Alert, ClearScope,
$scope, $rootScope, $state, $stateParams, $timeout, $q, Alert,
ConfigurationService, ConfigurationUtils, CreateDialog, CreateSelect2, i18n, ParseTypeChange, ProcessErrors, Store,
Wait, configDataResolve, ToJSON,
//Form definitions
@ -450,12 +450,39 @@ export default [
};
var resetAll = function() {
var keys = _.keys(formDefs[formTracker.getCurrent()].fields);
var payload = {};
clearApiErrors();
_.each(keys, function(key) {
payload[key] = $scope.configDataResolve[key].default;
});
Wait('start');
ConfigurationService.resetAll()
ConfigurationService.patchConfiguration(payload)
.then(function() {
populateFromApi();
$scope[formTracker.currentFormName()].$setPristine();
$scope.$broadcast('CUSTOM_LOGO_reverted');
let keys = _.keys(formDefs[formTracker.getCurrent()].fields);
_.each(keys, function(key) {
$scope[key] = $scope.configDataResolve[key].default;
if($scope[key + '_field'].type === "select"){
// We need to re-instantiate the Select2 element
// after resetting the value. Example:
$scope.$broadcast(key+'_populated', null, false);
}
else if($scope[key + '_field'].reset === "CUSTOM_LOGO"){
$scope.$broadcast(key+'_reverted');
}
else if($scope[key + '_field'].type === "textarea" && _.isArray($scope.configDataResolve[key].default)){
$scope[key] = ConfigurationUtils.arrayToList($scope[key], key);
}
else if($scope[key + '_field'].hasOwnProperty('codeMirror')){
$scope[key] = '{}';
$scope.$broadcast('codeMirror_populated', key);
}
});
})
.catch(function(error) {
ProcessErrors($scope, error, status, formDefs[formTracker.getCurrent()],

View File

@ -68,21 +68,6 @@ export default ['$rootScope', 'GetBasePath', 'ProcessErrors', '$q', '$http', 'Re
deferred.reject(error);
});
return deferred.promise;
},
resetAll: function() {
var deferred = $q.defer();
Rest.setUrl(url);
Rest.destroy()
.success(function(data) {
deferred.resolve(data);
})
.error(function(error) {
deferred.reject(error);
});
return deferred.promise;
}
};

View File

@ -61,10 +61,18 @@ export default ['$rootScope', '$scope', 'Wait', 'CredentialTypesList',
Rest.setUrl(url);
Rest.destroy()
.success(function() {
let reloadListStateParams = null;
if($scope.credential_types.length === 1 && $state.params.credential_type_search && !_.isEmpty($state.params.credential_type_search.page) && $state.params.credential_type_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.credential_type_search.page = (parseInt(reloadListStateParams.credential_type_search.page)-1).toString();
}
if (parseInt($state.params.credential_type_id) === id) {
$state.go('^', null, { reload: true });
$state.go('^', reloadListStateParams, { reload: true });
} else {
$state.go('.', null, { reload: true });
$state.go('.', reloadListStateParams, { reload: true });
}
})
.error(function(data, status) {

View File

@ -4,14 +4,12 @@
* All Rights Reserved
*************************************************/
export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ClearScope',
export default ['$scope', 'Rest', 'CredentialList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n',
function($scope, Rest, CredentialList, Prompt, ClearScope,
function($scope, Rest, CredentialList, Prompt,
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset,
i18n) {
ClearScope();
var list = CredentialList,
defaultUrl = GetBasePath('credentials');
@ -76,10 +74,18 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ClearScope',
Rest.setUrl(url);
Rest.destroy()
.success(function() {
let reloadListStateParams = null;
if($scope.credentials.length === 1 && $state.params.credential_search && !_.isEmpty($state.params.credential_search.page) && $state.params.credential_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.credential_search.page = (parseInt(reloadListStateParams.credential_search.page)-1).toString();
}
if (parseInt($state.params.credential_id) === id) {
$state.go("^", null, { reload: true });
$state.go("^", reloadListStateParams, { reload: true });
} else {
$state.go('.', null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
}
Wait('stop');
})

View File

@ -5,11 +5,9 @@
*************************************************/
export default ['$scope', '$rootScope','Wait',
'ClearScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'graphData',
'Rest', 'GetBasePath', 'ProcessErrors', 'graphData',
function($scope, $rootScope, Wait,
ClearScope, Rest, GetBasePath, ProcessErrors, graphData) {
ClearScope('home');
Rest, GetBasePath, ProcessErrors, graphData) {
var dataCount = 0;

View File

@ -68,31 +68,6 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
$scope.goToInsights = function(id){
$state.go('hosts.edit.insights', {host_id:id});
};
$scope.deleteHost = function(id, name){
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the host below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(name) + '</div>';
var action = function(){
delete $rootScope.promptActionBtnClass;
Wait('start');
HostsService.delete(id).then(() => {
$('#prompt-modal').modal('hide');
if (parseInt($state.params.host_id) === id) {
$state.go("hosts", null, {reload: true});
} else {
$state.go($state.current.name, null, {reload: true});
}
Wait('stop');
});
};
// Prompt depends on having $rootScope.promptActionBtnClass available...
Prompt({
hdr: 'Delete Host',
body: body,
action: action,
actionText: 'DELETE',
});
$rootScope.promptActionBtnClass = 'Modal-errorButton';
};
$scope.toggleHost = function(event, host) {
try {
$(event.target).tooltip('hide');

View File

@ -70,7 +70,15 @@
$('#host-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
$('#host-disassociate-modal').off('hidden.bs.modal');
$state.go('.', null, {reload: true});
let reloadListStateParams = null;
if($scope.groups.length === 1 && $state.params.group_search && !_.isEmpty($state.params.group_search.page) && $state.params.group_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.group_search.page = (parseInt(reloadListStateParams.group_search.page)-1).toString();
}
$state.go('.', reloadListStateParams, {reload: true});
});
GroupsService.disassociateHost(host.id, $scope.disassociateGroup.id).then(() => {

View File

@ -11,11 +11,9 @@
*/
function adhocController($q, $scope, $stateParams,
$state, CheckPasswords, PromptForPasswords, CreateLaunchDialog, CreateSelect2, adhocForm,
GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, GetChoices,
GenerateForm, Rest, ProcessErrors, GetBasePath, GetChoices,
KindChange, Wait, ParseTypeChange) {
ClearScope();
// this is done so that we can access private functions for testing, but
// we don't want to populate the "public" scope with these internal
// functions
@ -302,6 +300,6 @@ function adhocController($q, $scope, $stateParams,
export default ['$q', '$scope', '$stateParams',
'$state', 'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'CreateSelect2',
'adhocForm', 'GenerateForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
'adhocForm', 'GenerateForm', 'Rest', 'ProcessErrors', 'GetBasePath',
'GetChoices', 'KindChange', 'Wait', 'ParseTypeChange',
adhocController];

View File

@ -9,7 +9,12 @@ export default {
},
data: {
activityStream: true,
activityStreamTarget: 'inventory'
activityStreamTarget: 'inventory',
socket: {
"groups": {
"inventories": ["status_changed"]
}
}
},
views: {
'@': {

View File

@ -18,6 +18,7 @@ export default ['i18n', function(i18n) {
hover: true,
basePath: 'inventory',
title: false,
disableRow: "{{ inventory.pending_deletion }}",
fields: {
status: {
@ -27,7 +28,7 @@ export default ['i18n', function(i18n) {
ngClick: "null",
iconOnly: true,
excludeModal: true,
template: `<source-summary-popover inventory="inventory" ng-if="inventory.kind === ''"></source-summary-popover><host-summary-popover inventory="inventory" ng-class="{'HostSummaryPopover-noSourceSummary': inventory.kind !== ''}"></host-summary-popover>`,
template: `<source-summary-popover inventory="inventory" ng-hide="inventory.pending_deletion" ng-if="inventory.kind === ''"></source-summary-popover><host-summary-popover inventory="inventory" ng-hide="inventory.pending_deletion" ng-class="{'HostSummaryPopover-noSourceSummary': inventory.kind !== ''}"></host-summary-popover>`,
icons: [{
icon: "{{ 'icon-cloud-' + inventory.syncStatus }}",
awToolTip: "{{ inventory.syncTip }}",
@ -97,7 +98,8 @@ export default ['i18n', function(i18n) {
ngClick: 'editInventory(inventory)',
awToolTip: i18n._('Edit inventory'),
dataPlacement: 'top',
ngShow: 'inventory.summary_fields.user_capabilities.edit'
ngShow: 'inventory.summary_fields.user_capabilities.edit',
ngHide: 'inventory.pending_deletion'
},
view: {
label: i18n._('View'),
@ -111,7 +113,12 @@ export default ['i18n', function(i18n) {
ngClick: "deleteInventory(inventory.id, inventory.name)",
awToolTip: i18n._('Delete inventory'),
dataPlacement: 'top',
ngShow: 'inventory.summary_fields.user_capabilities.delete'
ngShow: 'inventory.summary_fields.user_capabilities.delete',
ngHide: 'inventory.pending_deletion'
},
pending_deletion: {
label: i18n._('Pending Delete'),
}
}
};}];

View File

@ -86,12 +86,7 @@ function InventoriesList($scope,
Rest.setUrl(url);
Rest.destroy()
.success(function () {
if (parseInt($state.params.inventory_id) === id) {
$state.go("^", null, {reload: true});
} else {
$state.go('.', null, {reload: true});
Wait('stop');
}
Wait('stop');
})
.error(function (data, status) {
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
@ -102,11 +97,33 @@ function InventoriesList($scope,
Prompt({
hdr: 'Delete',
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the inventory below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the inventory below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>' +
'<div class="Prompt-bodyNote"><span class="Prompt-bodyNote--emphasis">Note:</span> The inventory will be in a pending status until the final delete is processed.</div>',
action: action,
actionText: 'DELETE'
});
};
$scope.$on(`ws-inventories`, function(e, data){
let inventory = $scope.inventories.find((inventory) => inventory.id === data.inventory_id);
if (data.status === 'pending_deletion') {
inventory.pending_deletion = true;
}
if (data.status === 'deleted') {
let reloadListStateParams = null;
if($scope.inventories.length === 1 && $state.params.inventory_search && !_.isEmpty($state.params.inventory_search.page) && $state.params.inventory_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.inventory_search.page = (parseInt(reloadListStateParams.inventory_search.page)-1).toString();
}
if (parseInt($state.params.inventory_id) === data.inventory_id) {
$state.go("^", reloadListStateParams, {reload: true});
} else {
$state.go('.', reloadListStateParams, {reload: true});
}
}
});
}
export default ['$scope',

View File

@ -100,14 +100,21 @@
$state.go('.', null, {reload: true});
});
let reloadListStateParams = null;
if($scope.groups.length === 1 && $state.params.group_search && !_.isEmpty($state.params.group_search.page) && $state.params.group_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.group_search.page = (parseInt(reloadListStateParams.group_search.page)-1).toString();
}
switch($scope.deleteOption){
case 'promote':
GroupsService.promote($scope.toDelete.id, $stateParams.inventory_id)
.then(() => {
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
$state.go("^", null, {reload: true});
$state.go("^", reloadListStateParams, {reload: true});
} else {
$state.go($state.current, null, {reload: true});
$state.go($state.current, reloadListStateParams, {reload: true});
}
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');
@ -117,9 +124,9 @@
default:
GroupsService.delete($scope.toDelete.id).then(() => {
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
$state.go("^", null, {reload: true});
$state.go("^", reloadListStateParams, {reload: true});
} else {
$state.go($state.current, null, {reload: true});
$state.go($state.current, reloadListStateParams, {reload: true});
}
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');

View File

@ -87,8 +87,16 @@
$('#group-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
// Remove the event handler so that we don't end up with multiple bindings
$('#group-disassociate-modal').off('hidden.bs.modal');
let reloadListStateParams = null;
if($scope.nested_groups.length === 1 && $state.params.nested_group_search && !_.isEmpty($state.params.nested_group_search.page) && $state.params.nested_group_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.nested_group_search.page = (parseInt(reloadListStateParams.nested_group_search.page)-1).toString();
}
// Reload the inventory manage page and show that the group has been removed
$state.go('.', null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
});
let closeModal = function(){

View File

@ -100,8 +100,16 @@ export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePat
$('#host-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
// Remove the event handler so that we don't end up with multiple bindings
$('#host-disassociate-modal').off('hidden.bs.modal');
let reloadListStateParams = null;
if($scope.nested_hosts.length === 1 && $state.params.nested_host_search && !_.isEmpty($state.params.nested_host_search.page) && $state.params.nested_host_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.nested_host_search.page = (parseInt(reloadListStateParams.nested_host_search.page)-1).toString();
}
// Reload the inventory manage page and show that the group has been removed
$state.go('.', null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
});
let closeModal = function(){

View File

@ -96,10 +96,18 @@ export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
Wait('start');
HostsService.delete(id).then(() => {
$('#prompt-modal').modal('hide');
let reloadListStateParams = null;
if($scope.hosts.length === 1 && $state.params.host_search && !_.isEmpty($state.params.host_search.page) && $state.params.host_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.host_search.page = (parseInt(reloadListStateParams.host_search.page)-1).toString();
}
if (parseInt($state.params.host_id) === id) {
$state.go("hosts", null, {reload: true});
$state.go('^', reloadListStateParams, {reload: true});
} else {
$state.go($state.current.name, null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
}
Wait('stop');
});

View File

@ -83,8 +83,16 @@
$('#group-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
// Remove the event handler so that we don't end up with multiple bindings
$('#group-disassociate-modal').off('hidden.bs.modal');
let reloadListStateParams = null;
if($scope.nested_groups.length === 1 && $state.params.nested_group_search && !_.isEmpty($state.params.nested_group_search.page) && $state.params.nested_group_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.nested_group_search.page = (parseInt(reloadListStateParams.nested_group_search.page)-1).toString();
}
// Reload the inventory manage page and show that the group has been removed
$state.go('.', null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
});
let closeModal = function(){

View File

@ -123,10 +123,16 @@
Wait('start');
SourcesService.delete(inventory_source.id).then(() => {
$('#prompt-modal').modal('hide');
let reloadListStateParams = null;
if($scope.inventory_sources.length === 1 && $state.params.inventory_source_search && !_.isEmpty($state.params.inventory_source_search.page) && $state.params.inventory_source_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.inventory_source_search.page = (parseInt(reloadListStateParams.inventory_source_search.page)-1).toString();
}
if (parseInt($state.params.inventory_source_id) === inventory_source.id) {
$state.go("inventories.edit.inventory_sources", {inventory_id: $scope.inventory_id}, {reload: true});
$state.go('^', reloadListStateParams, {reload: true});
} else {
$state.go($state.current.name, null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
}
Wait('stop');
});

View File

@ -12,13 +12,11 @@
function SmartInventoryAdd($scope, $location,
GenerateForm, smartInventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
GetBasePath, ParseTypeChange, Wait, ToJSON,
$state, canAdd) {
$scope.canAdd = canAdd;
ClearScope();
// Inject dynamic view
var defaultUrl = GetBasePath('inventory'),
form = smartInventoryForm;
@ -90,6 +88,6 @@ function SmartInventoryAdd($scope, $location,
export default ['$scope', '$location',
'GenerateForm', 'smartInventoryForm', 'rbacUiControlService', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange',
'ProcessErrors', 'GetBasePath', 'ParseTypeChange',
'Wait', 'ToJSON', '$state', 'canAdd', SmartInventoryAdd
];

View File

@ -6,7 +6,7 @@
function SmartInventoryEdit($scope, $location,
$stateParams, InventoryForm, Rest, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
GetBasePath, ParseTypeChange, Wait, ToJSON,
ParseVariableString, $state, OrgAdminLookup, resourceData, $rootScope) {
// Inject dynamic view
@ -14,12 +14,9 @@ function SmartInventoryEdit($scope, $location,
form = InventoryForm,
inventory_id = $stateParams.smartinventory_id,
inventoryData = resourceData.data;
ClearScope();
init();
function init() {
ClearScope();
form.formLabelSize = null;
form.formFieldSize = null;
$scope.inventory_id = inventory_id;
@ -97,7 +94,7 @@ function SmartInventoryEdit($scope, $location,
export default [ '$scope', '$location',
'$stateParams', 'InventoryForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait',
'ProcessErrors', 'GetBasePath', 'ParseTypeChange', 'Wait',
'ToJSON', 'ParseVariableString',
'$state', 'OrgAdminLookup', 'resourceData', '$rootScope', SmartInventoryEdit
];

View File

@ -12,13 +12,11 @@
function InventoriesAdd($scope, $location,
GenerateForm, InventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
GetBasePath, ParseTypeChange, Wait, ToJSON,
$state, canAdd, CreateSelect2, InstanceGroupsService) {
$scope.canAdd = canAdd;
ClearScope();
// Inject dynamic view
var defaultUrl = GetBasePath('inventory'),
form = InventoryForm;
@ -96,6 +94,6 @@ function InventoriesAdd($scope, $location,
export default ['$scope', '$location',
'GenerateForm', 'InventoryForm', 'rbacUiControlService', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange',
'ProcessErrors', 'GetBasePath', 'ParseTypeChange',
'Wait', 'ToJSON', '$state','canAdd', 'CreateSelect2', 'InstanceGroupsService', InventoriesAdd
];
];

View File

@ -12,7 +12,7 @@
function InventoriesEdit($scope, $location,
$stateParams, InventoryForm, Rest, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
GetBasePath, ParseTypeChange, Wait, ToJSON,
ParseVariableString, $state, OrgAdminLookup, $rootScope, resourceData, CreateSelect2, InstanceGroupsService, InstanceGroupsData) {
// Inject dynamic view
@ -111,7 +111,7 @@ function InventoriesEdit($scope, $location,
export default ['$scope', '$location',
'$stateParams', 'InventoryForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait',
'ProcessErrors', 'GetBasePath', 'ParseTypeChange', 'Wait',
'ToJSON', 'ParseVariableString',
'$state', 'OrgAdminLookup', '$rootScope', 'resourceData', 'CreateSelect2', 'InstanceGroupsService', 'InstanceGroupsData', InventoriesEdit,
];

View File

@ -76,7 +76,7 @@ function(i18n, InventoryCompletedJobsList) {
sourceModel: 'insights_credential',
sourceField: 'name',
search: {
credential_type: 13 //insights
credential_type: '13' //insights
}
},
instance_groups: {
@ -201,7 +201,7 @@ function(i18n, InventoryCompletedJobsList) {
relatedButtons: {
remediate_inventory: {
ngClick: 'remediateInventory(id, name, insights_credential)',
ngShow: 'insights_credential!==null',
ngShow: 'insights_credential!==null && mode !== "add"',
label: i18n._('Remediate Inventory'),
class: 'Form-primaryButton'
}

View File

@ -51,10 +51,18 @@ export default ['$rootScope', '$scope', 'Wait', 'InventoryScriptsList',
Rest.setUrl(url);
Rest.destroy()
.success(function() {
let reloadListStateParams = null;
if($scope.inventory_scripts.length === 1 && $state.params.inventory_script_search && !_.isEmpty($state.params.inventory_script_search.page) && $state.params.inventory_script_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.inventory_script_search.page = (parseInt(reloadListStateParams.inventory_script_search.page)-1).toString();
}
if (parseInt($state.params.inventory_script_id) === id) {
$state.go('^', null, { reload: true });
$state.go('^', reloadListStateParams, { reload: true });
} else {
$state.go('.', null, { reload: true });
$state.go('.', reloadListStateParams, { reload: true });
}
})
.error(function(data, status) {

View File

@ -70,7 +70,14 @@ export default
scope.$emit(callback, action_label);
}
else {
$state.reload();
let reloadListStateParams = null;
if(scope.jobs.length === 1 && $state.params.job_search && !_.isEmpty($state.params.job_search.page) && $state.params.job_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.job_search.page = (parseInt(reloadListStateParams.job_search.page)-1).toString();
}
$state.go('.', reloadListStateParams, {reload: true});
Wait('stop');
}
})

View File

@ -11,14 +11,12 @@
*/
export default ['$state', '$rootScope', '$scope', '$stateParams',
'ClearScope', 'Find', 'DeleteJob', 'RelaunchJob',
'Find', 'DeleteJob', 'RelaunchJob',
'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate',
function($state, $rootScope, $scope, $stateParams,
ClearScope, Find, DeleteJob, RelaunchJob,
Find, DeleteJob, RelaunchJob,
GetBasePath, Dataset, qs, ListDefinition, $interpolate) {
ClearScope();
var list = ListDefinition;
init();

View File

@ -8,9 +8,8 @@ import authenticationController from './loginModal.controller';
/* jshint unused: vars */
export default
[ 'templateUrl',
'ClearScope',
'Wait',
function(templateUrl, ClearScope, Wait) {
function(templateUrl, Wait) {
return {
restrict: 'E',
scope: true,
@ -19,7 +18,6 @@ export default
link: function(scope, element, attrs) {
var setLoginFocus = function () {
// Need to clear out any open dialog windows that might be open when this modal opens.
ClearScope();
$('#login-username').focus();
};

View File

@ -165,10 +165,18 @@
Rest.setUrl(url);
Rest.destroy()
.success(function() {
let reloadListStateParams = null;
if($scope.notification_templates.length === 1 && $state.params.notification_template_search && !_.isEmpty($state.params.notification_template_search.page) && $state.params.notification_template_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.notification_template_search.page = (parseInt(reloadListStateParams.notification_template_search.page)-1).toString();
}
if (parseInt($state.params.notification_template_id) === id) {
$state.go("^", null, { reload: true });
$state.go("^", reloadListStateParams, { reload: true });
} else {
$state.go('.', null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
}
Wait('stop');
})

View File

@ -6,9 +6,9 @@
export default ['$scope', '$rootScope', '$location', '$stateParams',
'OrganizationForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', 'CreateSelect2', '$state','InstanceGroupsService',
'ProcessErrors', 'GetBasePath', 'Wait', 'CreateSelect2', '$state','InstanceGroupsService',
function($scope, $rootScope, $location, $stateParams, OrganizationForm,
GenerateForm, Rest, Alert, ProcessErrors, ClearScope, GetBasePath, Wait, CreateSelect2, $state, InstanceGroupsService) {
GenerateForm, Rest, Alert, ProcessErrors, GetBasePath, Wait, CreateSelect2, $state, InstanceGroupsService) {
Rest.setUrl(GetBasePath('organizations'));
Rest.options()
@ -19,8 +19,6 @@ export default ['$scope', '$rootScope', '$location', '$stateParams',
}
});
ClearScope();
var form = OrganizationForm(),
base = $location.path().replace(/^\//, '').split('/')[0];
init();

View File

@ -5,14 +5,12 @@
*************************************************/
export default ['$scope', '$location', '$stateParams',
'OrganizationForm', 'Rest', 'ProcessErrors', 'Prompt', 'ClearScope',
'OrganizationForm', 'Rest', 'ProcessErrors', 'Prompt',
'GetBasePath', 'Wait', '$state', 'ToggleNotification', 'CreateSelect2', 'InstanceGroupsService', 'InstanceGroupsData',
function($scope, $location, $stateParams,
OrganizationForm, Rest, ProcessErrors, Prompt, ClearScope,
OrganizationForm, Rest, ProcessErrors, Prompt,
GetBasePath, Wait, $state, ToggleNotification, CreateSelect2, InstanceGroupsService, InstanceGroupsData) {
ClearScope();
let form = OrganizationForm(),
defaultUrl = GetBasePath('organizations'),
base = $location.path().replace(/^\//, '').split('/')[0],

View File

@ -6,14 +6,12 @@
export default ['$stateParams', '$scope', '$rootScope',
'Rest', 'OrganizationList', 'Prompt', 'ClearScope',
'Rest', 'OrganizationList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', 'rbacUiControlService', '$filter', 'Dataset', 'i18n',
function($stateParams, $scope, $rootScope,
Rest, OrganizationList, Prompt, ClearScope,
Rest, OrganizationList, Prompt,
ProcessErrors, GetBasePath, Wait, $state, rbacUiControlService, $filter, Dataset, i18n) {
ClearScope();
var defaultUrl = GetBasePath('organizations'),
list = OrganizationList;
@ -147,10 +145,18 @@ export default ['$stateParams', '$scope', '$rootScope',
Rest.destroy()
.success(function() {
Wait('stop');
let reloadListStateParams = null;
if($scope.organizations.length === 1 && $state.params.organization_search && !_.isEmpty($state.params.organization_search.page) && $state.params.organization_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.organization_search.page = (parseInt(reloadListStateParams.organization_search.page)-1).toString();
}
if (isDeletedOrganizationBeingEdited(id, parseInt($stateParams.organization_id)) === true) {
$state.go('^', null, { reload: true });
$state.go('^', reloadListStateParams, { reload: true });
} else {
$state.reload('organizations');
$state.go('.', reloadListStateParams, { reload: true });
}
})
.error(function(data, status) {

View File

@ -5,17 +5,15 @@
*************************************************/
export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest',
'Alert', 'ProcessErrors', 'GenerateForm', 'Prompt', 'ClearScope',
'Alert', 'ProcessErrors', 'GenerateForm', 'Prompt',
'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty',
'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification',
'i18n', 'CredentialTypes',
function($scope, $rootScope, $stateParams, ProjectsForm, Rest, Alert,
ProcessErrors, GenerateForm, Prompt, ClearScope, GetBasePath,
ProcessErrors, GenerateForm, Prompt, GetBasePath,
GetProjectPath, Authorization, GetChoices, Empty, Wait, ProjectUpdate,
$state, CreateSelect2, ToggleNotification, i18n, CredentialTypes) {
ClearScope('htmlTemplate');
var form = ProjectsForm(),
defaultUrl = GetBasePath('projects') + $stateParams.project_id + '/',
master = {},

View File

@ -179,10 +179,18 @@ export default ['$scope', '$rootScope', '$log', 'Rest', 'Alert',
Rest.setUrl(url);
Rest.destroy()
.success(function() {
let reloadListStateParams = null;
if($scope.projects.length === 1 && $state.params.project_search && !_.isEmpty($state.params.project_search.page) && $state.params.project_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.project_search.page = (parseInt(reloadListStateParams.project_search.page)-1).toString();
}
if (parseInt($state.params.project_id) === id) {
$state.go("^", null, { reload: true });
$state.go("^", reloadListStateParams, { reload: true });
} else {
$state.go('.', null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
}
})
.error(function (data, status) {

View File

@ -25,11 +25,19 @@ export default
.success(function () {
$('#prompt-modal').modal('hide');
scope.$emit(callback, id);
if (new RegExp('/' + id + '$').test($location.$$url)) {
$location.url($location.url().replace(/[/][0-9]+$/, "")); // go to list view
let reloadListStateParams = null;
if(scope.schedules.length === 1 && $state.params.schedule_search && !_.isEmpty($state.params.schedule_search.page) && $state.params.schedule_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.schedule_search.page = (parseInt(reloadListStateParams.schedule_search.page)-1).toString();
}
if (parseInt($state.params.schedule_id) === id) {
$state.go('^', reloadListStateParams, {reload: true});
}
else{
$state.go('.', null, {reload: true});
$state.go('.', reloadListStateParams, {reload: true});
}
})
.error(function (data, status) {

View File

@ -13,16 +13,14 @@
export default [
'$scope', '$location', '$stateParams', 'ScheduleList', 'Rest',
'ClearScope', 'rbacUiControlService',
'rbacUiControlService',
'ToggleSchedule', 'DeleteSchedule', '$q', '$state', 'Dataset', 'ParentObject', 'UnifiedJobsOptions',
function($scope, $location, $stateParams,
ScheduleList, Rest, ClearScope,
ScheduleList, Rest,
rbacUiControlService,
ToggleSchedule, DeleteSchedule,
$q, $state, Dataset, ParentObject, UnifiedJobsOptions) {
ClearScope();
var base, scheduleEndpoint,
list = ScheduleList;

View File

@ -20,43 +20,6 @@
export default
angular.module('Utilities', ['RestServices', 'Utilities'])
/**
* @ngdoc method
* @name shared.function:Utilities#ClearScope
* @methodOf shared.function:Utilities
* @description
* Place to remove things that might be lingering from a prior tab or view.
* This used to destroy the scope, but that causes issues in angular 1.2.x
*/
.factory('ClearScope', ['$rootScope', function($rootScope) {
return function() {
$rootScope.flashMessage = null;
//$('#form-modal .modal-body').empty();
$('#form-modal2 .modal-body').empty();
$('.tooltip').each(function() {
$(this).remove();
});
$('.popover').each(function() {
$(this).remove();
});
$('.ui-dialog-content').each(function() {
$(this).dialog('close');
});
try {
$('#help-modal').dialog('close');
} catch (e) {
// ignore
}
};
}])
/**
* @ngdoc method
* @name shared.function:Utilities#Empty

View File

@ -272,6 +272,21 @@ function(ConfigurationUtils, i18n, $rootScope) {
};
})
// the disableRow directive disables table row click events
.directive('disableRow', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function(event) {
if (attrs.disableRow) {
event.preventDefault();
}
return;
});
}
};
})
.directive('awSurveyQuestion', function() {
return {

View File

@ -292,6 +292,7 @@ export default ['$compile', 'Attr', 'Icon',
innerTable += options.mode === 'lookup' ? `<tbody ng-init="selection.${list.iterator} = {id: $parent.${list.iterator}, name: $parent.${list.iterator}_name}">` : `"<tbody>\n"`;
innerTable += "<tr ng-class=\"[" + list.iterator;
innerTable += (options.mode === 'lookup' || options.mode === 'select') ? ".success_class" : ".active_class";
innerTable += (list.disableRow) ? `, {true: 'List-tableRow--disabled'}[${list.iterator}.pending_deletion]` : "";
if (list.multiSelect) {
innerTable += ", " + list.iterator + ".isSelected ? 'is-selected-row' : ''";
@ -300,7 +301,8 @@ export default ['$compile', 'Attr', 'Icon',
innerTable += "]\" ";
innerTable += "id=\"{{ " + list.iterator + ".id }}\" ";
innerTable += "class=\"List-tableRow " + list.iterator + "_class\" ";
innerTable += "ng-repeat=\"" + list.iterator + " in " + list.name;
innerTable += "ng-repeat=\"" + list.iterator + " in " + list.name + "\"";
innerTable += (list.disableRow) ? " disable-row=" + list.disableRow + " " : "";
innerTable += (list.trackBy) ? " track by " + list.trackBy : "";
innerTable += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : "";
innerTable += (list.filterBy) ? " | filter: " + list.filterBy : "";
@ -379,7 +381,11 @@ export default ['$compile', 'Attr', 'Icon',
type: 'fieldActions',
td: false
});
} else {
}
if (field_action === 'pending_deletion') {
innerTable += `<a ng-if='${list.iterator}.pending_deletion'>Pending Delete</a>`;
}
else {
fAction = list.fieldActions[field_action];
innerTable += "<button id=\"";
innerTable += (fAction.id) ? fAction.id : field_action + "-action";
@ -402,6 +408,7 @@ export default ['$compile', 'Attr', 'Icon',
innerTable += `ng-class="{'List-editButton--selected' : $stateParams['${list.iterator}_id'] == ${list.iterator}.id}"`;
}
}
innerTable += (fAction.ngDisabled) ? "ng-disabled=\"" + fAction.ngDisabled + "\"" : "";
innerTable += (fAction.awPopOver) ? "aw-pop-over=\"" + fAction.awPopOver + "\" " : "";
innerTable += (fAction.dataPlacement) ? Attr(fAction, 'dataPlacement') : "";
innerTable += (fAction.dataTitle) ? Attr(fAction, 'dataTitle') : "";
@ -430,7 +437,6 @@ export default ['$compile', 'Attr', 'Icon',
}
innerTable += "</tr>\n";
// End List
innerTable += "</tbody>\n";

View File

@ -11,6 +11,16 @@
word-break: break-word;
}
.Prompt-bodyNote {
margin: 20px 0;
color: @default-interface-txt;
}
.Prompt-bodyNote--emphasis {
text-transform: uppercase;
color: @default-err;
}
.Prompt-emphasis {
font-weight: bold;
text-transform: uppercase;

View File

@ -11,11 +11,9 @@
*/
export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
ClearScope, GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName,
GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName,
ParseTypeChange, ParseVariableString, RelaunchJob, DeleteJob, Wait, i18n) {
ClearScope();
var job_id = $stateParams.id,
jobType = $state.current.data.jobType;
@ -279,6 +277,6 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
}
JobStdoutController.$inject = [ '$rootScope', '$scope', '$state',
'$stateParams', 'ClearScope', 'GetBasePath', 'Rest', 'ProcessErrors',
'$stateParams', 'GetBasePath', 'Rest', 'ProcessErrors',
'Empty', 'GetChoices', 'LookUpName', 'ParseTypeChange',
'ParseVariableString', 'RelaunchJob', 'DeleteJob', 'Wait', 'i18n'];

View File

@ -5,12 +5,9 @@
*************************************************/
export default ['$scope', '$rootScope', 'TeamForm', 'GenerateForm', 'Rest',
'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state',
'Alert', 'ProcessErrors', 'GetBasePath', 'Wait', '$state',
function($scope, $rootScope, TeamForm, GenerateForm, Rest, Alert,
ProcessErrors, ClearScope, GetBasePath, Wait, $state) {
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//$scope.
ProcessErrors, GetBasePath, Wait, $state) {
Rest.setUrl(GetBasePath('teams'));
Rest.options()

View File

@ -5,10 +5,9 @@
*************************************************/
export default ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state',
'ProcessErrors', 'GetBasePath', 'Wait', '$state',
function($scope, $rootScope, $stateParams, TeamForm, Rest, ProcessErrors,
ClearScope, GetBasePath, Wait, $state) {
ClearScope();
GetBasePath, Wait, $state) {
var form = TeamForm,
id = $stateParams.team_id,

View File

@ -4,14 +4,12 @@
* All Rights Reserved
*************************************************/
export default ['$scope', 'Rest', 'TeamList', 'Prompt', 'ClearScope',
export default ['$scope', 'Rest', 'TeamList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter',
'rbacUiControlService', 'Dataset',
function($scope, Rest, TeamList, Prompt, ClearScope, ProcessErrors,
function($scope, Rest, TeamList, Prompt, ProcessErrors,
GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) {
ClearScope();
var list = TeamList,
defaultUrl = GetBasePath('teams');
@ -53,10 +51,18 @@ export default ['$scope', 'Rest', 'TeamList', 'Prompt', 'ClearScope',
.success(function() {
Wait('stop');
$('#prompt-modal').modal('hide');
let reloadListStateParams = null;
if($scope.teams.length === 1 && $state.params.team_search && !_.isEmpty($state.params.team_search.page) && $state.params.team_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.team_search.page = (parseInt(reloadListStateParams.team_search.page)-1).toString();
}
if (parseInt($state.params.team_id) === id) {
$state.go('^', null, { reload: true });
$state.go('^', reloadListStateParams, { reload: true });
} else {
$state.go('.', null, { reload: true });
$state.go('.', reloadListStateParams, { reload: true });
}
})
.error(function(data, status) {

View File

@ -7,18 +7,17 @@
export default
[ '$filter', '$scope',
'$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'Wait',
'ProcessErrors', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'Wait',
'Empty', 'ToJSON', 'CallbackHelpInit', 'GetChoices', '$state', 'availableLabels',
'CreateSelect2', '$q', 'i18n', 'Inventory', 'Project', 'InstanceGroupsService', 'MultiCredentialService',
'CreateSelect2', '$q', 'i18n', 'Inventory', 'Project', 'InstanceGroupsService', 'MultiCredentialService',
function(
$filter, $scope,
$stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
ProcessErrors, ClearScope, GetBasePath, md5Setup, ParseTypeChange, Wait,
ProcessErrors, GetBasePath, md5Setup, ParseTypeChange, Wait,
Empty, ToJSON, CallbackHelpInit, GetChoices,
$state, availableLabels, CreateSelect2, $q, i18n, Inventory, Project, InstanceGroupsService, MultiCredentialService
) {
ClearScope();
// Inject dynamic view
let defaultUrl = GetBasePath('job_templates'),
form = JobTemplateForm(),

View File

@ -13,7 +13,7 @@
export default
[ '$filter', '$scope', '$rootScope',
'$location', '$stateParams', 'JobTemplateForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'md5Setup',
'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'md5Setup',
'ParseTypeChange', 'Wait', 'selectedLabels',
'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit',
'InitiatePlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
@ -21,14 +21,12 @@ export default
function(
$filter, $scope, $rootScope,
$location, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
ProcessErrors, ClearScope, GetBasePath, md5Setup,
ProcessErrors, GetBasePath, md5Setup,
ParseTypeChange, Wait, selectedLabels,
Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit, InitiatePlaybookRun, SurveyControllerInit, $state,
CreateSelect2, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, MultiCredentialService, availableLabels
) {
ClearScope();
$scope.$watch('job_template_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAddJobTemplate = false;

View File

@ -5,17 +5,16 @@
*************************************************/
export default ['$scope', '$rootScope',
'Alert','TemplateList', 'Prompt', 'ClearScope', 'ProcessErrors',
'Alert','TemplateList', 'Prompt', 'ProcessErrors',
'GetBasePath', 'InitiatePlaybookRun', 'Wait', '$state', '$filter',
'Dataset', 'rbacUiControlService', 'TemplatesService','QuerySet',
'TemplateCopyService',
function(
$scope, $rootScope, Alert,
TemplateList, Prompt, ClearScope, ProcessErrors, GetBasePath,
TemplateList, Prompt, ProcessErrors, GetBasePath,
InitiatePlaybookRun, Wait, $state, $filter, Dataset, rbacUiControlService, TemplatesService,
qs, TemplateCopyService
) {
ClearScope();
var list = TemplateList;
@ -77,7 +76,7 @@ export default ['$scope', '$rootScope',
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
});
});
$scope.editJobTemplate = function(template) {
if(template) {
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
@ -106,11 +105,19 @@ export default ['$scope', '$rootScope',
function handleSuccessfulDelete(isWorkflow) {
let stateParamId = isWorkflow ? $state.params.workflow_job_template_id : $state.params.job_template_id;
let reloadListStateParams = null;
if($scope.templates.length === 1 && $state.params.template_search && !_.isEmpty($state.params.template_search.page) && $state.params.template_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.template_search.page = (parseInt(reloadListStateParams.template_search.page)-1).toString();
}
if (parseInt(stateParamId) === template.id) {
// Move the user back to the templates list
$state.go("templates", null, {reload: true});
$state.go("templates", reloadListStateParams, {reload: true});
} else {
$state.go(".", null, {reload: true});
$state.go(".", reloadListStateParams, {reload: true});
}
Wait('stop');
}

View File

@ -17,8 +17,8 @@ function link($timeout, CreateSelect2, scope, element, attrs, ngModel) {
element: element.find('select'),
multiple: scope.isMultipleSelect(),
minimumResultsForSearch: scope.isMultipleSelect() ? Infinity : 10,
customDropdownAdapter: scope.preview ? false : true,
disabledOptions: true
customDropdownAdapter: true,
disabledOptions: scope.preview ? true : false
});
});

View File

@ -10,7 +10,8 @@
question="question"
choices="choices"
ng-required="isRequired === 'true'"
ng-model="defaultValue">
ng-model="defaultValue"
preview="preview">
</multiple-choice>
</div>
<div ng-if="question.type === 'password'" class="input_area input-group">

View File

@ -6,13 +6,12 @@
export default [
'$scope', 'WorkflowForm', 'GenerateForm', 'Alert', 'ProcessErrors',
'ClearScope', 'Wait', '$state', 'CreateSelect2', 'TemplatesService',
'Wait', '$state', 'CreateSelect2', 'TemplatesService',
'ToJSON', 'ParseTypeChange', '$q', 'Rest', 'GetBasePath', 'availableLabels',
function($scope, WorkflowForm, GenerateForm, Alert, ProcessErrors,
ClearScope, Wait, $state, CreateSelect2, TemplatesService, ToJSON,
Wait, $state, CreateSelect2, TemplatesService, ToJSON,
ParseTypeChange, $q, Rest, GetBasePath, availableLabels) {
ClearScope();
// Inject dynamic view
let form = WorkflowForm(),
generator = GenerateForm;

View File

@ -6,16 +6,15 @@
export default [
'$scope', '$stateParams', 'WorkflowForm', 'GenerateForm', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', '$q', 'ParseTypeChange',
'ProcessErrors', 'GetBasePath', '$q', 'ParseTypeChange',
'Wait', 'Empty', 'ToJSON', 'initSurvey', '$state', 'CreateSelect2',
'ParseVariableString', 'TemplatesService', 'Rest', 'ToggleNotification',
'OrgAdminLookup', 'availableLabels', 'selectedLabels', 'workflowJobTemplateData',
function($scope, $stateParams, WorkflowForm, GenerateForm, Alert,
ProcessErrors, ClearScope, GetBasePath, $q, ParseTypeChange, Wait, Empty,
ProcessErrors, GetBasePath, $q, ParseTypeChange, Wait, Empty,
ToJSON, SurveyControllerInit, $state, CreateSelect2, ParseVariableString,
TemplatesService, Rest, ToggleNotification, OrgAdminLookup, availableLabels, selectedLabels, workflowJobTemplateData) {
ClearScope();
$scope.$watch('workflow_job_template_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAddWorkflowJobTemplate = false;

View File

@ -13,12 +13,11 @@ const user_type_options = [
];
export default ['$scope', '$rootScope', 'UserForm', 'GenerateForm', 'Rest',
'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath',
'Alert', 'ProcessErrors', 'ReturnToCaller', 'GetBasePath',
'Wait', 'CreateSelect2', '$state', '$location', 'i18n',
function($scope, $rootScope, UserForm, GenerateForm, Rest, Alert,
ProcessErrors, ReturnToCaller, ClearScope, GetBasePath, Wait, CreateSelect2,
ProcessErrors, ReturnToCaller, GetBasePath, Wait, CreateSelect2,
$state, $location, i18n) {
ClearScope();
var defaultUrl = GetBasePath('organizations'),
form = UserForm;

View File

@ -13,15 +13,14 @@ const user_type_options = [
];
export default ['$scope', '$rootScope', '$stateParams', 'UserForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', 'CreateSelect2',
'ProcessErrors', 'GetBasePath', 'Wait', 'CreateSelect2',
'$state', 'i18n',
function($scope, $rootScope, $stateParams, UserForm, Rest, ProcessErrors,
ClearScope, GetBasePath, Wait, CreateSelect2, $state, i18n) {
GetBasePath, Wait, CreateSelect2, $state, i18n) {
for (var i = 0; i < user_type_options.length; i++) {
user_type_options[i].label = i18n._(user_type_options[i].label);
}
ClearScope();
var form = UserForm,
master = {},

View File

@ -13,9 +13,9 @@ const user_type_options = [
];
export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt',
'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter',
'rbacUiControlService', 'Dataset', 'i18n',
function($scope, $rootScope, Rest, UserList, Prompt, ClearScope,
function($scope, $rootScope, Rest, UserList, Prompt,
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService,
Dataset, i18n) {
@ -23,8 +23,6 @@ export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt',
user_type_options[i].label = i18n._(user_type_options[i].label);
}
ClearScope();
var list = UserList,
defaultUrl = GetBasePath('users');
@ -65,6 +63,14 @@ export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt',
Rest.setUrl(url);
Rest.destroy()
.success(function() {
let reloadListStateParams = null;
if($scope.users.length === 1 && $state.params.user_search && !_.isEmpty($state.params.user_search.page) && $state.params.user_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.user_search.page = (parseInt(reloadListStateParams.user_search.page)-1).toString();
}
if (parseInt($state.params.user_id) === id) {
$state.go('^', null, { reload: true });
} else {

View File

@ -6,7 +6,6 @@ describe('Controller: TemplatesList', () => {
rootScope,
state,
TemplatesListController,
ClearScope,
GetChoices,
Alert,
Prompt,
@ -53,7 +52,6 @@ describe('Controller: TemplatesList', () => {
}
};
ClearScope = jasmine.createSpy('ClearScope');
GetChoices = jasmine.createSpy('GetChoices');
Alert = jasmine.createSpy('Alert');
Prompt = jasmine.createSpy('Prompt').and.callFake(function(args) {
@ -61,7 +59,6 @@ describe('Controller: TemplatesList', () => {
});
InitiatePlaybookRun = jasmine.createSpy('InitiatePlaybookRun');
$provide.value('ClearScope', ClearScope);
$provide.value('GetChoices', GetChoices);
$provide.value('Alert', Alert);
$provide.value('Prompt', Prompt);
@ -69,12 +66,11 @@ describe('Controller: TemplatesList', () => {
$provide.value('InitiatePlaybookRun', InitiatePlaybookRun);
}));
beforeEach(angular.mock.inject( ($rootScope, $controller, $q, _state_, _ConfigService_, _ClearScope_, _GetChoices_, _Alert_, _Prompt_, _InitiatePlaybookRun_) => {
beforeEach(angular.mock.inject( ($rootScope, $controller, $q, _state_, _ConfigService_, _GetChoices_, _Alert_, _Prompt_, _InitiatePlaybookRun_) => {
scope = $rootScope.$new();
rootScope = $rootScope;
q = $q;
state = _state_;
ClearScope = _ClearScope_;
GetChoices = _GetChoices_;
Alert = _Alert_;
Prompt = _Prompt_;
@ -92,7 +88,6 @@ describe('Controller: TemplatesList', () => {
$scope: scope,
$rootScope: rootScope,
$state: state,
ClearScope: ClearScope,
GetChoices: GetChoices,
Alert: Alert,
Prompt: Prompt,

View File

@ -5,7 +5,6 @@ describe('Controller: WorkflowAdd', () => {
let scope,
state,
WorkflowAdd,
ClearScope,
Alert,
GenerateForm,
TemplatesService,
@ -49,8 +48,7 @@ describe('Controller: WorkflowAdd', () => {
name: "foo",
id: "1"
}];
ClearScope = jasmine.createSpy('ClearScope');
Alert = jasmine.createSpy('Alert');
ProcessErrors = jasmine.createSpy('ProcessErrors');
CreateSelect2 = jasmine.createSpy('CreateSelect2');
@ -58,7 +56,6 @@ describe('Controller: WorkflowAdd', () => {
ParseTypeChange = jasmine.createSpy('ParseTypeChange');
ToJSON = jasmine.createSpy('ToJSON');
$provide.value('ClearScope', ClearScope);
$provide.value('Alert', Alert);
$provide.value('GenerateForm', GenerateForm);
$provide.value('state', state);
@ -70,11 +67,10 @@ describe('Controller: WorkflowAdd', () => {
$provide.value('availableLabels', availableLabels);
}));
beforeEach(angular.mock.inject( ($rootScope, $controller, $q, $httpBackend, _state_, _ConfigService_, _ClearScope_, _GetChoices_, _Alert_, _GenerateForm_, _ProcessErrors_, _CreateSelect2_, _Wait_, _ParseTypeChange_, _ToJSON_, _availableLabels_) => {
beforeEach(angular.mock.inject( ($rootScope, $controller, $q, $httpBackend, _state_, _ConfigService_, _GetChoices_, _Alert_, _GenerateForm_, _ProcessErrors_, _CreateSelect2_, _Wait_, _ParseTypeChange_, _ToJSON_, _availableLabels_) => {
scope = $rootScope.$new();
state = _state_;
q = $q;
ClearScope = _ClearScope_;
Alert = _Alert_;
GenerateForm = _GenerateForm_;
httpBackend = $httpBackend;
@ -99,7 +95,6 @@ describe('Controller: WorkflowAdd', () => {
WorkflowAdd = $controller('WorkflowAdd', {
$scope: scope,
$state: state,
ClearScope: ClearScope,
Alert: Alert,
GenerateForm: GenerateForm,
TemplatesService: TemplatesService,
@ -112,10 +107,6 @@ describe('Controller: WorkflowAdd', () => {
});
}));
it('should call ClearScope', ()=>{
expect(ClearScope).toHaveBeenCalled();
});
it('should get/set the label options and select2-ify the input', ()=>{
// We expect the digest cycle to fire off this call to /static/config.js so we go ahead and handle it
httpBackend.expectGET('/static/config.js').respond(200);

View File

@ -140,12 +140,11 @@ controller=security
```
In the isolated rampart model, "controller" instances interact with "isolated"
instances via a series of Ansible playbooks over SSH. As such, all isolated instances
must be preconfigured by the installer with passwordless SSH access from any potential
controller instances. In the example above, the `isolatedA` and `isolatedB` hosts
must be reachable from `towerB` and `towerC` hosts via `ssh
awx@<isolated-hostname>` (meaning, `authorized_keys` must be pre-distributed to
the `isolatedA` and `isolatedB` hosts).
instances via a series of Ansible playbooks over SSH. At installation time,
a randomized RSA key is generated and distributed as an authorized key to all
"isolated" instances. The private half of the key is encrypted and stored
within Tower, and is used to authenticate from "controller" instances to
"isolated" instances when jobs are run.
When a job is scheduled to run on an "isolated" instance:
@ -185,6 +184,20 @@ Recommendations for system configuration with isolated groups:
variable - the behavior in this case can not be predicted.
- Do not put an isolated instance in more than 1 isolated group.
Isolated Node Authentication
----------------------------
By default - at installation time - a randomized RSA key is generated and
distributed as an authorized key to all "isolated" instances. The private half
of the key is encrypted and stored within Tower, and is used to authenticate
from "controller" instances to "isolated" instances when jobs are run.
For users who wish to manage SSH authentication from controlling nodes to
isolated nodes via some system _outside_ of Tower (such as externally-managed
passwordless SSH keys), this behavior can be disabled by unsetting two Tower
API settings values:
`HTTP PATCH /api/v2/settings/jobs/ {'AWX_ISOLATED_PRIVATE_KEY': '', 'AWX_ISOLATED_PUBLIC_KEY': ''}`
### Provisioning and Deprovisioning Instances and Groups

View File

@ -40,27 +40,6 @@ and they are structured as follows:
The `controller` for the group "thepentagon" and all hosts therein is
determined by a ForeignKey within the instance group.
## Development Testing Notes
### Test the SSH connection between containers
While the environment is running, you can test the connection like so:
```bash
docker exec -i -t tools_tower_1 /bin/bash
```
Inside the context of that container:
```bash
ssh root@isolated
```
(note: awx user has been deprecated)
This should give a shell to the `tools_isolated_1` container, as the
`tools_tower_1` container sees it.
### Run a playbook
In order to run an isolated job, associate the instance group `thepentagon` with