Latest job detail page changes. Fixed lookup modal horizontal scroll.

This commit is contained in:
Chris Houseknecht
2014-04-14 00:32:34 -04:00
parent 06cf3a2aec
commit b6dc4047f7
5 changed files with 408 additions and 159 deletions

View File

@@ -23,6 +23,8 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc
$scope.plays = []; $scope.plays = [];
$scope.tasks = []; $scope.tasks = [];
$scope.hosts = [];
$scope.hostResults = [];
// Apply each event to the view // Apply each event to the view
if ($scope.removeEventsReady) { if ($scope.removeEventsReady) {

View File

@@ -39,7 +39,8 @@
angular.module('JobDetailHelper', ['Utilities', 'RestServices']) angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', function(UpdatePlayStatus, UpdatePlayNoHostsMatched) { .factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult',
function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
events = params.events; events = params.events;
@@ -48,8 +49,22 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
scope.plays.push({ scope.plays.push({
id: event.id, id: event.id,
name: event.play, name: event.play,
status: (event.failed) ? 'failed' : 'successful',
children: []
});
}
if (event.event === 'playbook_on_setup') {
scope.tasks.push({
id: event.id,
name: event.event_display,
play_id: event.parent,
status: (event.failed) ? 'failed' : 'successful' status: (event.failed) ? 'failed' : 'successful'
}); });
UpdatePlayStatus({
scope: scope,
play_id: event.parent,
failed: event.failed
});
} }
if (event.event === 'playbook_on_task_start') { if (event.event === 'playbook_on_task_start') {
scope.tasks.push({ scope.tasks.push({
@@ -58,18 +73,68 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
play_id: event.parent, play_id: event.parent,
status: (event.failed) ? 'failed' : 'successful' status: (event.failed) ? 'failed' : 'successful'
}); });
UpdatePlayStatus({ scope: scope, play_id: event.parent, status: event.status }); UpdatePlayStatus({
scope: scope,
play_id: event.parent,
failed: event.failed
});
} }
if (event.event === 'playbook_on_no_hosts_matched') { if (event.event === 'playbook_on_no_hosts_matched') {
UpdatePlayNoHostsMatched({ scope: scope, play_id: event.parent }); UpdatePlayNoHostsMatched({ scope: scope, play_id: event.parent });
} }
if (event.event === 'runner_on_failed') { if (event.event === 'runner_on_failed') {
}
if (event.event === 'runner_on_ok') {
UpdateHostStatus({
scope: scope,
name: event.event_data.host,
host_id: event.host_id,
task_id: event.parent,
status: (event.changed) ? 'changed' : 'ok',
results: (event.res && event.res.results) ? event.res.results : ''
});
AddHostResult({
scope: scope,
event: event
});
} }
if (event.event === 'playbook_on_stats') { if (event.event === 'playbook_on_stats') {
} }
});
};
}])
.factory('UpdatePlayChild', [ function() {
return function(params) {
var scope = params.scope,
id = params.id,
play_id = params.play_id,
failed = params.failed,
name = params.name,
found_child = false;
scope.plays.every(function(play, i) {
if (play.id === play_id) {
scope.plays[i].children.every(function(child, j) {
if (child.id === id) {
scope.plays[i].children[j].status = (failed) ? 'failed' : 'successful';
found_child = true;
return false;
}
return true;
});
if (!found_child) {
scope.plays[i].children.push({
id: id,
name: name,
status: (failed) ? 'failed' : 'successful'
});
}
scope.plays[i].status = (failed) ? 'failed' : 'successful';
return false;
}
return true;
}); });
}; };
}]) }])
@@ -78,11 +143,11 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
.factory('UpdatePlayStatus', [ function() { .factory('UpdatePlayStatus', [ function() {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
status = params.status, failed = params.failed,
id = params.play_id; id = params.play_id;
scope.plays.every(function(play,idx) { scope.plays.every(function(play,idx) {
if (play.id === id) { if (play.id === id) {
scope.plays[idx].status = (status) ? 'failed' : 'successful'; scope.plays[idx].status = (failed) ? 'failed' : 'successful';
return false; return false;
} }
return true; return true;
@@ -90,6 +155,19 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
}; };
}]) }])
.factory('UpdateTaskStatus', [ function() {
return function(params) {
var scope = params.scope,
task_id = params.task_id,
failed = params.failed;
scope.tasks.every(function (task, i) {
if (task.id === task_id) {
scope.tasks[i].status = (failed) ? 'failed' : 'successful';
}
});
};
}])
.factory('UpdatePlayNoHostsMatched', [ function() { .factory('UpdatePlayNoHostsMatched', [ function() {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
@@ -102,6 +180,90 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices'])
return true; return true;
}); });
}; };
}])
// Update or add a new host
.factory('UpdateHostStatus', ['UpdateTaskStatus', function(UpdateTaskStatus) {
return function(params) {
var scope = params.scope,
status = params.status, // ok, changed, unreachable, failed
name = params.name,
host_id = params.host_id,
task_id = params.task_id,
host_found = false;
scope.hosts.every(function(host, i) {
if (host.id === host_id) {
scope.hosts[i].ok += (status === 'ok' || status === 'changed') ? 1 : 0;
scope.hosts[i].changed += (status === 'changed') ? 1 : 0;
scope.hosts[i].unreachable += (status === 'unreachable') ? 1 : 0;
scope.hosts[i].failed += (status === 'failed') ? 1 : 0;
host_found = true;
return false;
}
return true;
});
if (!host_found) {
scope.hosts.push({
id: host_id,
name: name,
ok: (status === 'ok' || status === 'changed') ? 1 : 0,
changed: (status === 'changed') ? 1 : 0,
unreachable: (status === 'unreachable') ? 1 : 0,
failed: (status === 'failed') ? 1 : 0
});
}
// Mark task failed
if (status === 'failed') {
UpdateTaskStatus({
scope: scope,
task_id: task_id,
failed: true
});
}
};
}])
// Add a new host result
.factory('AddHostResult', ['Empty', function(Empty) {
return function(params) {
var scope = params.scope,
event = params.event,
id, status, host_id, play_name, task_name, module_name, module_args,
results, rc;
id = event.id;
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
host_id = event.host;
play_name = event.play;
task_name = event.task;
if (event.event_data.res && event.event_data.res.invocation) {
module_name = event.event_data.res.invocation.module_name;
module_args = event.event_data.res.invocation.module_args;
}
else {
module_name = '';
module_args = '';
}
if (event.event_data.res && event.event_data.res.results) {
results = '';
event.event_data.res.results.forEach(function(row) {
results += row;
});
}
rc = (event.event_data.res && !Empty(event.event_data.res.rc)) ? event.event_data.res.rc : '';
scope.hostResults.push({
id: id,
status: status,
host_id: host_id,
play_name: play_name,
task_name : task_name,
module_name: module_name,
module_args: module_args,
results: results,
rc: rc
});
};
}]); }]);

