add event processing for stats and host status components

This commit is contained in:
Jake McDermott
2018-03-23 11:38:20 -04:00
parent faa33e0bec
commit 450eaeca96
19 changed files with 227 additions and 118 deletions

View File

@@ -206,12 +206,11 @@
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
width: 100%; width: 100%;
margin-top: 10px;
} }
.HostStatusBar-ok, .HostStatusBar-ok,
.HostStatusBar-changed, .HostStatusBar-changed,
.HostStatusBar-unreachable, .HostStatusBar-dark,
.HostStatusBar-failed, .HostStatusBar-failed,
.HostStatusBar-skipped, .HostStatusBar-skipped,
.HostStatusBar-noData { .HostStatusBar-noData {
@@ -231,7 +230,7 @@
flex: 0 0 auto; flex: 0 0 auto;
} }
.HostStatusBar-unreachable { .HostStatusBar-dark {
background-color: @default-unreachable; background-color: @default-unreachable;
flex: 0 0 auto; flex: 0 0 auto;
} }
@@ -265,7 +264,7 @@
background-color: @default-succ; background-color: @default-succ;
} }
.HostStatusBar-tooltipBadge--unreachable { .HostStatusBar-tooltipBadge--dark {
background-color: @default-unreachable; background-color: @default-unreachable;
} }

View File

@@ -441,7 +441,7 @@ function AtDetailsController (
vm.init = _$scope_ => { vm.init = _$scope_ => {
$scope = _$scope_; $scope = _$scope_;
resource = $scope.resource; resource = $scope.resource; // eslint-disable-line prefer-destructuring
vm.status = getStatusDetails(); vm.status = getStatusDetails();
vm.started = getStartTimeDetails(); vm.started = getStartTimeDetails();
@@ -466,27 +466,35 @@ function AtDetailsController (
vm.extraVars = getExtraVarsDetails(); vm.extraVars = getExtraVarsDetails();
vm.labels = getLabelDetails(); vm.labels = getLabelDetails();
// Relaunch Component
vm.job = _.get(resource.model, 'model.GET', {});
// XX - Codemirror
if (vm.extraVars) {
const cm = {
parseType: 'yaml',
$apply: $scope.$apply,
variables: vm.extraVars.value,
};
ParseTypeChange({
field_id: 'cm-extra-vars',
readOnly: true,
scope: cm,
});
}
vm.cancelJob = cancelJob; vm.cancelJob = cancelJob;
vm.deleteJob = deleteJob; vm.deleteJob = deleteJob;
vm.toggleLabels = toggleLabels; vm.toggleLabels = toggleLabels;
const observe = (key, transform) => { const observe = (key, transform) => {
$scope.$watch(key, value => { this[key] = transform(value); }); $scope.$watch(key, value => { vm[key] = transform(value); });
}; };
observe('finished', getFinishTimeDetails);
observe('status', getStatusDetails); observe('status', getStatusDetails);
observe('started', getStartTimeDetails); 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 });
}
}; };
} }

View File

@@ -1,11 +1,10 @@
<!-- todo: styling, css etc. - disposition according to project lib conventions -->
<!-- LEFT PANE HEADER -->
<div class="JobResults-panelHeader"> <div class="JobResults-panelHeader">
<div class="JobResults-panelHeaderText" translate> DETAILS</div> <div class="JobResults-panelHeaderText" translate> DETAILS</div>
<!-- LEFT PANE HEADER ACTIONS --> <!-- LEFT PANE HEADER ACTIONS -->
<div class="JobResults-panelHeaderButtonActions"> <div class="JobResults-panelHeaderButtonActions">
<!-- RELAUNCH ACTION --> <!-- RELAUNCH ACTION -->
<at-relaunch state="vm.job"></at-relaunch> <at-relaunch job="vm.job"></at-relaunch>
<!-- CANCEL ACTION --> <!-- CANCEL ACTION -->
<button class="List-actionButton List-actionButton--delete" <button class="List-actionButton List-actionButton--delete"

View File

