mirror of
https://github.com/ansible/awx.git
synced 2024-10-28 02:25:27 +03:00
Merge remote-tracking branch 'origin/release_3.1.2' into devel
This commit is contained in:
commit
bad9670a0b
4
Makefile
4
Makefile
@ -830,7 +830,7 @@ amazon-ebs:
|
||||
cd packaging/packer && $(PACKER) build -only $@ $(PACKER_BUILD_OPTS) -var "aws_instance_count=$(AWS_INSTANCE_COUNT)" -var "product_version=$(VERSION)" packer-$(NAME).json
|
||||
|
||||
# Vagrant box using virtualbox provider
|
||||
vagrant-virtualbox: packaging/packer/ansible-tower-$(VERSION)-virtualbox.box
|
||||
vagrant-virtualbox: packaging/packer/ansible-tower-$(VERSION)-virtualbox.box tar-build/$(SETUP_TAR_FILE)
|
||||
|
||||
packaging/packer/ansible-tower-$(VERSION)-virtualbox.box: packaging/packer/output-virtualbox-iso/centos-7.ovf
|
||||
cd packaging/packer && $(PACKER) build -only virtualbox-ovf $(PACKER_BUILD_OPTS) -var "aws_instance_count=$(AWS_INSTANCE_COUNT)" -var "product_version=$(VERSION)" packer-$(NAME).json
|
||||
@ -841,7 +841,7 @@ packaging/packer/output-virtualbox-iso/centos-7.ovf:
|
||||
virtualbox-iso: packaging/packer/output-virtualbox-iso/centos-7.ovf
|
||||
|
||||
# Vagrant box using VMware provider
|
||||
vagrant-vmware: packaging/packer/ansible-tower-$(VERSION)-vmware.box
|
||||
vagrant-vmware: packaging/packer/ansible-tower-$(VERSION)-vmware.box tar-build/$(SETUP_TAR_FILE)
|
||||
|
||||
packaging/packer/output-vmware-iso/centos-7.vmx:
|
||||
cd packaging/packer && $(PACKER) build -only vmware-iso packer-centos-7.json
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
# Python
|
||||
import re
|
||||
import json
|
||||
|
||||
# Django
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
@ -296,7 +297,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
except (FieldError, FieldDoesNotExist, ValueError, TypeError) as e:
|
||||
raise ParseError(e.args[0])
|
||||
except ValidationError as e:
|
||||
raise ParseError(e.messages)
|
||||
raise ParseError(json.dumps(e.messages, ensure_ascii=False))
|
||||
|
||||
|
||||
class OrderByBackend(BaseFilterBackend):
|
||||
|
@ -3503,6 +3503,7 @@ class BaseJobEventsList(SubListAPIView):
|
||||
parent_model = None # Subclasses must define this attribute.
|
||||
relationship = 'job_events'
|
||||
view_name = _('Job Events List')
|
||||
search_fields = ('stdout',)
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
response['X-UI-Max-Events'] = settings.RECOMMENDED_MAX_EVENTS_DISPLAY_HEADER
|
||||
|
@ -304,6 +304,12 @@ class BaseCallbackModule(CallbackBase):
|
||||
|
||||
def v2_runner_on_ok(self, result):
|
||||
# FIXME: Display detailed results or not based on verbosity.
|
||||
|
||||
# strip environment vars from the job event; it already exists on the
|
||||
# job and sensitive values are filtered there
|
||||
if result._task.get_name() == 'setup':
|
||||
result._result.get('ansible_facts', {}).pop('ansible_env', None)
|
||||
|
||||
event_data = dict(
|
||||
host=result._host.get_name(),
|
||||
remote_addr=result._host.address,
|
||||
|
24
awx/main/migrations/0036_v311_insights.py
Normal file
24
awx/main/migrations/0036_v311_insights.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0035_v310_remove_tower_settings'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='scm_type',
|
||||
field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='projectupdate',
|
||||
name='scm_type',
|
||||
field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'),
|
||||
),
|
||||
]
|
@ -43,6 +43,7 @@ class ProjectOptions(models.Model):
|
||||
('git', _('Git')),
|
||||
('hg', _('Mercurial')),
|
||||
('svn', _('Subversion')),
|
||||
('insights', _('Red Hat Insights')),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
@ -120,6 +121,8 @@ class ProjectOptions(models.Model):
|
||||
return self.scm_type or ''
|
||||
|
||||
def clean_scm_url(self):
|
||||
if self.scm_type == 'insights':
|
||||
self.scm_url = settings.INSIGHTS_URL_BASE
|
||||
scm_url = unicode(self.scm_url or '')
|
||||
if not self.scm_type:
|
||||
return ''
|
||||
@ -141,6 +144,8 @@ class ProjectOptions(models.Model):
|
||||
if cred.kind != 'scm':
|
||||
raise ValidationError(_("Credential kind must be 'scm'."))
|
||||
try:
|
||||
if self.scm_type == 'insights':
|
||||
self.scm_url = settings.INSIGHTS_URL_BASE
|
||||
scm_url = update_scm_url(self.scm_type, self.scm_url,
|
||||
check_special_cases=False)
|
||||
scm_url_parts = urlparse.urlsplit(scm_url)
|
||||
|
@ -471,24 +471,24 @@ class BaseTask(Task):
|
||||
env['PROOT_TMP_DIR'] = settings.AWX_PROOT_BASE_PATH
|
||||
return env
|
||||
|
||||
def build_safe_env(self, instance, **kwargs):
|
||||
def build_safe_env(self, env, **kwargs):
|
||||
'''
|
||||
Build environment dictionary, hiding potentially sensitive information
|
||||
such as passwords or keys.
|
||||
'''
|
||||
hidden_re = re.compile(r'API|TOKEN|KEY|SECRET|PASS', re.I)
|
||||
urlpass_re = re.compile(r'^.*?://.?:(.*?)@.*?$')
|
||||
env = self.build_env(instance, **kwargs)
|
||||
for k,v in env.items():
|
||||
urlpass_re = re.compile(r'^.*?://[^:]+:(.*?)@.*?$')
|
||||
safe_env = dict(env)
|
||||
for k,v in safe_env.items():
|
||||
if k in ('REST_API_URL', 'AWS_ACCESS_KEY', 'AWS_ACCESS_KEY_ID'):
|
||||
continue
|
||||
elif k.startswith('ANSIBLE_') and not k.startswith('ANSIBLE_NET'):
|
||||
continue
|
||||
elif hidden_re.search(k):
|
||||
env[k] = HIDDEN_PASSWORD
|
||||
safe_env[k] = HIDDEN_PASSWORD
|
||||
elif type(v) == str and urlpass_re.match(v):
|
||||
env[k] = urlpass_re.sub(HIDDEN_PASSWORD, v)
|
||||
return env
|
||||
safe_env[k] = urlpass_re.sub(HIDDEN_PASSWORD, v)
|
||||
return safe_env
|
||||
|
||||
def args2cmdline(self, *args):
|
||||
return ' '.join([pipes.quote(a) for a in args])
|
||||
@ -699,7 +699,7 @@ class BaseTask(Task):
|
||||
output_replacements = self.build_output_replacements(instance, **kwargs)
|
||||
cwd = self.build_cwd(instance, **kwargs)
|
||||
env = self.build_env(instance, **kwargs)
|
||||
safe_env = self.build_safe_env(instance, **kwargs)
|
||||
safe_env = self.build_safe_env(env, **kwargs)
|
||||
stdout_handle = self.get_stdout_handle(instance)
|
||||
if self.should_use_proot(instance, **kwargs):
|
||||
if not check_proot_installed():
|
||||
@ -1189,6 +1189,9 @@ class RunProjectUpdate(BaseTask):
|
||||
scm_username = False
|
||||
elif scm_url_parts.scheme.endswith('ssh'):
|
||||
scm_password = False
|
||||
elif scm_type == 'insights':
|
||||
extra_vars['scm_username'] = scm_username
|
||||
extra_vars['scm_password'] = scm_password
|
||||
scm_url = update_scm_url(scm_type, scm_url, scm_username,
|
||||
scm_password, scp_format=True)
|
||||
else:
|
||||
@ -1218,6 +1221,7 @@ class RunProjectUpdate(BaseTask):
|
||||
scm_branch = project_update.scm_branch or {'hg': 'tip'}.get(project_update.scm_type, 'HEAD')
|
||||
extra_vars.update({
|
||||
'project_path': project_update.get_project_path(check_if_exists=False),
|
||||
'insights_url': settings.INSIGHTS_URL_BASE,
|
||||
'scm_type': project_update.scm_type,
|
||||
'scm_url': scm_url,
|
||||
'scm_branch': scm_branch,
|
||||
@ -1314,10 +1318,10 @@ class RunProjectUpdate(BaseTask):
|
||||
lines = fd.readlines()
|
||||
if lines:
|
||||
p.scm_revision = lines[0].strip()
|
||||
p.playbook_files = p.playbooks
|
||||
p.save()
|
||||
else:
|
||||
logger.error("Could not find scm revision in check")
|
||||
logger.info("Could not find scm revision in check")
|
||||
p.playbook_files = p.playbooks
|
||||
p.save()
|
||||
try:
|
||||
os.remove(self.revision_path)
|
||||
except Exception, e:
|
||||
|
@ -1,11 +1,13 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import pytest
|
||||
from pip.operations import freeze
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="This test needs some love")
|
||||
def test_env_matches_requirements_txt():
|
||||
def check_is_in(src, dests):
|
||||
if src not in dests:
|
||||
|
@ -71,6 +71,25 @@ def test_run_admin_checks_usage(mocker, current_instances, call_count):
|
||||
assert 'expire' in mock_sm.call_args_list[0][0][0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key,value", [
|
||||
('REST_API_TOKEN', 'SECRET'),
|
||||
('SECRET_KEY', 'SECRET'),
|
||||
('RABBITMQ_PASS', 'SECRET'),
|
||||
('VMWARE_PASSWORD', 'SECRET'),
|
||||
('API_SECRET', 'SECRET'),
|
||||
('CALLBACK_CONNECTION', 'amqp://tower:password@localhost:5672/tower'),
|
||||
])
|
||||
def test_safe_env_filtering(key, value):
|
||||
task = tasks.RunJob()
|
||||
assert task.build_safe_env({key: value})[key] == tasks.HIDDEN_PASSWORD
|
||||
|
||||
|
||||
def test_safe_env_returns_new_copy():
|
||||
task = tasks.RunJob()
|
||||
env = {'foo': 'bar'}
|
||||
assert task.build_safe_env(env) is not env
|
||||
|
||||
|
||||
def test_openstack_client_config_generation(mocker):
|
||||
update = tasks.RunInventoryUpdate()
|
||||
inventory_update = mocker.Mock(**{
|
||||
|
@ -261,7 +261,7 @@ def update_scm_url(scm_type, url, username=True, password=True,
|
||||
# git: https://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS
|
||||
# hg: http://www.selenic.com/mercurial/hg.1.html#url-paths
|
||||
# svn: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.advanced.reposurls
|
||||
if scm_type not in ('git', 'hg', 'svn'):
|
||||
if scm_type not in ('git', 'hg', 'svn', 'insights'):
|
||||
raise ValueError(_('Unsupported SCM type "%s"') % str(scm_type))
|
||||
if not url.strip():
|
||||
return ''
|
||||
@ -307,6 +307,7 @@ def update_scm_url(scm_type, url, username=True, password=True,
|
||||
'git': ('ssh', 'git', 'git+ssh', 'http', 'https', 'ftp', 'ftps', 'file'),
|
||||
'hg': ('http', 'https', 'ssh', 'file'),
|
||||
'svn': ('http', 'https', 'svn', 'svn+ssh', 'file'),
|
||||
'insights': ('http', 'https')
|
||||
}
|
||||
if parts.scheme not in scm_type_schemes.get(scm_type, ()):
|
||||
raise ValueError(_('Unsupported %s URL') % scm_type)
|
||||
@ -342,7 +343,7 @@ def update_scm_url(scm_type, url, username=True, password=True,
|
||||
#raise ValueError('Password not supported for SSH with Mercurial.')
|
||||
netloc_password = ''
|
||||
|
||||
if netloc_username and parts.scheme != 'file':
|
||||
if netloc_username and parts.scheme != 'file' and scm_type != "insights":
|
||||
netloc = u':'.join([urllib.quote(x) for x in (netloc_username, netloc_password) if x])
|
||||
else:
|
||||
netloc = u''
|
||||
|
@ -13,7 +13,9 @@ class LogstashFormatter(LogstashFormatterVersion1):
|
||||
ret = super(LogstashFormatter, self).__init__(**kwargs)
|
||||
if settings_module:
|
||||
self.host_id = settings_module.CLUSTER_HOST_ID
|
||||
self.tower_uuid = settings_module.LOG_AGGREGATOR_TOWER_UUID
|
||||
if hasattr(settings_module, 'LOG_AGGREGATOR_TOWER_UUID'):
|
||||
self.tower_uuid = settings_module.LOG_AGGREGATOR_TOWER_UUID
|
||||
self.message_type = settings_module.LOG_AGGREGATOR_TYPE
|
||||
return ret
|
||||
|
||||
def reformat_data_for_log(self, raw_data, kind=None):
|
||||
|
@ -105,6 +105,45 @@
|
||||
scm_version: "{{ scm_result['after'] }}"
|
||||
when: "'after' in scm_result"
|
||||
|
||||
- name: update project using insights
|
||||
uri:
|
||||
url: "{{insights_url}}/r/insights/v1/maintenance?ansible=true"
|
||||
user: "{{scm_username|quote}}"
|
||||
password: "{{scm_password|quote}}"
|
||||
force_basic_auth: yes
|
||||
when: scm_type == 'insights'
|
||||
register: insights_output
|
||||
|
||||
- name: Ensure the project directory is present
|
||||
file:
|
||||
dest: "{{project_path|quote}}"
|
||||
state: directory
|
||||
when: scm_type == 'insights'
|
||||
|
||||
- name: Fetch Insights Playbook With Name
|
||||
get_url:
|
||||
url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook"
|
||||
dest: "{{project_path|quote}}/{{item.name}}-{{item.maintenance_id}}.yml"
|
||||
url_username: "{{scm_username|quote}}"
|
||||
url_password: "{{scm_password|quote}}"
|
||||
force_basic_auth: yes
|
||||
force: yes
|
||||
when: scm_type == 'insights' and item.name != None
|
||||
with_items: "{{insights_output.json}}"
|
||||
failed_when: false
|
||||
|
||||
- name: Fetch Insights Playbook
|
||||
get_url:
|
||||
url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook"
|
||||
dest: "{{project_path|quote}}/insights-plan-{{item.maintenance_id}}.yml"
|
||||
url_username: "{{scm_username|quote}}"
|
||||
url_password: "{{scm_password|quote}}"
|
||||
force_basic_auth: yes
|
||||
force: yes
|
||||
when: scm_type == 'insights' and item.name == None
|
||||
with_items: "{{insights_output.json}}"
|
||||
failed_when: false
|
||||
|
||||
- name: detect requirements.yml
|
||||
stat: path={{project_path|quote}}/roles/requirements.yml
|
||||
register: doesRequirementsExist
|
||||
|
@ -862,6 +862,8 @@ TOWER_ADMIN_ALERTS = True
|
||||
# Note: This setting may be overridden by database settings.
|
||||
TOWER_URL_BASE = "https://towerhost"
|
||||
|
||||
INSIGHTS_URL_BASE = "https://access.redhat.com"
|
||||
|
||||
TOWER_SETTINGS_MANIFEST = {}
|
||||
|
||||
LOG_AGGREGATOR_ENABLED = False
|
||||
|
@ -377,9 +377,9 @@ register(
|
||||
help_text=_('User profile flags updated from group membership (key is user '
|
||||
'attribute name, value is group DN). These are boolean fields '
|
||||
'that are matched based on whether the user is a member of the '
|
||||
'given group. So far only is_superuser is settable via this '
|
||||
'method. This flag is set both true and false at login time '
|
||||
'based on current LDAP settings.'),
|
||||
'given group. So far only is_superuser and is_system_auditor '
|
||||
'are settable via this method. This flag is set both true and '
|
||||
'false at login time based on current LDAP settings.'),
|
||||
category=_('LDAP'),
|
||||
category_slug='ldap',
|
||||
placeholder=collections.OrderedDict([
|
||||
|
@ -322,7 +322,7 @@ class LDAPUserFlagsField(fields.DictField):
|
||||
default_error_messages = {
|
||||
'invalid_flag': _('Invalid user flag: "{invalid_flag}".'),
|
||||
}
|
||||
valid_user_flags = {'is_superuser'}
|
||||
valid_user_flags = {'is_superuser', 'is_system_auditor'}
|
||||
child = LDAPDNField()
|
||||
|
||||
def to_internal_value(self, data):
|
||||
|
@ -40,7 +40,6 @@
|
||||
|
||||
.Form-title{
|
||||
flex: 0 1 auto;
|
||||
text-transform: uppercase;
|
||||
color: @list-header-txt;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
@ -50,6 +49,10 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.Form-title--uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.Form-secondaryTitle{
|
||||
color: @default-icon;
|
||||
padding-bottom: 20px;
|
||||
@ -98,8 +101,8 @@
|
||||
|
||||
.Form-tabHolder{
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
min-height: 30px;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
|
||||
.Form-tabs {
|
||||
@ -115,6 +118,7 @@
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 5px;
|
||||
@ -560,6 +564,8 @@ input[type='radio']:checked:before {
|
||||
padding-left:15px;
|
||||
padding-right: 15px;
|
||||
margin-right: 20px;
|
||||
min-height: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.Form-primaryButton:hover {
|
||||
|
@ -147,7 +147,6 @@ table, tbody {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.List-actionHolder {
|
||||
|
@ -1,47 +1,60 @@
|
||||
/** @define About */
|
||||
@import "./client/src/shared/branding/colors.default.less";
|
||||
|
||||
.About-cowsay--container{
|
||||
width: 340px;
|
||||
margin: 0 auto;
|
||||
|
||||
.About-ansibleVersion,
|
||||
.About-cowsayCode {
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
.About-cowsay--code{
|
||||
background-color: @default-bg;
|
||||
padding-left: 30px;
|
||||
border-style: none;
|
||||
max-width: 340px;
|
||||
padding-left: 30px;
|
||||
|
||||
.About-cowsayContainer {
|
||||
width: 340px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.About .modal-header{
|
||||
border: none;
|
||||
padding-bottom: 0px;
|
||||
.About-cowsayCode {
|
||||
background-color: @default-bg;
|
||||
padding-left: 30px;
|
||||
border-style: none;
|
||||
max-width: 340px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
.About .modal-dialog{
|
||||
max-width: 500px;
|
||||
.About-modalHeader {
|
||||
border: none;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.About .modal-body{
|
||||
padding-top: 0px;
|
||||
.About-modalDialog {
|
||||
max-width: 500px;
|
||||
}
|
||||
.About-brand--redhat{
|
||||
|
||||
.About-modalBody {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.About-brandImg {
|
||||
float: left;
|
||||
width: 112px;
|
||||
padding-top: 13px;
|
||||
}
|
||||
.About-brand--ansible{
|
||||
max-width: 120px;
|
||||
margin: 0 auto;
|
||||
|
||||
.About-close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 10;
|
||||
}
|
||||
.About-close{
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 10;
|
||||
|
||||
.About-modalFooter {
|
||||
clear: both;
|
||||
}
|
||||
.About p{
|
||||
color: @default-interface-txt;
|
||||
|
||||
.About-footerText {
|
||||
text-align: right;
|
||||
color: @default-interface-txt;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.About-modal--footer {
|
||||
clear: both;
|
||||
|
||||
.About-ansibleVersion {
|
||||
color: @default-data-txt;
|
||||
}
|
||||
|
@ -1,27 +1,12 @@
|
||||
export default
|
||||
['$scope', '$state', 'ConfigService', 'i18n',
|
||||
function($scope, $state, ConfigService, i18n){
|
||||
var processVersion = function(version){
|
||||
// prettify version & calculate padding
|
||||
// e,g 3.0.0-0.git201602191743/ -> 3.0.0
|
||||
var split = version.split('-')[0];
|
||||
var spaces = Math.floor((16-split.length)/2),
|
||||
paddedStr = "";
|
||||
for(var i=0; i<=spaces; i++){
|
||||
paddedStr = paddedStr +" ";
|
||||
}
|
||||
paddedStr = paddedStr + split;
|
||||
for(var j = paddedStr.length; j<16; j++){
|
||||
paddedStr = paddedStr + " ";
|
||||
}
|
||||
return paddedStr;
|
||||
};
|
||||
['$scope', '$state', 'ConfigService',
|
||||
function($scope, $state, ConfigService){
|
||||
var init = function(){
|
||||
ConfigService.getConfig()
|
||||
.then(function(config){
|
||||
$scope.version = config.version.split('-')[0];
|
||||
$scope.ansible_version = config.ansible_version;
|
||||
$scope.subscription = config.license_info.subscription_name;
|
||||
$scope.version = processVersion(config.version);
|
||||
$scope.version_str = i18n._("Version");
|
||||
$('#about-modal').modal('show');
|
||||
});
|
||||
};
|
||||
|
@ -1,19 +1,18 @@
|
||||
<div class="About modal fade" id="about-modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog About-modalDialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header About-modalHeader">
|
||||
<button data-dismiss="modal" type="button" class="close About-close">
|
||||
<span class="fa fa-times-circle"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="About-cowsay--container">
|
||||
<div class="modal-body About-modalBody">
|
||||
<div class="About-cowsayContainer">
|
||||
<!-- Don't indent this properly, you'll break the cow -->
|
||||
<pre class="About-cowsay--code">
|
||||
________________
|
||||
/ Tower {{version_str}} \
|
||||
\<span>{{version}}</span>/
|
||||
----------------
|
||||
<pre class="About-cowsayCode">
|
||||
_______________
|
||||
< Tower {{version}} >
|
||||
---------------
|
||||
\ ^__^
|
||||
\ (oo)\_______
|
||||
(__) A )\/\
|
||||
@ -21,10 +20,15 @@
|
||||
|| ||
|
||||
</pre>
|
||||
</div>
|
||||
<div class="About-modal--footer">
|
||||
<img class="About-brand--redhat img-responsive" src="/static/assets/tower-logo-login.svg" />
|
||||
<p class="text-right">Copyright © 2017 Red Hat, Inc. <br>
|
||||
Visit <a href="http://www.ansible.com/" target="_blank">Ansible.com</a> for more information.<br>
|
||||
<div class="About-modalFooter">
|
||||
<img class="About-brandImg img-responsive" src="/static/assets/tower-logo-login.svg" />
|
||||
<p class="About-footerText">
|
||||
<span class="About-ansibleVersion">
|
||||
Ansible {{ ansible_version }}
|
||||
</span> <br>
|
||||
Copyright © 2017 Red Hat, Inc. <br>
|
||||
Visit <a href="http://www.ansible.com/" target="_blank">Ansible.com</a> for more information.<br>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,7 +65,6 @@
|
||||
.BreadCrumb-item {
|
||||
display: inline-block;
|
||||
color: @default-interface-txt;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -36,19 +36,11 @@
|
||||
|
||||
.Form-nav--dropdownContainer {
|
||||
width: 285px;
|
||||
margin-top: -52px;
|
||||
margin-bottom: 22px;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.Form-nav--dropdownContainer {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Form-nav--dropdown {
|
||||
width: 60%;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="tab-pane" id="configuration-panel">
|
||||
<div ng-cloak id="htmlTemplate" class="Panel">
|
||||
<div class="Form-header">
|
||||
<div class="Form-title" translate>Configure Tower</div>
|
||||
<div class="Form-title" translate>CONFIGURE TOWER</div>
|
||||
</div>
|
||||
<div class="row Form-tabRow">
|
||||
<div class="col-lg-12">
|
||||
|
@ -27,7 +27,7 @@
|
||||
},
|
||||
ncyBreadcrumb: {
|
||||
parent: 'setup',
|
||||
label: N_("Edit Configuration")
|
||||
label: N_("EDIT CONFIGURATION")
|
||||
},
|
||||
controller: ConfigurationController,
|
||||
resolve: {
|
||||
|
@ -22,7 +22,7 @@ export default
|
||||
return {
|
||||
|
||||
name: 'activity',
|
||||
editTitle: i18n._('Activity Detail'),
|
||||
editTitle: i18n._('ACTIVITY DETAIL'),
|
||||
well: false,
|
||||
'class': 'horizontal-narrow',
|
||||
formFieldSize: 'col-lg-10',
|
||||
|
@ -15,7 +15,7 @@ export default
|
||||
.factory('CredentialForm', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('Create Credential'), //Legend in add mode
|
||||
addTitle: i18n._('CREATE CREDENTIAL'), //Legend in add mode
|
||||
editTitle: '{{ name }}', //Legend in edit mode
|
||||
name: 'credential',
|
||||
// the top-most node of generated state tree
|
||||
|
@ -14,7 +14,7 @@ export default
|
||||
angular.module('GroupFormDefinition', [])
|
||||
.value('GroupFormObject', {
|
||||
|
||||
addTitle: 'Create Group',
|
||||
addTitle: 'CREATE GROUP',
|
||||
editTitle: '{{ name }}',
|
||||
showTitle: true,
|
||||
name: 'group',
|
||||
|
@ -14,7 +14,7 @@ export default
|
||||
angular.module('HostGroupsFormDefinition', [])
|
||||
.value('HostGroupsForm', {
|
||||
|
||||
editTitle: 'Host Groups',
|
||||
editTitle: 'HOST GROUPS',
|
||||
name: 'host',
|
||||
well: false,
|
||||
formLabelSize: 'col-lg-3',
|
||||
|
@ -15,7 +15,7 @@ export default
|
||||
.factory('HostForm', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('Create Host'),
|
||||
addTitle: i18n._('CREATE HOST'),
|
||||
editTitle: '{{ host.name }}',
|
||||
name: 'host',
|
||||
basePath: 'hosts',
|
||||
|
@ -15,7 +15,7 @@ angular.module('InventoryFormDefinition', [])
|
||||
.factory('InventoryForm', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Inventory'),
|
||||
addTitle: i18n._('NEW INVENTORY'),
|
||||
editTitle: '{{ inventory_name }}',
|
||||
name: 'inventory',
|
||||
basePath: 'inventory',
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name forms.function:InventoryStatus
|
||||
@ -14,7 +14,7 @@ export default
|
||||
.value('InventoryStatusForm', {
|
||||
|
||||
name: 'inventory_update',
|
||||
editTitle: 'Inventory Status',
|
||||
editTitle: 'INVENTORY STATUS',
|
||||
well: false,
|
||||
'class': 'horizontal-narrow',
|
||||
|
||||
|
@ -17,7 +17,7 @@ export default
|
||||
.factory('JobTemplateFormObject', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Job Template'),
|
||||
addTitle: i18n._('NEW JOB TEMPLATE'),
|
||||
editTitle: '{{ name }}',
|
||||
name: 'job_template',
|
||||
breadcrumbName: i18n._('JOB TEMPLATE'),
|
||||
|
@ -15,7 +15,7 @@ export default
|
||||
.factory('OrganizationFormObject', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Organization'), //Title in add mode
|
||||
addTitle: i18n._('NEW ORGANIZATION'), //Title in add mode
|
||||
editTitle: '{{ name }}', //Title in edit mode
|
||||
name: 'organization', //entity or model name in singular form
|
||||
stateTree: 'organizations',
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name forms.function:ProjectStatus
|
||||
@ -15,7 +15,7 @@ export default
|
||||
.value('ProjectStatusForm', {
|
||||
|
||||
name: 'project_update',
|
||||
editTitle: 'SCM Status',
|
||||
editTitle: 'SCM STATUS',
|
||||
well: false,
|
||||
'class': 'horizontal-narrow',
|
||||
|
||||
|
@ -15,7 +15,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
.factory('ProjectsFormObject', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Project'),
|
||||
addTitle: i18n._('NEW PROJECT'),
|
||||
editTitle: '{{ name }}',
|
||||
name: 'project',
|
||||
basePath: 'projects',
|
||||
@ -105,7 +105,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
scm_url: {
|
||||
label: 'SCM URL',
|
||||
type: 'text',
|
||||
ngShow: "scm_type && scm_type.value !== 'manual'",
|
||||
ngShow: "scm_type && scm_type.value !== 'manual' && scm_type.value !== 'insights' ",
|
||||
awRequiredWhen: {
|
||||
reqExpression: "scmRequired",
|
||||
init: false
|
||||
@ -122,12 +122,12 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
|
||||
scm_branch: {
|
||||
labelBind: "scmBranchLabel",
|
||||
type: 'text',
|
||||
ngShow: "scm_type && scm_type.value !== 'manual'",
|
||||
ngShow: "scm_type && scm_type.value !== 'manual' && scm_type.value !== 'insights'",
|
||||
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
subForm: 'sourceSubForm',
|
||||
},
|
||||
credential: {
|
||||
label: i18n._('SCM Credential'),
|
||||
labelBind: 'credentialLabel',
|
||||
type: 'lookup',
|
||||
basePath: 'credentials',
|
||||
list: 'CredentialList',
|
||||
|
@ -15,7 +15,7 @@ export default
|
||||
.factory('TeamForm', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Team'), //Legend in add mode
|
||||
addTitle: i18n._('NEW TEAM'), //Legend in add mode
|
||||
editTitle: '{{ name }}', //Legend in edit mode
|
||||
name: 'team',
|
||||
// the top-most node of generated state tree
|
||||
|
@ -15,7 +15,7 @@ export default
|
||||
.factory('UserForm', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New User'),
|
||||
addTitle: i18n._('NEW USER'),
|
||||
editTitle: '{{ username }}',
|
||||
name: 'user',
|
||||
// the top-most node of generated state tree
|
||||
@ -38,6 +38,17 @@ export default
|
||||
required: true,
|
||||
capitalize: true
|
||||
},
|
||||
organization: {
|
||||
label: i18n._('Organization'),
|
||||
type: 'lookup',
|
||||
list: 'OrganizationList',
|
||||
basePath: 'organizations',
|
||||
sourceModel: 'organization',
|
||||
sourceField: 'name',
|
||||
required: true,
|
||||
excludeMode: 'edit',
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
email: {
|
||||
label: i18n._('Email'),
|
||||
type: 'email',
|
||||
@ -55,17 +66,6 @@ export default
|
||||
autocomplete: false,
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
organization: {
|
||||
label: i18n._('Organization'),
|
||||
type: 'lookup',
|
||||
list: 'OrganizationList',
|
||||
basePath: 'organizations',
|
||||
sourceModel: 'organization',
|
||||
sourceField: 'name',
|
||||
required: true,
|
||||
excludeMode: 'edit',
|
||||
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
password: {
|
||||
label: i18n._('Password'),
|
||||
type: 'sensitive',
|
||||
|
@ -16,7 +16,7 @@ export default
|
||||
.factory('WorkflowFormObject', ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Workflow Job Template'),
|
||||
addTitle: i18n._('NEW WORKFLOW JOB TEMPLATE'),
|
||||
editTitle: '{{ name }}',
|
||||
name: 'workflow_job_template',
|
||||
breadcrumbName: i18n._('WORKFLOW'),
|
||||
|
@ -10,8 +10,8 @@ export default [ 'i18n', function(i18n){
|
||||
name: 'hosts',
|
||||
iterator: 'host',
|
||||
selectTitle: i18n._('Add Existing Hosts'),
|
||||
editTitle: i18n._('Hosts'),
|
||||
listTitle: i18n._('Hosts'),
|
||||
editTitle: i18n._('HOSTS'),
|
||||
listTitle: i18n._('HOSTS'),
|
||||
index: false,
|
||||
hover: true,
|
||||
well: true,
|
||||
|
@ -97,7 +97,7 @@ angular.module('inventory', [
|
||||
'@': {
|
||||
templateProvider: function(ScheduleList, generateList, ParentObject) {
|
||||
// include name of parent resource in listTitle
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('SCHEDULES');
|
||||
let html = generateList.build({
|
||||
list: ScheduleList,
|
||||
mode: 'edit'
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
addTitle: 'Execute Command',
|
||||
addTitle: 'EXECUTE COMMAND',
|
||||
name: 'adhoc',
|
||||
well: true,
|
||||
forceListeners: true,
|
||||
|
@ -13,7 +13,7 @@
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Custom Inventory'),
|
||||
addTitle: i18n._('NEW CUSTOM INVENTORY'),
|
||||
editTitle: '{{ name }}',
|
||||
name: 'inventory_script',
|
||||
basePath: 'inventory_scripts',
|
||||
|
@ -9,7 +9,7 @@
|
||||
export default ['i18n', function(i18n){
|
||||
return {
|
||||
name: 'inventory_scripts' ,
|
||||
listTitle: i18n._('Inventory Scripts'),
|
||||
listTitle: i18n._('INVENTORY SCRIPTS'),
|
||||
iterator: 'inventory_script',
|
||||
index: false,
|
||||
hover: false,
|
||||
|
@ -161,6 +161,24 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.JobResults-panelRightTitle{
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.JobResults-panelRightTitleText{
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.JobResults-badgeAndActionRow{
|
||||
display:flex;
|
||||
flex: 1 0 auto;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.StandardOut-panelHeader {
|
||||
flex: initial;
|
||||
}
|
||||
|
@ -38,12 +38,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy
|
||||
|
||||
// used for tag search
|
||||
$scope.list = {
|
||||
basePath: jobData.related.job_events,
|
||||
defaultSearchParams: function(term){
|
||||
return {
|
||||
or__stdout__icontains: term,
|
||||
};
|
||||
},
|
||||
basePath: jobData.related.job_events
|
||||
};
|
||||
|
||||
// used for tag search
|
||||
|
@ -431,8 +431,8 @@
|
||||
<div class="Panel JobResults-panelRight">
|
||||
|
||||
<!-- RIGHT PANE HEADER -->
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText">
|
||||
<div class="StandardOut-panelHeader JobResults-panelRightTitle">
|
||||
<div class="StandardOut-panelHeaderText JobResults-panelRightTitleText">
|
||||
<i class="JobResults-statusResultIcon
|
||||
fa icon-job-{{ job_status }}"
|
||||
ng-show="stdoutFullScreen"
|
||||
@ -443,76 +443,77 @@
|
||||
</i>
|
||||
{{ job.name }}
|
||||
</div>
|
||||
<div class="JobResults-badgeAndActionRow">
|
||||
<!-- HEADER COUNTS -->
|
||||
<div class="JobResults-badgeRow">
|
||||
<!-- PLAYS COUNT -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Plays
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ playCount || 0}}
|
||||
</span>
|
||||
|
||||
<!-- HEADER COUNTS -->
|
||||
<div class="JobResults-badgeRow">
|
||||
<!-- PLAYS COUNT -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Plays
|
||||
<!-- TASKS COUNT -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Tasks
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ taskCount || 0}}
|
||||
</span>
|
||||
|
||||
<!-- HOSTS COUNT -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Hosts
|
||||
</div>
|
||||
<span class="badge List-titleBadge"
|
||||
ng-if="jobFinished">
|
||||
{{ hostCount || 0}}
|
||||
</span>
|
||||
|
||||
<span class="badge List-titleBadge"
|
||||
aw-tool-tip="The host count will update when the job is complete."
|
||||
data-placement="top"
|
||||
ng-if="!jobFinished">
|
||||
<i class="fa fa-ellipsis-h"></i>
|
||||
</span>
|
||||
|
||||
<!-- ELAPSED TIME -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Elapsed
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ job.elapsed * 1000 | duration: "hh:mm:ss" }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ playCount || 0}}
|
||||
</span>
|
||||
|
||||
<!-- TASKS COUNT -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Tasks
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ taskCount || 0}}
|
||||
</span>
|
||||
<!-- HEADER ACTIONS -->
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
|
||||
<!-- HOSTS COUNT -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Hosts
|
||||
</div>
|
||||
<span class="badge List-titleBadge"
|
||||
ng-if="jobFinished">
|
||||
{{ hostCount || 0}}
|
||||
</span>
|
||||
|
||||
<span class="badge List-titleBadge"
|
||||
aw-tool-tip="The host count will update when the job is complete."
|
||||
data-placement="top"
|
||||
ng-if="!jobFinished">
|
||||
<i class="fa fa-ellipsis-h"></i>
|
||||
</span>
|
||||
|
||||
<!-- ELAPSED TIME -->
|
||||
<div class="JobResults-badgeTitle">
|
||||
Elapsed
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ job.elapsed * 1000 | duration: "hh:mm:ss" }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- HEADER ACTIONS -->
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
|
||||
<!-- FULL-SCREEN TOGGLE ACTION -->
|
||||
<button class="StandardOut-actionButton"
|
||||
aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}"
|
||||
data-tip-watch="toggleStdoutFullscreenTooltip"
|
||||
data-placement="top"
|
||||
ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"
|
||||
ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
|
||||
<!-- DOWNLOAD ACTION -->
|
||||
<a ng-show="job.status === 'failed' ||
|
||||
job.status === 'successful' ||
|
||||
job.status === 'canceled'"
|
||||
href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download">
|
||||
<!-- FULL-SCREEN TOGGLE ACTION -->
|
||||
<button class="StandardOut-actionButton"
|
||||
aw-tool-tip="{{ standardOutTooltip }}"
|
||||
data-tip-watch="standardOutTooltip"
|
||||
data-placement="top">
|
||||
<i class="fa fa-download"></i>
|
||||
aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}"
|
||||
data-tip-watch="toggleStdoutFullscreenTooltip"
|
||||
data-placement="top"
|
||||
ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"
|
||||
ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<!-- DOWNLOAD ACTION -->
|
||||
<a ng-show="job.status === 'failed' ||
|
||||
job.status === 'successful' ||
|
||||
job.status === 'canceled'"
|
||||
href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download">
|
||||
<button class="StandardOut-actionButton"
|
||||
aw-tool-tip="{{ standardOutTooltip }}"
|
||||
data-tip-watch="standardOutTooltip"
|
||||
data-placement="top">
|
||||
<i class="fa fa-download"></i>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<host-status-bar></host-status-bar>
|
||||
|
@ -23,19 +23,20 @@
|
||||
.JobSubmission-header {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
}
|
||||
.JobSubmission-title {
|
||||
align-items: center;
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
max-width: 98%;
|
||||
}
|
||||
.JobSubmission-titleText {
|
||||
color: @list-title-txt;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.JobSubmission-titleLockup {
|
||||
margin-left: 4px;
|
||||
|
@ -13,7 +13,7 @@ export default
|
||||
name: 'jobs',
|
||||
basePath: 'unified_jobs',
|
||||
iterator: 'job',
|
||||
editTitle: i18n._('All Jobs'),
|
||||
editTitle: i18n._('ALL JOBS'),
|
||||
index: false,
|
||||
hover: true,
|
||||
well: false,
|
||||
|
@ -14,7 +14,7 @@ export default
|
||||
name: 'cloudcredentials',
|
||||
iterator: 'cloudcredential',
|
||||
selectTitle: 'Add Cloud Credentials',
|
||||
editTitle: 'Cloud Credentials',
|
||||
editTitle: 'CLOUD CREDENTIALS',
|
||||
selectInstructions: '<p>Select existing credentials by clicking each credential or checking the related checkbox. When finished, click the blue ' +
|
||||
'<em>Select</em> button, located bottom right.</p> <p>Create a brand new credential by clicking the <i class=\"fa fa-plus"></i> button.</p>',
|
||||
index: false,
|
||||
|
@ -15,7 +15,7 @@ export default
|
||||
name: 'completed_jobs',
|
||||
basePath: 'api/v1/job_templates/{{$stateParams.job_template_id}}/jobs/?or__status=successful&or__status=failed&or__status=error&or__status=canceled',
|
||||
iterator: 'completed_job',
|
||||
editTitle: i18n._('Completed Jobs'),
|
||||
editTitle: i18n._('COMPLETED JOBS'),
|
||||
index: false,
|
||||
hover: true,
|
||||
well: false,
|
||||
|
@ -15,8 +15,8 @@ export default
|
||||
name: 'credentials',
|
||||
iterator: 'credential',
|
||||
selectTitle: i18n._('Add Credentials'),
|
||||
editTitle: i18n._('Credentials'),
|
||||
listTitle: i18n._('Credentials'),
|
||||
editTitle: i18n._('CREDENTIALS'),
|
||||
listTitle: i18n._('CREDENTIALS'),
|
||||
selectInstructions: "<p>Select existing credentials by clicking each credential or checking the related checkbox. When " +
|
||||
"finished, click the blue <em>Select</em> button, located bottom right.</p> <p>Create a brand new credential by clicking ",
|
||||
index: false,
|
||||
|
@ -13,8 +13,8 @@ export default
|
||||
name: 'inventories',
|
||||
iterator: 'inventory',
|
||||
selectTitle: i18n._('Add Inventories'),
|
||||
editTitle: i18n._('Inventories'),
|
||||
listTitle: i18n._('Inventories'),
|
||||
editTitle: i18n._('INVENTORIES'),
|
||||
listTitle: i18n._('INVENTORIES'),
|
||||
selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Click the %s button to create a new inventory."), "<i class=\"icon-plus\"></i> "),
|
||||
index: false,
|
||||
hover: true,
|
||||
|
@ -11,7 +11,7 @@ export default
|
||||
name: 'groups',
|
||||
iterator: 'group',
|
||||
editTitle: '{{ inventory.name }}',
|
||||
listTitle: 'Groups',
|
||||
listTitle: 'GROUPS',
|
||||
searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
|
||||
showTitle: false,
|
||||
well: true,
|
||||
|
@ -11,7 +11,7 @@ export default
|
||||
name: 'hosts',
|
||||
iterator: 'host',
|
||||
editTitle: '{{ selected_group }}',
|
||||
listTitle: 'Hosts',
|
||||
listTitle: 'HOSTS',
|
||||
searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
|
||||
showTitle: false,
|
||||
well: true,
|
||||
|
@ -12,7 +12,7 @@ export default
|
||||
name: 'workflow_inventory_sources',
|
||||
iterator: 'inventory_source',
|
||||
basePath: 'inventory_sources',
|
||||
listTitle: 'Inventory Sources',
|
||||
listTitle: 'INVENTORY SOURCES',
|
||||
index: false,
|
||||
hover: true,
|
||||
|
||||
|
@ -12,7 +12,7 @@ export default
|
||||
|
||||
name: 'jobevents',
|
||||
iterator: 'jobevent',
|
||||
editTitle: i18n._('Job Events'),
|
||||
editTitle: i18n._('JOB EVENTS'),
|
||||
index: false,
|
||||
hover: true,
|
||||
"class": "condensed",
|
||||
|
@ -11,7 +11,7 @@ export default
|
||||
|
||||
name: 'jobhosts',
|
||||
iterator: 'jobhost',
|
||||
editTitle: 'All summaries',
|
||||
editTitle: 'ALL SUMMARIES',
|
||||
"class": "table-condensed",
|
||||
index: false,
|
||||
hover: true,
|
||||
|
@ -11,7 +11,7 @@ export default
|
||||
|
||||
name: 'jobs',
|
||||
iterator: 'job',
|
||||
editTitle: 'Jobs',
|
||||
editTitle: 'JOBS',
|
||||
'class': 'table-condensed',
|
||||
index: false,
|
||||
hover: true,
|
||||
|
@ -15,7 +15,7 @@ export default
|
||||
selectInstructions: '<p>Select existing organizations by clicking each organization or checking the related checkbox. When finished, ' +
|
||||
'click the blue <em>Select</em> button, located bottom right.</p><p>Create a new organization by clicking the ' +
|
||||
'<i class=\"fa fa-plus\"></i> button.</p>',
|
||||
editTitle: 'Organizations',
|
||||
editTitle: 'ORGANIZATIONS',
|
||||
hover: true,
|
||||
index: false,
|
||||
|
||||
|
@ -12,8 +12,8 @@ export default
|
||||
|
||||
name: 'job_templates',
|
||||
iterator: 'job_template',
|
||||
editTitle: i18n._('Job Templates'),
|
||||
listTitle: i18n._('Job Templates'),
|
||||
editTitle: i18n._('JOB TEMPLATES'),
|
||||
listTitle: i18n._('JOB TEMPLATES'),
|
||||
index: false,
|
||||
hover: true,
|
||||
well: true,
|
||||
|
@ -12,11 +12,11 @@ export default
|
||||
|
||||
name: 'jobs',
|
||||
iterator: 'job',
|
||||
editTitle: i18n._('Jobs'),
|
||||
editTitle: i18n._('JOBS'),
|
||||
index: false,
|
||||
hover: true,
|
||||
well: true,
|
||||
listTitle: i18n._('Jobs'),
|
||||
listTitle: i18n._('JOBS'),
|
||||
emptyListText: i18n._('There are no jobs to display at this time'),
|
||||
|
||||
fields: {
|
||||
|
@ -13,8 +13,8 @@ export default
|
||||
iterator: 'project',
|
||||
basePath: 'projects',
|
||||
selectTitle: i18n._('Add Project'),
|
||||
editTitle: i18n._('Projects'),
|
||||
listTitle: i18n._('Projects'),
|
||||
editTitle: i18n._('PROJECTS'),
|
||||
listTitle: i18n._('PROJECTS'),
|
||||
selectInstructions: '<p>Select existing projects by clicking each project or checking the related checkbox. When finished, click the blue ' +
|
||||
'<em>Select</em> button, located bottom right.</p><p>Create a new project by clicking the <i class=\"fa fa-plus\"></i> button.</p>',
|
||||
index: false,
|
||||
|
@ -12,7 +12,7 @@ export default
|
||||
|
||||
name: 'schedules',
|
||||
iterator: 'schedule',
|
||||
editTitle: i18n._('Scheduled Jobs'),
|
||||
editTitle: i18n._('SCHEDULED JOBS'),
|
||||
hover: true,
|
||||
well: false,
|
||||
emptyListText: i18n._('No schedules exist'),
|
||||
|
@ -13,8 +13,8 @@ export default
|
||||
name: 'schedules',
|
||||
iterator: 'schedule',
|
||||
selectTitle: '',
|
||||
editTitle: 'Schedules',
|
||||
listTitle: '{{parentObject}} || Schedules',
|
||||
editTitle: 'SCHEDULES',
|
||||
listTitle: '{{parentObject}} || SCHEDULES',
|
||||
index: false,
|
||||
hover: true,
|
||||
|
||||
|
@ -13,8 +13,8 @@ export default
|
||||
name: 'activities',
|
||||
iterator: 'activity',
|
||||
basePath: 'activity_stream',
|
||||
editTitle: i18n._('Activity Stream'),
|
||||
listTitle: i18n._('Activity Stream') + '<span ng-show="streamSubTitle"><div class="List-titleLockup"></div>{{streamSubTitle}}<span>',
|
||||
editTitle: i18n._('ACTIVITY STREAM'),
|
||||
listTitle: i18n._('ACTIVITY STREAM') + '<span ng-show="streamSubTitle"><div class="List-titleLockup"></div>{{streamSubTitle}}<span>',
|
||||
listTitleBadge: false,
|
||||
emptyListText: i18n._('There are no events to display at this time'),
|
||||
selectInstructions: '',
|
||||
|
@ -13,8 +13,8 @@ export default
|
||||
name: 'teams',
|
||||
iterator: 'team',
|
||||
selectTitle: i18n._('Add Team'),
|
||||
editTitle: i18n._('Teams'),
|
||||
listTitle: i18n._('Teams'),
|
||||
editTitle: i18n._('TEAMS'),
|
||||
listTitle: i18n._('TEAMS'),
|
||||
selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Click the %s button to create a new team."), "<i class=\"icon-plus\"></i> "),
|
||||
index: false,
|
||||
hover: true,
|
||||
|
@ -14,8 +14,8 @@ export default
|
||||
iterator: 'template',
|
||||
basePath: 'unified_job_templates',
|
||||
selectTitle: i18n._('Template'),
|
||||
editTitle: i18n._('Templates'),
|
||||
listTitle: i18n._('Templates'),
|
||||
editTitle: i18n._('TEMPLATES'),
|
||||
listTitle: i18n._('TEMPLATES'),
|
||||
selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Use the %s button to create a new job template."), "<i class=\"icon-plus\"></i> "),
|
||||
index: false,
|
||||
hover: true,
|
||||
|
@ -17,8 +17,8 @@ export default
|
||||
},
|
||||
iterator: 'user',
|
||||
selectTitle: i18n._('Add Users'),
|
||||
editTitle: i18n._('Users'),
|
||||
listTitle: i18n._('Users'),
|
||||
editTitle: i18n._('USERS'),
|
||||
listTitle: i18n._('USERS'),
|
||||
selectInstructions: '<p>Select existing users by clicking each user or checking the related checkbox. When finished, click the blue ' +
|
||||
'<em>Select</em> button, located bottom right.</p> <p>When available, a brand new user can be created by clicking the ' +
|
||||
'<i class=\"fa fa-plus\"></i> button.</p>',
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="tab-pane Panel" id="management_jobs">
|
||||
<div class="List-title">
|
||||
<div class="List-titleText" translate>
|
||||
Management Jobs
|
||||
MANAGEMENT JOBS
|
||||
</div>
|
||||
<span class="badge List-titleBadge ng-binding">
|
||||
{{ mgmtCards.length }}
|
||||
|
@ -39,7 +39,6 @@
|
||||
|
||||
.MgmtCards-label {
|
||||
margin-top: 0px;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: @default-interface-txt;
|
||||
|
@ -10,7 +10,7 @@ export default function(){
|
||||
iterator: 'configure_job',
|
||||
index: false,
|
||||
hover: true,
|
||||
listTitle: 'Management Jobs',
|
||||
listTitle: 'MANAGEMENT JOBS',
|
||||
|
||||
fields: {
|
||||
name: {
|
||||
|
@ -29,7 +29,7 @@ angular.module('managementJobScheduler', [])
|
||||
'@': {
|
||||
templateProvider: function(ScheduleList, generateList, ParentObject) {
|
||||
// include name of parent resource in listTitle
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('SCHEDULES');
|
||||
let html = generateList.build({
|
||||
list: ScheduleList,
|
||||
mode: 'edit'
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div id="htmlTemplate" class=" SchedulerFormPanel Panel" ng-hide="hideForm">
|
||||
<div class="Form-header">
|
||||
<div class="Form-title" ng-show="!isEdit">{{ schedulerName || "Add Schedule"}}</div>
|
||||
<div class="Form-title" ng-show="isEdit">{{ schedulerName || "Edit Schedule"}}</div>
|
||||
<div class="Form-title" ng-show="!isEdit">{{ schedulerName || "ADD SCHEDULE"}}</div>
|
||||
<div class="Form-title" ng-show="isEdit">{{ schedulerName || "EDIT SCHEDULE"}}</div>
|
||||
<div class="Form-header--fields"></div>
|
||||
<div class="Form-exitHolder">
|
||||
<button class="Form-exit" ng-click="formCancel()">
|
||||
|
@ -13,7 +13,7 @@
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('New Notification Template'),
|
||||
addTitle: i18n._('NEW NOTIFICATION TEMPLATE'),
|
||||
editTitle: '{{ name }}',
|
||||
name: 'notification_template',
|
||||
// I18N for "CREATE NOTIFICATION_TEMPLATE"
|
||||
@ -70,20 +70,20 @@ export default ['i18n', function(i18n) {
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
|
||||
host: {
|
||||
label: i18n._('Host'),
|
||||
type: 'text',
|
||||
password: {
|
||||
labelBind: 'passwordLabel',
|
||||
type: 'sensitive',
|
||||
hasShowInputButton: true,
|
||||
awRequiredWhen: {
|
||||
reqExpression: "email_required",
|
||||
reqExpression: "password_required" ,
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'email' ",
|
||||
ngShow: "notification_type.value == 'email' || notification_type.value == 'irc' ",
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
sender: {
|
||||
label: i18n._('Sender Email'),
|
||||
host: {
|
||||
label: i18n._('Host'),
|
||||
type: 'text',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "email_required",
|
||||
@ -110,15 +110,14 @@ export default ['i18n', function(i18n) {
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
password: {
|
||||
labelBind: 'passwordLabel',
|
||||
type: 'sensitive',
|
||||
hasShowInputButton: true,
|
||||
sender: {
|
||||
label: i18n._('Sender Email'),
|
||||
type: 'text',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "password_required" ,
|
||||
reqExpression: "email_required",
|
||||
init: "false"
|
||||
},
|
||||
ngShow: "notification_type.value == 'email' || notification_type.value == 'irc' ",
|
||||
ngShow: "notification_type.value == 'email' ",
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
@ -269,9 +268,10 @@ export default ['i18n', function(i18n) {
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
message_from: {
|
||||
label: i18n._('Label to be shown with notification'),
|
||||
api_url: {
|
||||
label: 'API URL',
|
||||
type: 'text',
|
||||
placeholder: 'https://mycompany.hipchat.com',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "hipchat_required",
|
||||
init: "false"
|
||||
@ -280,10 +280,9 @@ export default ['i18n', function(i18n) {
|
||||
subForm: 'typeSubForm',
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
api_url: {
|
||||
label: 'API URL',
|
||||
message_from: {
|
||||
label: i18n._('Notification Label'),
|
||||
type: 'text',
|
||||
placeholder: 'https://mycompany.hipchat.com',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "hipchat_required",
|
||||
init: "false"
|
||||
|
@ -11,7 +11,7 @@
|
||||
export default ['i18n', function(i18n){
|
||||
return {
|
||||
name: 'notification_templates' ,
|
||||
listTitle: i18n._('Notification Templates'),
|
||||
listTitle: i18n._('NOTIFICATION TEMPLATES'),
|
||||
iterator: 'notification_template',
|
||||
index: false,
|
||||
hover: false,
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="AddUsers-header">
|
||||
<div class="List-header">
|
||||
<div class="List-title">
|
||||
<div class="List-titleText ng-binding">{{ $parent.organization_name }}<div class="List-titleLockup"></div><span translate>Add</span> {{ addType | capitalize}}
|
||||
<div class="List-titleText ng-binding">{{ $parent.organization_name }}<div class="List-titleLockup"></div><span translate>ADD</span> {{ addType | capitalize}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Form-exitHolder">
|
||||
|
@ -58,8 +58,8 @@
|
||||
|
||||
.OrgCards-label {
|
||||
margin-top: 0px;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
height: 17px;
|
||||
font-weight: bold;
|
||||
color: @default-interface-txt;
|
||||
margin-bottom: 25px;
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="List-header">
|
||||
<div class="List-title">
|
||||
<div class="List-titleText">
|
||||
organizations
|
||||
ORGANIZATIONS
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ orgCount }}
|
||||
@ -59,4 +59,4 @@
|
||||
</div>
|
||||
<div id="pagination-container" ng-hide="organization_num_pages < 2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -94,7 +94,7 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams',
|
||||
|
||||
$scope.reloadList = function(){
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
qs.search(path, $stateParams[`${list.iterator}_search`])
|
||||
qs.search(path, $state.params[`${list.iterator}_search`])
|
||||
.then(function(searchResponse) {
|
||||
$scope[`${list.iterator}_dataset`] = searchResponse.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
|
@ -83,7 +83,7 @@ export default
|
||||
'@': {
|
||||
templateProvider: function(ScheduleList, generateList, ParentObject){
|
||||
// include name of parent resource in listTitle
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('SCHEDULES');
|
||||
let html = generateList.build({
|
||||
list: ScheduleList,
|
||||
mode: 'edit'
|
||||
@ -176,7 +176,7 @@ export default
|
||||
'@': {
|
||||
templateProvider: function(ScheduleList, generateList, ParentObject){
|
||||
// include name of parent resource in listTitle
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('SCHEDULES');
|
||||
let html = generateList.build({
|
||||
list: ScheduleList,
|
||||
mode: 'edit'
|
||||
@ -266,7 +266,7 @@ export default
|
||||
'@': {
|
||||
templateProvider: function(ScheduleList, generateList, ParentObject){
|
||||
// include name of parent resource in listTitle
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('Schedules');
|
||||
ScheduleList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>` + N_('SCHEDULES');
|
||||
let html = generateList.build({
|
||||
list: ScheduleList,
|
||||
mode: 'edit'
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div id="htmlTemplate" class=" SchedulerFormPanel Panel" ng-hide="hideForm">
|
||||
<div class="Form-header">
|
||||
<div class="Form-title" ng-show="!isEdit">{{ schedulerName || "Add Schedule"}}</div>
|
||||
<div class="Form-title" ng-show="isEdit">{{ schedulerName || "Edit Schedule"}}</div>
|
||||
<div class="Form-title" ng-show="!isEdit">{{ schedulerName || "ADD SCHEDULE"}}</div>
|
||||
<div class="Form-title" ng-show="isEdit">{{ schedulerName || "EDIT SCHEDULE"}}</div>
|
||||
<div class="Form-header--fields"></div>
|
||||
<div class="Form-exitHolder">
|
||||
<button class="Form-exit" ng-click="formCancel()">
|
||||
|
@ -110,7 +110,12 @@ export default [
|
||||
};
|
||||
|
||||
$scope.addSchedule = function() {
|
||||
$state.go('.add');
|
||||
if($state.current.name.endsWith('.edit')) {
|
||||
$state.go('^.add');
|
||||
}
|
||||
else if(!$state.current.name.endsWith('.add')) {
|
||||
$state.go('.add');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.editSchedule = function(schedule) {
|
||||
@ -118,7 +123,15 @@ export default [
|
||||
routeToScheduleForm(schedule, 'edit');
|
||||
}
|
||||
else {
|
||||
$state.go('.edit', { schedule_id: schedule.id });
|
||||
if($state.current.name.endsWith('.add')) {
|
||||
$state.go('^.edit', { schedule_id: schedule.id });
|
||||
}
|
||||
else if($state.current.name.endsWith('.edit')) {
|
||||
$state.go('.', { schedule_id: schedule.id });
|
||||
}
|
||||
else {
|
||||
$state.go('.edit', { schedule_id: schedule.id });
|
||||
}
|
||||
}
|
||||
|
||||
function buildStateMap(schedule){
|
||||
|
@ -662,6 +662,20 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
|
||||
|
||||
$(element).select2(config);
|
||||
|
||||
// Don't toggle the dropdown when a multiselect option is
|
||||
// being removed
|
||||
if (multiple) {
|
||||
$(element).on('select2:opening', (e) => {
|
||||
var unselecting = $(e.target).data('select2-unselecting');
|
||||
if (unselecting === true) {
|
||||
$(e.target).removeData('select2-unselecting');
|
||||
e.preventDefault();
|
||||
}
|
||||
}).on('select2:unselecting', (e) => {
|
||||
$(e.target).data('select2-unselecting', true);
|
||||
});
|
||||
}
|
||||
|
||||
if (options) {
|
||||
for (var d = 0; d < $(element + " option").length; d++) {
|
||||
var item = $(element + " option")[d];
|
||||
|
@ -169,6 +169,15 @@ function(ConfigurationUtils, i18n, $rootScope) {
|
||||
scope.imageData = $rootScope.custom_logo;
|
||||
});
|
||||
|
||||
scope.$watch('imagePresent', (val) => {
|
||||
if(val){
|
||||
filePickerButton.html(removeText);
|
||||
}
|
||||
else{
|
||||
filePickerButton.html(browseText);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on(fieldKey+'_reverted', function(e) {
|
||||
scope.update(e, true);
|
||||
});
|
||||
|
@ -52,6 +52,5 @@
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
}
|
||||
|
@ -48,7 +48,6 @@
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header Form-header">
|
||||
<div class="Form-title">Select {{list.iterator}}</div>
|
||||
<div class="Form-title Form-title--uppercase">Select {{list.iterator}}</div>
|
||||
<!-- optional: transclude header fields -->
|
||||
<div class="Form-header--fields"></div>
|
||||
<div class="Form-exitHolder">
|
||||
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-click="cancelForm()" class="Lookup-cancel btn btn-default">Cancel</button>
|
||||
<button ng-click="saveForm()" class="Lookup-save btn btn-primary" ng-disabled="!currentSelection || !currentSelection.id" ng-bind="list.lookupConfirmText ? list.lookupConfirmText : 'Save'"></button>
|
||||
<button ng-click="saveForm()" class="Lookup-save btn btn-primary" ng-disabled="!currentSelection || !currentSelection.id" ng-bind="list.lookupConfirmText ? list.lookupConfirmText : 'Select'"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -260,9 +260,15 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
|
||||
}.bind(this));
|
||||
},
|
||||
error(data, status) {
|
||||
ProcessErrors($rootScope, null, status, null, {
|
||||
if(data && data.detail){
|
||||
let error = JSON.parse(data.detail);
|
||||
if(_.isArray(error)){
|
||||
data.detail = error[0];
|
||||
}
|
||||
}
|
||||
ProcessErrors($rootScope, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: "Invalid search term entered."
|
||||
msg: `Invalid search term entered. GET returned: ${status}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<select class="form-control SurveyMaker-previewSelect" ng-model="selectedValue" multi-select ng-required="isRequired" ng-disabled="isDisabled">
|
||||
<option ng-repeat="choice in choices" value="{{choice}}" ng-selected="selectedValue.indexOf(choice) !== -1">{{choice}}</option>
|
||||
<option ng-repeat="choice in choices" value="{{choice}}">{{choice}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -13,8 +13,8 @@
|
||||
export default
|
||||
{
|
||||
|
||||
addTitle: 'Add Survey Prompt',
|
||||
editTitle: 'Edit Survey Prompt',
|
||||
addTitle: 'ADD SURVEY PROMPT',
|
||||
editTitle: 'EDIT SURVEY PROMPT',
|
||||
titleClass: 'Form-secondaryTitle',
|
||||
base: 'survey_question',
|
||||
name: 'survey_question',
|
||||
|
@ -10,19 +10,21 @@
|
||||
}
|
||||
.SurveyMaker-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.SurveyMaker-title {
|
||||
align-items: center;
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
max-width: 98%;
|
||||
}
|
||||
.SurveyMaker-titleText {
|
||||
color: @list-title-txt;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
max-width: 75%;
|
||||
}
|
||||
.SurveyMaker-titleLockup {
|
||||
margin-left: 4px;
|
||||
|
@ -114,3 +114,23 @@
|
||||
padding: 1px 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.WorkflowChart-nameText {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.WorkflowChart-tooltipContents {
|
||||
padding: 10px;
|
||||
background-color: #707070;
|
||||
color: #FFFFFF;
|
||||
border-radius: 4px;
|
||||
word-wrap: break-word;
|
||||
max-width: 325px;
|
||||
}
|
||||
.WorkflowChart-tooltipArrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid #707070;
|
||||
margin: auto;
|
||||
}
|
||||
|
@ -23,12 +23,13 @@ export default [ '$state','moment', '$timeout', '$window',
|
||||
|
||||
let margin = {top: 20, right: 20, bottom: 20, left: 20},
|
||||
i = 0,
|
||||
nodeW = 120,
|
||||
nodeW = 180,
|
||||
nodeH = 60,
|
||||
rootW = 60,
|
||||
rootH = 40,
|
||||
startNodeOffsetY = scope.mode === 'details' ? 17 : 10,
|
||||
verticalSpaceBetweenNodes = 20,
|
||||
maxNodeTextLength = 27,
|
||||
windowHeight,
|
||||
windowWidth,
|
||||
tree,
|
||||
@ -125,11 +126,8 @@ export default [ '$state','moment', '$timeout', '$window',
|
||||
// TODO: this function is hacky and we need to come up with a better solution
|
||||
// see: http://stackoverflow.com/questions/15975440/add-ellipses-to-overflowing-text-in-svg#answer-27723752
|
||||
function wrap(text) {
|
||||
|
||||
let maxLength = scope.mode === 'details' ? 14 : 15;
|
||||
|
||||
if(text && text.length > maxLength) {
|
||||
return text.substring(0,maxLength) + '...';
|
||||
if(text && text.length > maxNodeTextLength) {
|
||||
return text.substring(0,maxNodeTextLength) + '...';
|
||||
}
|
||||
else {
|
||||
return text;
|
||||
@ -216,7 +214,7 @@ export default [ '$state','moment', '$timeout', '$window',
|
||||
links = tree.links(nodes);
|
||||
let node = svgGroup.selectAll("g.node")
|
||||
.data(nodes, function(d) {
|
||||
d.y = d.depth * 180;
|
||||
d.y = d.depth * 240;
|
||||
return d.id || (d.id = ++i);
|
||||
});
|
||||
|
||||
@ -347,11 +345,32 @@ export default [ '$state','moment', '$timeout', '$window',
|
||||
.call(edit_node)
|
||||
.on("mouseover", function(d) {
|
||||
if(!d.isStartNode) {
|
||||
let resourceName = (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : "";
|
||||
if(resourceName && resourceName.length > maxNodeTextLength) {
|
||||
// Render the tooltip quickly in the dom and then remove. This lets us know how big the tooltip is so that we can place
|
||||
// it properly on the workflow
|
||||
let tooltipDimensionChecker = $("<div style='visibility:hidden;font-size:12px;position:absolute;' class='WorkflowChart-tooltipContents'><span>" + resourceName + "</span></div>");
|
||||
$('body').append(tooltipDimensionChecker);
|
||||
let tipWidth = $(tooltipDimensionChecker).outerWidth();
|
||||
let tipHeight = $(tooltipDimensionChecker).outerHeight();
|
||||
$(tooltipDimensionChecker).remove();
|
||||
|
||||
thisNode.append("foreignObject")
|
||||
.attr("x", (nodeW / 2) - (tipWidth / 2))
|
||||
.attr("y", (tipHeight + 15) * -1)
|
||||
.attr("width", tipWidth)
|
||||
.attr("height", tipHeight+20)
|
||||
.attr("class", "WorkflowChart-tooltip")
|
||||
.html(function(){
|
||||
return "<div class='WorkflowChart-tooltipContents'><span>" + resourceName + "</span></div><div class='WorkflowChart-tooltipArrow'></div>";
|
||||
});
|
||||
}
|
||||
d3.select("#node-" + d.id)
|
||||
.classed("hovering", true);
|
||||
}
|
||||
})
|
||||
.on("mouseout", function(d){
|
||||
$('.WorkflowChart-tooltip').remove();
|
||||
if(!d.isStartNode) {
|
||||
d3.select("#node-" + d.id)
|
||||
.classed("hovering", false);
|
||||
|
@ -15,7 +15,6 @@
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.WorkflowMaker-exitHolder {
|
||||
justify-content: flex-end;
|
||||
|
@ -35,6 +35,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: @breakpoint-md) {
|
||||
.WorkflowResults-rightSide {
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.WorkflowResults-stdoutActionButton--active {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
@ -102,6 +108,24 @@
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.WorkflowResults-panelRightTitle{
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.WorkflowResults-panelRightTitleText{
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.WorkflowResults-badgeAndActionRow{
|
||||
display:flex;
|
||||
flex: 1 0 auto;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.WorkflowResults-badgeRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -213,8 +213,8 @@
|
||||
<div class="Panel">
|
||||
|
||||
<!-- RIGHT PANE HEADER -->
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText">
|
||||
<div class="StandardOut-panelHeader WorkflowResults-panelRightTitle">
|
||||
<div class="StandardOut-panelHeaderText WorkflowResults-panelRightTitleText">
|
||||
<i class="WorkflowResults-statusResultIcon
|
||||
fa icon-job-{{ workflow.status }}"
|
||||
ng-show="stdoutFullScreen"
|
||||
@ -224,39 +224,40 @@
|
||||
</i>
|
||||
{{ workflow.name }}
|
||||
</div>
|
||||
<div class="WorkflowResults-badgeAndActionRow">
|
||||
<!-- HEADER COUNTS -->
|
||||
<div class="WorkflowResults-badgeRow">
|
||||
<!-- PLAYS COUNT -->
|
||||
<div class="WorkflowResults-badgeTitle">
|
||||
Total Jobs
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ workflow_nodes.length || 0}}
|
||||
</span>
|
||||
|
||||
<!-- HEADER COUNTS -->
|
||||
<div class="WorkflowResults-badgeRow">
|
||||
<!-- PLAYS COUNT -->
|
||||
<div class="WorkflowResults-badgeTitle">
|
||||
Total Jobs
|
||||
<!-- ELAPSED TIME -->
|
||||
<div class="WorkflowResults-badgeTitle">
|
||||
Elapsed
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ workflow.elapsed * 1000 | duration: "hh:mm:ss"}}
|
||||
</span>
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ workflow_nodes.length || 0}}
|
||||
</span>
|
||||
|
||||
<!-- ELAPSED TIME -->
|
||||
<div class="WorkflowResults-badgeTitle">
|
||||
Elapsed
|
||||
<!-- HEADER ACTIONS -->
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
|
||||
<!-- FULL-SCREEN TOGGLE ACTION -->
|
||||
<button class="StandardOut-actionButton"
|
||||
aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}"
|
||||
data-tip-watch="toggleStdoutFullscreenTooltip"
|
||||
data-placement="top"
|
||||
ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"
|
||||
ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ workflow.elapsed * 1000 | duration: "hh:mm:ss"}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- HEADER ACTIONS -->
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
|
||||
<!-- FULL-SCREEN TOGGLE ACTION -->
|
||||
<button class="StandardOut-actionButton"
|
||||
aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}"
|
||||
data-tip-watch="toggleStdoutFullscreenTooltip"
|
||||
data-placement="top"
|
||||
ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"
|
||||
ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<workflow-status-bar></workflow-status-bar>
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: '2'
|
||||
version: '3'
|
||||
services:
|
||||
# Primary Tower Development Container
|
||||
tower:
|
||||
@ -20,13 +20,17 @@ services:
|
||||
- postgres
|
||||
- memcached
|
||||
- rabbitmq
|
||||
- logstash
|
||||
# - sync
|
||||
# volumes_from:
|
||||
# - sync
|
||||
volumes:
|
||||
- "../:/tower_devel"
|
||||
privileged: true
|
||||
|
||||
logstash:
|
||||
build:
|
||||
context: ./docker-compose
|
||||
dockerfile: Dockerfile-logstash
|
||||
# Postgres Database Container
|
||||
postgres:
|
||||
image: postgres:9.4.1
|
||||
@ -38,16 +42,3 @@ services:
|
||||
image: rabbitmq:3-management
|
||||
ports:
|
||||
- "15672:15672"
|
||||
|
||||
# Source Code Synchronization Container
|
||||
# sync:
|
||||
# build:
|
||||
# context: ./docker-compose
|
||||
# dockerfile: Dockerfile-sync
|
||||
# command: "lsyncd -delay 1 -nodaemon -rsync /src /tower_devel"
|
||||
# volumes:
|
||||
# - /tower_devel
|
||||
# - "../:/src"
|
||||
# working_dir: /src
|
||||
# stdin_open: true
|
||||
# tty: true
|
||||
|
3
tools/docker-compose/Dockerfile-logstash
Normal file
3
tools/docker-compose/Dockerfile-logstash
Normal file
@ -0,0 +1,3 @@
|
||||
FROM logstash:5-alpine
|
||||
COPY logstash.conf /
|
||||
CMD ["-f", "/logstash.conf"]
|
19
tools/docker-compose/logstash.conf
Normal file
19
tools/docker-compose/logstash.conf
Normal file
@ -0,0 +1,19 @@
|
||||
input {
|
||||
http {
|
||||
port => 8085
|
||||
user => awx_logger
|
||||
password => "workflows"
|
||||
}
|
||||
}
|
||||
|
||||
## Add your filters / logstash plugins configuration here
|
||||
|
||||
filter {
|
||||
json {
|
||||
source => "message"
|
||||
}
|
||||
}
|
||||
|
||||
output {
|
||||
stdout { codec => rubydebug }
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
FROM logstash:5
|
||||
|
||||
# Add your logstash plugins setup here
|
||||
# Example: RUN logstash-plugin install logstash-filter-json
|
||||
# Example: RUN logstash-plugin install logstash-filter-json
|
||||
|
Loading…
Reference in New Issue
Block a user