View File

@@ -1,8 +1,8 @@
/********************************************* /*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc. * Copyright (c) 2014 AnsibleWorks, Inc.
* *
* ansible-ui.css * ansible-ui.css
* *
* custom styles for ansible-ui * custom styles for ansible-ui
* *
*/ */
@@ -120,7 +120,7 @@ a:focus {
} }
.btn-disabled { .btn-disabled {
cursor: not-allowed; cursor: not-allowed;
} }
/* Bring primary (blue) buttons in line with link colors */ /* Bring primary (blue) buttons in line with link colors */
@@ -151,12 +151,12 @@ a:focus {
#home_groups_table .actions .cancel { padding-right: 3px; } #home_groups_table .actions .cancel { padding-right: 3px; }
.success-badge { .success-badge {
color: #ffffff; color: #ffffff;
background-color: #5cb85c; background-color: #5cb85c;
} }
.bold-text .checkbox-inline { .bold-text .checkbox-inline {
font-weight: bold; font-weight: bold;
} }
@@ -176,9 +176,9 @@ textarea.allowresize {
z-index: 2000; z-index: 2000;
width: 75px; width: 75px;
height: 75px; height: 75px;
text-align:center; text-align:center;
color: #eee; color: #eee;
background-color: @black; background-color: @black;
border: 1px solid @grey; border: 1px solid @grey;
border-radius: 6px; border-radius: 6px;
padding-top: 10px; padding-top: 10px;
@@ -199,7 +199,7 @@ textarea.allowresize {
} }
.license-version { .license-version {
font-size: 18px; font-size: 18px;
color: @grey-txt; color: @grey-txt;
} }
@@ -212,9 +212,9 @@ textarea.allowresize {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
z-index: 1999; z-index: 1999;
background-color: @black; background-color: @black;
opacity: 0; opacity: 0;
} }
/* TB tooltip overrides */ /* TB tooltip overrides */
@@ -225,7 +225,7 @@ textarea.allowresize {
font-size: 12px; font-size: 12px;
} }
.flyout thead> tr> th, .flyout tbody> tr> td, .flyout tbody> tr> td> a { .flyout thead> tr> th, .flyout tbody> tr> td, .flyout tbody> tr> td> a {
font-size: 12px; font-size: 12px;
} }
.popover-title { .popover-title {
padding-top: 5px; padding-top: 5px;
@@ -272,7 +272,7 @@ hr {
.help-auto-off { .help-auto-off {
margin-top: 15px; margin-top: 15px;
margin-bottom: 15px; margin-bottom: 15px;
margin-left: 10px; margin-left: 10px;
label { label {
font-weight: normal; font-weight: normal;
} }
@@ -284,7 +284,7 @@ hr {
} }
.panel-heading:hover { .panel-heading:hover {
cursor: pointer; cursor: pointer;
} }
.panel-default>.panel-heading .collapse-help-icon { .panel-default>.panel-heading .collapse-help-icon {
@@ -296,10 +296,10 @@ hr {
margin-bottom: 20px; margin-bottom: 20px;
dl { dl {
margin-left: 15px; margin-left: 15px;
} }
dt { dt {
margin-top: 15px; margin-top: 15px;
} }
} }
th.actions-column, th.actions-column,
@@ -341,7 +341,7 @@ td.actions {
} }
.ssh-key-field, .mono-space { .ssh-key-field, .mono-space {
font-family: Fixed, monospace; font-family: Fixed, monospace;
} }
dd { dd {
@@ -423,7 +423,7 @@ dd {
} }
.navbar-collapse { .navbar-collapse {
padding-right: 0; padding-right: 0;
} }
.main-menu .nav >li >a:last-child { .main-menu .nav >li >a:last-child {
@@ -437,7 +437,7 @@ dd {
margin-top: 11px; margin-top: 11px;
} }
/* Using inline-block rather than block keeps /* Using inline-block rather than block keeps
brand img from right aligning into the collapse button brand img from right aligning into the collapse button
on mobile screens */ on mobile screens */
.main-menu .navbar-brand { .main-menu .navbar-brand {
@@ -480,7 +480,7 @@ dd {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
font-size: 12px; font-size: 12px;
a, a,
a:active, a:active,
a:visited { a:visited {
@@ -501,7 +501,7 @@ dd {
} }
.logo img { .logo img {
max-width: 46px; max-width: 46px;
} }
.copyright { .copyright {
@@ -526,7 +526,7 @@ dd {
width: 327px; width: 327px;
} }
.form-title { .form-title {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
vertical-align: middle; vertical-align: middle;
@@ -536,7 +536,7 @@ dd {
} }
.form-group { .form-group {
margin-bottom: 25px; margin-bottom: 25px;
} }
.form-cancel { .form-cancel {
@@ -549,7 +549,7 @@ dd {
} }
.form-horizontal .buttons { .form-horizontal .buttons {
margin-top: 25px; margin-top: 25px;
} }
.label-text { .label-text {
@@ -615,7 +615,7 @@ legend {
} }
/* Pagination */ /* Pagination */
.page-label { .page-label {
font-size: 12px; font-size: 12px;
margin-top: 0; margin-top: 0;
text-align: right; text-align: right;
@@ -630,7 +630,7 @@ legend {
padding: 3px 6px; padding: 3px 6px;
} }
.modal-body { .modal-body {
.pagination { .pagination {
margin-top: 15px; margin-top: 15px;
margin-bottom: 0; margin-bottom: 0;
@@ -658,9 +658,12 @@ legend {
} }
#lookup-modal-dialog .instructions { #lookup-modal-dialog
margin-top: 0; overflow-x: hidden;
margin-bottom: 20px; .instructions {
margin-top: 0;
margin-bottom: 20px;
}
} }
.related-footer { .related-footer {
@@ -701,27 +704,27 @@ select.page-size {
} }
/* Search Widget */ /* Search Widget */
.search-widget label { .search-widget label {
display: inline-block; display: inline-block;
padding-right: 15px; padding-right: 15px;
vertical-align: middle; vertical-align: middle;
} }
#search_value_input { #search_value_input {
padding-right: 25px; padding-right: 25px;
} }
.search-reset-start { .search-reset-start {
color: @grey; color: @grey;
float: right; float: right;
position: relative; position: relative;
top: -25px; top: -25px;
left: -10px; left: -10px;
z-index: 10; z-index: 10;
} }
.search-reset-start:hover { .search-reset-start:hover {
cursor: pointer; cursor: pointer;
color: @black; color: @black;
} }
@@ -777,9 +780,9 @@ select.page-size {
} }
/* Display drop-down menus on hover. Remove margin between toggle link /* Display drop-down menus on hover. Remove margin between toggle link
and menu, allowing smooth mouse movement between toggle and menu and menu, allowing smooth mouse movement between toggle and menu
http://stackoverflow.com/questions/8878033/how-to-make-twitter-bootstrap-menu-dropdown-on-hover-rather-than-click http://stackoverflow.com/questions/8878033/how-to-make-twitter-bootstrap-menu-dropdown-on-hover-rather-than-click
*/ */
.dropdown-toggle:hover .dropdown-menu, .dropdown:hover .dropdown-menu { .dropdown-toggle:hover .dropdown-menu, .dropdown:hover .dropdown-menu {
display: block; display: block;
@@ -787,7 +790,7 @@ select.page-size {
.dropdown-menu li:hover { .dropdown-menu li:hover {
visibility: visible; visibility: visible;
} }
.dropdown-menu { .dropdown-menu {
margin-top: 0; margin-top: 0;
@@ -866,15 +869,15 @@ input[type="checkbox"].checkbox-no-label {
} }
/*.list-wrapper { /*.list-wrapper {
background-color: @well; background-color: @well;
padding: 10px; padding: 10px;
border-radius: 4px; border-radius: 4px;
border: 1px solid @well-border; border: 1px solid @well-border;
}*/ }*/
.ui-accordion-content { .ui-accordion-content {
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;
} }
.page-label { .page-label {
margin-top: 5px; margin-top: 5px;
} }
} }
@@ -891,7 +894,7 @@ input[type="checkbox"].checkbox-no-label {
.table-hover tbody tr:hover > td, .table-hover tbody tr:hover > td,
.table-hover tbody tr:hover > th { .table-hover tbody tr:hover > th {
background-color: #fff; background-color: #fff;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
} }
.table-hover-inverse tbody tr:hover > td, .table-hover-inverse tbody tr:hover > td,
@@ -947,7 +950,7 @@ input[type="checkbox"].checkbox-no-label {
/* Table info rows */ /* Table info rows */
.loading-info { .loading-info {
color: @grey-txt; color: @grey-txt;
font-weight: normal; font-weight: normal;
padding: 15px 0; padding: 15px 0;
} }
@@ -978,7 +981,7 @@ input[type="checkbox"].checkbox-no-label {
padding-top: 5px; padding-top: 5px;
} }
.job-new, .job-new,
.license-valid, .license-valid,
.job-success, .job-success,
@@ -1014,11 +1017,11 @@ input[type="checkbox"].checkbox-no-label {
.icon-job-successful { .icon-job-successful {
color: @green; color: @green;
} }
.icon-job-running { .icon-job-running {
.pulsate(); .pulsate();
} }
.icon-job-changed, .icon-job-changed,
.job-changed { .job-changed {
color: @warning; color: @warning;
@@ -1053,7 +1056,7 @@ input[type="checkbox"].checkbox-no-label {
font-weight: normal; font-weight: normal;
line-height: 1; line-height: 1;
} }
.job-list.ui-accordion-content { .job-list.ui-accordion-content {
padding: 25px 15px 25px 15px; padding: 25px 15px 25px 15px;
} }
@@ -1088,7 +1091,7 @@ input[type="checkbox"].checkbox-no-label {
margin-top: 0; margin-top: 0;
margin-bottom: 25px; margin-bottom: 25px;
} }
/* Inventory job status badge */ /* Inventory job status badge */
.failures-true { .failures-true {
background-color: @red; background-color: @red;
@@ -1101,7 +1104,7 @@ input[type="checkbox"].checkbox-no-label {
} }
/* Cloud inventory status. i.e. inventory_source.status values */ /* Cloud inventory status. i.e. inventory_source.status values */
.icon-cloud-na:before, .icon-cloud-na:before,
.icon-cloud-never:before, .icon-cloud-never:before,
.icon-cloud-updating:before, .icon-cloud-updating:before,
@@ -1118,7 +1121,7 @@ input[type="checkbox"].checkbox-no-label {
}*/ }*/
.icon-cloud-na, .icon-cloud-na,
.icon-cloud-never, .icon-cloud-never,
a.icon-cloud-na:hover, a.icon-cloud-na:hover,
a.icon-cloud-never:hover { a.icon-cloud-never:hover {
color: @grey; color: @grey;
@@ -1160,14 +1163,14 @@ input[type="checkbox"].checkbox-no-label {
color: @red; color: @red;
width: 14px; width: 14px;
} }
/* Inventory cloud sourced? indicator */ /* Inventory cloud sourced? indicator */
.icon-cloud-true:before { .icon-cloud-true:before {
content: "\f111"; content: "\f111";
} }
.icon-cloud-false:before { .icon-cloud-false:before {
content: "\f111"; content: "\f111";
} }
.icon-cloud-true { .icon-cloud-true {
@@ -1176,7 +1179,7 @@ input[type="checkbox"].checkbox-no-label {
.icon-cloud-false { .icon-cloud-false {
color: @grey; color: @grey;
} }
/* end */ /* end */
.field-success { .field-success {
@@ -1255,13 +1258,13 @@ input[type="checkbox"].checkbox-no-label {
overflow: hidden; overflow: hidden;
} }
/* Inventory nav links */ /* Inventory nav links */
.navigation-links { .navigation-links {
padding: 0; padding: 0;
margin-top: -10px; margin-top: -10px;
a { a {
margin-right: 15px; margin-right: 15px;
} }
@@ -1280,12 +1283,12 @@ input[type="checkbox"].checkbox-no-label {
.selected { .selected {
font-weight: bold; font-weight: bold;
color: @blue-dark; color: @blue-dark;
} }
.inventory-title { .inventory-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
} }
.active-row { .active-row {
@@ -1339,11 +1342,11 @@ input[type="checkbox"].checkbox-no-label {
} }
a.disabled:hover { a.disabled:hover {
color: @grey; color: @grey;
cursor: not-allowed; cursor: not-allowed;
} }
a.btn-disabled:hover { a.btn-disabled:hover {
cursor: not-allowed; cursor: not-allowed;
} }
/* Variable Editing */ /* Variable Editing */
@@ -1366,7 +1369,7 @@ a.btn-disabled:hover {
display: inline-block; display: inline-block;
margin-left: 20px; margin-left: 20px;
} }
.slider { .slider {
display: inline-block; display: inline-block;
width: 100px; width: 100px;
@@ -1497,7 +1500,7 @@ tr td button i {
li { li {
line-height: normal; line-height: normal;
margin-bottom: 10px; margin-bottom: 10px;
} }
ul:last-child { ul:last-child {
margin-top: 10px; margin-top: 10px;
} }
@@ -1526,7 +1529,7 @@ tr td button i {
overflow: hidden; overflow: hidden;
padding: 10px; padding: 10px;
img { img {
max-width: 450px; max-width: 450px;
margin-top: 15px; margin-top: 15px;
margin-bottom: 15px; margin-bottom: 15px;
@@ -1550,7 +1553,7 @@ tr td button i {
} }
/* Activity Stream Widget */ /* Activity Stream Widget */
#stream-container { #stream-container {
display: none; display: none;
@@ -1573,18 +1576,74 @@ tr td button i {
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
/* New job detail page */ /* New job detail page */
.job-detail-tables { .relative-position {
position: relative;
}
.job-detail-tables, .job_options {
.table {
margin-bottom: 0;
}
.table>tbody>tr>td { .table>tbody>tr>td {
border-top-color: #fff; border-top-color: #fff;
padding: 0;
} }
.table>thead>tr>th { .table>thead>tr>th {
border-bottom-color: #fff; border-bottom-color: #fff;
padding: 0;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li ul {
margin-left: 20px;
} }
} }
.job_well {
padding: 8px;
background-color: @well;
border: 1px solid @well-border;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
.job_options {
height: 100px;
overflow-y: auto;
overflow-x: none;
}
.scroll-up-indicator {
position: absolute;
right: 18px;
font-size: 14px;
bottom: -2px;
padding: 0;
}
.scroll-down-indicator {
position: absolute;
right: 18px;
bottom: -2px;
font-size: 14px;
padding: 0;
}
.scroll-up-indicator,
.scroll-up-indicator:hover,
.scroll-up-indicator:visited,
.scroll-down-indicator,
.scroll-down-indicator:hover,
.scroll-down-indicator:visited {
color: @grey;
}
/* ng-cloak directive */ /* ng-cloak directive */
@@ -1621,7 +1680,7 @@ tr td button i {
}*/ }*/
@media (min-width: 768px) and (max-width: 1199px) { @media (min-width: 768px) and (max-width: 1199px) {
.level-1, .level-1,
.level-2, .level-2,
.level-3, .level-3,
@@ -1637,7 +1696,7 @@ tr td button i {
} }
.list-actions button, .list-actions .checkbox-inline { .list-actions button, .list-actions .checkbox-inline {
margin-top: 10px; margin-top: 10px;
} }
.label-text { .label-text {
@@ -1647,16 +1706,16 @@ tr td button i {
.group-name { .group-name {
width: 80%; width: 80%;
} }
} }
/* Landscape phone to portrait tablet */ /* Landscape phone to portrait tablet */
@media (max-width: 767px) { @media (max-width: 767px) {
/* Job events */ /* Job events */
.level-1, .level-1,
.level-2, .level-2,
.level-3, .level-3,
@@ -1687,7 +1746,7 @@ tr td button i {
} }
.list-actions button, .list-actions .checkbox-inline { .list-actions button, .list-actions .checkbox-inline {
margin-top: 10px; margin-top: 10px;
} }
.group-name { .group-name {

View File

@@ -1,7 +1,7 @@
/********************************************* /*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc. * Copyright (c) 2014 AnsibleWorks, Inc.
* *
* Custom directives for form validation * Custom directives for form validation
* *
*/ */
@@ -10,7 +10,7 @@
/* global chkPass:false */ /* global chkPass:false */
angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'JobsHelper']) angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'JobsHelper'])
// awpassmatch: Add to password_confirm field. Will test if value // awpassmatch: Add to password_confirm field. Will test if value
// matches that of 'input[name="password"]' // matches that of 'input[name="password"]'
.directive('awpassmatch', function() { .directive('awpassmatch', function() {
@@ -32,11 +32,11 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}) })
// caplitalize Add to any input field where the first letter of each // caplitalize Add to any input field where the first letter of each
// word should be capitalized. Use in place of css test-transform. // word should be capitalized. Use in place of css test-transform.
// For some reason "text-transform: capitalize" in breadcrumbs // For some reason "text-transform: capitalize" in breadcrumbs
// causes a break at each blank space. And of course, // causes a break at each blank space. And of course,
// "autocapitalize='word'" only works in iOS. Use this as a fix. // "autocapitalize='word'" only works in iOS. Use this as a fix.
.directive('capitalize', function() { .directive('capitalize', function() {
return { return {
@@ -93,18 +93,18 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}) })
// //
// awRequiredWhen: { variable: "<variable to watch for true|false>", init:"true|false" } // awRequiredWhen: { variable: "<variable to watch for true|false>", init:"true|false" }
// //
// Make a field required conditionally using a scope variable. If the scope variable is true, the // Make a field required conditionally using a scope variable. If the scope variable is true, the
// field will be required. Otherwise, the required attribute will be removed. // field will be required. Otherwise, the required attribute will be removed.
// //
.directive('awRequiredWhen', function() { .directive('awRequiredWhen', function() {
return { return {
require: 'ngModel', require: 'ngModel',
link: function(scope, elm, attrs, ctrl) { link: function(scope, elm, attrs, ctrl) {
function checkIt () { function checkIt () {
var viewValue = elm.val(), label, validity = true; var viewValue = elm.val(), label, validity = true;
if ( scope[attrs.awRequiredWhen] && (elm.attr('required') === null || elm.attr('required') === undefined) ) { if ( scope[attrs.awRequiredWhen] && (elm.attr('required') === null || elm.attr('required') === undefined) ) {
@@ -131,12 +131,12 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
ctrl.$setValidity('required', validity); ctrl.$setValidity('required', validity);
} }
if (attrs.awrequiredInit !== undefined && attrs.awrequiredInit !== null) { if (attrs.awrequiredInit !== undefined && attrs.awrequiredInit !== null) {
scope[attrs.awRequiredWhen] = attrs.awrequiredInit; scope[attrs.awRequiredWhen] = attrs.awrequiredInit;
checkIt(); checkIt();
} }
scope.$watch(attrs.awRequiredWhen, function() { scope.$watch(attrs.awRequiredWhen, function() {
// watch for the aw-required-when expression to change value // watch for the aw-required-when expression to change value
checkIt(); checkIt();
@@ -149,7 +149,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}) })
// awPlaceholder: Dynamic placeholder set to a scope variable you want watched. // awPlaceholder: Dynamic placeholder set to a scope variable you want watched.
// Value will be place in field placeholder attribute. // Value will be place in field placeholder attribute.
.directive('awPlaceholder', [ function() { .directive('awPlaceholder', [ function() {
@@ -165,7 +165,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
}]) }])
// lookup Validate lookup value against API // lookup Validate lookup value against API
// //
.directive('awlookup', ['Rest', function(Rest) { .directive('awlookup', ['Rest', function(Rest) {
return { return {
require: 'ngModel', require: 'ngModel',
@@ -198,10 +198,10 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}]) }])
// //
// awValidUrl // awValidUrl
// //
.directive('awValidUrl', [ function() { .directive('awValidUrl', [ function() {
return { return {
require: 'ngModel', require: 'ngModel',
@@ -222,13 +222,13 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
}; };
}]) }])
/* /*
* Enable TB tooltips. To add a tooltip to an element, include the following directive in * Enable TB tooltips. To add a tooltip to an element, include the following directive in
* the element's attributes: * the element's attributes:
* *
* aw-tool-tip="<< tooltip text here >>" * aw-tool-tip="<< tooltip text here >>"
* *
* Include the standard TB data-XXX attributes to controll a tooltip's appearance. We will * Include the standard TB data-XXX attributes to controll a tooltip's appearance. We will
* default placement to the left and delay to 2 seconds. * default placement to the left and delay to 2 seconds.
*/ */
.directive('awToolTip', function() { .directive('awToolTip', function() {
@@ -241,11 +241,11 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
else { else {
placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left'; placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left';
} }
$(element).on('hidden.bs.tooltip', function( ) { $(element).on('hidden.bs.tooltip', function( ) {
// TB3RC1 is leaving behind tooltip <div> elements. This will remove them // TB3RC1 is leaving behind tooltip <div> elements. This will remove them
// after a tooltip fades away. If not, they lay overtop of other elements and // after a tooltip fades away. If not, they lay overtop of other elements and
// honk up the page. // honk up the page.
$('.tooltip').each(function() { $('.tooltip').each(function() {
$(this).remove(); $(this).remove();
}); });
@@ -272,7 +272,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}) })
/* /*
* Enable TB pop-overs. To add a pop-over to an element, include the following directive in * Enable TB pop-overs. To add a pop-over to an element, include the following directive in
* the element's attributes: * the element's attributes:
@@ -470,7 +470,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}]) }])
// //
// awRefresh // awRefresh
// //
@@ -507,8 +507,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
}; };
}]) }])
/* /*
awMultiSelect awMultiSelect
Relies on select2.js to create a multi-select with tags. Relies on select2.js to create a multi-select with tags.
*/ */
@@ -543,8 +543,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
}; };
}]) }])
/* /*
* Make an element draggable. Used on inventory groups tree. * Make an element draggable. Used on inventory groups tree.
* *
* awDraggable: boolean || {{ expression }} * awDraggable: boolean || {{ expression }}
@@ -552,7 +552,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
*/ */
.directive('awDraggable', [ function() { .directive('awDraggable', [ function() {
return function(scope, element, attrs) { return function(scope, element, attrs) {
if (attrs.awDraggable === "true") { if (attrs.awDraggable === "true") {
var containment = attrs.containment; //provide dataContainment:"#id" var containment = attrs.containment; //provide dataContainment:"#id"
$(element).draggable({ $(element).draggable({
@@ -569,8 +569,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}]) }])
/* /*
* Make an element droppable- it can receive draggable elements * Make an element droppable- it can receive draggable elements
* *
* awDroppable: boolean || {{ expression }} * awDroppable: boolean || {{ expression }}
@@ -581,7 +581,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
var node; var node;
if (attrs.awDroppable === "true") { if (attrs.awDroppable === "true") {
$(element).droppable({ $(element).droppable({
// the following is inventory specific accept checking and // the following is inventory specific accept checking and
// drop processing. // drop processing.
accept: function(draggable) { accept: function(draggable) {
if (draggable.attr('data-type') === 'group') { if (draggable.attr('data-type') === 'group') {
@@ -643,7 +643,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
var active, var active,
list = Store('accordions'), list = Store('accordions'),
id, base; id, base;
if (!Empty(attrs.openFirst)) { if (!Empty(attrs.openFirst)) {
active = 0; active = 0;
} }
@@ -696,6 +696,51 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}); });
}; };
}])
.directive('awScrollDown', [ function() {
return function(scope, element, attrs) {
var element_to_scroll = attrs.awScrollDown, elem;
$(element).on('click', function() {
var height = document.getElementById(element_to_scroll).scrollHeight;
$(element).hide();
$('#' + element_to_scroll).animate({scrollTop:height}, 800);
});
elem = document.getElementById(element_to_scroll);
$('#' + element_to_scroll).on('scroll', function() {
if (elem.scrollTop > 0 && $(element).is(':visible')) {
$(element).hide();
}
else if (elem.scrollTop === 0 && !$(element).is(':visible')) {
$(element).fadeIn(2500);
}
});
if (elem.scrollTop > 0 && $(element).is(':visible')) {
$(element).hide();
}
};
}])
.directive('awScrollUp', [ function() {
return function(scope, element, attrs) {
var element_to_scroll = attrs.awScrollUp, elem;
$(element).on('click', function() {
$(element).hide();
$('#' + element_to_scroll).animate({scrollTop:0}, 'slow');
});
elem = document.getElementById(element_to_scroll);
$('#' + element_to_scroll).on('scroll', function() {
if (elem.scrollTop === 0 && $(element).is(':visible')) {
$(element).hide();
}
else if (elem.scrollTop > 0 && !$(element).is(':visible')) {
$(element).fadeIn(2500);
}
});
if (elem.scrollTop === 0) {
$(element).hide();
}
};
}]); }]);
/* /*
@@ -714,4 +759,4 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
} }
}; };
}]); }]);
*/ */

View File

@@ -2,13 +2,13 @@
<div ng-cloak id="htmlTemplate"> <div ng-cloak id="htmlTemplate">
<div class="row"> <div class="row">
<div class="col-md-12" id="breadcrumbs"></div> <div class="col-md-12" id="breadcrumbs"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h4>Job Options</h4> <h5>Options</h5>
<div class="job_options"> <div class="job_options job_well" id="job_options">
<table class="table table-condensed"> <table class="table table-condensed">
<tbody> <tbody>
<tr ng-show="job_template_url"> <tr ng-show="job_template_url">
<td class="col-md-3 col-sm-2">Job Template</td> <td class="col-md-3 col-sm-2">Job Template</td>
<td><a ng-href="{{ job_template_url }}">{{ job_template_name }}</a></td> <td><a ng-href="{{ job_template_url }}">{{ job_template_name }}</a></td>
@@ -16,11 +16,11 @@
<tr ng-show="project_url"> <tr ng-show="project_url">
<td class="col-md-3 col-sm-2">Project</td> <td class="col-md-3 col-sm-2">Project</td>
<td><a ng-href="{{ project_url }}">{{ project_name }}</a></td> <td><a ng-href="{{ project_url }}">{{ project_name }}</a></td>
</tr> </tr>
<tr ng-show="inventory_url"> <tr ng-show="inventory_url">
<td class="col-md-3 col-sm-2">Inventory</td> <td class="col-md-3 col-sm-2">Inventory</td>
<td><a ng-href="{{ inventory_url }}">{{ inventory_name }}</a></td> <td><a ng-href="{{ inventory_url }}">{{ inventory_name }}</a></td>
</tr> </tr>
<tr ng-show="playbook"> <tr ng-show="playbook">
<td class="col-md-3 col-sm-2">Playbook</td> <td class="col-md-3 col-sm-2">Playbook</td>
<td>{{ playbook }}</td> <td>{{ playbook }}</td>
@@ -52,29 +52,27 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<a id="job_options_scroll_down_indicator" href="" aw-scroll-down="job_options" class="scroll-down-indicator">more <i class="fa fa-chevron-circle-down"></i></a>
<a id="job_options_scroll_up_indicator" href="" aw-scroll-up="job_options" class="scroll-up-indicator">more <i class="fa fa-chevron-circle-up"></i></a>
</div> </div>
</div><!-- row --> </div><!-- row -->
<div class="row"> <div class="row">
<div class="col-md-6 job-detail-tables"> <div class="col-md-6 job-detail-tables">
<h4>Plays</h4> <h5>Plays</h5>
<div class="job_plays"> <div class="job_plays job_well">
<table class="table table-condensed job-detail-table"> <ul>
<thead> <li ng-repeat="play in plays">
<tr> <i class="fa icon-job-{{ play.status }}"></i> {{play.name }}
<th class="col-md-1">Status</th> <ul ng-show="play.children.length > 0">
<th>Name</th> <li ng-repeat="child in play.children">
</tr> <i class="fa icon-job-{{ child.status }}"></i> {{child.name }}
</thead> </li>
<tbody> </ul>
<tr ng-repeat="play in plays"> </li>
<td><i class="fa icon-job-{{ play.status }}"></i></td> </ul>
<td>{{ play.name }}</td>
<td>{{ play.state }}</td>
</tr>
</tbody>
</table>
</div> </div>
<h4>Tasks</h4> <h4>Tasks</h4>
@@ -90,10 +88,9 @@
<tr ng-repeat="task in tasks"> <tr ng-repeat="task in tasks">
<td><i class="fa icon-job-{{ task.status }}"></i></td> <td><i class="fa icon-job-{{ task.status }}"></i></td>
<td>{{ task.name }}</td> <td>{{ task.name }}</td>
<td>{{ task.state }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<h4>Hosts</h4> <h4>Hosts</h4>
@@ -101,22 +98,20 @@
<table class="table table-condensed job-detail-table"> <table class="table table-condensed job-detail-table">
<thead> <thead>
<tr> <tr>
<th class="col-md-1">Status</th>
<th>Name</th> <th>Name</th>
<th>OK</th> <th class="text-center">OK</th>
<th>Changed</th> <th class="text-center">Changed</th>
<th>Unreachable</th> <th class="text-center">Unreachable</th>
<th>Failed</th> <th class="text-center">Failed</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="host in hosts"> <tr ng-repeat="host in hosts">
<td><i class="fa icon-job-{{ task.status }}"></i></td>
<td>{{ host.name }}</td> <td>{{ host.name }}</td>
<td>{{ host.ok }}</td> <td class="text-center">{{ host.ok }}</td>
<td>{{ host.changed }}</td> <td class="text-center">{{ host.changed }}</td>
<td>{{ host.unreachable }}</td> <td class="text-center">{{ host.unreachable }}</td>
<td>{{ host.failed }}</td> <td class="text-center">{{ host.failed }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -127,17 +122,3 @@
</div> </div>
</div> </div>