@@ -1,16 +1,24 @@
let vm; const JOB_START = 'playbook_on_start';
const JOB_END = 'playbook_on_stats';
const PLAY_START = 'playbook_on_play_start';
const TASK_START = 'playbook_on_task_start';
let $compile; let $compile;
let $scope;
let $q; let $q;
let $scope;
let $state;
let moment;
let page; let page;
let qs;
let render; let render;
let resource;
let scroll; let scroll;
let stream; let stream;
let resource;
let $state;
let qs;
let hack; let vm;
let eventCounter;
let statsEvent;
function JobsIndexController ( function JobsIndexController (
_resource_, _resource_,
@@ -23,6 +31,7 @@ function JobsIndexController (
_$q_, _$q_,
_$state_, _$state_,
_qs_, _qs_,
_moment_,
) { ) {
vm = this || {}; vm = this || {};
@@ -36,6 +45,8 @@ function JobsIndexController (
render = _render_; render = _render_;
stream = _stream_; stream = _stream_;
moment = _moment_;
// Development helper(s) // Development helper(s)
vm.clear = devClear; vm.clear = devClear;
@@ -69,11 +80,19 @@ function JobsIndexController (
vm.removeSearchTag = removeSearchTag; vm.removeSearchTag = removeSearchTag;
vm.searchTags = getSearchTags(getCurrentQueryset()); vm.searchTags = getSearchTags(getCurrentQueryset());
// Host Status Bar // Events
eventCounter = null;
statsEvent = resource.stats;
// Status Bar
vm.status = { vm.status = {
stats: statsEvent,
elapsed: resource.model.get('elapsed'),
running: Boolean(resource.model.get('started')) && !resource.model.get('finished'), running: Boolean(resource.model.get('started')) && !resource.model.get('finished'),
stats: resource.stats, title: resource.model.get('name'),
} plays: null,
tasks: null,
};
// Details // Details
vm.details = { vm.details = {
@@ -83,33 +102,10 @@ function JobsIndexController (
status: resource.model.get('status'), status: resource.model.get('status'),
}; };
render.requestAnimationFrame(() => init()); render.requestAnimationFrame(() => init(!vm.status.running));
} }
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,
}); });
@@ -123,26 +119,68 @@ function init (pageMode) {
scroll.init({ scroll.init({
isAtRest: scrollIsAtRest, isAtRest: scrollIsAtRest,
previous, previous,
next next,
}); });
stream.init({ stream.init({
page, page,
scroll, scroll,
resource, resource,
onStreamStart, onEventFrame (events) {
onStreamFinish, return shift().then(() => append(events, true));
render: events => shift().then(() => append(events, true)),
listen: (namespace, listener) => {
$scope.$on(namespace, (scope, data) => listener(data));
}, },
onStart () {
vm.status.plays = 0;
vm.status.tasks = 0;
vm.status.running = true;
},
onStop () {
vm.status.stats = statsEvent;
vm.status.running = false;
vm.details.status = statsEvent.failed ? 'failed' : 'successful';
vm.details.finished = statsEvent.created;
}
}); });
$scope.$on(resource.ws.namespace, handleSocketEvent);
if (pageMode) { if (pageMode) {
next(); next();
} }
} }
function handleSocketEvent (scope, data) {
const isLatest = ((!eventCounter) || (data.counter > eventCounter));
if (isLatest) {
eventCounter = data.counter;
vm.details.status = _.get(data, 'summary_fields.job.status');
vm.status.elapsed = moment(data.created)
.diff(resource.model.get('created'), 'seconds');
}
if (data.event === JOB_START) {
vm.details.started = data.created;
}
if (data.event === PLAY_START) {
vm.status.plays++;
}
if (data.event === TASK_START) {
vm.status.tasks++;
}
if (data.event === JOB_END) {
statsEvent = data;
}
stream.pushEventData(data);
}
function devClear (pageMode) { function devClear (pageMode) {
init(pageMode); init(pageMode);
render.clear(); render.clear();
@@ -158,9 +196,11 @@ function next () {
return shift() return shift()
.then(() => append(events)) .then(() => append(events))
.then(() => { .then(() => {
if(scroll.isMissing()) { if (scroll.isMissing()) {
return next(); return next();
} }
return $q.resolve();
}); });
}); });
} }
@@ -242,9 +282,11 @@ function scrollHome () {
scroll.resume(); scroll.resume();
}) })
.then(() => { .then(() => {
if(scroll.isMissing()) { if (scroll.isMissing()) {
return next(); return next();
} }
return $q.resolve();
}); });
}); });
} }
@@ -359,7 +401,7 @@ function toggleSearchKey () {
} }
function getCurrentQueryset () { function getCurrentQueryset () {
const { job_event_search } = $state.params; const { job_event_search } = $state.params; // eslint-disable-line camelcase
return qs.decodeArr(job_event_search); return qs.decodeArr(job_event_search);
} }
@@ -414,6 +456,7 @@ JobsIndexController.$inject = [
'$q', '$q',
'$state', '$state',
'QuerySet', 'QuerySet',
'moment',
]; ];
module.exports = JobsIndexController; module.exports = JobsIndexController;

