Merge branch 'devel' of github.com:ansible/ansible-tower into 11th-hour

This commit is contained in:
Akita Noek 2016-04-16 18:35:25 -04:00
commit 7637b51992
40 changed files with 1502 additions and 1817 deletions

View File

@ -503,7 +503,7 @@
}
#graph-section svg{
margin-top: 15px;
margin: 0 auto;
}
path.slice{

View File

@ -40,9 +40,9 @@ export default
angular.module('JobDetailHelper', ['Utilities', 'RestServices', 'ModalDialog'])
.factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult',
'GetElapsed', 'UpdateTaskStatus', 'DrawGraph', 'LoadHostSummary', 'JobIsFinished', 'AddNewTask', 'AddNewPlay',
'GetElapsed', 'UpdateTaskStatus', 'JobIsFinished', 'AddNewTask', 'AddNewPlay',
function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, GetElapsed,
UpdateTaskStatus, DrawGraph, LoadHostSummary, JobIsFinished, AddNewTask, AddNewPlay) {
UpdateTaskStatus, JobIsFinished, AddNewTask, AddNewPlay, longDateFilter) {
return function(params) {
var scope = params.scope,
@ -185,7 +185,7 @@ export default
};
}])
.factory('GetElapsed', [ function() {
.factory('GetElapsed', [function() {
return function(params) {
var start = params.start,
end = params.end,
@ -299,7 +299,7 @@ export default
};
}])
.factory('AddNewTask', ['DrawGraph', 'UpdatePlayStatus', 'SetActivePlay', 'SetActiveTask', function(DrawGraph, UpdatePlayStatus, SetActivePlay, SetActiveTask) {
.factory('AddNewTask', ['UpdatePlayStatus', 'SetActivePlay', 'SetActiveTask', function(UpdatePlayStatus, SetActivePlay, SetActiveTask) {
return function(params) {
var scope = params.scope,
event = params.event,
@ -363,17 +363,17 @@ export default
scope.job_status.status = 'failed';
}
if (JobIsFinished(scope) && !Empty(modified)) {
scope.job_status.finished = modified;
scope.job_status.finished = longDateFilter(modified)
}
if (!Empty(started) && Empty(scope.job_status.started)) {
scope.job_status.started = started;
scope.job_status.started = longDateFilter(modified)
}
if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) {
scope.job_status.elapsed = GetElapsed({
start: scope.job_status.started,
end: scope.job_status.finished
start: started,
end: finished
});
}
}
};
}])
@ -488,15 +488,6 @@ export default
counter = params.counter,
h, host;
/*
scope.host_summary.ok += (status === 'successful') ? 1 : 0;
scope.host_summary.changed += (status === 'changed') ? 1 : 0;
scope.host_summary.unreachable += (status === 'unreachable') ? 1 : 0;
scope.host_summary.failed += (status === 'failed') ? 1 : 0;
scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable +
scope.host_summary.failed;
*/
if (scope.jobData.hostSummaries[host_id] !== undefined) {
scope.jobData.hostSummaries[host_id].ok += (status === 'successful') ? 1 : 0;
scope.jobData.hostSummaries[host_id].changed += (status === 'changed') ? 1 : 0;
@ -517,29 +508,6 @@ export default
status: (status === 'failed' || status === 'unreachable') ? 'failed' : 'successful'
};
}
scope.host_summary.ok = 0;
scope.host_summary.changed = 0;
scope.host_summary.unreachable = 0;
scope.host_summary.failed = 0;
for (h in scope.jobData.hostSummaries) {
host = scope.jobData.hostSummaries[h];
if (host.ok > 0 && host.failed === 0 && host.unreachable === 0 && host.changed === 0) {
scope.host_summary.ok++;
}
if (host.changed > 0 && host.failed === 0 && host.unreachable === 0) {
scope.host_summary.changed++;
}
if (host.failed > 0) {
scope.host_summary.failed++;
}
if (host.unreachable > 0) {
scope.host_summary.unreachable++;
}
}
scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable +
scope.host_summary.failed;
UpdateTaskStatus({
scope: scope,
task_id: task_id,
@ -822,7 +790,6 @@ export default
url += (scope.search_task_name) ? '&task__icontains=' + scope.search_task_name : '';
url += (scope.search_task_status === 'failed') ? '&failed=true' : '';
url += '&page_size=' + scope.tasksMaxRows + '&order=id';
scope.plays.every(function(p, idx) {
if (p.id === scope.selectedPlay) {
play = scope.plays[idx];
@ -931,7 +898,7 @@ export default
}])
// Call when the selected task needs to change
.factory('SelectTask', ['LoadHosts', function(LoadHosts) {
.factory('SelectTask', ['JobDetailService', function(JobDetailService) {
return function(params) {
var scope = params.scope,
id = params.id,
@ -946,239 +913,52 @@ export default
scope.tasks[idx].taskActiveClass = '';
}
});
LoadHosts({
scope: scope,
callback: callback,
clear: true
var params = {
parent: scope.selectedTask,
event__startswith: 'runner',
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
};
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostEvents(res.results)
scope.hostResultsLoading = false;
});
};
}])
// Refresh the list of hosts
.factory('LoadHosts', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) {
return function(params) {
var scope = params.scope,
callback = params.callback,
url;
scope.hostResults = [];
if (scope.selectedTask) {
// If we have a selected task, then get the list of hosts
url = scope.job.related.job_events + '?parent=' + scope.selectedTask + '&';
url += (scope.search_host_name) ? 'host__name__icontains=' + scope.search_host_name + '&' : '';
url += (scope.search_host_status === 'failed') ? 'failed=true&' : '';
url += 'event__startswith=runner&page_size=' + scope.hostResultsMaxRows + '&order=host_name,counter';
scope.hostResultsLoading = true;
Rest.setUrl(url);
Rest.get()
.success(function(data) {
scope.next_host_results = data.next;
scope.hostResults = [];
data.results.forEach(function(event) {
var status, status_text, item, msg;
if (event.event === "runner_on_skipped") {
status = 'skipped';
}
else if (event.event === "runner_on_unreachable") {
status = 'unreachable';
}
else {
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
}
switch(status) {
case "successful":
status_text = 'OK';
break;
case "changed":
status_text = "Changed";
break;
case "failed":
status_text = "Failed";
break;
case "unreachable":
status_text = "Unreachable";
break;
case "skipped":
status_text = "Skipped";
}
if (event.event_data && event.event_data.res) {
item = event.event_data.res.item;
if (typeof item === "object") {
item = JSON.stringify(item);
item = item.replace(/\"/g,'').replace(/:/g,': ').replace(/,/g,', ');
}
}
msg = '';
if (event.event_data && event.event_data.res) {
if (typeof event.event_data.res === 'object') {
msg = event.event_data.res.msg;
} else {
msg = event.event_data.res;
}
}
if (event.event !== "runner_on_no_hosts") {
scope.hostResults.push({
id: event.id,
status: status,
status_text: status_text,
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
created: event.created,
msg: msg,
item: item
});
}
});
scope.hostResultsLoading = false;
if (callback) {
scope.$emit(callback);
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
if (callback) {
scope.$emit(callback);
}
//$('#hosts-table-detail').mCustomScrollbar("update");
}
};
}])
// Refresh the list of hosts in the hosts summary section
.factory('ReloadHostSummaryList', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) {
return function(params) {
var scope = params.scope,
callback = params.callback,
url;
url = scope.job.related.job_host_summaries + '?';
url += (scope.search_host_summary_name) ? 'host_name__icontains=' + scope.search_host_summary_name + '&': '';
url += (scope.search_host_summary_status === 'failed') ? 'failed=true&' : '';
url += '&page_size=' + scope.hostSummariesMaxRows + '&order=host_name';
scope.hosts = [];
scope.hostSummariesLoading = true;
Rest.setUrl(url);
Rest.get()
.success(function(data) {
scope.next_host_summaries = data.next;
scope.hosts = [];
data.results.forEach(function(event) {
var name;
if (event.host_name) {
name = event.host_name;
}
else {
name = "<deleted host>";
}
scope.hosts.push({
id: name,
name: event.host_name,
ok: event.ok,
changed: event.changed,
unreachable: event.dark,
failed: event.failures,
status: (event.failed) ? 'failed' : 'successful'
});
});
scope.hostSummariesLoading = false;
if (callback) {
scope.$emit(callback);
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
};
}])
.factory('LoadHostSummary', [ function() {
return function(params) {
var scope = params.scope,
data = params.data,
host;
scope.host_summary.ok = 0;
for (host in data.ok) {
if (!data.changed[host] && !data.dark[host] && !data.failures[host]) {
scope.host_summary.ok += 1;
}
}
scope.host_summary.changed = 0;
for (host in data.changed) {
if (!data.dark[host] && !data.failures[host]) {
scope.host_summary.changed += 1;
}
}
scope.host_summary.unreachable = 0;
for (host in data.dark) {
scope.host_summary.unreachable += 1;
}
scope.host_summary.failed = 0;
for (host in data.failures) {
scope.host_summary.failed += 1;
}
scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed +
scope.host_summary.unreachable + scope.host_summary.failed;
};
}])
.factory('DrawGraph', ['DonutChart', function(DonutChart) {
return function(params) {
var scope = params.scope,
var count = params.count,
graph_data = [];
// Ready the data
if (scope.host_summary.ok) {
if (count.ok.length > 0) {
graph_data.push({
label: 'OK',
value: scope.host_summary.ok,
value: count.ok.length,
color: '#60D66F'
});
}
if (scope.host_summary.changed) {
if (count.changed.length > 0) {
graph_data.push({
label: 'CHANGED',
value: scope.host_summary.changed,
value: count.changed.length,
color: '#FF9900'
});
}
if (scope.host_summary.unreachable) {
if (count.unreachable.length > 0) {
graph_data.push({
label: 'UNREACHABLE',
value: scope.host_summary.unreachable,
value: count.unreachable.length,
color: '#FF0000'
});
}
if (scope.host_summary.failed) {
if (count.failures.length > 0) {
graph_data.push({
label: 'FAILED',
value: scope.host_summary.failed,
value: count.failures.length,
color: '#ff5850'
});
}
scope.graph_data = graph_data;
var total_count = 0, gd_obj;
for (gd_obj in graph_data) {
total_count += graph_data[gd_obj].value;
}
scope.total_count_for_graph = total_count;
DonutChart({
data: graph_data
});
@ -1220,44 +1000,42 @@ export default
"font-family": 'Open Sans',
"font-style": "normal",
"font-weight":400,
"src": "url(/static/assets/OpenSans-Regular.ttf)"
"src": "url(/static/assets/OpenSans-Regular.ttf)",
"width": 500,
"height": 300,
});
d3.select(element.find(".nv-label text")[0])
.attr("class", "DashboardGraphs-hostStatusLabel--successful")
.attr("class", "HostSummary-graph--successful")
.style({
"font-family": 'Open Sans',
"text-anchor": "start",
"font-size": "16px",
"text-transform" : "uppercase",
"fill" : colors[0],
"src": "url(/static/assets/OpenSans-Regular.ttf)"
});
d3.select(element.find(".nv-label text")[1])
.attr("class", "DashboardGraphs-hostStatusLabel--failed")
.attr("class", "HostSummary-graph--changed")
.style({
"font-family": 'Open Sans',
"text-anchor" : "end !imporant",
"font-size": "16px",
"text-transform" : "uppercase",
"fill" : colors[1],
"src": "url(/static/assets/OpenSans-Regular.ttf)"
});
d3.select(element.find(".nv-label text")[2])
.attr("class", "DashboardGraphs-hostStatusLabel--successful")
.attr("class", "HostSummary-graph--failed")
.style({
"font-family": 'Open Sans',
"text-anchor" : "end !imporant",
"font-size": "16px",
"text-transform" : "uppercase",
"fill" : colors[2],
"src": "url(/static/assets/OpenSans-Regular.ttf)"
});
d3.select(element.find(".nv-label text")[3])
.attr("class", "DashboardGraphs-hostStatusLabel--failed")
.attr("class", "HostSummary-graph--unreachable")
.style({
"font-family": 'Open Sans',
"text-anchor" : "end !imporant",
"font-size": "16px",
"text-transform" : "uppercase",
"fill" : colors[3],
@ -1274,7 +1052,8 @@ export default
idx = 0,
result = [],
newKeys = [],
plays = JSON.parse(JSON.stringify(scope.jobData.plays)),
//plays = JSON.parse(JSON.stringify(scope.jobData.plays)),
plays = scope.jobData.plays,
filteredListX = [],
filteredListA = [],
filteredListB = [],
@ -1363,7 +1142,8 @@ export default
if (scope.activePlay && scope.jobData.plays[scope.activePlay]) {
tasks = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks));
//tasks = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks));
tasks = scope.jobData.plays[scope.activePlay].tasks;
// Only draw tasks that are in the 'active' list
for (key in tasks) {
@ -1437,7 +1217,8 @@ export default
if (scope.activePlay && scope.activeTask && scope.jobData.plays[scope.activePlay] &&
scope.jobData.plays[scope.activePlay].tasks[scope.activeTask]) {
hostResults = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults));
//hostResults = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults));
hostResults = scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults;
if (scope.search_host_name) {
for (key in hostResults) {
@ -1498,88 +1279,20 @@ export default
};
}])
.factory('DrawHostSummaries', [ function() {
return function(params) {
var scope = params.scope,
result = [],
filteredListA = [],
filteredListB = [],
idx = 0,
hostSummaries,
key,
keys = Object.keys(scope.jobData.hostSummaries);
if (keys.length > 0) {
hostSummaries = JSON.parse(JSON.stringify(scope.jobData.hostSummaries));
if (scope.search_host_summary_name) {
for (key in hostSummaries) {
if (hostSummaries[key].name.indexOf(scope.search_host_summary_name) > 0) {
filteredListA[key] = hostSummaries[key];
}
}
}
else {
filteredListA = hostSummaries;
}
if (scope.search_host_summary_status === 'failed') {
for (key in filteredListA) {
if (filteredListA[key].status === 'failed' || filteredListA[key].status === 'unreachable') {
filteredListB[key] = filteredListA[key];
}
}
}
else {
filteredListB = filteredListA;
}
keys = Object.keys(filteredListB);
keys.sort(function(a,b) {
if (filteredListB[a].name > filteredListB[b].name) {
return 1;
}
if (filteredListB[a].name < filteredListB[b].name) {
return -1;
}
// a must be equal to b
return 0;
});
while (idx < keys.length && result.length < scope.hostSummariesMaxRows) {
result.push(filteredListB[keys[idx]]);
idx++;
}
}
setTimeout( function() {
scope.$apply( function() {
scope.hosts = result;
});
});
};
}])
.factory('UpdateDOM', ['DrawPlays', 'DrawTasks', 'DrawHostResults', 'DrawHostSummaries', 'DrawGraph',
function(DrawPlays, DrawTasks, DrawHostResults, DrawHostSummaries, DrawGraph) {
.factory('UpdateDOM', ['DrawPlays', 'DrawTasks', 'DrawHostResults',
function(DrawPlays, DrawTasks, DrawHostResults) {
return function(params) {
var scope = params.scope;
if (!scope.pauseLiveEvents) {
DrawPlays({ scope: scope });
DrawTasks({ scope: scope });
DrawHostResults({ scope: scope });
}
DrawHostSummaries({ scope: scope });
setTimeout(function() {
scope.playsLoading = false;
scope.tasksLoading = false;
scope.hostResultsLoading = false;
scope.LoadHostSummaries = false;
},100);
if (scope.host_summary.total > 0) {
DrawGraph({ scope: scope, resize: true });
}
};
}]);

View File

@ -752,9 +752,9 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
*
*/
// Submit request to run a playbook
.factory('PlaybookRun', ['$location','$stateParams', 'LaunchJob', 'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors', 'Wait', 'Empty',
.factory('PlaybookRun', ['$location', '$state', '$stateParams', 'LaunchJob', 'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors', 'Wait', 'Empty',
'PromptForCredential', 'PromptForVars', 'PromptForSurvey' , 'CreateLaunchDialog',
function ($location, $stateParams, LaunchJob, PromptForPasswords, Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty,
function ($location, $state, $stateParams, LaunchJob, PromptForPasswords, Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty,
PromptForCredential, PromptForVars, PromptForSurvey, CreateLaunchDialog) {
return function (params) {
var //parent_scope = params.scope,
@ -803,7 +803,8 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
var job = data.job || data.system_job;
if((scope.portalMode===false || scope.$parent.portalMode===false ) && Empty(data.system_job) ||
(base === 'home')){
$location.path('/jobs/' + job);
// use $state.go with reload: true option to re-instantiate sockets in
$state.go('jobDetail', {id: job}, {reload: true})
}
});

View File

@ -75,7 +75,7 @@ export default
scope.viewJobDetails = function(job) {
var goToJobDetails = function(state) {
$state.go(state, {id: job.id});
$state.go(state, {id: job.id}, {reload:true});
}
switch(job.type) {

View File

@ -173,8 +173,8 @@ export default
}
$state.go("^");
});
scope.saveSchedule = function() {
schedule.extra_data = scope.serializedExtraVars;
SchedulePost({
scope: scope,
url: url,
@ -192,6 +192,7 @@ export default
Rest.get()
.success(function(data) {
schedule = data;
scope.serializedExtraVars = schedule.extra_data;
if(schedule.extra_data.hasOwnProperty('granularity')){
scope.isFactCleanup = true;
}
@ -312,7 +313,6 @@ export default
schedule = (params.schedule) ? params.schedule : {},
callback = params.callback,
newSchedule, rrule, extra_vars;
if (scheduler.isValid()) {
Wait('start');
newSchedule = scheduler.getValue();
@ -326,14 +326,16 @@ export default
"older_than": scope.scheduler_form.keep_amount.$viewValue + scope.scheduler_form.keep_unit.$viewValue.value,
"granularity": scope.scheduler_form.granularity_keep_amount.$viewValue + scope.scheduler_form.granularity_keep_unit.$viewValue.value
};
schedule.extra_data = JSON.stringify(extra_vars);
} else if (scope.cleanupJob) {
extra_vars = {
"days" : scope.scheduler_form.schedulerPurgeDays.$viewValue
};
schedule.extra_data = JSON.stringify(extra_vars);
}
else if (scope.serializedExtraVars){
schedule.extra_data = scope.serializedExtraVars;
}
schedule.extra_data = JSON.stringify(extra_vars);
Rest.setUrl(url);
if (mode === 'add') {
Rest.post(schedule)

View File

@ -1,49 +1,49 @@
<div class="HostEvent-details--left">
<div class="HostEvent-field">
<div class="HostEvent-title">EVENT</div>
<span class="HostEvent-field--content"></span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">HOST</span>
<span class="HostEvent-field--content">
<a ui-sref="jobDetail.host-events({hostName: event.host_name})">{{event.host_name || "No result found"}}</a></span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">STATUS</span>
<span class="HostEvent-field--content">
<a class="HostEvents-status">
<i class="fa fa-circle" ng-class="processEventStatus"></i>
</a>
{{event.status || "No result found"}}
</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">ID</span>
<span class="HostEvent-field--content">{{event.id || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">CREATED</span>
<span class="HostEvent-field--content">{{event.created || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">PLAY</span>
<span class="HostEvent-field--content">{{event.play || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">TASK</span>
<span class="HostEvent-field--content">{{event.task || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">MODULE</span>
<span class="HostEvent-field--content">{{event.event_data.res.invocation.module_name || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<div class="HostEvent-title">EVENT</div>
<span class="HostEvent-field--content"></span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">HOST</span>
<span class="HostEvent-field--content">
<a ui-sref="jobDetail.host-events({hostName: event.host_name})">{{event.host_name || "No result found"}}</a></span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">STATUS</span>
<span class="HostEvent-field--content">
<a class="HostEvents-status">
<i class="fa fa-circle" ng-class="processEventStatus(event).class"></i>
</a>
{{processEventStatus(event).status || "No result found"}}
</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">ID</span>
<span class="HostEvent-field--content">{{event.id || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">CREATED</span>
<span class="HostEvent-field--content">{{event.created || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">PLAY</span>
<span class="HostEvent-field--content">{{event.play || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">TASK</span>
<span class="HostEvent-field--content">{{event.task || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">MODULE</span>
<span class="HostEvent-field--content">{{event.event_data.res.invocation.module_name || "No result found"}}</span>
</div>
</div>
<div class="HostEvent-details--right" ng-show="event.event_data.res">
<div class="HostEvent-title">RESULTS</div>
<!-- discard any objects in the ansible response until we decide to flatten them -->
<div class="HostEvent-field" ng-repeat="(key, value) in results = event.event_data.res track by $index" ng-if="processResults(value)">
<span class="HostEvent-field--label">{{key}}</span>
<span class="HostEvent-field--content">{{value}}</span>
</div>
<div class="HostEvent-title">RESULTS</div>
<!-- discard any objects in the ansible response until we decide to flatten them -->
<div class="HostEvent-field" ng-repeat="(key, value) in results = event.event_data.res track by $index" ng-if="processResults(value)">
<span class="HostEvent-field--label">{{key}}</span>
<span class="HostEvent-field--content">{{value}}</span>
</div>
</div>

View File

@ -1,4 +1,4 @@
<div id="HostEvent" class="HostEvent modal fade" role="dialog">
<div id="HostEvent" class="HostEvent modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<!-- modal body -->
@ -14,8 +14,7 @@
<!-- view navigation buttons -->
<button ui-sref="jobDetail.host-event.details" type="button" class="btn btn-sm btn-default" >Details</button>
<button ui-sref="jobDetail.host-event.json" type="button" class="btn btn-sm btn-default ">JSON</button>
<button ng-show="event.stdout" ui-sref="jobDetail.host-event.stdout" type="button" class="btn btn-sm btn-default ">Standard Out</button>
<button ng-show="event.timing" ui-sref="jobDetail.host-event.timing" type="button" class="btn btn-sm btn-default ">Timing</button>
<button ng-show="stdout" ui-sref="jobDetail.host-event.stdout" type="button" class="btn btn-sm btn-default ">Standard Out</button>
</div>
<div class="HostEvent-body">

View File

@ -1,13 +1,2 @@
<div class="EventHost-stdoutPanel Panel">
<div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">STANDARD OUT</div>
<div class="StandardOut-panelHeaderActions">
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top">
<i class="fa fa-download"></i>
</button>
</a>
</div>
</div>
<standard-out-log stdout-endpoint="event._stdout"></standard-out-log>
</div>
<textarea id="HostEvent-stdout" class="HostEvent-stdout">
</textarea>

View File

@ -49,6 +49,7 @@
width: 80px;
margin-right: 20px;
font-size: 12px;
word-wrap: break-word;
}
.HostEvent-field{
.OnePlusTwo-left--detailsRow;
@ -58,12 +59,17 @@
}
.HostEvent-details--left, .HostEvent-details--right{
vertical-align:top;
width:270px;
width:265px;
display: inline-block;
}
.HostEvent-details--left{
margin-right: 10px;
}
.HostEvent-details--right{
.HostEvent-field--label{
width: 170px;
width: auto;
}
}
.HostEvent-field--content{
text-align: right;
}
}

View File

@ -4,68 +4,76 @@
* All Rights Reserved
*************************************************/
export default
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'moment', 'event',
function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){
// Avoid rendering objects in the details fieldset
// ng-if="processResults(value)" via host-event-details.partial.html
$scope.processResults = function(value){
if (typeof value == 'object'){return false}
else {return true}
};
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'event',
function($stateParams, $scope, $state, Wait, JobDetailService, event){
var codeMirror = function(){
var el = $('#HostEvent-json')[0];
var editor = CodeMirror.fromTextArea(el, {
lineNumbers: true,
mode: {name: "javascript", json: true}
});
editor.getDoc().setValue(JSON.stringify($scope.json, null, 4));
};
$scope.processEventStatus = JobDetailService.processEventStatus;
$scope.hostResults = [];
// Avoid rendering objects in the details fieldset
// ng-if="processResults(value)" via host-event-details.partial.html
$scope.processResults = function(value){
if (typeof value == 'object'){return false;}
else {return true;}
};
$scope.getActiveHostIndex = function(){
var result = $scope.hostResults.filter(function( obj ) {
return obj.id == $scope.event.id;
});
return $scope.hostResults.indexOf(result[0])
};
var codeMirror = function(el, json){
var container = $(el)[0];
var editor = CodeMirror.fromTextArea(container, {
lineNumbers: true,
mode: {name: "javascript", json: true}
});
editor.setSize("100%", 300)
editor.getDoc().setValue(JSON.stringify(json, null, 4));
};
$scope.showPrev = function(){
return $scope.getActiveHostIndex() != 0
};
$scope.getActiveHostIndex = function(){
var result = $scope.hostResults.filter(function( obj ) {
return obj.id == $scope.event.id;
});
return $scope.hostResults.indexOf(result[0]);
};
$scope.showNext = function(){
return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1])
};
$scope.showPrev = function(){
return $scope.getActiveHostIndex() != 0;
};
$scope.goNext = function(){
var index = $scope.getActiveHostIndex() + 1;
var id = $scope.hostResults[index].id;
$state.go('jobDetail.host-event.details', {eventId: id})
};
$scope.showNext = function(){
return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1]);
};
$scope.goPrev = function(){
var index = $scope.getActiveHostIndex() - 1;
var id = $scope.hostResults[index].id;
$state.go('jobDetail.host-event.details', {eventId: id})
};
$scope.goNext = function(){
var index = $scope.getActiveHostIndex() + 1;
var id = $scope.hostResults[index].id;
$state.go('jobDetail.host-event.details', {eventId: id});
};
var init = function(){
$scope.event = event.data.results[0];
$scope.event.created = moment($scope.event.created).format();
$scope.processEventStatus = JobDetailService.processEventStatus($scope.event);
$scope.hostResults = $stateParams.hostResults;
$scope.json = JobDetailService.processJson($scope.event);
if ($state.current.name == 'jobDetail.host-event.json'){
codeMirror();
}
try {
$scope.stdout = $scope.event.event_data.res.stdout
}
catch(err){
$scope.sdout = null;
}
$('#HostEvent').modal('show');
};
init();
}];
$scope.goPrev = function(){
var index = $scope.getActiveHostIndex() - 1;
var id = $scope.hostResults[index].id;
$state.go('jobDetail.host-event.details', {eventId: id});
};
var init = function(){
$scope.event = event;
JobDetailService.getJobEventChildren($stateParams.taskId).success(function(res){
$scope.hostResults = res.results;
});
$scope.json = JobDetailService.processJson($scope.event);
if ($state.current.name == 'jobDetail.host-event.json'){
codeMirror('#HostEvent-json', $scope.json);
}
try {
$scope.stdout = JobDetailService.processJson($scope.event.event_data.res)
if ($state.current.name == 'jobDetail.host-event.stdout'){
codeMirror('#HostEvent-stdout', $scope.stdout);
}
}
catch(err){
$scope.sdout = null;
}
$('#HostEvent').modal('show');
};
init();
}];

View File

@ -8,79 +8,51 @@
var hostEventModal = {
name: 'jobDetail.host-event',
url: '/host-event/:eventId',
url: '/task/:taskId/host-event/:eventId',
controller: 'HostEventController',
params:{
hostResults: {
value: null,
squash: false,
}
},
templateUrl: templateUrl('job-detail/host-event/host-event-modal'),
resolve: {
features: ['FeaturesService', function(FeaturesService){
return FeaturesService.get();
}],
event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
return JobDetailService.getRelatedJobEvents($stateParams.id, {
id: $stateParams.eventId
}).success(function(res){ return res.results[0]})
event: ['JobDetailService','$stateParams', 'moment', function(JobDetailService, $stateParams, moment) {
return JobDetailService.getRelatedJobEvents($stateParams.id, {
id: $stateParams.eventId,
}).then(function(res){
res.data.results[0].created = moment(res.data.results[0].created).format('MMMM Do YYYY, h:mm:ss a');
return res.data.results[0];
});
}]
},
onExit: function($state){
// close the modal
// close the modal
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
$('#HostEvent').modal('hide');
// hacky way to handle user browsing away via URL bar
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
};
var hostEventDetails = {
name: 'jobDetail.host-event.details',
url: '/details',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-details'),
resolve: {
features: ['FeaturesService', function(FeaturesService){
return FeaturesService.get();
}]
}
}
};
var hostEventJson = {
name: 'jobDetail.host-event.json',
url: '/json',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-json'),
resolve: {
features: ['FeaturesService', function(FeaturesService){
return FeaturesService.get();
}]
}
};
var hostEventTiming = {
name: 'jobDetail.host-event.timing',
url: '/timing',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-timing'),
resolve: {
features: ['FeaturesService', function(FeaturesService){
return FeaturesService.get();
}]
}
templateUrl: templateUrl('job-detail/host-event/host-event-json')
};
var hostEventStdout = {
name: 'jobDetail.host-event.stdout',
url: '/stdout',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-stdout'),
resolve: {
features: ['FeaturesService', function(FeaturesService){
return FeaturesService.get();
}]
}
templateUrl: templateUrl('job-detail/host-event/host-event-stdout')
};
export {hostEventDetails, hostEventJson, hostEventTiming, hostEventStdout, hostEventModal}
export {hostEventDetails, hostEventJson, hostEventStdout, hostEventModal}

View File

@ -4,7 +4,7 @@
* All Rights Reserved
*************************************************/
import {hostEventModal, hostEventDetails, hostEventTiming,
import {hostEventModal, hostEventDetails,
hostEventJson, hostEventStdout} from './host-event.route';
import controller from './host-event.controller';
@ -15,7 +15,6 @@
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(hostEventModal);
$stateExtender.addState(hostEventDetails);
$stateExtender.addState(hostEventTiming);
$stateExtender.addState(hostEventJson);
$stateExtender.addState(hostEventStdout);
}]);

View File

@ -1,6 +1,9 @@
@import "awx/ui/client/src/shared/branding/colors.less";
@import "awx/ui/client/src/shared/branding/colors.default.less";
.HostEvents .CodeMirror{
border: none;
}
.HostEvents .modal-footer{
border: 0;
margin-top: 0px;

View File

@ -72,7 +72,8 @@
if (filter == 'ok'){
return JobDetailService.getRelatedJobEvents($stateParams.id, {
host_name: $stateParams.hostName,
event: 'runner_on_ok',
or__field__event: 'runner_on_ok',
or__field__event: 'runner_on_ok_async',
changed: false
})
.success(function(res){
@ -92,7 +93,7 @@
}
if (filter == 'failed'){
return JobDetailService.getRelatedJobEvents($stateParams.id, {
host_name: $stateParams.hostName,
host_name: $stateParams.hostName,
failed: true})
.success(function(res){
$scope.results = res.results;

View File

@ -36,9 +36,9 @@
<td class=HostEvents-table--cell>
<!-- status circles -->
<a class="HostEvents-status">
<i class="fa fa-circle" ng-class="processEventStatus(event)"></i>
<i class="fa fa-circle" ng-class="processEventStatus(event).class"></i>
</a>
{{event.status}}
{{processEventStatus(event).status}}
</td>
<td class=HostEvents-table--cell>{{event.play}}</td>
<td class=HostEvents-table--cell>{{event.task}}</td>

View File

@ -0,0 +1,13 @@
.HostSummary-graph--successful{
text-anchor: start !important;
}
.HostSummary-graph--failed{
text-anchor: end !important;
}
.HostSummary-graph--changed{
text-anchor: end !important;
}
.HostSUmmary-graph--unreachable{}
.HostSummary-loading{
border: none;
}

View File

@ -0,0 +1,139 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$stateParams', 'Wait', 'JobDetailService', 'jobSocket', 'DrawGraph', function($scope, $rootScope, $stateParams, Wait, JobDetailService, jobSocket, DrawGraph){
var page_size = 200;
$scope.loading = $scope.hosts.length > 0 ? false : true;
$scope.filter = 'all';
$scope.search = null;
var buildTooltips = function(hosts){
// status waterfall: unreachable > failed > changed > ok > skipped
var count, grammar, text = {};
count = {
ok : _.filter(hosts, function(o){
return o.failures === 0 && o.changed === 0 && o.ok > 0;
}),
skipped : _.filter(hosts, function(o){
return o.skipped > 0;
}),
unreachable : _.filter(hosts, function(o){
return o.dark > 0;
}),
failures : _.filter(hosts, function(o){
return o.failed === true;
}),
changed : _.filter(hosts, function(o){
return o.changed > 0;
})
};
var grammar = function(n, status){
var dict = {
0: 'No host events were ',
1: ' host event was ',
2: ' host events were '
};
if (n >= 2){
return n + dict[2] + status;
}
else{
return n !== 0 ? n + dict[n] + status : dict[n] + status;
}
};
_.forIn(count, function(value, key){
text[key] = grammar(value.length, key);
});
return {count, text}
};
var socketListener = function(){
// emitted by the API in the same function used to persist host summary data
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
jobSocket.on('summary_complete', function(data) {
// discard socket msgs we don't care about in this context
if ($stateParams.id == data['unified_job_id']){
init()
}
});
// UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py
jobSocket.on('status_changed', function(data) {
if ($stateParams.id == data['unified_job_id']){
$scope.status = data['status'];
}
});
};
$scope.getNextPage = function(){
if ($scope.next){
JobDetailService.getNextPage($scope.next).success(function(res){
res.results.forEach(function(key, index){
$scope.hosts.push(res.results[index]);
})
$scope.hosts.push(res.results);
$scope.next = res.next;
});
}
};
$scope.search = function(){
Wait('start')
JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size,
host_name__icontains: $scope.searchTerm,
}).success(function(res){
$scope.hosts = res.results;
$scope.next = res.next;
Wait('stop')
})
};
$scope.setFilter = function(filter){
$scope.filter = filter;
var getAll = function(){
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size
}).success(function(res){
Wait('stop')
$scope.hosts = res.results;
$scope.next = res.next;
});
};
var getFailed = function(){
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size,
failed: true
}).success(function(res){
Wait('stop')
$scope.hosts = res.results;
$scope.next = res.next;
});
}
var get = filter == 'all' ? getAll() : getFailed()
};
$scope.$watchCollection('hosts', function(curr, prev){
$scope.tooltips = buildTooltips(curr);
DrawGraph({count: $scope.tooltips.count, resize:true});
});
var init = function(){
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {page_size: page_size})
.success(function(res){
$scope.hosts = res.results;
$scope.next = res.next;
Wait('stop');
});
JobDetailService.getJob($stateParams.id)
.success(function(res){
$scope.status = status;
});
};
socketListener();
init();
}];

View File

@ -0,0 +1,71 @@
<div id="hosts-summary-section" class="section">
<div class="JobDetail-searchHeaderRow" ng-hide="hosts.length == 0">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
<form ng-submit="search()">
<input type="text" class="JobDetail-searchInput form-control List-searchInput" id="search_host_summary_name" ng-model="searchTerm" placeholder="Host Name" />
<a class="List-searchInputIcon search-icon" ng-click="search()"><i class="fa fa-search"></i></a>
</form>
</div>
</div>
<div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" >
<button
ng-click="setFilter('all')"
class="JobDetail-tableToggle btn btn-xs" ng-class="{'btn-default': filter == 'failed', 'btn-primary': filter == 'all'}">All</button>
<button ng-click="setFilter('failed')"
ng-class="{'btn-default': filter == 'all', 'btn-primary': filter == 'failed'}" class="JobDetail-tableToggle btn btn-xs">Failed</button>
</div>
</div>
</div>
<div class="table-header" ng-hide="hosts.length == 0">
<table class="table table-condensed">
<thead>
<tr>
<th class="List-tableHeader col-lg-6 col-md-6 col-sm-6 col-xs-6">Hosts</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-6 col-md-5 col-sm-5 col-xs-5">Completed Tasks</th>
</tr>
</thead>
</table>
</div>
<div id="hosts-summary-table" class="table-detail" lr-infinite-scroll="getNextPage" scroll-threshold="10" time-threshold="500">
<table class="table">
<tbody>
<tr class="List-tableRow" ng-repeat="host in hosts track by $index" id="{{ host.id }}" ng-class-even="'List-tableRow--evenRow'" ng-class-odd="'List-tableRow--oddRow'">
<td class="List-tableCell name col-lg-6 col-md-6 col-sm-6 col-xs-6">
<a ui-sref="jobDetail.host-events({hostName: host.host_name})" aw-tool-tip="View events" data-placement="top">{{ host.host_name }}</a>
</td>
<td class="List-tableCell col-lg-6 col-md-5 col-sm-5 col-xs-5 badge-column">
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'ok'})" aw-tool-tip="{{ tooltips.text.ok }}" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok - host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'changed'})" aw-tool-tip="{{ tooltips.text.changed }}" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'skipped'})" aw-tool-tip="{{ tooltips.text.skipped }}" data-placement="top" ng-hide="host.skipped == 0"><span class="badge skipped-hosts">{{ host.skipped }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'unreachable'})" aw-tool-tip="{{ tooltips.text.unreachable}}" data-placement="top" ng-hide="host.dark == 0"><span class="badge unreachable-hosts">{{ host.dark }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.name, filter: 'failed'})" aw-tool-tip="{{ tooltips.text.failures}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a>
</td>
</tr>
<tr ng-show="hosts.length === 0 && status === 'pending'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Waiting...</td>
</tr>
<tr ng-show="hosts.length === 0 && status === 'running' ">
<td colspan="5" class="col-lg-12 HostSummary-loading">Loading...</td>
</tr>
<tr ng-show="status === 'failed' || status === 'successful' && hosts.length === 0 ">
<td colspan="2" class="col-lg-12 HostSummary-loading">No matching hosts</td>
</tr>
</tbody>
</table>
</div>
<div class="scroll-spinner" id="hostSummariesMoreRows">
<i class="fa fa-cog fa-spin"></i>
</div>
</div><!-- section -->
<div id="graph-section" class="JobDetail-graphSection">
<svg></svg>
</div>

View File

@ -2,28 +2,26 @@
@import '../shared/branding/colors.less';
@import '../shared/branding/colors.default.less';
@import '../shared/layouts/one-plus-one.less';
@breakpoint-md: 1200px;
@breakpoint-sm: 420px;
.JobDetail{
display: flex;
flex-direction: row;
.OnePlusOne-container(100%, @breakpoint-md);
}
.JobDetail-leftSide{
flex: 1 0 auto;
width: 50%;
padding-right: 20px;
.OnePlusOne-panel--left(100%, @breakpoint-md);
}
.JobDetail-rightSide{
flex: 1 0 auto;
width: 50%;
.OnePlusOne-panel--right(100%, @breakpoint-md);
}
.JobDetail-panelHeader{
display: flex;
height: 30px;
}
.JobDetail-expandContainer{
flex: 1;
margin: 0px;
@ -62,11 +60,17 @@
flex-wrap: wrap;
flex-direction: row;
padding-top: 25px;
@media screen and(max-width: @breakpoint-sm){
flex-direction: column;
}
}
.JobDetail-resultRow{
width: 50%;
display: flex;
@media screen and(max-width: @breakpoint-sm){
width: 100%;
}
}
.JobDetail-resultRowLabel{
@ -78,6 +82,9 @@
font-size: 14px;
font-weight: normal!important;
flex: 1 0 auto;
@media screen and(max-width: @breakpoint-md){
flex: 2.5 0 auto;
}
}
.JobDetail-resultRow--variables{
@ -109,10 +116,16 @@
flex-direction: row;
height: 50px;
margin-top: 25px;
@media screen and(max-width: @breakpoint-sm){
height: auto;
}
}
.JobDetail-searchContainer{
flex: 1 0 auto;
flex: 2 0 auto;
@media screen and(max-width: @breakpoint-sm){
margin-bottom: 0px;
}
}
.JobDetail-tableToggleContainer{

View File

@ -13,19 +13,18 @@
export default
[ '$location', '$rootScope', '$filter', '$scope', '$compile',
'$stateParams', '$log', 'ClearScope', 'GetBasePath', 'Wait',
'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed',
'DrawGraph', 'LoadHostSummary', 'ReloadHostSummaryList',
'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed',
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit',
'LoadPlays', 'LoadTasks', 'HostsEdit',
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
'EditSchedule', 'ParseTypeChange', 'JobDetailService',
function(
$location, $rootScope, $filter, $scope, $compile, $stateParams,
$log, ClearScope, GetBasePath, Wait, ProcessErrors,
SelectPlay, SelectTask, Socket, GetElapsed, DrawGraph,
LoadHostSummary, ReloadHostSummaryList, JobIsFinished,
SelectPlay, SelectTask, Socket, GetElapsed,
JobIsFinished,
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
PlaybookRun, LoadPlays, LoadTasks, LoadHosts,
PlaybookRun, LoadPlays, LoadTasks,
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService
) {
@ -82,38 +81,6 @@ export default
}
});
scope.hosts = [];
scope.$watch('hosts', function(hosts) {
for (var host in hosts) {
if (hosts[host].ok) {
hosts[host].okTip = hosts[host].ok;
hosts[host].okTip += (hosts[host].ok === 1) ? " host event was" : " host events were";
hosts[host].okTip += " ok.";
} else {
hosts[host].okTip = "No host events were ok.";
}
if (hosts[host].changed) {
hosts[host].changedTip = hosts[host].changed;
hosts[host].changedTip += (hosts[host].changed === 1) ? " host event" : " host events";
hosts[host].changedTip += " changed.";
} else {
hosts[host].changedTip = "No host events changed.";
}
if (hosts[host].failed) {
hosts[host].failedTip = hosts[host].failed;
hosts[host].failedTip += (hosts[host].failed === 1) ? " host event" : " host events";
hosts[host].failedTip += " failed.";
} else {
hosts[host].failedTip = "No host events failed.";
}
if (hosts[host].unreachable) {
hosts[host].unreachableTip = hosts[host].unreachable;
hosts[host].unreachableTip += (hosts[host].unreachable === 1) ? " host event was" : " hosts events were";
hosts[host].unreachableTip += " unreachable";
} else {
hosts[host].unreachableTip = "No host events were unreachable.";
}
}
});
scope.tasks = [];
scope.$watch('tasks', function(tasks) {
for (var task in tasks) {
@ -169,7 +136,6 @@ export default
scope.hostResults = [];
scope.hostResultsMaxRows = 200;
scope.hostSummariesMaxRows = 200;
scope.tasksMaxRows = 200;
scope.playsMaxRows = 200;
@ -177,7 +143,6 @@ export default
scope.playsLoading = true;
scope.tasksLoading = true;
scope.hostResultsLoading = true;
scope.hostSummariesLoading = true;
// Turn on the 'Waiting...' message until events begin arriving
scope.waiting = true;
@ -192,11 +157,9 @@ export default
scope.searchPlaysEnabled = true;
scope.searchTasksEnabled = true;
scope.searchHostsEnabled = true;
scope.searchHostSummaryEnabled = true;
scope.search_play_status = 'all';
scope.search_task_status = 'all';
scope.search_host_status = 'all';
scope.search_host_summary_status = 'all';
scope.haltEventQueue = false;
scope.processing = false;
@ -204,13 +167,6 @@ export default
scope.lessDetail = false;
scope.lessEvents = true;
scope.host_summary = {};
scope.host_summary.ok = 0;
scope.host_summary.changed = 0;
scope.host_summary.unreachable = 0;
scope.host_summary.failed = 0;
scope.host_summary.total = 0;
scope.jobData = {};
scope.jobData.hostSummaries = {};
@ -230,7 +186,6 @@ export default
url: GetBasePath('unified_jobs'),
field: 'status',
variable: 'status_choices',
// callback: 'choicesReady'
});
scope.eventsHelpText = "<p><i class=\"fa fa-circle successful-hosts-color\"></i> Successful</p>\n" +
@ -244,6 +199,7 @@ export default
data.event = data.event_name;
DigestEvent({ scope: scope, event: data });
}
UpdateDOM({ scope: scope });
});
}
openSocket();
@ -258,9 +214,6 @@ export default
if (data.status === 'failed' || data.status === 'canceled' ||
data.status === 'error' || data.status === 'successful' || data.status === 'running') {
$scope.liveEventProcessing = false;
if ($rootScope.jobDetailInterval) {
window.clearInterval($rootScope.jobDetailInterval);
}
if (!scope.pauseLiveEvents) {
$scope.$emit('LoadJob'); //this is what is used for the refresh
}
@ -274,10 +227,9 @@ export default
$rootScope.removeJobSummaryComplete = $rootScope.$on('JobSummaryComplete', function() {
// the job host summary should now be available from the API
$log.debug('Trigging reload of job_host_summaries');
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
});
if (scope.removeInitialLoadComplete) {
scope.removeInitialLoadComplete();
}
@ -292,75 +244,21 @@ export default
};
JobDetailService.getRelatedJobEvents(scope.job.id, params)
.success(function(data) {
if (data.results.length > 0) {
LoadHostSummary({
scope: scope,
data: data.results[0].event_data
});
}
UpdateDOM({ scope: scope });
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
if ($rootScope.jobDetailInterval) {
window.clearInterval($rootScope.jobDetailInterval);
}
$log.debug('Job completed!');
$log.debug(scope.jobData);
}
else {
api_complete = true; //trigger events to start processing
if ($rootScope.jobDetailInterval) {
window.clearInterval($rootScope.jobDetailInterval);
}
$rootScope.jobDetailInterval = setInterval(function() {
UpdateDOM({ scope: scope });
}, 2000);
UpdateDOM({ scope: scope})
}
});
if (scope.removeLoadHostSummaries) {
scope.removeLoadHostSummaries();
}
scope.removeHostSummaries = scope.$on('LoadHostSummaries', function() {
if(scope.job){
var params = {
page_size: scope.hostSummariesMaxRows,
order: 'host_name'
};
JobDetailService.getJobHostSummaries(scope.job.id, params)
.success(function(data) {
scope.next_host_summaries = data.next;
if (data.results.length > 0) {
// only dump what's in memory when job_host_summaries is available.
scope.jobData.hostSummaries = {};
}
data.results.forEach(function(event) {
var name;
if (event.host_name) {
name = event.host_name;
}
else {
name = "<deleted host>";
}
scope.jobData.hostSummaries[event.host] = {
id: event.host,
name: name,
ok: event.ok,
changed: event.changed,
unreachable: event.dark,
failed: event.failures,
status: (event.failed) ? 'failed' : 'successful'
};
});
scope.$emit('InitialLoadComplete');
});
}
});
if (scope.removeLoadHosts) {
scope.removeLoadHosts();
}
@ -376,81 +274,22 @@ export default
var params = {
parent: task.id,
event__startswith: 'runner',
page_size: scope.hostResultsMaxRows
};
JobDetailService.getRelatedJobEvents(scope.job.id, params)
.success(function(data) {
var idx, event, status, status_text, item, msg;
var event, status, status_text, item, msg;
if (data.results.length > 0) {
lastEventId = data.results[0].id;
}
scope.next_host_results = data.next;
for (idx=data.results.length - 1; idx >= 0; idx--) {
event = data.results[idx];
if (event.event === "runner_on_skipped") {
status = 'skipped';
}
else if (event.event === "runner_on_unreachable") {
status = 'unreachable';
}
else {
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
}
switch(status) {
case "successful":
status_text = 'OK';
break;
case "changed":
status_text = "Changed";
break;
case "failed":
status_text = "Failed";
break;
case "unreachable":
status_text = "Unreachable";
break;
case "skipped":
status_text = "Skipped";
}
if (event.event_data && event.event_data.res) {
item = event.event_data.res.item;
if (typeof item === "object") {
item = JSON.stringify(item);
}
}
msg = '';
if (event.event_data && event.event_data.res) {
if (typeof event.event_data.res === 'object') {
msg = event.event_data.res.msg;
} else {
msg = event.event_data.res;
}
}
if (event.event !== "runner_on_no_hosts") {
task.hostResults[event.id] = {
id: event.id,
status: status,
status_text: status_text,
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
created: event.created,
msg: msg,
counter: event.counter,
item: item
};
}
}
scope.$emit('LoadHostSummaries');
task.hostResults = JobDetailService.processHostEvents(data.results);
scope.$emit('InitialLoadComplete');
});
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
});
@ -559,10 +398,10 @@ export default
msg: 'Call to ' + url + '. GET returned: ' + status });
});
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
} else {
scope.$emit('LoadHostSummaries');
scope.$emit('InitialLoadComplete');
}
});
@ -570,12 +409,6 @@ export default
scope.removeLoadPlays();
}
scope.removeLoadPlays = scope.$on('LoadPlays', function(e, events_url) {
scope.host_summary.ok = 0;
scope.host_summary.changed = 0;
scope.host_summary.unreachable = 0;
scope.host_summary.failed = 0;
scope.host_summary.total = 0;
scope.jobData.plays = {};
var params = {
order_by: 'id'
@ -659,13 +492,6 @@ export default
scope.jobData.plays[event.id].status_text = 'No matching hosts';
scope.jobData.plays[event.id].status_tip = "Event ID: " + event.id + "<br />Status: No matching hosts";
}
scope.host_summary.ok += ok;
scope.host_summary.changed += changed;
scope.host_summary.unreachable += (event.unreachable_count) ? event.unreachable_count : 0;
scope.host_summary.failed += failed;
scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable +
scope.host_summary.failed;
});
if (scope.activePlay && scope.jobData.plays[scope.activePlay]) {
scope.jobData.plays[scope.activePlay].playActiveClass = 'JobDetail-tableRow--selected';
@ -673,7 +499,7 @@ export default
scope.$emit('LoadTasks', events_url);
});
});
if (scope.removeLoadJob) {
scope.removeLoadJob();
@ -685,7 +511,6 @@ export default
scope.playsLoading = true;
scope.tasksLoading = true;
scope.hostResultsLoading = true;
scope.LoadHostSummaries = true;
// Load the job record
JobDetailService.getJob(job_id)
@ -769,7 +594,6 @@ export default
scope.playsLoading = false;
scope.tasksLoading = false;
scope.hostResultsLoading = false;
scope.hostSummariesLoading = false;
}
else {
scope.job_status.finished = null;
@ -811,10 +635,6 @@ export default
// First time. User just loaded page.
scope.$emit('LoadJob');
}
else {
// Check if the graph needs to redraw
setTimeout(function() { DrawGraph({ scope: scope, resize: true }); }, 500);
}
});
scope.adjustSize = function() {
@ -859,11 +679,6 @@ export default
$('#tasks-table-detail').height(120 + (height * 0.20));
$('#hosts-table-detail').height(150 + (height * 0.70));
}
// Summary table height adjusting.
height = ($('#job-detail-container').height() / 2) - $('#hosts-summary-section .JobDetail-searchHeaderRow').outerHeight() -
$('#hosts-summary-section .table-header').outerHeight() - 20;
// $('#hosts-summary-table').height(height);
//$('#hosts-summary-table').mCustomScrollbar("update");
scope.$emit('RefreshCompleted');
};
@ -912,52 +727,6 @@ export default
}
};
scope.toggleSummary = function(hide) {
var docw, doch, height = $('#job-detail-container').height(), slide_width;
if (!hide) {
docw = $(window).width();
doch = $(window).height();
slide_width = (docw < 840) ? '100%' : '80%';
$('#summary-button').hide();
$('.overlay').css({
width: $(document).width(),
height: $(document).height()
}).show();
// Adjust the summary table height
$('#job-summary-container .job_well').height(height - 18).css({
'box-shadow': '-3px 3px 5px 0 #ccc'
});
height = Math.floor($('#job-detail-container').height() * 0.5) -
$('#hosts-summary-section .header').outerHeight() -
$('#hosts-summary-section .table-header').outerHeight() -
$('#hide-summary-button').outerHeight() -
$('#summary-search-section').outerHeight() -
$('#hosts-summary-section .header').outerHeight() -
$('#hosts-summary-section .legend').outerHeight();
$('#hosts-summary-table').height(height - 50);
//$('#hosts-summary-table').mCustomScrollbar("update");
$('#hide-summary-button').show();
$('#job-summary-container').css({
top: 0,
right: 0,
width: slide_width,
'z-index': 1090,
'padding-right': '15px',
'padding-left': '15px'
}).show('slide', {'direction': 'right'});
setTimeout(function() { DrawGraph({ scope: scope, resize: true }); }, 500);
}
else {
$('.overlay').hide();
$('#summary-button').show();
$('#job-summary-container').hide('slide', {'direction': 'right'});
}
};
scope.objectIsEmpty = function(obj) {
if (angular.isObject(obj)) {
return (Object.keys(obj).length > 0) ? false : true;
@ -965,6 +734,17 @@ export default
return true;
};
scope.toggleLessEvents = function() {
if (!scope.lessEvents) {
$('#events-summary').slideUp(200);
scope.lessEvents = true;
}
else {
$('#events-summary').slideDown(200);
scope.lessEvents = false;
}
};
scope.toggleLessStatus = function() {
if (!scope.lessStatus) {
$('#job-status-form').slideUp(200);
@ -987,18 +767,6 @@ export default
}
};
scope.toggleLessEvents = function() {
if (!scope.lessEvents) {
$('#events-summary').slideUp(200);
scope.lessEvents = true;
}
else {
$('#events-summary').slideDown(200);
scope.lessEvents = false;
DrawGraph({scope:scope});
}
};
scope.filterPlayStatus = function() {
scope.search_play_status = (scope.search_play_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
@ -1037,6 +805,9 @@ export default
scope.searchTasksEnabled = true;
}
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
if (scope.search_task_status === 'failed'){
params.failed = true;
}
LoadTasks({
scope: scope
});
@ -1058,66 +829,24 @@ export default
scope.searchHostsEnabled = true;
}
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadHosts({
scope: scope
scope.hostResultsLoading = true;
var params = {
parent: scope.selectedTask,
event__startswith: 'runner',
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
host_name__icontains: scope.search_host_name
}
if (scope.search_host_status === 'failed'){
params.failed = true;
}
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostEvents(res.results)
scope.hostResultsLoading = false;
});
}
};
scope.searchHostsKeyPress = function(e) {
if (e.keyCode === 13) {
scope.searchHosts();
e.stopPropagation();
}
};
scope.searchHostSummary = function() {
if (scope.search_host_summary_name) {
scope.searchHostSummaryEnabled = false;
}
else {
scope.searchHostSummaryEnabled = true;
}
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
ReloadHostSummaryList({
scope: scope
});
}
};
scope.searchHostSummaryKeyPress = function(e) {
if (e.keyCode === 13) {
scope.searchHostSummary();
e.stopPropagation();
}
};
scope.filterTaskStatus = function() {
scope.search_task_status = (scope.search_task_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadTasks({
scope: scope
});
}
};
scope.filterHostStatus = function() {
scope.search_host_status = (scope.search_host_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadHosts({
scope: scope
});
}
};
scope.filterHostSummaryStatus = function() {
scope.search_host_summary_status = (scope.search_host_summary_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
ReloadHostSummaryList({
scope: scope
});
}
};
if (scope.removeDeleteFinished) {
scope.removeDeleteFinished();
@ -1354,41 +1083,6 @@ export default
}
};
scope.hostSummariesScrollDown = function() {
// check for more hosts when user scrolls to bottom of host summaries list...
if (((!scope.liveEventProcessing) || (scope.liveEventProcessing && scope.pauseLiveEvents)) && scope.next_host_summaries) {
scope.hostSummariesLoading = true;
JobDetailService.getNextPage(scope.next_host_summaries)
.success(function(data) {
scope.next_host_summaries = data.next;
data.results.forEach(function(row) {
var name;
if (row.host_name) {
name = row.host_name;
}
else {
name = "<deleted host>";
}
scope.hosts.push({
id: row.id,
name: name,
ok: row.ok,
changed: row.changed,
unreachable: row.dark,
failed: row.failures
});
});
$('#hostSummariesMoreRows').fadeOut();
scope.hostSummariesLoading = false;
})
.error(function(data, status) {
$('#hostSummariesMoreRows').fadeOut();
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + scope.next_host_summaries + '. GET returned: ' + status });
});
}
};
scope.refresh = function(){
$scope.$emit('LoadJob');
};

