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
97 changed files with 1469 additions and 924 deletions

View File

@@ -353,6 +353,7 @@ init:
if [ "$(EXTRA_GROUP_QUEUES)" == "thepentagon" ]; then \ if [ "$(EXTRA_GROUP_QUEUES)" == "thepentagon" ]; then \
tower-manage register_instance --hostname=isolated; \ tower-manage register_instance --hostname=isolated; \
tower-manage register_queue --queuename='thepentagon' --hostnames=isolated --controller=tower; \ 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 \ elif [ "$(EXTRA_GROUP_QUEUES)" != "" ]; then \
tower-manage register_queue --queuename=$(EXTRA_GROUP_QUEUES) --hostnames=$(COMPOSE_HOST); \ tower-manage register_queue --queuename=$(EXTRA_GROUP_QUEUES) --hostnames=$(COMPOSE_HOST); \
fi; fi;

View File

@@ -155,6 +155,47 @@ register(
category_slug='jobs', 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( register(
'STDOUT_MAX_BYTES_DISPLAY', 'STDOUT_MAX_BYTES_DISPLAY',
field_class=fields.IntegerField, field_class=fields.IntegerField,

View File

@@ -5,7 +5,9 @@ import StringIO
import json import json
import os import os
import re import re
import shutil
import stat import stat
import tempfile
import time import time
import logging import logging
@@ -141,7 +143,7 @@ class IsolatedManager(object):
args.append('-%s' % ('v' * min(5, self.instance.verbosity))) args.append('-%s' % ('v' * min(5, self.instance.verbosity)))
buff = StringIO.StringIO() buff = StringIO.StringIO()
logger.debug('Starting job on isolated host with `run_isolated.yml` playbook.') 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, args, self.awx_playbook_path(), self.env, buff,
expect_passwords={ expect_passwords={
re.compile(r'Secret:\s*?$', re.M): base64.b64encode(json.dumps(secrets)) 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()) self.stdout_handle.write(buff.getvalue())
return status, rc 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): def build_isolated_job_data(self):
''' '''
Write the playbook and metadata into a collection of files on the local Write the playbook and metadata into a collection of files on the local
@@ -251,7 +269,7 @@ class IsolatedManager(object):
buff = cStringIO.StringIO() buff = cStringIO.StringIO()
logger.debug('Checking job on isolated host with `check_isolated.yml` playbook.') 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, args, self.awx_playbook_path(), self.env, buff,
cancelled_callback=self.cancelled_callback, cancelled_callback=self.cancelled_callback,
idle_timeout=remaining, idle_timeout=remaining,
@@ -302,7 +320,7 @@ class IsolatedManager(object):
json.dumps(extra_vars)] json.dumps(extra_vars)]
logger.debug('Cleaning up job on isolated host with `clean_isolated.yml` playbook.') logger.debug('Cleaning up job on isolated host with `clean_isolated.yml` playbook.')
buff = cStringIO.StringIO() buff = cStringIO.StringIO()
status, rc = run.run_pexpect( status, rc = IsolatedManager.run_pexpect(
args, self.awx_playbook_path(), self.env, buff, args, self.awx_playbook_path(), self.env, buff,
idle_timeout=60, job_timeout=60, idle_timeout=60, job_timeout=60,
pexpect_timeout=5 pexpect_timeout=5
@@ -333,7 +351,7 @@ class IsolatedManager(object):
env['ANSIBLE_STDOUT_CALLBACK'] = 'json' env['ANSIBLE_STDOUT_CALLBACK'] = 'json'
buff = cStringIO.StringIO() buff = cStringIO.StringIO()
status, rc = run.run_pexpect( status, rc = IsolatedManager.run_pexpect(
args, cls.awx_playbook_path(), env, buff, args, cls.awx_playbook_path(), env, buff,
idle_timeout=60, job_timeout=60, idle_timeout=60, job_timeout=60,
pexpect_timeout=5 pexpect_timeout=5
@@ -357,7 +375,7 @@ class IsolatedManager(object):
continue continue
if 'capacity' in task_result: if 'capacity' in task_result:
instance.capacity = int(task_result['capacity']) instance.capacity = int(task_result['capacity'])
instance.save(update_fields=['capacity']) instance.save(update_fields=['capacity', 'modified'])
else: else:
logger.warning('Could not update capacity of {}, msg={}'.format( logger.warning('Could not update capacity of {}, msg={}'.format(
instance.hostname, task_result.get('msg', 'unknown failure'))) 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 # The timeout (in seconds) for launching jobs on isolated nodes
AWX_ISOLATED_LAUNCH_TIMEOUT = 600 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 AWX_ISOLATED_PERIODIC_CHECK = 600
# Enable Pendo on the UI, possible values are 'off', 'anonymous', and 'detailed' # 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.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
# Django REST Framework
from rest_framework import serializers
# Tower # Tower
from awx.conf import register from awx.conf import register, register_validate
from awx.sso import fields from awx.sso import fields
from awx.main.validators import validate_private_key, validate_certificate from awx.main.validators import validate_private_key, validate_certificate
from awx.sso.validators import * # noqa from awx.sso.validators import * # noqa
@@ -1083,3 +1086,23 @@ register(
placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
feature_required='enterprise_auth', 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 me = models.me;
let credential = models.credential; let credential = models.credential;
let credentialType = models.credentialType; let credentialType = models.credentialType;
let organization = models.organization;
vm.panelTitle = 'NEW CREDENTIAL'; vm.panelTitle = 'NEW CREDENTIAL';
@@ -23,22 +22,18 @@ function AddCredentialsController (models, $state) {
omit: ['user', 'team', 'inputs'] omit: ['user', 'team', 'inputs']
}); });
vm.form.organization._placeholder = DEFAULT_ORGANIZATION_PLACEHOLDER; vm.form.organization._resource = 'organization';
vm.form.organization._data = organization.get('results'); vm.form.organization._route = 'credentials.add.organization';
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.credential_type._data = credentialType.get('results'); vm.form.credential_type._resource = 'credential_type';
vm.form.credential_type._placeholder = 'SELECT A TYPE'; vm.form.credential_type._route = 'credentials.add.credentialType';
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.inputs = { vm.form.inputs = {
_get: credentialType.mergeInputProperties, _get: id => {
let type = credentialType.getById(id);
return credentialType.mergeInputProperties(type);
},
_source: vm.form.credential_type, _source: vm.form.credential_type,
_reference: 'vm.form.inputs', _reference: 'vm.form.inputs',
_key: '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-panel-heading>{{ vm.panelTitle }}</at-panel-heading>
<at-tab-group> <at-tab-group>
@@ -10,27 +10,26 @@
<at-form state="vm.form"> <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="1" state="vm.form.name"></at-input-text>
<at-input-text col="4" tab="2" state="vm.form.description"></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-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 Type Details
</at-input-group> </at-input-group>
<at-action-group col="12" pos="right"> <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-form-action type="save"></at-form-action>
</at-action-group> </at-action-group>
</at-form> </at-form>
</at-panel-body> </at-panel-body>
</at-panel> </at-panel>
<at-panel ng-if="$state.current.name === 'credentials.edit.permissions' || <at-panel ng-if="$state.current.name.includes('permissions')">
$state.current.name === 'credentials.edit.permissions.add'"> <at-panel-heading>CREDENTIALS PERMISSIONS</at-panel-heading>
<at-panel-heading>Credentials Permissions</at-panel-heading>
<at-tab-group> <at-tab-group>
<at-tab state="vm.tab.details">Details</at-tab> <at-tab state="vm.tab.details">Details</at-tab>
@@ -42,5 +41,5 @@
</at-panel-body> </at-panel-body>
</at-panel> </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 me = models.me;
let credential = models.credential; let credential = models.credential;
let credentialType = models.credentialType; let credentialType = models.credentialType;
let organization = models.organization; let selectedCredentialType = credentialType.getById(credential.get('credential_type'));
vm.tab = { vm.tab = {
details: { details: {
@@ -21,9 +21,9 @@ function EditCredentialsController (models, $state, $scope) {
}; };
$scope.$watch('$state.current.name', (value) => { $scope.$watch('$state.current.name', (value) => {
if (value === 'credentials.edit') { if (/credentials.edit($|\.organization$)/.test(value)) {
vm.tab.details._active = true; vm.tab.details._active = true;
vm.tab.details._permissions = false; vm.tab.permissions._active = false;
} else { } else {
vm.tab.permissions._active = true; vm.tab.permissions._active = true;
vm.tab.details._active = false; vm.tab.details._active = false;
@@ -39,23 +39,19 @@ function EditCredentialsController (models, $state, $scope) {
omit: ['user', 'team', 'inputs'] omit: ['user', 'team', 'inputs']
}); });
vm.form.organization._placeholder = DEFAULT_ORGANIZATION_PLACEHOLDER; vm.form.organization._resource = 'organization';
vm.form.organization._data = organization.get('results'); vm.form.organization._route = 'credentials.edit.organization';
vm.form.organization._format = 'objects'; vm.form.organization._value = credential.get('summary_fields.organization.id');
vm.form.organization._exp = 'org as org.name for org in state._data'; vm.form.organization._displayValue = credential.get('summary_fields.organization.name');
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._resource = 'credential_type';
vm.form.credential_type._format = 'grouped-object'; vm.form.credential_type._route = 'credentials.edit.credentialType';
vm.form.credential_type._display = 'name'; vm.form.credential_type._value = selectedCredentialType.id;
vm.form.credential_type._key = 'id'; vm.form.credential_type._displayValue = selectedCredentialType.name;
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.inputs = { vm.form.inputs = {
_get (type) { _get (id) {
let type = credentialType.getById(id);
let inputs = credentialType.mergeInputProperties(type); let inputs = credentialType.mergeInputProperties(type);
if (type.id === credential.get('credential_type')) { if (type.id === credential.get('credential_type')) {
@@ -77,7 +73,7 @@ function EditCredentialsController (models, $state, $scope) {
}; };
vm.form.onSaveSuccess = res => { 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 LegacyCredentials from './legacy.credentials';
import CredentialForm from '../../src/credentials/credentials.form';
import CredentialList from '../../src/credentials/credentials.list';
import ListController from '../../src/credentials/list/credentials-list.controller';
import AddController from './add-credentials.controller.js'; import AddController from './add-credentials.controller.js';
import EditController from './edit-credentials.controller.js'; import EditController from './edit-credentials.controller.js';
import { N_ } from '../../src/i18n'; 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 id = $stateParams.credential_id;
let promises = { let promises = {
me: new Me('get'), me: new Me('get'),
credentialType: new CredentialType('get'), credentialType: new CredentialType('get')
organization: new Organization('get')
}; };
if (id) { if (id) {
@@ -29,46 +25,13 @@ CredentialsResolve.$inject = [
'$stateParams', '$stateParams',
'MeModel', 'MeModel',
'CredentialModel', 'CredentialModel',
'CredentialTypeModel', 'CredentialTypeModel'
'OrganizationModel'
]; ];
function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceProvider) { function CredentialsConfig ($stateExtenderProvider, legacyProvider, pathProvider) {
let pathService = pathServiceProvider.$get(); let path = pathProvider.$get();
let stateExtender = $stateExtenderProvider.$get(); let stateExtender = $stateExtenderProvider.$get();
let legacy = legacyProvider.$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`]);
}
]
}
});
stateExtender.addState({ stateExtender.addState({
name: 'credentials.add', name: 'credentials.add',
@@ -78,7 +41,7 @@ function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceP
}, },
views: { views: {
'add@credentials': { 'add@credentials': {
templateUrl: pathService.getViewPath('credentials/add-edit-credentials'), templateUrl: path.getViewPath('credentials/add-edit-credentials'),
controller: AddController, controller: AddController,
controllerAs: 'vm' controllerAs: 'vm'
} }
@@ -96,7 +59,7 @@ function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceP
}, },
views: { views: {
'edit@credentials': { 'edit@credentials': {
templateUrl: pathService.getViewPath('credentials/add-edit-credentials'), templateUrl: path.getViewPath('credentials/add-edit-credentials'),
controller: EditController, controller: EditController,
controllerAs: 'vm' controllerAs: 'vm'
} }
@@ -106,178 +69,24 @@ function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceP
} }
}); });
stateExtender.addState({ stateExtender.addState(legacy.getStateConfiguration('list'));
name: "credentials.edit.permissions", stateExtender.addState(legacy.getStateConfiguration('edit-permissions'));
url: "/permissions?{permission_search:queryset}", stateExtender.addState(legacy.getStateConfiguration('add-permissions'));
resolve: { stateExtender.addState(legacy.getStateConfiguration('add-organization'));
ListDefinition: () => { stateExtender.addState(legacy.getStateConfiguration('edit-organization'));
return { stateExtender.addState(legacy.getStateConfiguration('add-credential-type'));
name: 'permissions', stateExtender.addState(legacy.getStateConfiguration('edit-credential-type'));
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');
}
}
});
} }
CredentialsConfig.$inject = [ CredentialsConfig.$inject = [
'$stateProvider', '$stateExtenderProvider',
'$stateExtenderProvider', 'LegacyCredentialsServiceProvider',
'PathServiceProvider' 'PathServiceProvider'
]; ];
angular angular
.module('at.features.credentials', []) .module('at.features.credentials', [])
.config(CredentialsConfig) .config(CredentialsConfig)
.controller('AddController', AddController) .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; 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 { .List-tableCell {
padding: 7px 15px; padding: 7px 15px;
border-top:0px!important; border-top:0px!important;

View File

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

View File

@@ -1,7 +1,7 @@
.at-ActionGroup { .at-ActionGroup {
margin-top: @at-space-6x; margin-top: @at-margin-panel;
button:last-child { 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 = () => { vm.setCancelDefaults = () => {
scope.text = 'CANCEL'; scope.text = 'CANCEL';
scope.fill = 'Hollow'; scope.fill = 'Hollow';
scope.color = 'white'; scope.color = 'default';
scope.action = () => $state.go('^'); scope.action = () => $state.go(scope.to || '^');
}; };
vm.setSaveDefaults = () => { vm.setSaveDefaults = () => {
scope.text = 'SAVE'; scope.text = 'SAVE';
scope.fill = ''; scope.fill = '';
scope.color = 'green'; scope.color = 'success';
scope.action = () => form.submit(); scope.action = () => form.submit();
}; };
} }
@@ -64,7 +64,8 @@ function atFormAction (pathService) {
link, link,
scope: { scope: {
state: '=', state: '=',
type: '@' type: '@',
to: '@'
} }
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,43 +9,46 @@ function atInputLookupLink (scope, element, attrs, controllers) {
inputController.init(scope, element, formController); inputController.init(scope, element, formController);
} }
function AtInputLookupController (baseInputController) { function AtInputLookupController (baseInputController, $state, $stateParams) {
let vm = this || {}; let vm = this || {};
vm.lookup = {}; let scope;
vm.init = (scope, element, form) => { vm.init = (_scope_, element, form) => {
baseInputController.call(vm, 'input', scope, element, form); baseInputController.call(vm, 'input', _scope_, element, form);
vm.lookup.modal = { scope = _scope_;
title: 'Select Organization',
buttons: [
{
type: 'cancel'
},
{
type: 'select'
}
]
};
vm.lookup.search = { scope.$watch(scope.state._resource, vm.watchResource);
placeholder: 'test'
};
vm.lookup.table = {
};
vm.check(); 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.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) { function atInputLookup (pathService) {
return { return {

View File

@@ -4,7 +4,7 @@
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <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-disabled="state._disabled || form.disabled"
ng-click="vm.search()"> ng-click="vm.search()">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
@@ -13,7 +13,7 @@
<input type="text" <input type="text"
class="form-control at-Input" class="form-control at-Input"
ng-class="{ 'at-Input--rejected': state.rejected }" ng-class="{ 'at-Input--rejected': state.rejected }"
ng-model="state._value" ng-model="state._displayValue"
ng-attr-tabindex="{{ tab || undefined }}" ng-attr-tabindex="{{ tab || undefined }}"
ng-attr-placeholder="{{::state._placeholder || undefined }}" ng-attr-placeholder="{{::state._placeholder || undefined }}"
ng-change="vm.check()" ng-change="vm.check()"
@@ -23,8 +23,5 @@
<at-input-message></at-input-message> <at-input-message></at-input-message>
</div> </div>
<at-modal state="vm.lookup"> <div ui-view="{{ state._resource }}"></div>
<at-search></at-search>
<at-table></at-table>
</at-modal>
</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 { .at-Modal-title {
margin: 0; margin: 0;
padding: 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; const DEFAULT_ANIMATION_DURATION = 150;
function atModalLink (scope, el, attr, controllers) { function atModalLink (scope, el, attrs, controllers) {
let modalController = controllers[0]; 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 vm = this;
let scope; let overlay;
let container; let modal;
let listeners;
vm.init = (_scope_, _container_) => { vm.init = (scope, el) => {
scope = _scope_; overlay = el[0];
container = _container_; modal = el.find('.at-Modal-window')[0];
scope.state.show = vm.show; vm.modal = scope[scope.ns].modal;
scope.state.hide = vm.hide; vm.modal.show = vm.show;
vm.modal.hide = vm.hide;
}; };
vm.show = (title, message) => { vm.show = (title, message) => {
scope.title = title; vm.modal.title = title;
scope.message = message; vm.modal.message = message;
container.style.display = 'block'; event.stopPropagation();
container.style.opacity = 1;
listeners = eventService.addListeners([
[window, 'click', vm.clickToHide]
]);
overlay.style.display = 'block';
overlay.style.opacity = 1;
}; };
vm.hide = () => { vm.hide = () => {
container.style.opacity = 0; overlay.style.opacity = 0;
setTimeout(() => { eventService.remove(listeners);
container.style.display = 'none';
scope.message = ''; setTimeout(() => overlay.style.display = 'none', DEFAULT_ANIMATION_DURATION);
scope.title = ''; };
}, 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) { function atModal (pathService) {
return { return {
restrict: 'E', restrict: 'E',
@@ -50,9 +79,7 @@ function atModal (pathService) {
controller: AtModalController, controller: AtModalController,
controllerAs: 'vm', controllerAs: 'vm',
link: atModalLink, link: atModalLink,
scope: { scope: true
state: '='
}
}; };
} }

View File

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

View File

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

View File

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

View File

@@ -1,26 +1,27 @@
.at-TabGroup { .at-TabGroup {
margin-top: @at-space-6x; margin-top: @at-margin-panel;
} }
.at-Tab { .at-Tab {
margin: 0 @at-space-5x 0 0; margin: 0 @at-margin-item-column 0 0;
font-size: @at-font-size; font-size: @at-font-size-body;
line-height: 1;
} }
.at-Tab--active { .at-Tab--active {
&, &:hover, &:active, &:focus { &, &:hover, &:active, &:focus {
color: @at-white; color: @at-color-tab-text-default-active;
background-color: @at-gray-dark-3x; background-color: @at-color-tab-default-active;
border-color: @at-gray-dark-3x; border-color: @at-color-tab-border-default-active;
cursor: default; cursor: default;
} }
} }
.at-Tab--disabled { .at-Tab--disabled {
&, &:hover, &:active, &:focus { &, &:hover, &:active, &:focus {
background-color: @at-white; background-color: @at-color-tab-default-disabled;
color: @at-gray-dark-2x; color: @at-color-tab-text-default-disabled;
border-color: @at-gray-dark-2x; border-color: @at-color-tab-border-default-disabled;
opacity: 0.65; opacity: 0.65;
cursor: not-allowed; 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-attr-disabled="{{ state._disabled || undefined }}"
ng-class="{ 'at-Tab--active': state._active, 'at-Tab--disabled': state._disabled }" ng-class="{ 'at-Tab--active': state._active, 'at-Tab--disabled': state._disabled }"
ng-click="vm.go()"> ng-click="vm.go()">

View File

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

View File

@@ -3,7 +3,7 @@ const ENCRYPTED_VALUE = '$encrypted$';
let BaseModel; let BaseModel;
function createFormSchema (method, config) { 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) { if (config && config.omit) {
config.omit.forEach(key => { 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) { .at-mixin-Heading (@size) {
color: @at-gray-dark-4x; color: @at-color-body-text;
font-size: @size; font-size: @size;
font-weight: @at-font-weight-2x; font-weight: @at-font-weight-heading;
line-height: @at-line-height-short; line-height: @at-line-height-short;
text-transform: uppercase; text-transform: uppercase;
margin: 0; margin: 0;
@@ -21,12 +21,13 @@
} }
.at-mixin-Button () { .at-mixin-Button () {
height: @at-input-height; height: @at-height-input;
padding: @at-space-2x @at-space-4x; padding: @at-padding-button-vertical @at-padding-button-horizontal;
font-size: @at-font-size; 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; background-color: @@background;
&, &:hover, &:focus { &, &:hover, &:focus {
@@ -42,21 +43,23 @@
} }
} }
.at-mixin-ButtonHollow (@color, @accent) { .at-mixin-ButtonHollow (@bg, @border, @text) {
background-color: @at-white; @hover: '@{bg}-hover';
color: @@color;
border-color: @@color; background-color: @@bg;
color: @@text;
border-color: @@border;
&:hover, &:active { &:hover, &:active {
color: @@color; color: @@text;
background-color: @at-white--hover; background-color: @@hover;
box-shadow: none; box-shadow: none;
} }
&:focus { &:focus {
color: @at-white; color: @@text;
background-color: @@accent; background-color: @@hover;
border-color: @@accent; border-color: @@border;
cursor: default; cursor: default;
} }
@@ -67,14 +70,14 @@
.at-mixin-ButtonIcon () { .at-mixin-ButtonIcon () {
line-height: @at-line-height-short; line-height: @at-line-height-short;
color: @at-gray-dark-2x;
& > i { & > i {
cursor: pointer; cursor: pointer;
transition: color @at-transition-icon-button;
} }
& > i:hover { & > 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 // App-wide styles
@import '_variables'; @import '_base-variables';
@import '_contextual-variables';
@import '_mixins'; @import '_mixins';
@import '_utility'; @import '_utility';
@import '_common'; @import '_global';
// Aggregated component and feature specific styles // Aggregated component and feature specific styles
@import '../components/_index'; @import '../components/_index';

View File

@@ -203,12 +203,12 @@ var tower = angular.module('Tower', [
]) ])
.run(['$stateExtender', '$q', '$compile', '$cookies', '$rootScope', '$log', '$stateParams', .run(['$stateExtender', '$q', '$compile', '$cookies', '$rootScope', '$log', '$stateParams',
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'FeaturesService', '$filter', 'SocketService', 'FeaturesService', '$filter', 'SocketService',
function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams, function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer, CheckLicense, $location, Authorization, LoadBasePaths, Timer,
ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait, LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
$filter, SocketService) { $filter, SocketService) {
@@ -305,6 +305,28 @@ var tower = angular.module('Tower', [
window.clearInterval($rootScope.jobStdOutInterval); 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 // On each navigation request, check that the user is logged in
if (!/^\/(login|logout)/.test($location.path())) { if (!/^\/(login|logout)/.test($location.path())) {
// capture most recent URL, excluding login/logout // capture most recent URL, excluding login/logout

View File

@@ -5,7 +5,7 @@
*************************************************/ *************************************************/
export default [ 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', 'ConfigurationService', 'ConfigurationUtils', 'CreateDialog', 'CreateSelect2', 'i18n', 'ParseTypeChange', 'ProcessErrors', 'Store',
'Wait', 'configDataResolve', 'ToJSON', 'Wait', 'configDataResolve', 'ToJSON',
//Form definitions //Form definitions
@@ -24,7 +24,7 @@ export default [
'ConfigurationJobsForm', 'ConfigurationJobsForm',
'ConfigurationUiForm', 'ConfigurationUiForm',
function( function(
$scope, $rootScope, $state, $stateParams, $timeout, $q, Alert, ClearScope, $scope, $rootScope, $state, $stateParams, $timeout, $q, Alert,
ConfigurationService, ConfigurationUtils, CreateDialog, CreateSelect2, i18n, ParseTypeChange, ProcessErrors, Store, ConfigurationService, ConfigurationUtils, CreateDialog, CreateSelect2, i18n, ParseTypeChange, ProcessErrors, Store,
Wait, configDataResolve, ToJSON, Wait, configDataResolve, ToJSON,
//Form definitions //Form definitions
@@ -450,12 +450,39 @@ export default [
}; };
var resetAll = function() { 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'); Wait('start');
ConfigurationService.resetAll() ConfigurationService.patchConfiguration(payload)
.then(function() { .then(function() {
populateFromApi(); populateFromApi();
$scope[formTracker.currentFormName()].$setPristine(); $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) { .catch(function(error) {
ProcessErrors($scope, error, status, formDefs[formTracker.getCurrent()], ProcessErrors($scope, error, status, formDefs[formTracker.getCurrent()],

View File

@@ -68,21 +68,6 @@ export default ['$rootScope', 'GetBasePath', 'ProcessErrors', '$q', '$http', 'Re
deferred.reject(error); 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; return deferred.promise;
} }
}; };

View File

@@ -61,10 +61,18 @@ export default ['$rootScope', '$scope', 'Wait', 'CredentialTypesList',
Rest.setUrl(url); Rest.setUrl(url);
Rest.destroy() Rest.destroy()
.success(function() { .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) { if (parseInt($state.params.credential_type_id) === id) {
$state.go('^', null, { reload: true }); $state.go('^', reloadListStateParams, { reload: true });
} else { } else {
$state.go('.', null, { reload: true }); $state.go('.', reloadListStateParams, { reload: true });
} }
}) })
.error(function(data, status) { .error(function(data, status) {

View File

@@ -4,14 +4,12 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ClearScope', export default ['$scope', 'Rest', 'CredentialList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n', '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, ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset,
i18n) { i18n) {
ClearScope();
var list = CredentialList, var list = CredentialList,
defaultUrl = GetBasePath('credentials'); defaultUrl = GetBasePath('credentials');
@@ -76,10 +74,18 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ClearScope',
Rest.setUrl(url); Rest.setUrl(url);
Rest.destroy() Rest.destroy()
.success(function() { .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) { if (parseInt($state.params.credential_id) === id) {
$state.go("^", null, { reload: true }); $state.go("^", reloadListStateParams, { reload: true });
} else { } else {
$state.go('.', null, {reload: true}); $state.go('.', reloadListStateParams, {reload: true});
} }
Wait('stop'); Wait('stop');
}) })

View File

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

View File

@@ -68,31 +68,6 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
$scope.goToInsights = function(id){ $scope.goToInsights = function(id){
$state.go('hosts.edit.insights', {host_id: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) { $scope.toggleHost = function(event, host) {
try { try {
$(event.target).tooltip('hide'); $(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').on('hidden.bs.modal', function () {
$('#host-disassociate-modal').off('hidden.bs.modal'); $('#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(() => { GroupsService.disassociateHost(host.id, $scope.disassociateGroup.id).then(() => {

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ export default ['i18n', function(i18n) {
hover: true, hover: true,
basePath: 'inventory', basePath: 'inventory',
title: false, title: false,
disableRow: "{{ inventory.pending_deletion }}",
fields: { fields: {
status: { status: {
@@ -27,7 +28,7 @@ export default ['i18n', function(i18n) {
ngClick: "null", ngClick: "null",
iconOnly: true, iconOnly: true,
excludeModal: 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: [{ icons: [{
icon: "{{ 'icon-cloud-' + inventory.syncStatus }}", icon: "{{ 'icon-cloud-' + inventory.syncStatus }}",
awToolTip: "{{ inventory.syncTip }}", awToolTip: "{{ inventory.syncTip }}",
@@ -97,7 +98,8 @@ export default ['i18n', function(i18n) {
ngClick: 'editInventory(inventory)', ngClick: 'editInventory(inventory)',
awToolTip: i18n._('Edit inventory'), awToolTip: i18n._('Edit inventory'),
dataPlacement: 'top', dataPlacement: 'top',
ngShow: 'inventory.summary_fields.user_capabilities.edit' ngShow: 'inventory.summary_fields.user_capabilities.edit',
ngHide: 'inventory.pending_deletion'
}, },
view: { view: {
label: i18n._('View'), label: i18n._('View'),
@@ -111,7 +113,12 @@ export default ['i18n', function(i18n) {
ngClick: "deleteInventory(inventory.id, inventory.name)", ngClick: "deleteInventory(inventory.id, inventory.name)",
awToolTip: i18n._('Delete inventory'), awToolTip: i18n._('Delete inventory'),
dataPlacement: 'top', 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.setUrl(url);
Rest.destroy() Rest.destroy()
.success(function () { .success(function () {
if (parseInt($state.params.inventory_id) === id) { Wait('stop');
$state.go("^", null, {reload: true});
} else {
$state.go('.', null, {reload: true});
Wait('stop');
}
}) })
.error(function (data, status) { .error(function (data, status) {
ProcessErrors( $scope, data, status, null, { hdr: 'Error!', ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
@@ -102,11 +97,33 @@ function InventoriesList($scope,
Prompt({ Prompt({
hdr: 'Delete', 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, action: action,
actionText: 'DELETE' 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', export default ['$scope',

View File

@@ -100,14 +100,21 @@
$state.go('.', null, {reload: true}); $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){ switch($scope.deleteOption){
case 'promote': case 'promote':
GroupsService.promote($scope.toDelete.id, $stateParams.inventory_id) GroupsService.promote($scope.toDelete.id, $stateParams.inventory_id)
.then(() => { .then(() => {
if (parseInt($state.params.group_id) === $scope.toDelete.id) { if (parseInt($state.params.group_id) === $scope.toDelete.id) {
$state.go("^", null, {reload: true}); $state.go("^", reloadListStateParams, {reload: true});
} else { } else {
$state.go($state.current, null, {reload: true}); $state.go($state.current, reloadListStateParams, {reload: true});
} }
$('#group-delete-modal').modal('hide'); $('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open'); $('body').removeClass('modal-open');
@@ -117,9 +124,9 @@
default: default:
GroupsService.delete($scope.toDelete.id).then(() => { GroupsService.delete($scope.toDelete.id).then(() => {
if (parseInt($state.params.group_id) === $scope.toDelete.id) { if (parseInt($state.params.group_id) === $scope.toDelete.id) {
$state.go("^", null, {reload: true}); $state.go("^", reloadListStateParams, {reload: true});
} else { } else {
$state.go($state.current, null, {reload: true}); $state.go($state.current, reloadListStateParams, {reload: true});
} }
$('#group-delete-modal').modal('hide'); $('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open'); $('body').removeClass('modal-open');

View File

@@ -87,8 +87,16 @@
$('#group-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () { $('#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 // Remove the event handler so that we don't end up with multiple bindings
$('#group-disassociate-modal').off('hidden.bs.modal'); $('#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 // 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(){ 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 () { $('#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 // Remove the event handler so that we don't end up with multiple bindings
$('#host-disassociate-modal').off('hidden.bs.modal'); $('#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 // 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(){ let closeModal = function(){

View File

@@ -96,10 +96,18 @@ export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
Wait('start'); Wait('start');
HostsService.delete(id).then(() => { HostsService.delete(id).then(() => {
$('#prompt-modal').modal('hide'); $('#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) { if (parseInt($state.params.host_id) === id) {
$state.go("hosts", null, {reload: true}); $state.go('^', reloadListStateParams, {reload: true});
} else { } else {
$state.go($state.current.name, null, {reload: true}); $state.go('.', reloadListStateParams, {reload: true});
} }
Wait('stop'); Wait('stop');
}); });

View File

@@ -83,8 +83,16 @@
$('#group-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () { $('#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 // Remove the event handler so that we don't end up with multiple bindings
$('#group-disassociate-modal').off('hidden.bs.modal'); $('#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 // 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(){ let closeModal = function(){

View File

@@ -123,10 +123,16 @@
Wait('start'); Wait('start');
SourcesService.delete(inventory_source.id).then(() => { SourcesService.delete(inventory_source.id).then(() => {
$('#prompt-modal').modal('hide'); $('#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) { 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 { } else {
$state.go($state.current.name, null, {reload: true}); $state.go('.', reloadListStateParams, {reload: true});
} }
Wait('stop'); Wait('stop');
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,10 +51,18 @@ export default ['$rootScope', '$scope', 'Wait', 'InventoryScriptsList',
Rest.setUrl(url); Rest.setUrl(url);
Rest.destroy() Rest.destroy()
.success(function() { .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) { if (parseInt($state.params.inventory_script_id) === id) {
$state.go('^', null, { reload: true }); $state.go('^', reloadListStateParams, { reload: true });
} else { } else {
$state.go('.', null, { reload: true }); $state.go('.', reloadListStateParams, { reload: true });
} }
}) })
.error(function(data, status) { .error(function(data, status) {

View File

@@ -70,7 +70,14 @@ export default
scope.$emit(callback, action_label); scope.$emit(callback, action_label);
} }
else { 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'); Wait('stop');
} }
}) })

View File

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

View File

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

View File

@@ -165,10 +165,18 @@
Rest.setUrl(url); Rest.setUrl(url);
Rest.destroy() Rest.destroy()
.success(function() { .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) { if (parseInt($state.params.notification_template_id) === id) {
$state.go("^", null, { reload: true }); $state.go("^", reloadListStateParams, { reload: true });
} else { } else {
$state.go('.', null, {reload: true}); $state.go('.', reloadListStateParams, {reload: true});
} }
Wait('stop'); Wait('stop');
}) })

View File

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

View File

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

View File

@@ -6,14 +6,12 @@
export default ['$stateParams', '$scope', '$rootScope', export default ['$stateParams', '$scope', '$rootScope',
'Rest', 'OrganizationList', 'Prompt', 'ClearScope', 'Rest', 'OrganizationList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', 'rbacUiControlService', '$filter', 'Dataset', 'i18n', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', 'rbacUiControlService', '$filter', 'Dataset', 'i18n',
function($stateParams, $scope, $rootScope, function($stateParams, $scope, $rootScope,
Rest, OrganizationList, Prompt, ClearScope, Rest, OrganizationList, Prompt,
ProcessErrors, GetBasePath, Wait, $state, rbacUiControlService, $filter, Dataset, i18n) { ProcessErrors, GetBasePath, Wait, $state, rbacUiControlService, $filter, Dataset, i18n) {
ClearScope();
var defaultUrl = GetBasePath('organizations'), var defaultUrl = GetBasePath('organizations'),
list = OrganizationList; list = OrganizationList;
@@ -147,10 +145,18 @@ export default ['$stateParams', '$scope', '$rootScope',
Rest.destroy() Rest.destroy()
.success(function() { .success(function() {
Wait('stop'); 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) { if (isDeletedOrganizationBeingEdited(id, parseInt($stateParams.organization_id)) === true) {
$state.go('^', null, { reload: true }); $state.go('^', reloadListStateParams, { reload: true });
} else { } else {
$state.reload('organizations'); $state.go('.', reloadListStateParams, { reload: true });
} }
}) })
.error(function(data, status) { .error(function(data, status) {

View File

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

View File

@@ -179,10 +179,18 @@ export default ['$scope', '$rootScope', '$log', 'Rest', 'Alert',
Rest.setUrl(url); Rest.setUrl(url);
Rest.destroy() Rest.destroy()
.success(function() { .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) { if (parseInt($state.params.project_id) === id) {
$state.go("^", null, { reload: true }); $state.go("^", reloadListStateParams, { reload: true });
} else { } else {
$state.go('.', null, {reload: true}); $state.go('.', reloadListStateParams, {reload: true});
} }
}) })
.error(function (data, status) { .error(function (data, status) {

View File

@@ -25,11 +25,19 @@ export default
.success(function () { .success(function () {
$('#prompt-modal').modal('hide'); $('#prompt-modal').modal('hide');
scope.$emit(callback, id); 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{ else{
$state.go('.', null, {reload: true}); $state.go('.', reloadListStateParams, {reload: true});
} }
}) })
.error(function (data, status) { .error(function (data, status) {

View File

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

View File

@@ -20,43 +20,6 @@
export default export default
angular.module('Utilities', ['RestServices', 'Utilities']) 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 * @ngdoc method
* @name shared.function:Utilities#Empty * @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() { .directive('awSurveyQuestion', function() {
return { 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 += 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 += "<tr ng-class=\"[" + list.iterator;
innerTable += (options.mode === 'lookup' || options.mode === 'select') ? ".success_class" : ".active_class"; 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) { if (list.multiSelect) {
innerTable += ", " + list.iterator + ".isSelected ? 'is-selected-row' : ''"; innerTable += ", " + list.iterator + ".isSelected ? 'is-selected-row' : ''";
@@ -300,7 +301,8 @@ export default ['$compile', 'Attr', 'Icon',
innerTable += "]\" "; innerTable += "]\" ";
innerTable += "id=\"{{ " + list.iterator + ".id }}\" "; innerTable += "id=\"{{ " + list.iterator + ".id }}\" ";
innerTable += "class=\"List-tableRow " + list.iterator + "_class\" "; 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.trackBy) ? " track by " + list.trackBy : "";
innerTable += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : ""; innerTable += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : "";
innerTable += (list.filterBy) ? " | filter: " + list.filterBy : ""; innerTable += (list.filterBy) ? " | filter: " + list.filterBy : "";
@@ -379,7 +381,11 @@ export default ['$compile', 'Attr', 'Icon',
type: 'fieldActions', type: 'fieldActions',
td: false 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]; fAction = list.fieldActions[field_action];
innerTable += "<button id=\""; innerTable += "<button id=\"";
innerTable += (fAction.id) ? fAction.id : field_action + "-action"; 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 += `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.awPopOver) ? "aw-pop-over=\"" + fAction.awPopOver + "\" " : "";
innerTable += (fAction.dataPlacement) ? Attr(fAction, 'dataPlacement') : ""; innerTable += (fAction.dataPlacement) ? Attr(fAction, 'dataPlacement') : "";
innerTable += (fAction.dataTitle) ? Attr(fAction, 'dataTitle') : ""; innerTable += (fAction.dataTitle) ? Attr(fAction, 'dataTitle') : "";
@@ -430,7 +437,6 @@ export default ['$compile', 'Attr', 'Icon',
} }
innerTable += "</tr>\n"; innerTable += "</tr>\n";
// End List // End List
innerTable += "</tbody>\n"; innerTable += "</tbody>\n";

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,14 +4,12 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export default ['$scope', 'Rest', 'TeamList', 'Prompt', 'ClearScope', export default ['$scope', 'Rest', 'TeamList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter',
'rbacUiControlService', 'Dataset', 'rbacUiControlService', 'Dataset',
function($scope, Rest, TeamList, Prompt, ClearScope, ProcessErrors, function($scope, Rest, TeamList, Prompt, ProcessErrors,
GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) { GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) {
ClearScope();
var list = TeamList, var list = TeamList,
defaultUrl = GetBasePath('teams'); defaultUrl = GetBasePath('teams');
@@ -53,10 +51,18 @@ export default ['$scope', 'Rest', 'TeamList', 'Prompt', 'ClearScope',
.success(function() { .success(function() {
Wait('stop'); Wait('stop');
$('#prompt-modal').modal('hide'); $('#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) { if (parseInt($state.params.team_id) === id) {
$state.go('^', null, { reload: true }); $state.go('^', reloadListStateParams, { reload: true });
} else { } else {
$state.go('.', null, { reload: true }); $state.go('.', reloadListStateParams, { reload: true });
} }
}) })
.error(function(data, status) { .error(function(data, status) {

View File

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

View File

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

View File

@@ -5,17 +5,16 @@
*************************************************/ *************************************************/
export default ['$scope', '$rootScope', export default ['$scope', '$rootScope',
'Alert','TemplateList', 'Prompt', 'ClearScope', 'ProcessErrors', 'Alert','TemplateList', 'Prompt', 'ProcessErrors',
'GetBasePath', 'InitiatePlaybookRun', 'Wait', '$state', '$filter', 'GetBasePath', 'InitiatePlaybookRun', 'Wait', '$state', '$filter',
'Dataset', 'rbacUiControlService', 'TemplatesService','QuerySet', 'Dataset', 'rbacUiControlService', 'TemplatesService','QuerySet',
'TemplateCopyService', 'TemplateCopyService',
function( function(
$scope, $rootScope, Alert, $scope, $rootScope, Alert,
TemplateList, Prompt, ClearScope, ProcessErrors, GetBasePath, TemplateList, Prompt, ProcessErrors, GetBasePath,
InitiatePlaybookRun, Wait, $state, $filter, Dataset, rbacUiControlService, TemplatesService, InitiatePlaybookRun, Wait, $state, $filter, Dataset, rbacUiControlService, TemplatesService,
qs, TemplateCopyService qs, TemplateCopyService
) { ) {
ClearScope();
var list = TemplateList; var list = TemplateList;
@@ -106,11 +105,19 @@ export default ['$scope', '$rootScope',
function handleSuccessfulDelete(isWorkflow) { function handleSuccessfulDelete(isWorkflow) {
let stateParamId = isWorkflow ? $state.params.workflow_job_template_id : $state.params.job_template_id; 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) { if (parseInt(stateParamId) === template.id) {
// Move the user back to the templates list // Move the user back to the templates list
$state.go("templates", null, {reload: true}); $state.go("templates", reloadListStateParams, {reload: true});
} else { } else {
$state.go(".", null, {reload: true}); $state.go(".", reloadListStateParams, {reload: true});
} }
Wait('stop'); Wait('stop');
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,9 +13,9 @@ const user_type_options = [
]; ];
export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt', export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt',
'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter',
'rbacUiControlService', 'Dataset', 'i18n', 'rbacUiControlService', 'Dataset', 'i18n',
function($scope, $rootScope, Rest, UserList, Prompt, ClearScope, function($scope, $rootScope, Rest, UserList, Prompt,
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService,
Dataset, i18n) { Dataset, i18n) {
@@ -23,8 +23,6 @@ export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt',
user_type_options[i].label = i18n._(user_type_options[i].label); user_type_options[i].label = i18n._(user_type_options[i].label);
} }
ClearScope();
var list = UserList, var list = UserList,
defaultUrl = GetBasePath('users'); defaultUrl = GetBasePath('users');
@@ -65,6 +63,14 @@ export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt',
Rest.setUrl(url); Rest.setUrl(url);
Rest.destroy() Rest.destroy()
.success(function() { .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) { if (parseInt($state.params.user_id) === id) {
$state.go('^', null, { reload: true }); $state.go('^', null, { reload: true });
} else { } else {

View File

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

View File

@@ -5,7 +5,6 @@ describe('Controller: WorkflowAdd', () => {
let scope, let scope,
state, state,
WorkflowAdd, WorkflowAdd,
ClearScope,
Alert, Alert,
GenerateForm, GenerateForm,
TemplatesService, TemplatesService,
@@ -50,7 +49,6 @@ describe('Controller: WorkflowAdd', () => {
id: "1" id: "1"
}]; }];
ClearScope = jasmine.createSpy('ClearScope');
Alert = jasmine.createSpy('Alert'); Alert = jasmine.createSpy('Alert');
ProcessErrors = jasmine.createSpy('ProcessErrors'); ProcessErrors = jasmine.createSpy('ProcessErrors');
CreateSelect2 = jasmine.createSpy('CreateSelect2'); CreateSelect2 = jasmine.createSpy('CreateSelect2');
@@ -58,7 +56,6 @@ describe('Controller: WorkflowAdd', () => {
ParseTypeChange = jasmine.createSpy('ParseTypeChange'); ParseTypeChange = jasmine.createSpy('ParseTypeChange');
ToJSON = jasmine.createSpy('ToJSON'); ToJSON = jasmine.createSpy('ToJSON');
$provide.value('ClearScope', ClearScope);
$provide.value('Alert', Alert); $provide.value('Alert', Alert);
$provide.value('GenerateForm', GenerateForm); $provide.value('GenerateForm', GenerateForm);
$provide.value('state', state); $provide.value('state', state);
@@ -70,11 +67,10 @@ describe('Controller: WorkflowAdd', () => {
$provide.value('availableLabels', availableLabels); $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(); scope = $rootScope.$new();
state = _state_; state = _state_;
q = $q; q = $q;
ClearScope = _ClearScope_;
Alert = _Alert_; Alert = _Alert_;
GenerateForm = _GenerateForm_; GenerateForm = _GenerateForm_;
httpBackend = $httpBackend; httpBackend = $httpBackend;
@@ -99,7 +95,6 @@ describe('Controller: WorkflowAdd', () => {
WorkflowAdd = $controller('WorkflowAdd', { WorkflowAdd = $controller('WorkflowAdd', {
$scope: scope, $scope: scope,
$state: state, $state: state,
ClearScope: ClearScope,
Alert: Alert, Alert: Alert,
GenerateForm: GenerateForm, GenerateForm: GenerateForm,
TemplatesService: TemplatesService, 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', ()=>{ 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 // 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); 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" In the isolated rampart model, "controller" instances interact with "isolated"
instances via a series of Ansible playbooks over SSH. As such, all isolated instances instances via a series of Ansible playbooks over SSH. At installation time,
must be preconfigured by the installer with passwordless SSH access from any potential a randomized RSA key is generated and distributed as an authorized key to all
controller instances. In the example above, the `isolatedA` and `isolatedB` hosts "isolated" instances. The private half of the key is encrypted and stored
must be reachable from `towerB` and `towerC` hosts via `ssh within Tower, and is used to authenticate from "controller" instances to
awx@<isolated-hostname>` (meaning, `authorized_keys` must be pre-distributed to "isolated" instances when jobs are run.
the `isolatedA` and `isolatedB` hosts).
When a job is scheduled to run on an "isolated" instance: 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. variable - the behavior in this case can not be predicted.
- Do not put an isolated instance in more than 1 isolated group. - 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 ### 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 The `controller` for the group "thepentagon" and all hosts therein is
determined by a ForeignKey within the instance group. 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 ### Run a playbook
In order to run an isolated job, associate the instance group `thepentagon` with In order to run an isolated job, associate the instance group `thepentagon` with