View File

@@ -10,7 +10,7 @@ import StreamService from '~features/output/stream.service';
import DetailsDirective from '~features/output/details.directive'; import DetailsDirective from '~features/output/details.directive';
import SearchKeyDirective from '~features/output/search-key.directive'; import SearchKeyDirective from '~features/output/search-key.directive';
import StatusDirective from '~features/output/status.directive'; import StatsDirective from '~features/output/stats.directive';
const Template = require('~features/output/index.view.html'); const Template = require('~features/output/index.view.html');
@@ -31,7 +31,7 @@ function resolveResource (
qs, qs,
Wait Wait
) { ) {
const { id, type, job_event_search } = $stateParams; const { id, type, job_event_search } = $stateParams; // eslint-disable-line camelcase
let Resource; let Resource;
let related = 'events'; let related = 'events';
@@ -61,7 +61,7 @@ function resolveResource (
const params = { page_size: PAGE_SIZE, order_by: 'start_line' }; const params = { page_size: PAGE_SIZE, order_by: 'start_line' };
const config = { pageCache: PAGE_CACHE, pageLimit: PAGE_LIMIT, params }; const config = { pageCache: PAGE_CACHE, pageLimit: PAGE_LIMIT, params };
if (job_event_search) { if (job_event_search) { // eslint-disable-line camelcase
const queryParams = qs.encodeQuerysetObject(qs.decodeArr(job_event_search)); const queryParams = qs.encodeQuerysetObject(qs.decodeArr(job_event_search));
Object.assign(config.params, queryParams); Object.assign(config.params, queryParams);
@@ -211,7 +211,7 @@ angular
.service('JobStreamService', StreamService) .service('JobStreamService', StreamService)
.directive('atDetails', DetailsDirective) .directive('atDetails', DetailsDirective)
.directive('atSearchKey', SearchKeyDirective) .directive('atSearchKey', SearchKeyDirective)
.directive('atStatus', StatusDirective) .directive('atStats', StatsDirective)
.run(JobsRun); .run(JobsRun);
export default MODULE_NAME; export default MODULE_NAME;

View File

@@ -13,7 +13,14 @@
<div class="col-md-8"> <div class="col-md-8">
<at-panel class="at-Stdout"> <at-panel class="at-Stdout">
<at-status running="vm.status.running" stats="vm.status.stats"></at-status> <at-stats
elapsed="vm.status.elapsed"
running="vm.status.running"
stats="vm.status.stats"
title="vm.status.title"
plays="vm.status.plays"
tasks="vm.status.tasks">
</at-stats>
<!-- search ===================================================================================== --> <!-- search ===================================================================================== -->
<form ng-submit="vm.search()"> <form ng-submit="vm.search()">
<div class="input-group"> <div class="input-group">

View File

@@ -250,7 +250,6 @@ function JobRenderService ($q, $sce, $window) {
this.clear = () => { this.clear = () => {
const elements = this.el.children(); const elements = this.el.children();
return this.remove(elements); return this.remove(elements);
}; };

View File

@@ -1,4 +1,4 @@
const templateUrl = require('~features/output/status.partial.html'); const templateUrl = require('~features/output/stats.partial.html');
const HOST_STATUS_KEYS = ['dark', 'failures', 'changed', 'ok', 'skipped']; const HOST_STATUS_KEYS = ['dark', 'failures', 'changed', 'ok', 'skipped'];
@@ -21,23 +21,25 @@ function getHostStatusCounts (statsEvent) {
}); });
}); });
counts.hosts = countedHostNames.length;
return counts; return counts;
} }
function createStatusBarTooltip (key, count) { function createStatsBarTooltip (key, count) {
const label = `<span class='HostStatusBar-tooltipLabel'>${key}</span>`; const label = `<span class='HostStatusBar-tooltipLabel'>${key}</span>`;
const badge = `<span class='badge HostStatusBar-tooltipBadge HostStatusBar-tooltipBadge--${key}'>${count}</span>`; const badge = `<span class='badge HostStatusBar-tooltipBadge HostStatusBar-tooltipBadge--${key}'>${count}</span>`;
return `${label}${badge}`; return `${label}${badge}`;
} }
function atStatusLink (scope, el, attrs, controllers) { function atStatsLink (scope, el, attrs, controllers) {
const [atStatusController] = controllers; const [atStatsController] = controllers;
atStatusController.init(scope); atStatsController.init(scope);
} }
function AtStatusController (strings) { function AtStatsController (strings) {
const vm = this || {}; const vm = this || {};
vm.tooltips = { vm.tooltips = {
@@ -46,12 +48,22 @@ function AtStatusController (strings) {
}; };
vm.init = scope => { vm.init = scope => {
const { running, stats } = scope; const { elapsed, running, stats, title, plays, tasks } = scope;
vm.title = title;
vm.plays = plays;
vm.tasks = tasks;
vm.elapsed = elapsed;
vm.running = running || false; vm.running = running || false;
vm.setStats(stats); vm.setStats(stats);
scope.$watch('elapsed', value => { vm.elapsed = value; });
scope.$watch('running', value => { vm.running = value; }); scope.$watch('running', value => { vm.running = value; });
scope.$watch('plays', value => { vm.plays = value; });
scope.$watch('tasks', value => { vm.tasks = value; });
scope.$watch('stats', vm.setStats); scope.$watch('stats', vm.setStats);
}; };
@@ -64,29 +76,34 @@ function AtStatusController (strings) {
statusBarElement.css('flex', `${count} 0 auto`); statusBarElement.css('flex', `${count} 0 auto`);
vm.tooltips[key] = createStatusBarTooltip(key, count); vm.tooltips[key] = createStatsBarTooltip(key, count);
}); });
vm.hosts = counts.hosts;
vm.statsAreAvailable = Boolean(stats); vm.statsAreAvailable = Boolean(stats);
}; };
} }
function atStatus () { function atStats () {
return { return {
templateUrl, templateUrl,
restrict: 'E', restrict: 'E',
require: ['atStatus'], require: ['atStats'],
controllerAs: 'vm', controllerAs: 'vm',
link: atStatusLink, link: atStatsLink,
controller: [ controller: [
'JobStrings', 'JobStrings',
AtStatusController AtStatsController
], ],
scope: { scope: {
elapsed: '=',
running: '=', running: '=',
stats: '=', stats: '=',
title: '=',
plays: '=',
tasks: '=',
}, },
}; };
} }
export default atStatus; export default atStats;

