Fix assorted Job Details bugs

* fix strict equality check that broke host summary event signals, fix hosts value provided to host-event modal, add all jobDetail.* routes to event emitter used by stdout, resolves #1733

* fix over-broad clear on intervals driving job details / stdout updates #1799

* fixes assorted job details audit issues

* feedback on PR #1799
This commit is contained in:
Leigh
2016-05-06 12:23:08 -04:00
parent ea25742ea5
commit 1af0a172d3
14 changed files with 85 additions and 67 deletions

View File

@@ -670,15 +670,15 @@ var tower = angular.module('Tower', [
// accessing. // accessing.
if ($state.is('jobs')) { if ($state.is('jobs')) {
$rootScope.$emit('JobStatusChange-jobs', data); $rootScope.$emit('JobStatusChange-jobs', data);
} else if ($state.is('jobDetail') || } else if ($state.includes('jobDetail') ||
$state.is('adHocJobStdout') || $state.is('adHocJobStdout') ||
$state.is('inventorySyncStdout') || $state.is('inventorySyncStdout') ||
$state.is('managementJobStdout') || $state.is('managementJobStdout') ||
$state.is('scmUpdateStdout')) { $state.is('scmUpdateStdout')){
$log.debug("sending status to standard out"); $log.debug("sending status to standard out");
$rootScope.$emit('JobStatusChange-jobStdout', data); $rootScope.$emit('JobStatusChange-jobStdout', data);
} if ($state.is('jobDetail')) { } if ($state.includes('jobDetail')) {
$rootScope.$emit('JobStatusChange-jobDetails', data); $rootScope.$emit('JobStatusChange-jobDetails', data);
} else if ($state.is('dashboard')) { } else if ($state.is('dashboard')) {
$rootScope.$emit('JobStatusChange-home', data); $rootScope.$emit('JobStatusChange-home', data);
@@ -739,10 +739,19 @@ var tower = angular.module('Tower', [
} }
// remove any lingering intervals // remove any lingering intervals
if ($rootScope.jobDetailInterval) { // except on jobDetails.* states
var jobDetailStates = [
'jobDetail',
'jobDetail.host-summary',
'jobDetail.host-event.details',
'jobDetail.host-event.json',
'jobDetail.host-events',
'jobDetail.host-event.stdout'
];
if ($rootScope.jobDetailInterval && !_.includes(jobDetailStates, next.name) ) {
window.clearInterval($rootScope.jobDetailInterval); window.clearInterval($rootScope.jobDetailInterval);
} }
if ($rootScope.jobStdOutInterval) { if ($rootScope.jobStdOutInterval && !_.includes(jobDetailStates, next.name) ) {
window.clearInterval($rootScope.jobStdOutInterval); window.clearInterval($rootScope.jobStdOutInterval);
} }

View File

@@ -6,8 +6,8 @@
export default export default
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'event', 'CodeMirror', ['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'hostEvent',
function($stateParams, $scope, $state, Wait, JobDetailService, event, CodeMirror){ function($stateParams, $scope, $state, Wait, JobDetailService, hostEvent){
$scope.processEventStatus = JobDetailService.processEventStatus; $scope.processEventStatus = JobDetailService.processEventStatus;
$scope.hostResults = []; $scope.hostResults = [];
@@ -56,7 +56,7 @@
}; };
var init = function(){ var init = function(){
$scope.event = event; $scope.event = hostEvent;
JobDetailService.getJobEventChildren($stateParams.taskId).success(function(res){ JobDetailService.getJobEventChildren($stateParams.taskId).success(function(res){
$scope.hostResults = res.results; $scope.hostResults = res.results;
}); });

View File

@@ -12,10 +12,10 @@ var hostEventModal = {
controller: 'HostEventController', controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-modal'), templateUrl: templateUrl('job-detail/host-event/host-event-modal'),
resolve: { resolve: {
event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) { hostEvent: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
return JobDetailService.getRelatedJobEvents($stateParams.id, { return JobDetailService.getRelatedJobEvents($stateParams.id, {
id: $stateParams.eventId id: $stateParams.eventId
}).success(function(res){ return res.results[0];}); }).then(function(res){ return res.data.results[0];});
}] }]
}, },
onExit: function(){ onExit: function(){

View File

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

View File

@@ -5,7 +5,7 @@
text-anchor: end !important; text-anchor: end !important;
} }
.HostSummary-graph--changed{ .HostSummary-graph--changed{
text-anchor: end !important; text-anchor: start !important;
} }
.HostSUmmary-graph--unreachable{} .HostSUmmary-graph--unreachable{}
.HostSummary-loading{ .HostSummary-loading{

View File

@@ -53,13 +53,13 @@
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py // JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
jobSocket.on('summary_complete', function(data) { jobSocket.on('summary_complete', function(data) {
// discard socket msgs we don't care about in this context // discard socket msgs we don't care about in this context
if ($stateParams.id === data.unified_job_id){ if (parseInt($stateParams.id) === data.unified_job_id){
init(); init();
} }
}); });
// UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py // UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py
jobSocket.on('status_changed', function(data) { jobSocket.on('status_changed', function(data) {
if ($stateParams.id === data.unified_job_id){ if (parseInt($stateParams.id) === data.unified_job_id){
$scope.status = data.status; $scope.status = data.status;
} }
}); });
@@ -78,11 +78,6 @@
return n !== 0 ? n + dict[n] + status : dict[n] + status; return n !== 0 ? n + dict[n] + status : dict[n] + status;
} }
}; };
/*
_.forIn(count, function(value, key){
text[key] = grammar(value.length, key);
});
*/
return grammar(n, status); return grammar(n, status);
}; };
$scope.getNextPage = function(){ $scope.getNextPage = function(){

View File

@@ -1,5 +1,6 @@
<div id="hosts-summary-section" class="section"> <div id="hosts-summary-section" class="section">
<div class="JobDetail-instructions"><span class="badge">4</span> Please select a host below to view a summary of all associated tasks.</div>
<div class="JobDetail-searchHeaderRow" ng-hide="hosts.length == 0"> <div class="JobDetail-searchHeaderRow" ng-hide="hosts.length == 0">
<div class="JobDetail-searchContainer form-group"> <div class="JobDetail-searchContainer form-group">
<div class="search-name"> <div class="search-name">
@@ -43,17 +44,17 @@
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'ok'})" aw-tool-tip="{{ buildTooltip(host.ok - host.changed, '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: 'ok'})" aw-tool-tip="{{ buildTooltip(host.ok - host.changed, '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="{{buildTooltip(host.changed, '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: 'changed'})" aw-tool-tip="{{buildTooltip(host.changed, '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="{{buildTooltip(host.skipped, '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: 'skipped'})" aw-tool-tip="{{buildTooltip(host.skipped, '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="{{buildTooltip(host.dark, '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.host_name, filter: 'unreachable'})" aw-tool-tip="{{buildTooltip(host.dark, '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="{{ buildTooltip(host.failures, 'failed')}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a> <a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'failed'})" aw-tool-tip="{{ buildTooltip(host.failures, 'failed')}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a>
</td> </td>
</tr> </tr>
<tr ng-show="hosts.length === 0 && status === 'pending'"> <tr ng-show="status == 'pending'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Waiting...</td> <td colspan="5" class="col-lg-12 HostSummary-loading">Initiating job run.</td>
</tr> </tr>
<tr ng-show="hosts.length === 0 && status === 'running' "> <tr ng-show="status == 'running'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Loading...</td> <td colspan="5" class="col-lg-12 HostSummary-loading">Job is running. Summary will be available on completion.</td>
</tr> </tr>
<tr ng-show="status === 'failed' || status === 'successful' && hosts.length === 0 "> <tr ng-show="(status === 'failed' || status === 'successful') && hosts.length === 0 ">
<td colspan="2" class="col-lg-12 HostSummary-loading">No matching hosts</td> <td colspan="2" class="col-lg-12 HostSummary-loading">No matching hosts</td>
</tr> </tr>
</tbody> </tbody>
@@ -65,7 +66,7 @@
</div> </div>
</div><!-- section --> </div><!-- section -->
<div class="JobDetail-panelHeaderText">Host Status Summary</div>
<div id="graph-section" class="JobDetail-graphSection"> <div id="graph-section" class="JobDetail-graphSection">
<svg></svg> <svg></svg>
</div> </div>

View File

@@ -8,16 +8,21 @@ import {templateUrl} from '../../shared/template-url/template-url.factory';
export default { export default {
name: 'jobDetail.host-summary', name: 'jobDetail.host-summary',
url: '/event-summary',
/*
resolve: { resolve: {
jobSocket: ['Socket', '$rootScope', function(Socket, $rootScope) { jobSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
var job_socket = Socket({ var job_socket = Socket({
scope: $rootScope, scope: $rootScope,
endpoint: "jobs" endpoint: "jobs"
}); });
job_socket.init(); if (!$rootScope.event_socket){
return job_socket; job_socket.init();
}
return job_socket;
}] }]
}, },
*/
views:{ views:{
'host-summary': { 'host-summary': {
controller: 'HostSummaryController', controller: 'HostSummaryController',

View File

@@ -7,6 +7,13 @@
@breakpoint-md: 1200px; @breakpoint-md: 1200px;
@breakpoint-sm: 420px; @breakpoint-sm: 420px;
.JobDetail-tasks.section{
margin-top:40px;
}
.JobDetail-instructions{
color: @default-interface-txt;
margin: 10px 0 10px 0;
}
.JobDetail{ .JobDetail{
.OnePlusOne-container(100%, @breakpoint-md); .OnePlusOne-container(100%, @breakpoint-md);
} }
@@ -115,7 +122,7 @@
flex-wrap: wrap; flex-wrap: wrap;
flex-direction: row; flex-direction: row;
height: 50px; height: 50px;
margin-top: 25px; margin-top: 20px;
@media screen and(max-width: @breakpoint-sm){ @media screen and(max-width: @breakpoint-sm){
height: auto; height: auto;
} }

View File

@@ -13,7 +13,7 @@
export default export default
[ '$location', '$rootScope', '$filter', '$scope', '$compile', [ '$location', '$rootScope', '$filter', '$scope', '$compile',
'$stateParams', '$log', 'ClearScope', 'GetBasePath', 'Wait', '$stateParams', '$log', 'ClearScope', 'GetBasePath', 'Wait',
'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'GetElapsed',
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun', 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
'LoadPlays', 'LoadTasks', 'HostsEdit', 'LoadPlays', 'LoadTasks', 'HostsEdit',
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels', 'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
@@ -21,7 +21,7 @@ export default
function( function(
$location, $rootScope, $filter, $scope, $compile, $stateParams, $location, $rootScope, $filter, $scope, $compile, $stateParams,
$log, ClearScope, GetBasePath, Wait, ProcessErrors, $log, ClearScope, GetBasePath, Wait, ProcessErrors,
SelectPlay, SelectTask, Socket, GetElapsed, SelectPlay, SelectTask, GetElapsed,
JobIsFinished, JobIsFinished,
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob, SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
PlaybookRun, LoadPlays, LoadTasks, PlaybookRun, LoadPlays, LoadTasks,
@@ -680,7 +680,7 @@ export default
// there's a bunch of white space at the bottom, let's use it // there's a bunch of white space at the bottom, let's use it
$('#plays-table-detail').height(80 + (height * 0.10)); $('#plays-table-detail').height(80 + (height * 0.10));
$('#tasks-table-detail').height(120 + (height * 0.20)); $('#tasks-table-detail').height(120 + (height * 0.20));
$('#hosts-table-detail').height(150 + (height * 0.70)); $('#hosts-table-detail').height(150 + (height * 0.10));
} }
scope.$emit('RefreshCompleted'); scope.$emit('RefreshCompleted');
}; };
@@ -737,7 +737,7 @@ export default
return true; return true;
}; };
scope.toggleLessEvents = function() { scope.toggleLessEvents = function(state) {
if (!scope.lessEvents) { if (!scope.lessEvents) {
$('#events-summary').slideUp(200); $('#events-summary').slideUp(200);
scope.lessEvents = true; scope.lessEvents = true;

View File

@@ -171,6 +171,7 @@
<div id="job-detail-details"> <div id="job-detail-details">
<div id="play-section"> <div id="play-section">
<div class="JobDetail-instructions"><span class="badge">1</span> Please select from a play below to view its associated tasks.</div>
<div class="JobDetail-searchHeaderRow"> <div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group"> <div class="JobDetail-searchContainer form-group">
<div class="search-name"> <div class="search-name">
@@ -228,7 +229,8 @@
</div> </div>
<!-- end of plays section of details--> <!-- end of plays section of details-->
<div id="task-section" class="section" > <div id="task-section" class="section JobDetail-tasks" >
<div class="JobDetail-instructions"><span class="badge">2</span> Please select a task below to view its associated hosts</div>
<div class="JobDetail-searchHeaderRow"> <div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group"> <div class="JobDetail-searchContainer form-group">
<div class="search-name"> <div class="search-name">
@@ -312,6 +314,7 @@
<!--end of tasks section of details--> <!--end of tasks section of details-->
<div id="task-hosts-section" class="section"> <div id="task-hosts-section" class="section">
<div class="JobDetail-instructions"><span class="badge">3</span> Please select a host below to view associated task details.</div>
<div class="JobDetail-searchHeaderRow"> <div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group"> <div class="JobDetail-searchContainer form-group">
<div class="search-name"> <div class="search-name">
@@ -368,19 +371,12 @@
</div> </div>
<!--end of details--> <!--end of details-->
</div>
<!--end of job-detail-container (left side)-->
<!--beginning of stdout-->
<div class="JobDetail-rightSide">
<!--beginning of events summary--> <!--beginning of events summary-->
<div id="events-summary-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen && job.summary_fields.unified_job_template.unified_job_type == 'job'"> <div id="events-summary-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen && job.summary_fields.unified_job_template.unified_job_type == 'job'">
<div class="JobDetail-panelHeader"> <div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer"> <div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessEvents" ui-sref="jobDetail.host-summary" ng-click="toggleLessEvents()"> <a class="JobDetail-panelHeaderText" ng-show="lessEvents" ui-sref="jobDetail.host-summary" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-left"></i> EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a> </a>
<a class="JobDetail-panelHeaderText" ng-show="!lessEvents" ui-sref="jobDetail" ng-click="toggleLessEvents()"> <a class="JobDetail-panelHeaderText" ng-show="!lessEvents" ui-sref="jobDetail" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-down"></i> EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-down"></i>
@@ -396,6 +392,11 @@
<!-- end of events summary--> <!-- end of events summary-->
</div>
<!--end of job-detail-container (left side)-->
<!--beginning of stdout-->
<div class="JobDetail-rightSide">
<div class="JobDetail-stdoutPanel Panel"> <div class="JobDetail-stdoutPanel Panel">
<div class="StandardOut-panelHeader"> <div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText">STANDARD OUT</div> <div class="StandardOut-panelHeaderText">STANDARD OUT</div>
@@ -403,7 +404,7 @@
<button class="StandardOut-actionButton" aw-tool-tip="Toggle Output" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()"> <button class="StandardOut-actionButton" aw-tool-tip="Toggle Output" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i> <i class="fa fa-arrows-alt"></i>
</button> </button>
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}"> <a ng-show="job_status.status === ('failed' || 'successful')" href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top"> <button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top">
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
</button> </button>

View File

@@ -35,10 +35,6 @@ angular.module('ApiLoader', ['Utilities'])
.success(function (data) { .success(function (data) {
data.base = base; data.base = base;
$rootScope.defaultUrls = data; $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); Store('api', data);
}) })
.error(function (data, status) { .error(function (data, status) {

View File

@@ -22,14 +22,14 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
// Open up a socket for events depending on the type of job // Open up a socket for events depending on the type of job
function openSockets() { function openSockets() {
if ($state.current.name === 'jobDetail') { if ($state.current.name === 'jobDetail') {
$log.debug("socket watching on job_events-" + job_id); $log.debug("socket watching on job_events-" + job_id);
$rootScope.event_socket.on("job_events-" + job_id, function() { $rootScope.event_socket.on("job_events-" + job_id, function() {
$log.debug("socket fired on job_events-" + job_id); $log.debug("socket fired on job_events-" + job_id);
if (api_complete) { if (api_complete) {
event_queue++; event_queue++;
} }
}); });
} }
if ($state.current.name === 'adHocJobStdout') { if ($state.current.name === 'adHocJobStdout') {
$log.debug("socket watching on ad_hoc_command_events-" + job_id); $log.debug("socket watching on ad_hoc_command_events-" + job_id);
$rootScope.adhoc_event_socket.on("ad_hoc_command_events-" + job_id, function() { $rootScope.adhoc_event_socket.on("ad_hoc_command_events-" + job_id, function() {
@@ -131,8 +131,6 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
start: (data.range.start < 0) ? 0 : data.range.start, start: (data.range.start < 0) ? 0 : data.range.start,
end: data.range.end end: data.range.end
}); });
//console.log('loaded start: ' + data.range.start + ' end: ' + data.range.end);
//console.log(data.content);
if ($scope.should_apply_live_events) { if ($scope.should_apply_live_events) {
// if user has not disabled live event view by scrolling upward, then scroll down to the new content // if user has not disabled live event view by scrolling upward, then scroll down to the new content
current_range = data.range; current_range = data.range;

View File

@@ -3,6 +3,11 @@
/** @define StandardOut */ /** @define StandardOut */
.StandardOut-preContent{
font-size: 12px;
padding: 0 20px 0 20px;
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
}
.StandardOut-container { .StandardOut-container {
.OnePlusTwo-container; .OnePlusTwo-container;
} }
@@ -24,8 +29,8 @@
min-height: 200px; min-height: 200px;
background-color: @default-secondary-bg; background-color: @default-secondary-bg;
border-radius: 5px; border-radius: 5px;
height: ~"calc(100% - 74px)"; max-height: 1600px;
overflow: scroll; overflow: auto;
} }
.StandardOut-details { .StandardOut-details {
@@ -81,7 +86,7 @@
font-size: 16px; font-size: 16px;
height: 30px; height: 30px;
min-width: 30px; min-width: 30px;
color: @default-data-txt; color: @list-action-icon;
background-color: inherit; background-color: inherit;
border: none; border: none;
border-radius: 50%; border-radius: 50%;