mirror of
https://github.com/ansible/awx.git
synced 2026-03-04 18:21:03 -03:30
event processing for details panel and initial stats bar integration
This commit is contained in:
@@ -199,3 +199,85 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Status Bar -----------------------------------------------------------------------------
|
||||||
|
.HostStatusBar {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-ok,
|
||||||
|
.HostStatusBar-changed,
|
||||||
|
.HostStatusBar-unreachable,
|
||||||
|
.HostStatusBar-failed,
|
||||||
|
.HostStatusBar-skipped,
|
||||||
|
.HostStatusBar-noData {
|
||||||
|
height: 15px;
|
||||||
|
border-top: 5px solid @default-bg;
|
||||||
|
border-bottom: 5px solid @default-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-ok {
|
||||||
|
background-color: @default-succ;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-changed {
|
||||||
|
background-color: @default-warning;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-unreachable {
|
||||||
|
background-color: @default-unreachable;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-failed {
|
||||||
|
background-color: @default-err;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-skipped {
|
||||||
|
background-color: @default-link;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-noData {
|
||||||
|
background-color: @default-icon-hov;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-tooltipLabel {
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-tooltipBadge {
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid @default-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-tooltipBadge--ok {
|
||||||
|
background-color: @default-succ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-tooltipBadge--unreachable {
|
||||||
|
background-color: @default-unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-tooltipBadge--skipped {
|
||||||
|
background-color: @default-link;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-tooltipBadge--changed {
|
||||||
|
background-color: @default-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HostStatusBar-tooltipBadge--failed {
|
||||||
|
background-color: @default-err;
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const templateUrl = require('~features/output/details.partial.html');
|
|||||||
|
|
||||||
let $http;
|
let $http;
|
||||||
let $filter;
|
let $filter;
|
||||||
|
let $scope;
|
||||||
let $state;
|
let $state;
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
@@ -12,68 +13,86 @@ let strings;
|
|||||||
let wait;
|
let wait;
|
||||||
|
|
||||||
function mapChoices (choices) {
|
function mapChoices (choices) {
|
||||||
return Object.assign(...choices.map(([k, v]) => ({[k]: v})));
|
if (!choices) return {};
|
||||||
|
return Object.assign(...choices.map(([k, v]) => ({ [k]: v })));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusDetails (status) {
|
function getStatusDetails (status) {
|
||||||
const value = status || resource.model.get('status');
|
const unmapped = status || resource.model.get('status');
|
||||||
const label = 'Status';
|
|
||||||
|
if (!unmapped) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const choices = mapChoices(resource.model.options('actions.GET.status.choices'));
|
const choices = mapChoices(resource.model.options('actions.GET.status.choices'));
|
||||||
|
|
||||||
const displayValue = choices[value];
|
const label = 'Status';
|
||||||
|
const icon = `fa icon-job-${unmapped}`;
|
||||||
|
const value = choices[unmapped];
|
||||||
|
|
||||||
return { displayValue, label, value };
|
return { label, icon, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStartTimeDetails (started) {
|
function getStartTimeDetails (started) {
|
||||||
const value = started || resource.model.get('started');
|
const unfiltered = started || resource.model.get('started');
|
||||||
|
|
||||||
const label = 'Started';
|
const label = 'Started';
|
||||||
|
|
||||||
let displayValue;
|
let value;
|
||||||
|
|
||||||
if (value) {
|
if (unfiltered) {
|
||||||
displayValue = $filter('longDate')(value);
|
value = $filter('longDate')(unfiltered);
|
||||||
} else {
|
} else {
|
||||||
displayValue = 'Not Started';
|
value = 'Not Started';
|
||||||
}
|
}
|
||||||
|
|
||||||
return { displayValue, label, value };
|
return { label, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFinishTimeDetails (finished) {
|
function getFinishTimeDetails (finished) {
|
||||||
const value = finished || resource.model.get('finished');
|
const unfiltered = finished || resource.model.get('finished');
|
||||||
|
|
||||||
const label = 'Finished';
|
const label = 'Finished';
|
||||||
|
|
||||||
let displayValue;
|
let value;
|
||||||
|
|
||||||
if (value) {
|
if (unfiltered) {
|
||||||
displayValue = $filter('longDate')(value);
|
value = $filter('longDate')(unfiltered);
|
||||||
} else {
|
} else {
|
||||||
displayValue = 'Not Finished';
|
value = 'Not Finished';
|
||||||
}
|
}
|
||||||
|
|
||||||
return { displayValue, label, value };
|
return { label, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJobTypeDetails () {
|
function getJobTypeDetails () {
|
||||||
const value = resource.model.get('job_type');
|
const unmapped = resource.model.get('job_type');
|
||||||
const label = 'Job Type';
|
|
||||||
|
if (!unmapped) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const choices = mapChoices(resource.model.options('actions.GET.job_type.choices'));
|
const choices = mapChoices(resource.model.options('actions.GET.job_type.choices'));
|
||||||
|
|
||||||
const displayValue = choices[value];
|
const label = 'Job Type';
|
||||||
|
const value = choices[unmapped];
|
||||||
|
|
||||||
return { displayValue, label, value };
|
return { label, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getVerbosityDetails () {
|
function getVerbosityDetails () {
|
||||||
const value = resource.model.get('verbosity');
|
const verbosity = resource.model.get('verbosity');
|
||||||
|
|
||||||
|
if (!verbosity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const choices = mapChoices(resource.model.options('actions.GET.verbosity.choices'));
|
const choices = mapChoices(resource.model.options('actions.GET.verbosity.choices'));
|
||||||
|
|
||||||
const displayValue = choices[value];
|
|
||||||
const label = 'Verbosity';
|
const label = 'Verbosity';
|
||||||
|
const value = choices[value];
|
||||||
|
|
||||||
return { displayValue, label, value };
|
return { label, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSourceWorkflowJobDetails () {
|
function getSourceWorkflowJobDetails () {
|
||||||
@@ -273,7 +292,6 @@ function getLimitDetails () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getInstanceGroupDetails () {
|
function getInstanceGroupDetails () {
|
||||||
|
|
||||||
const instanceGroup = resource.model.get('summary_fields.instance_group');
|
const instanceGroup = resource.model.get('summary_fields.instance_group');
|
||||||
|
|
||||||
if (!instanceGroup) {
|
if (!instanceGroup) {
|
||||||
@@ -336,9 +354,9 @@ function getLabelDetails () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const label = 'Labels';
|
const label = 'Labels';
|
||||||
const value = jobLabels.map(({ name }) => name).map($filter('sanitize'));
|
const more = false;
|
||||||
|
|
||||||
let more = false;
|
const value = jobLabels.map(({ name }) => name).map($filter('sanitize'));
|
||||||
|
|
||||||
return { label, more, value };
|
return { label, more, value };
|
||||||
}
|
}
|
||||||
@@ -396,9 +414,7 @@ function cancelJob () {
|
|||||||
prompt({ hdr, resourceName, body, actionText, action });
|
prompt({ hdr, resourceName, body, actionText, action });
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteJob () {
|
function deleteJob () {}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AtDetailsController (
|
function AtDetailsController (
|
||||||
_$http_,
|
_$http_,
|
||||||
@@ -418,21 +434,18 @@ function AtDetailsController (
|
|||||||
$state = _$state_;
|
$state = _$state_;
|
||||||
|
|
||||||
error = _error_;
|
error = _error_;
|
||||||
// resource = _resource_;
|
|
||||||
parse = ParseVariableString;
|
parse = ParseVariableString;
|
||||||
prompt = _prompt_;
|
prompt = _prompt_;
|
||||||
strings = _strings_;
|
strings = _strings_;
|
||||||
wait = _wait_;
|
wait = _wait_;
|
||||||
|
|
||||||
// statusChoices = mapChoices(resource.options('status.choices'));
|
vm.init = _$scope_ => {
|
||||||
|
$scope = _$scope_;
|
||||||
|
resource = $scope.resource;
|
||||||
|
|
||||||
vm.init = scope => {
|
vm.status = getStatusDetails();
|
||||||
vm.job = scope.job || {};
|
vm.started = getStartTimeDetails();
|
||||||
resource = scope.resource;
|
vm.finished = getFinishTimeDetails();
|
||||||
|
|
||||||
vm.status = getStatusDetails(scope.status);
|
|
||||||
vm.startTime = getStartTimeDetails();
|
|
||||||
vm.finishTime = getFinishTimeDetails();
|
|
||||||
vm.jobType = getJobTypeDetails();
|
vm.jobType = getJobTypeDetails();
|
||||||
vm.jobTemplate = getJobTemplateDetails();
|
vm.jobTemplate = getJobTemplateDetails();
|
||||||
vm.sourceWorkflowJob = getSourceWorkflowJobDetails();
|
vm.sourceWorkflowJob = getSourceWorkflowJobDetails();
|
||||||
@@ -457,12 +470,24 @@ function AtDetailsController (
|
|||||||
vm.deleteJob = deleteJob;
|
vm.deleteJob = deleteJob;
|
||||||
vm.toggleLabels = toggleLabels;
|
vm.toggleLabels = toggleLabels;
|
||||||
|
|
||||||
// codemirror
|
const observe = (key, transform) => {
|
||||||
const cm = { parseType: 'yaml', variables: vm.extraVars.value, $apply: scope.$apply };
|
$scope.$watch(key, value => { this[key] = transform(value); });
|
||||||
ParseTypeChange({ scope: cm, field_id: 'cm-extra-vars', readOnly: true });
|
};
|
||||||
|
|
||||||
scope.$watch('status', value => { vm.status = getStatusDetails(value); });
|
observe('status', getStatusDetails);
|
||||||
}
|
observe('started', getStartTimeDetails);
|
||||||
|
observe('finished', getFinishTimeDetails);
|
||||||
|
|
||||||
|
// relaunch component
|
||||||
|
$scope.job = _.get(resource.model, 'model.GET', {});
|
||||||
|
this.job = $scope.job;
|
||||||
|
|
||||||
|
// codemirror
|
||||||
|
if (this.extraVars) {
|
||||||
|
const cm = { parseType: 'yaml', variables: this.extraVars.value, $apply: $scope.$apply };
|
||||||
|
ParseTypeChange({ scope: cm, field_id: 'cm-extra-vars', readOnly: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
AtDetailsController.$inject = [
|
AtDetailsController.$inject = [
|
||||||
@@ -492,9 +517,10 @@ function atDetails () {
|
|||||||
link: atDetailsLink,
|
link: atDetailsLink,
|
||||||
controller: AtDetailsController,
|
controller: AtDetailsController,
|
||||||
scope: {
|
scope: {
|
||||||
job: '=',
|
|
||||||
status: '=',
|
|
||||||
resource: '=',
|
resource: '=',
|
||||||
|
status: '=',
|
||||||
|
started: '=',
|
||||||
|
finished: '=',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,25 +36,25 @@
|
|||||||
<div class="JobResults-resultRow">
|
<div class="JobResults-resultRow">
|
||||||
<label class="JobResults-resultRowLabel">{{ vm.status.label}}</label>
|
<label class="JobResults-resultRowLabel">{{ vm.status.label}}</label>
|
||||||
<div class="JobResults-resultRowText">
|
<div class="JobResults-resultRowText">
|
||||||
<i class="JobResults-statusResultIcon fa icon-job-{{ vm.status.value }}"></i>
|
<i class="JobResults-statusResultIcon {{ vm.status.icon }}"></i>
|
||||||
{{ vm.status.displayValue | translate }}
|
{{ vm.status.value }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- START TIME DETAIL -->
|
<!-- START TIME DETAIL -->
|
||||||
<div class="JobResults-resultRow" ng-if="vm.startTime">
|
<div class="JobResults-resultRow" ng-if="vm.started">
|
||||||
<label class="JobResults-resultRowLabel">{{ vm.startTime.label }}</label>
|
<label class="JobResults-resultRowLabel">{{ vm.started.label }}</label>
|
||||||
<div class="JobResults-resultRowText">
|
<div class="JobResults-resultRowText">
|
||||||
{{ vm.startTime.displayValue }}
|
{{ vm.started.value }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FINISHED TIME DETAIL -->
|
<!-- FINISHED TIME DETAIL -->
|
||||||
<div class="JobResults-resultRow" ng-show="vm.startTime">
|
<div class="JobResults-resultRow" ng-show="vm.started">
|
||||||
<label class="JobResults-resultRowLabel">{{ vm.finishTime.label }}</label>
|
<label class="JobResults-resultRowLabel">{{ vm.finished.label }}</label>
|
||||||
<div class="JobResults-resultRowText">
|
<div class="JobResults-resultRowText">
|
||||||
{{ vm.finishTime.displayValue }}
|
{{ vm.finished.value }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
<!-- JOB TYPE DETAIL -->
|
<!-- JOB TYPE DETAIL -->
|
||||||
<div class="JobResults-resultRow" ng-if="vm.jobType">
|
<div class="JobResults-resultRow" ng-if="vm.jobType">
|
||||||
<label class="JobResults-resultRowLabel">{{ vm.jobType.label }}</label>
|
<label class="JobResults-resultRowLabel">{{ vm.jobType.label }}</label>
|
||||||
<div class="JobResults-resultRowText">{{ vm.jobType.displayValue }}</div>
|
<div class="JobResults-resultRowText">{{ vm.jobType.value }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- LAUNCHED BY DETAIL -->
|
<!-- LAUNCHED BY DETAIL -->
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
<!-- VERBOSITY DETAIL -->
|
<!-- VERBOSITY DETAIL -->
|
||||||
<div class="JobResults-resultRow" ng-if="vm.verbosity">
|
<div class="JobResults-resultRow" ng-if="vm.verbosity">
|
||||||
<label class="JobResults-resultRowLabel">{{ vm.verbosity.label }}</label>
|
<label class="JobResults-resultRowLabel">{{ vm.verbosity.label }}</label>
|
||||||
<div class="JobResults-resultRowText">{{ vm.verbosity.displayValue }}</div>
|
<div class="JobResults-resultRowText">{{ vm.verbosity.value }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- IG DETAIL -->
|
<!-- IG DETAIL -->
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ let resource;
|
|||||||
let $state;
|
let $state;
|
||||||
let qs;
|
let qs;
|
||||||
|
|
||||||
|
let hack;
|
||||||
|
|
||||||
function JobsIndexController (
|
function JobsIndexController (
|
||||||
_resource_,
|
_resource_,
|
||||||
_page_,
|
_page_,
|
||||||
@@ -51,7 +53,7 @@ function JobsIndexController (
|
|||||||
vm.expand = expand;
|
vm.expand = expand;
|
||||||
vm.isExpanded = true;
|
vm.isExpanded = true;
|
||||||
|
|
||||||
// search
|
// Search
|
||||||
$state = _$state_;
|
$state = _$state_;
|
||||||
qs = _qs_;
|
qs = _qs_;
|
||||||
|
|
||||||
@@ -67,17 +69,47 @@ function JobsIndexController (
|
|||||||
vm.removeSearchTag = removeSearchTag;
|
vm.removeSearchTag = removeSearchTag;
|
||||||
vm.searchTags = getSearchTags(getCurrentQueryset());
|
vm.searchTags = getSearchTags(getCurrentQueryset());
|
||||||
|
|
||||||
// details
|
// Host Status Bar
|
||||||
|
vm.status = {
|
||||||
|
running: Boolean(resource.model.get('started')) && !resource.model.get('finished'),
|
||||||
|
stats: resource.stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Details
|
||||||
vm.details = {
|
vm.details = {
|
||||||
job: resource.model.model.GET,
|
|
||||||
status: resource.model.model.GET.status,
|
|
||||||
resource,
|
resource,
|
||||||
|
started: resource.model.get('started'),
|
||||||
|
finished: resource.model.get('finished'),
|
||||||
|
status: resource.model.get('status'),
|
||||||
};
|
};
|
||||||
|
|
||||||
render.requestAnimationFrame(() => init());
|
render.requestAnimationFrame(() => init());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onStreamStart (data) {
|
||||||
|
const status = _.get(data, 'summary_fields.job.status');
|
||||||
|
|
||||||
|
if (!hack) {
|
||||||
|
hack = true;
|
||||||
|
vm.details.status = status;
|
||||||
|
vm.details.started = data.created;
|
||||||
|
|
||||||
|
vm.status.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStreamFinish (data) {
|
||||||
|
const failed = _.get(data, 'summary_fields.job.failed');
|
||||||
|
|
||||||
|
vm.details.status = failed ? 'failed' : 'successful';
|
||||||
|
vm.details.finished = data.created;
|
||||||
|
|
||||||
|
vm.status = { stats: data, running: false };
|
||||||
|
};
|
||||||
|
|
||||||
function init (pageMode) {
|
function init (pageMode) {
|
||||||
|
hack = false;
|
||||||
|
|
||||||
page.init({
|
page.init({
|
||||||
resource,
|
resource,
|
||||||
});
|
});
|
||||||
@@ -98,10 +130,12 @@ function init (pageMode) {
|
|||||||
page,
|
page,
|
||||||
scroll,
|
scroll,
|
||||||
resource,
|
resource,
|
||||||
|
onStreamStart,
|
||||||
|
onStreamFinish,
|
||||||
render: events => shift().then(() => append(events, true)),
|
render: events => shift().then(() => append(events, true)),
|
||||||
listen: (namespace, listener) => {
|
listen: (namespace, listener) => {
|
||||||
$scope.$on(namespace, (scope, data) => listener(data));
|
$scope.$on(namespace, (scope, data) => listener(data));
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pageMode) {
|
if (pageMode) {
|
||||||
|
|||||||
@@ -6,13 +6,16 @@ import Controller from '~features/output/index.controller';
|
|||||||
import PageService from '~features/output/page.service';
|
import PageService from '~features/output/page.service';
|
||||||
import RenderService from '~features/output/render.service';
|
import RenderService from '~features/output/render.service';
|
||||||
import ScrollService from '~features/output/scroll.service';
|
import ScrollService from '~features/output/scroll.service';
|
||||||
import SearchKeyDirective from '~features/output/search-key.directive';
|
|
||||||
import StreamService from '~features/output/stream.service';
|
import StreamService from '~features/output/stream.service';
|
||||||
import DetailsDirective from '~features/output/details.directive.js';
|
|
||||||
|
import DetailsDirective from '~features/output/details.directive';
|
||||||
|
import SearchKeyDirective from '~features/output/search-key.directive';
|
||||||
|
import StatusDirective from '~features/output/status.directive';
|
||||||
|
|
||||||
const Template = require('~features/output/index.view.html');
|
const Template = require('~features/output/index.view.html');
|
||||||
|
|
||||||
const MODULE_NAME = 'at.features.output';
|
const MODULE_NAME = 'at.features.output';
|
||||||
|
|
||||||
const PAGE_CACHE = true;
|
const PAGE_CACHE = true;
|
||||||
const PAGE_LIMIT = 5;
|
const PAGE_LIMIT = 5;
|
||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
@@ -66,13 +69,21 @@ function resolveResource (
|
|||||||
|
|
||||||
Wait('start');
|
Wait('start');
|
||||||
return new Resource(['get', 'options'], [id, id])
|
return new Resource(['get', 'options'], [id, id])
|
||||||
.then(model => Promise.all([
|
.then(model => {
|
||||||
model.extend('labels'),
|
const promises = [model.getStats()];
|
||||||
model.extend(related, config)
|
|
||||||
]))
|
if (model.has('related.labels')) {
|
||||||
.then(([ model ]) => ({
|
promises.push(model.extend('labels'));
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(model.extend(related, config));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
})
|
||||||
|
.then(([stats, model]) => ({
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
|
stats,
|
||||||
model,
|
model,
|
||||||
related,
|
related,
|
||||||
ws: {
|
ws: {
|
||||||
@@ -200,6 +211,7 @@ angular
|
|||||||
.service('JobStreamService', StreamService)
|
.service('JobStreamService', StreamService)
|
||||||
.directive('atDetails', DetailsDirective)
|
.directive('atDetails', DetailsDirective)
|
||||||
.directive('atSearchKey', SearchKeyDirective)
|
.directive('atSearchKey', SearchKeyDirective)
|
||||||
|
.directive('atStatus', StatusDirective)
|
||||||
.run(JobsRun);
|
.run(JobsRun);
|
||||||
|
|
||||||
export default MODULE_NAME;
|
export default MODULE_NAME;
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<at-panel>
|
<at-panel>
|
||||||
<at-details job="vm.details.job" status="vm.details.status" resource="vm.details.resource"></at-details>
|
<at-details
|
||||||
<p><button class="btn" ng-click="vm.clear(true)">Page Mode</button></p>
|
resource="vm.details.resource"
|
||||||
|
status="vm.details.status"
|
||||||
|
started="vm.details.started"
|
||||||
|
finished="vm.details.finished">
|
||||||
|
</at-details>
|
||||||
|
<!-- --><p><button class="btn" ng-click="vm.clear(true)">Page Mode</button></p>
|
||||||
</at-panel>
|
</at-panel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<at-panel class="at-Stdout">
|
<at-panel class="at-Stdout">
|
||||||
<!-- search ============================================================================================================= -->
|
<at-status running="vm.status.running" stats="vm.status.stats"></at-status>
|
||||||
|
<!-- search ===================================================================================== -->
|
||||||
<form ng-submit="vm.search()">
|
<form ng-submit="vm.search()">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
@@ -52,7 +58,7 @@
|
|||||||
|
|
||||||
<at-search-key ng-show="vm.searchKey" fields="vm.searchKeyFields" examples="vm.searchKeyExamples"></at-search-key>
|
<at-search-key ng-show="vm.searchKey" fields="vm.searchKeyFields" examples="vm.searchKeyExamples"></at-search-key>
|
||||||
|
|
||||||
<!-- ==================================================================================================================== -->
|
<!-- ============================================================================================ -->
|
||||||
<div class="at-Stdout-menuTop">
|
<div class="at-Stdout-menuTop">
|
||||||
<div class="pull-left" ng-click="vm.expand()">
|
<div class="pull-left" ng-click="vm.expand()">
|
||||||
<i class="at-Stdout-menuIcon fa"
|
<i class="at-Stdout-menuIcon fa"
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ function JobsStrings (BaseString) {
|
|||||||
CANCEL_BODY: t.s('Are you sure you want to cancel this job?'),
|
CANCEL_BODY: t.s('Are you sure you want to cancel this job?'),
|
||||||
CANCEL_HEADER: t.s('Cancel Job'),
|
CANCEL_HEADER: t.s('Cancel Job'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ns.status = {
|
||||||
|
RUNNING: t.s('The host status bar will update when the job is complete.'),
|
||||||
|
UNAVAILABLE: t.s('Host status information for this job unavailable.'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
JobsStrings.$inject = ['BaseStringService'];
|
JobsStrings.$inject = ['BaseStringService'];
|
||||||
|
|||||||
92
awx/ui/client/features/output/status.directive.js
Normal file
92
awx/ui/client/features/output/status.directive.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
const templateUrl = require('~features/output/status.partial.html');
|
||||||
|
|
||||||
|
const HOST_STATUS_KEYS = ['dark', 'failures', 'changed', 'ok', 'skipped'];
|
||||||
|
|
||||||
|
function getHostStatusCounts (statsEvent) {
|
||||||
|
const countedHostNames = [];
|
||||||
|
|
||||||
|
const counts = Object.assign(...HOST_STATUS_KEYS.map(key => ({ [key]: 0 })));
|
||||||
|
|
||||||
|
HOST_STATUS_KEYS.forEach(key => {
|
||||||
|
const hostData = _.get(statsEvent, ['event_data', key], {});
|
||||||
|
|
||||||
|
Object.keys(hostData).forEach(hostName => {
|
||||||
|
const isAlreadyCounted = (countedHostNames.indexOf(hostName) > -1);
|
||||||
|
const shouldBeCounted = ((!isAlreadyCounted) && hostData[hostName] > 0);
|
||||||
|
|
||||||
|
if (shouldBeCounted) {
|
||||||
|
countedHostNames.push(hostName);
|
||||||
|
counts[key]++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStatusBarTooltip (key, count) {
|
||||||
|
const label = `<span class='HostStatusBar-tooltipLabel'>${key}</span>`;
|
||||||
|
const badge = `<span class='badge HostStatusBar-tooltipBadge HostStatusBar-tooltipBadge--${key}'>${count}</span>`;
|
||||||
|
|
||||||
|
return `${label}${badge}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function atStatusLink (scope, el, attrs, controllers) {
|
||||||
|
const [atStatusController] = controllers;
|
||||||
|
|
||||||
|
atStatusController.init(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AtStatusController (strings) {
|
||||||
|
const vm = this || {};
|
||||||
|
|
||||||
|
vm.tooltips = {
|
||||||
|
running: strings.get('status.RUNNING'),
|
||||||
|
unavailable: strings.get('status.UNAVAILABLE'),
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.init = scope => {
|
||||||
|
const { running, stats } = scope;
|
||||||
|
|
||||||
|
vm.running = running || false;
|
||||||
|
vm.setStats(stats);
|
||||||
|
|
||||||
|
scope.$watch('running', value => { vm.running = value; });
|
||||||
|
scope.$watch('stats', vm.setStats);
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.setStats = stats => {
|
||||||
|
const counts = getHostStatusCounts(stats);
|
||||||
|
|
||||||
|
HOST_STATUS_KEYS.forEach(key => {
|
||||||
|
const count = counts[key];
|
||||||
|
const statusBarElement = $(`.HostStatusBar-${key}`);
|
||||||
|
|
||||||
|
statusBarElement.css('flex', `${count} 0 auto`);
|
||||||
|
|
||||||
|
vm.tooltips[key] = createStatusBarTooltip(key, count);
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.statsAreAvailable = Boolean(stats);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function atStatus () {
|
||||||
|
return {
|
||||||
|
templateUrl,
|
||||||
|
restrict: 'E',
|
||||||
|
require: ['atStatus'],
|
||||||
|
controllerAs: 'vm',
|
||||||
|
link: atStatusLink,
|
||||||
|
controller: [
|
||||||
|
'JobStrings',
|
||||||
|
AtStatusController
|
||||||
|
],
|
||||||
|
scope: {
|
||||||
|
running: '=',
|
||||||
|
stats: '=',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default atStatus;
|
||||||
37
awx/ui/client/features/output/status.partial.html
Normal file
37
awx/ui/client/features/output/status.partial.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<div class="HostStatusBar">
|
||||||
|
<div class="HostStatusBar-ok"
|
||||||
|
data-placement="top"
|
||||||
|
aw-tool-tip="{{ vm.tooltips.ok }}"
|
||||||
|
data-tip-watch="vm.tooltips.ok">
|
||||||
|
</div>
|
||||||
|
<div class="HostStatusBar-skipped"
|
||||||
|
data-placement="top"
|
||||||
|
aw-tool-tip="{{ vm.tooltips.skipped }}"
|
||||||
|
data-tip-watch="vm.tooltips.skipped">
|
||||||
|
</div>
|
||||||
|
<div class="HostStatusBar-changed"
|
||||||
|
data-placement="top"
|
||||||
|
aw-tool-tip="{{ vm.tooltips.changed }}"
|
||||||
|
data-tip-watch="vm.tooltips.changed">
|
||||||
|
</div>
|
||||||
|
<div class="HostStatusBar-failures"
|
||||||
|
data-placement="top"
|
||||||
|
aw-tool-tip="{{ vm.tooltips.failures }}"
|
||||||
|
data-tip-watch="vm.tooltips.failures">
|
||||||
|
</div>
|
||||||
|
<div class="HostStatusBar-unreachable"
|
||||||
|
data-placement="top"
|
||||||
|
aw-tool-tip="{{ vm.tooltips.unreachable }}"
|
||||||
|
data-tip-watch="vm.tooltips.unreachable">
|
||||||
|
</div>
|
||||||
|
<div class="HostStatusBar-noData"
|
||||||
|
ng-show="vm.running"
|
||||||
|
data-placement="top"
|
||||||
|
aw-tool-tip="{{:: vm.tooltips.running }}">
|
||||||
|
</div>
|
||||||
|
<div class="HostStatusBar-noData"
|
||||||
|
ng-show="!vm.running && !vm.statsAreAvailable"
|
||||||
|
data-placement="top"
|
||||||
|
aw-tool-tip="{{:: vm.tooltips.unavailable }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -3,7 +3,7 @@ const JOB_END = 'playbook_on_stats';
|
|||||||
const MAX_LAG = 120;
|
const MAX_LAG = 120;
|
||||||
|
|
||||||
function JobStreamService ($q) {
|
function JobStreamService ($q) {
|
||||||
this.init = ({ resource, scroll, page, render, listen }) => {
|
this.init = ({ resource, scroll, page, onStreamStart, onStreamFinish, render, listen }) => {
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
this.scroll = scroll;
|
this.scroll = scroll;
|
||||||
this.page = page;
|
this.page = page;
|
||||||
@@ -23,8 +23,10 @@ function JobStreamService ($q) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.hooks = {
|
this.hooks = {
|
||||||
|
onStreamStart,
|
||||||
|
onStreamFinish,
|
||||||
render,
|
render,
|
||||||
listen
|
listen,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.lines = {
|
this.lines = {
|
||||||
@@ -35,7 +37,7 @@ function JobStreamService ($q) {
|
|||||||
max: 0
|
max: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hooks.listen(resource.ws.namespace, this.listen);
|
this.hooks.listen(resource.ws.namespace, this.listener);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getBatchFactors = size => {
|
this.getBatchFactors = size => {
|
||||||
@@ -105,19 +107,25 @@ function JobStreamService ($q) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.listen = data => {
|
this.listener = data => {
|
||||||
this.lag++;
|
this.lag++;
|
||||||
|
|
||||||
this.chain = this.chain
|
this.chain = this.chain
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
// console.log(data);
|
||||||
if (!this.isActive()) {
|
if (!this.isActive()) {
|
||||||
this.start();
|
this.start();
|
||||||
|
if (!this.isEnding()) {
|
||||||
|
this.hooks.onStreamStart(data);
|
||||||
|
}
|
||||||
} else if (data.event === JOB_END) {
|
} else if (data.event === JOB_END) {
|
||||||
if (this.isPaused()) {
|
if (this.isPaused()) {
|
||||||
this.end(true);
|
this.end(true);
|
||||||
} else {
|
} else {
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hooks.onStreamFinish(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkLines(data);
|
this.checkLines(data);
|
||||||
|
|||||||
@@ -23,26 +23,54 @@ function postRelaunch (params) {
|
|||||||
return $http(req);
|
return $http(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStats () {
|
||||||
|
if (!this.has('GET', 'id')) {
|
||||||
|
return Promise.reject(new Error('No property, id, exists'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.has('GET', 'related.job_events')) {
|
||||||
|
return Promise.reject(new Error('No related property, job_events, exists'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
method: 'GET',
|
||||||
|
url: `${this.path}${this.get('id')}/job_events/`,
|
||||||
|
params: { event: 'playbook_on_stats' },
|
||||||
|
};
|
||||||
|
|
||||||
|
return $http(req)
|
||||||
|
.then(({ data }) => {
|
||||||
|
if (data.results.length > 0) {
|
||||||
|
return data.results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function JobModel (method, resource, config) {
|
function JobModel (method, resource, config) {
|
||||||
BaseModel.call(this, 'jobs');
|
BaseModel.call(this, 'jobs');
|
||||||
|
|
||||||
this.Constructor = JobModel;
|
this.Constructor = JobModel;
|
||||||
|
|
||||||
this.postRelaunch = postRelaunch.bind(this);
|
this.postRelaunch = postRelaunch.bind(this);
|
||||||
this.getRelaunch = getRelaunch.bind(this);
|
this.getRelaunch = getRelaunch.bind(this);
|
||||||
|
this.getStats = getStats.bind(this);
|
||||||
|
|
||||||
return this.create(method, resource, config);
|
return this.create(method, resource, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function JobModelLoader (_BaseModel_, _$http_) {
|
function JobModelLoader (_$http_, _BaseModel_) {
|
||||||
BaseModel = _BaseModel_;
|
|
||||||
$http = _$http_;
|
$http = _$http_;
|
||||||
|
BaseModel = _BaseModel_;
|
||||||
|
|
||||||
return JobModel;
|
return JobModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobModelLoader.$inject = [
|
JobModelLoader.$inject = [
|
||||||
|
'$http',
|
||||||
'BaseModel',
|
'BaseModel',
|
||||||
'$http'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default JobModelLoader;
|
export default JobModelLoader;
|
||||||
|
|||||||
@@ -1,19 +1,54 @@
|
|||||||
|
let $http;
|
||||||
let BaseModel;
|
let BaseModel;
|
||||||
|
|
||||||
|
function getStats () {
|
||||||
|
if (!this.has('GET', 'id')) {
|
||||||
|
return Promise.reject(new Error('No property, id, exists'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.has('GET', 'related.events')) {
|
||||||
|
return Promise.reject(new Error('No related property, events, exists'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
method: 'GET',
|
||||||
|
url: `${this.path}${this.get('id')}/events/`,
|
||||||
|
params: { event: 'playbook_on_stats' },
|
||||||
|
};
|
||||||
|
|
||||||
|
return $http(req)
|
||||||
|
.then(({ data }) => {
|
||||||
|
console.log(data);
|
||||||
|
if (data.results.length > 0) {
|
||||||
|
return data.results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function ProjectUpdateModel (method, resource, config) {
|
function ProjectUpdateModel (method, resource, config) {
|
||||||
BaseModel.call(this, 'project_updates');
|
BaseModel.call(this, 'project_updates');
|
||||||
|
|
||||||
|
this.getStats = getStats;
|
||||||
|
|
||||||
this.Constructor = ProjectUpdateModel;
|
this.Constructor = ProjectUpdateModel;
|
||||||
|
|
||||||
return this.create(method, resource, config);
|
return this.create(method, resource, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProjectUpdateModelLoader (_BaseModel_) {
|
function ProjectUpdateModelLoader (_$http_, _BaseModel_) {
|
||||||
|
$http = _$http_;
|
||||||
BaseModel = _BaseModel_;
|
BaseModel = _BaseModel_;
|
||||||
|
|
||||||
return ProjectUpdateModel;
|
return ProjectUpdateModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectUpdateModelLoader.$inject = ['BaseModel'];
|
ProjectUpdateModelLoader.$inject = [
|
||||||
|
'$http',
|
||||||
|
'BaseModel'
|
||||||
|
];
|
||||||
|
|
||||||
export default ProjectUpdateModelLoader;
|
export default ProjectUpdateModelLoader;
|
||||||
|
|||||||
Reference in New Issue
Block a user