View File

@@ -1,9 +1,8 @@
const JOB_START = 'playbook_on_start';
const JOB_END = 'playbook_on_stats'; 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, onStreamStart, onStreamFinish, render, listen }) => { this.init = ({ resource, scroll, page, onEventFrame, onStart, onStop }) => {
this.resource = resource; this.resource = resource;
this.scroll = scroll; this.scroll = scroll;
this.page = page; this.page = page;
@@ -13,20 +12,21 @@ function JobStreamService ($q) {
this.pageCount = 0; this.pageCount = 0;
this.chain = $q.resolve(); this.chain = $q.resolve();
this.factors = this.getBatchFactors(this.resource.page.size); this.factors = this.getBatchFactors(this.resource.page.size);
this.state = { this.state = {
started: false, started: false,
paused: false, paused: false,
pausing: false, pausing: false,
resuming: false, resuming: false,
ending: false, ending: false,
ended: false ended: false,
counting: false,
}; };
this.hooks = { this.hooks = {
onStreamStart, onEventFrame,
onStreamFinish, onStart,
render, onStop,
listen,
}; };
this.lines = { this.lines = {
@@ -36,8 +36,6 @@ function JobStreamService ($q) {
min: 0, min: 0,
max: 0 max: 0
}; };
this.hooks.listen(resource.ws.namespace, this.listener);
}; };
this.getBatchFactors = size => { this.getBatchFactors = size => {
@@ -91,7 +89,7 @@ function JobStreamService ($q) {
this.lines.used.push(i); this.lines.used.push(i);
} }
let missing = []; const missing = [];
for (let i = this.lines.min; i < this.lines.max; i++) { for (let i = this.lines.min; i < this.lines.max; i++) {
if (this.lines.used.indexOf(i) === -1) { if (this.lines.used.indexOf(i) === -1) {
missing.push(i); missing.push(i);
@@ -107,25 +105,19 @@ function JobStreamService ($q) {
} }
}; };
this.listener = data => { this.pushEventData = 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);
@@ -142,9 +134,11 @@ function JobStreamService ($q) {
return this.renderFrame(events); return this.renderFrame(events);
}) })
.then(() => --this.lag); .then(() => --this.lag);
return this.chain;
}; };
this.renderFrame = events => this.hooks.render(events) this.renderFrame = events => this.hooks.onEventFrame(events)
.then(() => { .then(() => {
if (this.scroll.isLocked()) { if (this.scroll.isLocked()) {
this.scroll.scrollToBottom(); this.scroll.scrollToBottom();
@@ -190,9 +184,13 @@ function JobStreamService ($q) {
}; };
this.start = () => { this.start = () => {
this.state.started = true; if (!this.state.ending && !this.state.ended) {
this.scroll.pause(); this.state.started = true;
this.scroll.lock(); this.scroll.pause();
this.scroll.lock();
this.hooks.onStart();
}
}; };
this.end = done => { this.end = done => {
@@ -202,13 +200,15 @@ function JobStreamService ($q) {
this.scroll.unlock(); this.scroll.unlock();
this.scroll.resume(); this.scroll.resume();
this.hooks.onStop();
return; return;
} }
this.state.ending = true; this.state.ending = true;
}; };
this.isReadyToRender = () => this.isEnding() || this.isReadyToRender = () => this.isDone() ||
(!this.isPaused() && this.hasAllLines() && this.isBatchFull()); (!this.isPaused() && this.hasAllLines() && this.isBatchFull());
this.hasAllLines = () => this.lines.ready; this.hasAllLines = () => this.lines.ready;
this.isBatchFull = () => this.count % this.framesPerRender === 0; this.isBatchFull = () => this.count % this.framesPerRender === 0;

View File

@@ -44,3 +44,15 @@
text-align: center; text-align: center;
margin-left: 5px; margin-left: 5px;
} }
.at-Panel-label {
text-transform: uppercase;
color: @default-interface-txt;
font-size: 12px;
font-weight: normal!important;
width: 30%;
@media screen and (max-width: @breakpoint-md) {
flex: 2.5 0 auto;
}
}

View File

@@ -114,7 +114,8 @@ function atRelaunchCtrl (
jobObj.postRelaunch(launchParams) jobObj.postRelaunch(launchParams)
.then((launchRes) => { .then((launchRes) => {
if (!$state.includes('jobs')) { if (!$state.includes('jobs')) {
$state.go('jobResult', { id: launchRes.data.id }, { reload: true }); const relaunchType = launchRes.data.type === 'job' ? 'playbook' : launchRes.data.type;
$state.go('jobz', { id: launchRes.data.id, type: relaunchType }, { reload: true });
} }
}); });
} }

View File

@@ -354,7 +354,6 @@ function has (method, keys) {
} }
function extend (related, config = {}) { function extend (related, config = {}) {
const req = this.parseRequestConfig('GET', config); const req = this.parseRequestConfig('GET', config);
if (_.get(config, 'params.page_size')) { if (_.get(config, 'params.page_size')) {
@@ -614,7 +613,11 @@ function create (method, resource, config) {
} }
function setEndpoint (resource) { function setEndpoint (resource) {
this.endpoint = `${this.path}${resource}/`; if (Array.isArray(resource)) {
this.endpoint = `${this.path}${resource[0]}/`;
} else {
this.endpoint = `${this.path}${resource}/`;
}
} }
function parseRequestConfig (method, resource, config) { function parseRequestConfig (method, resource, config) {

View File

@@ -48,7 +48,6 @@ function getStats () {
}); });
} }
function JobModel (method, resource, config) { function JobModel (method, resource, config) {
BaseModel.call(this, 'jobs'); BaseModel.call(this, 'jobs');

View File

@@ -10,8 +10,6 @@ function getStats () {
return Promise.reject(new Error('No related property, events, exists')); return Promise.reject(new Error('No related property, events, exists'));
} }
const req = { const req = {
method: 'GET', method: 'GET',
url: `${this.path}${this.get('id')}/events/`, url: `${this.path}${this.get('id')}/events/`,
@@ -20,19 +18,18 @@ function getStats () {
return $http(req) return $http(req)
.then(({ data }) => { .then(({ data }) => {
console.log(data);
if (data.results.length > 0) { if (data.results.length > 0) {
return data.results[0]; return data.results[0];
} }
return null; 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.getStats = getStats.bind(this);
this.Constructor = ProjectUpdateModel; this.Constructor = ProjectUpdateModel;
@@ -40,7 +37,7 @@ function ProjectUpdateModel (method, resource, config) {
} }
function ProjectUpdateModelLoader (_$http_, _BaseModel_) { function ProjectUpdateModelLoader (_$http_, _BaseModel_) {
$http = _$http_; $http = _$http_;
BaseModel = _BaseModel_; BaseModel = _BaseModel_;
return ProjectUpdateModel; return ProjectUpdateModel;

View File

@@ -50,6 +50,17 @@
font-size: @at-font-size-body; font-size: @at-font-size-body;
} }
.at-ButtonIcon-noborder {
padding: 4px @at-padding-button-horizontal;
font-size: @at-font-size-body;
.at-mixin-Button();
.at-mixin-ButtonHollow(
'at-color-default',
'at-color-default',
'at-color-button-text-default'
);
}
.at-Button--expand { .at-Button--expand {
width: 100%; width: 100%;
} }

View File

@@ -20,3 +20,11 @@
.at-u-clear { .at-u-clear {
clear: both; clear: both;
} }
.at-u-noBorder {
border: none;
}
.at-u-floatRight {
float: right
}

View File

@@ -2,7 +2,6 @@
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
width: 100%; width: 100%;
margin-top: 10px;
} }
.HostStatusBar-ok, .HostStatusBar-ok,
@@ -32,6 +31,11 @@
flex: 0 0 auto; flex: 0 0 auto;
} }
.HostStatusBar-dark {
background-color: @default-unreachable;
flex: 0 0 auto;
}
.HostStatusBar-failures { .HostStatusBar-failures {
background-color: @default-err; background-color: @default-err;
flex: 0 0 auto; flex: 0 0 auto;
@@ -65,6 +69,10 @@
background-color: @default-unreachable; background-color: @default-unreachable;
} }
.HostStatusBar-tooltipBadge--dark {
background-color: @default-unreachable;
}
.HostStatusBar-tooltipBadge--skipped { .HostStatusBar-tooltipBadge--skipped {
background-color: @default-link; background-color: @default-link;
} }

View File

@@ -126,9 +126,8 @@ standard-out-log {
.StandardOut-panelHeaderActions { .StandardOut-panelHeaderActions {
justify-content: flex-end; justify-content: flex-end;
display: flex;
margin-left: 10px; margin-left: 10px;
font-size: 20px; font-size: 12px;
} }
.StandardOut-actions { .StandardOut-actions {
@@ -137,12 +136,11 @@ standard-out-log {
.StandardOut-actionButton { .StandardOut-actionButton {
font-size: 16px; font-size: 16px;
height: 30px; height: 20px;
min-width: 30px; min-width: 30px;
color: @list-action-icon; color: @list-action-icon;
background-color: inherit; background-color: inherit;
border: none; border: none;
border-radius: 50%;
} }
.StandardOut-actionButton:hover { .StandardOut-actionButton:hover {