View File

@ -62,7 +62,7 @@
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Started</label>
<div class="JobDetail-resultRowText">{{ job_status.started | date:'MM/dd/yy HH:mm:ss' }}</div>
<div class="JobDetail-resultRowText">{{ job_status.started | date:'M/d/yy HH:mm:ss a' }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_type">
@ -72,7 +72,7 @@
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Finished</label>
<div class="JobDetail-resultRowText">{{ job_status.finished | date:'MM/dd/yy HH:mm:ss' }}</div>
<div class="JobDetail-resultRowText">{{ job_status.finished | date:'M/d/yy HH:mm:ss a' }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="created_by">
@ -344,7 +344,7 @@
<tbody>
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="result in results = (hostResults) track by $index">
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column">
<a ui-sref="jobDetail.host-event.details({eventId: result.id, hostResults: hostResults})" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a>
<a ui-sref="jobDetail.host-event.details({eventId: result.id, taskId: selectedTask})" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a>
</td>
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3 item-column">{{ result.item }}</td>
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td>
@ -388,75 +388,10 @@
</div>
</div>
<div id="events-summary" style="display:none">
<div id="hosts-summary-section" class="section">
<div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
<input type="text" class="JobDetail-searchInput form-control List-searchInput" id="search_host_summary_name" ng-model="search_host_summary_name" placeholder="Host Name" ng-keypress="searchHostSummaryKeyPress($event)" >
<a class="List-searchInputIcon search-icon" ng-show="searchHostSummaryEnabled" ng-click="searchHostSummary()"><i class="fa fa-search"></i></a>
<a class="List-searchInputIcon search-icon" ng-show="!searchHostSummaryEnabled" ng-click="search_host_summary_name=''; searchHostSummary()"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" aw-toggle-button data-after-toggle="filterHostSummaryStatus">
<button class="JobDetail-tableToggle btn btn-xs btn-primary active">All</button>
<button class="JobDetail-tableToggle btn btn-xs btn-default">Failed</button>
</div>
</div>
</div>
<div class="table-header">
<table class="table table-condensed">
<thead>
<tr>
<th class="List-tableHeader col-lg-6 col-md-6 col-sm-6 col-xs-6">Hosts</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-6 col-md-5 col-sm-5 col-xs-5">Completed Tasks</th>
</tr>
</thead>
</table>
</div>
<div id="hosts-summary-table" class="table-detail" lr-infinite-scroll="hostSummariesScrollDown" scroll-threshold="10" time-threshold="500">
<table class="table">
<tbody>
<tr class="List-tableRow" ng-repeat="host in summaryList = (hosts) track by $index" id="{{ host.id }}" ng-class-even="'List-tableRow--evenRow'" ng-class-odd="'List-tableRow--oddRow'">
<td class="List-tableCell name col-lg-6 col-md-6 col-sm-6 col-xs-6">
<a ui-sref="jobDetail.host-events({hostName: host.name})" aw-tool-tip="View events" data-placement="top">{{ host.name }}</a>
</td>
<td class="List-tableCell col-lg-6 col-md-5 col-sm-5 col-xs-5 badge-column">
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'ok'})" aw-tool-tip="{{ host.okTip }}" data-tip-watch="host.okTip" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'changed'})" aw-tool-tip="{{ host.changedTip }}" data-tip-watch="host.changedTip" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'unreachable'})" aw-tool-tip="{{ host.unreachableTip }}" data-tip-watch="host.unreachableTip" data-placement="top" ng-hide="host.unreachable == 0"><span class="badge unreachable-hosts">{{ host.unreachable }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'failed'})" aw-tool-tip="{{ host.failedTip }}" data-tip-watch="host.failedTip" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failed }}</span></a>
</td>
</tr>
<tr ng-show="summaryList.length === 0 && waiting">
<td colspan="5" class="col-lg-12 loading-info">Waiting...</td>
</tr>
<tr ng-show="summaryList.length === 0 && hostSummariesLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info">Loading...</td>
</tr>
<tr ng-show="summaryList.length === 0 && !hostSummariesLoading && !waiting">
<td colspan="2" class="col-lg-12 loading-info">No matching hosts</td>
</tr>
</tbody>
</table>
</div>
<div class="scroll-spinner" id="hostSummariesMoreRows">
<i class="fa fa-cog fa-spin"></i>
</div>
</div><!-- section -->
<div id="graph-section" class="JobDetail-graphSection">
<svg width="100%" height="100%"></svg>
</div>
</div>
<!--end of events summary-->
<!-- Host Summary view -->
<div id="events-summary" ng-hide="lessEvents">
<div ui-view="host-summary@jobDetail"></div>
</div>
</div>
<!-- end of events summary-->

View File

@ -1,16 +1,15 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../shared/template-url/template-url.factory';
import HostSummaryController from './host-summary/host-summary.controller';
export default {
name: 'jobDetail',
url: '/jobs/:id',
templateUrl: templateUrl('job-detail/job-detail'),
controller: 'JobDetailController',
ncyBreadcrumb: {
parent: 'jobs',
label: "{{ job.id }} - {{ job.name }}"
@ -26,10 +25,32 @@ export default {
endpoint: "job_events"
});
$rootScope.event_socket.init();
// returns should really be providing $rootScope.event_socket
// otherwise, we have to inject the entire $rootScope into the controller
return true;
} else {
return true;
}
}],
jobSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
var job_socket = Socket({
scope: $rootScope,
endpoint: "jobs"
});
job_socket.init();
// returns should really be providing $rootScope.job_socket
// otherwise, we have to inject the entire $rootScope into the controller
return job_socket;
}]
},
views: {
'': {
templateUrl: templateUrl('job-detail/job-detail'),
controller: 'JobDetailController',
},
'host-summary@jobDetail': {
templateUrl: templateUrl('job-detail/host-summary/host-summary'),
controller: HostSummaryController
}
}
};

View File

@ -1,18 +1,17 @@
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
return {
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
return {
/*
For ES6
it might be useful to set some default params here, e.g.
getJobHostSummaries: function(id, page_size=200, order='host_name'){}
without ES6, we'd have to supply defaults like this:
this.page_size = params.page_size ? params.page_size : 200;
*/
/*
* For ES6
* it might be useful to set some default params here, e.g.
* getJobHostSummaries: function(id, page_size=200, order='host_name'){}
* without ES6, we'd have to supply defaults like this:
* this.page_size = params.page_size ? params.page_size : 200;
*/
// the the API passes through Ansible's event_data response
// we need to massage away the verbose and redundant properties
processJson: function(data){
// a deep copy
var result = $.extend(true, {}, data);
@ -28,7 +27,7 @@ export default
// remove ignored properties
Object.keys(result).forEach(function(key, index){
if (ignored.indexOf(key) > -1) {
delete result[key]
delete result[key];
}
});
@ -37,154 +36,209 @@ export default
result.event_data = {};
Object.keys(data.event_data.res).forEach(function(key, index){
if (ignored.indexOf(key) > -1) {
return
return;
}
else{
result.event_data[key] = data.event_data.res[key];
}
});
}
catch(err){result.event_data = null;}
catch(err){result.event_data = undefined;}
return result
return result;
},
processEventStatus: function(event){
// Generate a helper class for job_event statuses
// the stack for which status to display is
// unreachable > failed > changed > ok
// uses the API's runner events and convenience properties .failed .changed to determine status.
// see: job_event_callback.py
if (event.event == 'runner_on_unreachable'){
event.status = 'Unreachable';
return 'HostEvents-status--unreachable'
}
// equiv to 'runner_on_error' && 'runner on failed'
if (event.failed){
event.status = 'Failed';
return 'HostEvents-status--failed'
}
// catch the changed case before ok, because both can be true
if (event.changed){
event.status = 'Changed';
return 'HostEvents-status--changed'
}
if (event.event == 'runner_on_ok'){
event.status = 'OK';
return 'HostEvents-status--ok'
}
if (event.event == 'runner_on_skipped'){
event.status = 'Skipped';
return 'HostEvents-status--skipped'
}
else{
// study a case where none of these apply
}
},
// GET events related to a job run
// e.g.
// ?event=playbook_on_stats
// ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter
getRelatedJobEvents: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_events/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
// ?&event=playbook_on_start == ?event=playbook_on_stats
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET job host summaries related to a job run
// e.g. ?page_size=200&order=host_name
getJobHostSummaries: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_host_summaries/?'
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET job plays related to a job run
// e.g. ?page_size=200
getJobPlays: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_plays/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJobTasks: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_tasks/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJob: function(id){
var url = GetBasePath('jobs');
url = url + id;
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET next set of paginated results
// expects 'next' param returned by the API e.g.
// "/api/v1/jobs/51/job_plays/?order_by=id&page=2&page_size=1"
getNextPage: function(url){
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
// Return Ansible's passed-through response msg on a job_event
processEventMsg: function(event){
return typeof event.event_data.res === 'object' ? event.event_data.res.msg : event.event_data.res;
},
// Return only Ansible's passed-through response item on a job_event
processEventItem: function(event){
try{
var item = event.event_data.res.item;
return typeof item === 'object' ? JSON.stringify(item) : item;
}
}
}
];
catch(err){return;}
},
// Generate a helper class for job_event statuses
// the stack for which status to display is
// unreachable > failed > changed > ok
// uses the API's runner events and convenience properties .failed .changed to determine status.
// see: job_event_callback.py for more filters to support
processEventStatus: function(event){
if (event.event === 'runner_on_unreachable'){
return {
class: 'HostEvents-status--unreachable',
status: 'unreachable'
};
}
// equiv to 'runner_on_error' && 'runner on failed'
if (event.failed){
return {
class: 'HostEvents-status--failed',
status: 'failed'
}
}
// catch the changed case before ok, because both can be true
if (event.changed){
return {
class: 'HostEvents-status--changed',
status: 'changed'
};
}
if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok'){
return {
class: 'HostEvents-status--ok',
status: 'ok'
};
}
if (event.event === 'runner_on_skipped'){
return {
class: 'HostEvents-status--skipped',
status: 'skipped'
};
}
},
// Consumes a response from this.getRelatedJobEvents(id, params)
// returns an array for view logic to iterate over to build host result rows
processHostEvents: function(data){
var self = this;
var results = [];
data.forEach(function(event){
if (event.event !== 'runner_on_no_hosts'){
var status = self.processEventStatus(event);
var msg = self.processEventMsg(event);
var item = self.processEventItem(event);
results.push({
id: event.id,
status: status.status,
status_text: _.head(status.status).toUpperCase() + _.tail(status.status),
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
created: event.created,
msg: typeof msg === 'undefined' ? undefined : msg,
item: typeof item === 'undefined' ? undefined : item
});
}
});
return results;
},
// GET events related to a job run
// e.g.
// ?event=playbook_on_stats
// ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter
getRelatedJobEvents: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_events/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
// ?&event=playbook_on_start == ?event=playbook_on_stats
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJobEventChildren: function(id){
var url = GetBasePath('job_events');
url = url + id + '/children/';
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET job host summaries related to a job run
// e.g. ?page_size=200&order=host_name
getJobHostSummaries: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_host_summaries/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET job plays related to a job run
// e.g. ?page_size=200
getJobPlays: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_plays/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJobTasks: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_tasks/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJob: function(id){
var url = GetBasePath('jobs');
url = url + id;
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET next set of paginated results
// expects 'next' param returned by the API e.g.
// "/api/v1/jobs/51/job_plays/?order_by=id&page=2&page_size=1"
getNextPage: function(url){
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
};
}];

View File

@ -9,7 +9,7 @@ export default
angular.module('AllJobsDefinition', ['sanitizeFilter', 'capitalizeFilter'])
.value( 'AllJobsList', {
name: 'all_jobs',
name: 'jobs',
basePath: 'unified_jobs',
iterator: 'all_job',
editTitle: 'All Jobs',
@ -18,8 +18,9 @@ export default
well: false,
fields: {
status: {
label: 'Status',
columnClass: 'List-staticColumn--smallStatus',
label: '',
searchLabel: 'Status',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus',
awToolTip: "{{ all_job.status_tip }}",
awTipPlacement: "right",
dataTitle: "{{ all_job.status_popover_title }}",
@ -30,13 +31,10 @@ export default
searchType: 'select',
nosort: true,
searchOptions: [
{ name: "Success", value: "successful" },
{ name: "Error", value: "error" },
{ name: "Failed", value: "failed" },
{ name: "Canceled", value: "canceled" }
]
},
id: {
key: true,
label: 'ID',
ngClick:"viewJobDetails(all_job)",
searchType: 'int',
@ -45,6 +43,7 @@ export default
dataPlacement: 'top'
},
name: {
key: true,
label: 'Name',
columnClass: 'col-lg-3 col-md-3 col-sm-4 col-xs-6',
ngClick: "viewJobDetails(all_job)",

View File

@ -11,15 +11,13 @@ export default
name: 'job_templates',
iterator: 'job_template',
// selectTitle: 'Add Job Template',
editTitle: 'Job Templates',
listTitle: 'Job Templates',
// selectInstructions: "Click on a row to select it, and click Finished when done. Use the <i class=\"icon-plus\"></i> " +
// "button to create a new job template.",
index: false,
hover: true,
well: true,
searchSize: 'col-lg-8 col-md-8 col-sm-12 col-xs-12',
fields: {
name: {
key: true,

View File

@ -16,6 +16,7 @@ export default
hover: true,
well: true,
listTitle: 'Jobs',
searchSize: 'col-lg-8 col-md-8 col-sm-12 col-xs-12',
fields: {
status: {
@ -27,23 +28,9 @@ export default
searchable: true,
nosort: true,
searchType: 'select',
searchOptions: [
{ name: "Success", value: "successful" },
{ name: "Error", value: "error" },
{ name: "Failed", value: "failed" },
{ name: "Canceled", value: "canceled" }
]
searchOptions: [],
searchLabel: 'Status'
},
/*
id: {
label: 'ID',
key: true,
noLink: true, //undocumented: 'key' above will automatically made the fields a link, but 'noLink' will override this setting
desc: true,
searchType: 'int',
columnClass: 'col-xs-2 List-staticColumnAdjacent',
},
*/
name: {
key: true,
label: 'Name',

View File

@ -52,7 +52,7 @@ export default
filter: "longDate",
searchable: false,
columnClass: "List-staticColumn--schedulerTime hidden-xs"
}
},
},
actions: {

View File

@ -12,12 +12,12 @@ export default
'SchedulesList',
'Rest' , 'ProcessErrors', 'managementJobsListObject', '$rootScope',
'$state','$scope',
function( Wait, $location, $compile, CreateDialog,
function( Wait, $location, $compile, CreateDialog,
GetBasePath, SearchInit, PaginateInit,
SchedulesList,
Rest, ProcessErrors, managementJobsListObject, $rootScope,
$state, $scope) {
var defaultUrl = GetBasePath('system_job_templates');
var getManagementJobs = function(){
@ -32,7 +32,7 @@ export default
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
});
};
getManagementJobs();
getManagementJobs();
var scope = $rootScope.$new(),
parent_scope = scope,
list = managementJobsListObject;
@ -51,9 +51,6 @@ export default
if (scope.searchCleanup) {
scope.searchCleanup();
}
// if (!Empty(parent_scope) && parent_scope.restoreSearch) {
// parent_scope.restoreSearch();
// }
else {
Wait('stop');
}
@ -69,6 +66,7 @@ export default
height: 470,
minWidth: 200,
callback: 'PromptForDaysFacts',
resizable: false,
onOpen: function(){
scope.$watch('prompt_for_days_facts_form.$invalid', function(invalid) {
if (invalid === true) {
@ -113,41 +111,40 @@ export default
fieldScope.keep_amount = 30;
fieldScope.granularity_keep_amount = 1;
},
buttons: [{
"label": "Cancel",
"onClick": function() {
$(this).dialog('close');
buttons: [
{
"label": "Launch",
"onClick": function() {
var extra_vars = {
"older_than": scope.keep_amount+scope.keep_unit.value,
"granularity": scope.granularity_keep_amount+scope.granularity_keep_unit.value
},
data = {};
data.extra_vars = JSON.stringify(extra_vars);
},
"icon": "fa-times",
"class": "btn btn-default",
"id": "prompt-for-days-facts-cancel"
},{
"label": "Launch",
"onClick": function() {
var extra_vars = {
"older_than": scope.keep_amount+scope.keep_unit.value,
"granularity": scope.granularity_keep_amount+scope.granularity_keep_unit.value
Rest.setUrl(defaultUrl);
Rest.post(data)
.success(function() {
Wait('stop');
$("#prompt-for-days-facts").dialog("close");
$("#configure-tower-dialog").dialog('close');
$location.path('/jobs/');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status });
});
},
data = {};
data.extra_vars = JSON.stringify(extra_vars);
Rest.setUrl(defaultUrl);
Rest.post(data)
.success(function() {
Wait('stop');
$("#prompt-for-days-facts").dialog("close");
$("#configure-tower-dialog").dialog('close');
$location.path('/jobs/');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status });
});
"class": "btn btn-primary",
"id": "prompt-for-days-facts-launch",
},
"icon": "fa-rocket",
"class": "btn btn-primary",
"id": "prompt-for-days-facts-launch"
{
"label": "Cancel",
"onClick": function() {
$(this).dialog('close');
},
"class": "btn btn-default",
"id": "prompt-for-days-facts-cancel"
}]
});
@ -162,12 +159,8 @@ export default
});
};
$scope.submitJob = function (id, name) {
$scope.submitJob = function (id, name, card) {
Wait('start');
if(this.configure_job.job_type === "cleanup_facts"){
scope.submitCleanupJob(id, name);
}
else {
defaultUrl = GetBasePath('system_job_templates')+id+'/launch/';
CreateDialog({
id: 'prompt-for-days' ,
@ -177,6 +170,7 @@ export default
height: 300,
minWidth: 200,
callback: 'PromptForDays',
resizable: false,
onOpen: function(){
scope.$watch('prompt_for_days_form.$invalid', function(invalid) {
if (invalid === true) {
@ -191,16 +185,8 @@ export default
scope.prompt_for_days_form.$setPristine();
scope.prompt_for_days_form.$invalid = false;
},
buttons: [{
"label": "Cancel",
"onClick": function() {
$(this).dialog('close');
},
"icon": "fa-times",
"class": "btn btn-default",
"id": "prompt-for-days-cancel"
},{
buttons: [
{
"label": "Launch",
"onClick": function() {
var extra_vars = {"days": scope.days_to_keep },
@ -220,9 +206,17 @@ export default
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status });
});
},
"icon": "fa-rocket",
"class": "btn btn-primary",
"id": "prompt-for-days-launch"
},
{
"label": "Cancel",
"onClick": function() {
$(this).dialog('close');
},
"class": "btn btn-default",
"id": "prompt-for-days-cancel"
}]
});
@ -235,6 +229,14 @@ export default
$('#prompt-for-days').dialog('open');
Wait('stop');
});
};
$scope.chooseRunJob = function(id, name) {
if(id === 4) {
// Run only for 'Cleanup Fact Details'
$scope.submitCleanupJob(id, name);
} else {
$scope.submitJob(id, name);
}
};

View File

@ -7,7 +7,7 @@
<h3 class="MgmtCards-label"> {{ card.name }}</h3>
<div class="MgmtCards-actionItems">
<button class="MgmtCards-actionItem List-actionButton"
ng-click='submitCleanupJob(card.id, card.name)'>
ng-click='chooseRunJob(card.id, card.name)'>
<i class="MgmtCards-actionItemIcon fa fa-rocket"></i>
</button>
<button class="MgmtCards-actionItem List-actionButton"
@ -21,4 +21,4 @@
<p>{{card.description || "Place organization description here"}}</p>
<!-- End Card Body -->
</div>
</div>
</div>

View File

@ -114,3 +114,12 @@
margin-right: 0px;
}
}
#prompt-for-days-facts, #prompt-for-days {
overflow-x: hidden;
font-family: "Open Sans";
.label-text {
text-transform: uppercase;
font-weight: normal;
}
}

View File

@ -155,13 +155,13 @@ export default
Rest.post({})
.then(function () {
ngToast.success({
content: `<i class="fa fa-check-circle Toast-successIcon"></i> Test Notification Success: <b>${name}</b> `,
content: `<i class="fa fa-check-circle Toast-successIcon"></i> <b>${name}:</b> Notification Succeeded.`,
});
})
.catch(function () {
ngToast.danger({
content: 'Test Notification Failure'
content: `<i class="fa fa-check-circle Toast-successIcon"></i> <b>${name}:</b> Notification Failed.`,
});
});
};

View File

@ -21,7 +21,6 @@ export function PortalModeJobsController($scope, $state, $rootScope, GetBasePath
id: 'portal-jobs',
mode: 'edit',
scope: $scope,
searchSize: 'col-md-10 col-xs-12'
});
SearchInit({

View File

@ -34,7 +34,15 @@ export default
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}],
JobTemplateExtraVars: ['Rest', 'GetBasePath', 'ToJSON', '$stateParams', function(Rest, GetBasePath, ToJSON, $stateParams) {
var defaultUrl = GetBasePath('job_templates') + $stateParams.id + '/';
Rest.setUrl(defaultUrl);
return Rest.get().then(function(res){
// handle unescaped newlines
return JSON.parse(JSON.stringify(res.data.extra_vars))
});
}]
}
});
$stateExtender.addState({

View File

@ -1,4 +1,4 @@
export default ['$compile', '$state', '$stateParams', 'AddSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', function($compile, $state, $stateParams, AddSchedule, Wait, $scope, $rootScope, CreateSelect2) {
export default ['$compile', '$state', '$stateParams', 'AddSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'JobTemplateExtraVars', function($compile, $state, $stateParams, AddSchedule, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange, JobTemplateExtraVars) {
$scope.$on("ScheduleFormCreated", function(e, scope) {
$scope.hideForm = false;
$scope = angular.extend($scope, scope);
@ -41,10 +41,35 @@ export default ['$compile', '$state', '$stateParams', 'AddSchedule', 'Wait', '$s
$scope.hideForm = true;
$scope.formCancel = function() {
$state.go("^");
};
$scope.parseType = 'yaml';
$scope.extraVars = JobTemplateExtraVars === '' ? '---' : JobTemplateExtraVars;
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars'
});
$scope.$watch('extraVars', function(){
if ($scope.parseType === 'yaml'){
try{
$scope.serializedExtraVars = jsyaml.safeLoad($scope.extraVars);
}
catch(err){ return; }
}
else if ($scope.parseType === 'json'){
try{
$scope.serializedExtraVars = JSON.parse($scope.extraVars);
}
catch(err){ return; }
}
});
AddSchedule({
scope: $scope,
callback: 'SchedulesRefresh',

View File

@ -1,4 +1,4 @@
export default ['$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', function($compile, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, CreateSelect2) {
export default ['$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', function($compile, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange) {
$scope.$on("ScheduleFormCreated", function(e, scope) {
$scope.hideForm = false;
$scope = angular.extend($scope, scope);
@ -41,13 +41,49 @@ export default ['$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$
});
$scope.isEdit = true;
$scope.hideForm = true;
$scope.parseType = 'yaml';
$scope.formCancel = function() {
$state.go("^");
}
$scope.$on('ScheduleFound', function(){
if ($scope.parseType === 'yaml'){
try{
$scope.extraVars = '---\n' + jsyaml.safeDump($scope.serializedExtraVars);
}
catch(err){ return; }
}
else if ($scope.parseType === 'json'){
try{
$scope.extraVars = JSON.stringify($scope.serializedExtraVars, null, ' ');
}
catch(err){ return; }
}
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars'
});
});
$scope.$watch('extraVars', function(){
if ($scope.parseType === 'yaml'){
try{
$scope.serializedExtraVars = jsyaml.safeLoad($scope.extraVars);
}
catch(err){ return; }
}
else if ($scope.parseType === 'json'){
try{
$scope.serializedExtraVars = JSON.parse($scope.extraVars);
}
catch(err){ return; }
}
});
EditSchedule({
scope: $scope,
id: parseInt($stateParams.schedule_id),

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,8 @@ export default ['$scope', 'Refresh', 'tagSearchService',
function($scope, Refresh, tagSearchService) {
// JSONify passed field elements that can be searched
$scope.list = JSON.parse($scope.list);
// Access config lines from list spec
$scope.listConfig = $scope.$parent.list;
// Grab options for the left-dropdown of the searchbar
tagSearchService.getSearchTypes($scope.list, $scope.endpoint)
.then(function(searchTypes) {

View File

@ -1,5 +1,5 @@
<div class="TagSearch row">
<div class="col-lg-4 col-md-8 col-sm-12 col-xs-12">
<div ng-class="listConfig.searchSize || 'col-lg-4 col-md-8 col-sm-12 col-xs-12'">
<div class="TagSearch-bar">
<div class="TagSearch-typeDropdown"
ng-click="toggleTypeDropdown()"

View File

@ -35,6 +35,10 @@ angular.module('ApiLoader', ['Utilities'])
.success(function (data) {
data.base = base;
$rootScope.defaultUrls = data;
// tiny hack to side-step api/v1/job_events not being a visible endpoint @ GET api/v1/
if (!$rootScope.defaultUrls['job_events']){
$rootScope.defaultUrls['job_events'] = '/api/v1/job_events/';
}
Store('api', data);
})
.error(function (data, status) {

View File

@ -171,10 +171,11 @@
<div style="padding-bottom:15px;">For facts collected older than the time period specified,
save one fact scan (snapshot) per time window (frequency).
For example, facts older than 30 days are purged, while one
weekly fact scan is kept.<br>
Caution: Setting both numerical variables to "0" will delete all facts.<br>
weekly fact scan is kept.<br> <br>
CAUTION: Setting both numerical variables to "0" will delete all facts.<br><br>
</div>
<div class="form-group ">
<div class="form-group">
<label for="description">
<span class="label-text">
Select a time period after which to remove old facts
@ -197,7 +198,7 @@
<label for="description">
<span class="label-text">
Select a frequency for snapshot retention
</span>
</span>
</label>
<div class="row">
<div class="col-xs-4">