fix(log-viewer): strip headers in container logs when TTY is disabled (#1861)

pull/1862/head
Anthony Lapenna 2018-05-04 09:45:05 +02:00 committed by GitHub
parent 1e55ada6af
commit e0cf088428
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 81 additions and 26 deletions

View File

@ -0,0 +1,22 @@
angular.module('portainer.docker')
.factory('LogHelper', [function LogHelperFactory() {
'use strict';
var helper = {};
// Return an array with each line being an entry.
// It will also remove any ANSI code related character sequences.
// If the skipHeaders param is specified, it will strip the 8 first characters of each line.
helper.formatLogs = function(logs, skipHeaders) {
logs = logs.replace(
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
if (skipHeaders) {
logs = logs.substring(8);
logs = logs.replace(/\n(.{8})/g, '\n\r');
}
return logs.split('\n');
};
return helper;
}]);

View File

@ -16,7 +16,7 @@ angular.module('portainer.docker')
logs: { logs: {
method: 'GET', params: { id: '@id', action: 'logs' }, method: 'GET', params: { id: '@id', action: 'logs' },
timeout: 4500, ignoreLoadingBar: true, timeout: 4500, ignoreLoadingBar: true,
transformResponse: logsHandler, isArray: true transformResponse: logsHandler
}, },
stats: { stats: {
method: 'GET', params: { id: '@id', stream: false, action: 'stats' }, method: 'GET', params: { id: '@id', stream: false, action: 'stats' },

View File

@ -45,15 +45,11 @@ function genericHandler(data) {
} }
// The Docker API returns the logs as a single string. // The Docker API returns the logs as a single string.
// This handler will return an array with each line being an entry. // This handler wraps the data in a JSON object under the "logs" property.
// It will also strip the 8 first characters of each line and remove any ANSI code related character sequences.
function logsHandler(data) { function logsHandler(data) {
var logs = data; return {
logs = logs.substring(8); logs: data
logs = logs.replace(/\n(.{8})/g, '\n\r'); };
logs = logs.replace(
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
return logs.split('\n');
} }
// Image delete API returns an array on success (Docker 1.9 -> Docker 1.12). // Image delete API returns an array on success (Docker 1.9 -> Docker 1.12).

View File

@ -17,7 +17,7 @@ angular.module('portainer.docker')
logs: { logs: {
method: 'GET', params: { id: '@id', action: 'logs' }, method: 'GET', params: { id: '@id', action: 'logs' },
timeout: 4500, ignoreLoadingBar: true, timeout: 4500, ignoreLoadingBar: true,
transformResponse: logsHandler, isArray: true transformResponse: logsHandler
} }
}); });
}]); }]);

View File

@ -10,7 +10,7 @@ angular.module('portainer.docker')
logs: { logs: {
method: 'GET', params: { id: '@id', action: 'logs' }, method: 'GET', params: { id: '@id', action: 'logs' },
timeout: 4500, ignoreLoadingBar: true, timeout: 4500, ignoreLoadingBar: true,
transformResponse: logsHandler, isArray: true transformResponse: logsHandler
} }
}); });
}]); }]);

View File

@ -1,5 +1,6 @@
angular.module('portainer.docker') angular.module('portainer.docker')
.factory('ContainerService', ['$q', 'Container', 'ResourceControlService', function ContainerServiceFactory($q, Container, ResourceControlService) { .factory('ContainerService', ['$q', 'Container', 'ResourceControlService', 'LogHelper',
function ContainerServiceFactory($q, Container, ResourceControlService, LogHelper) {
'use strict'; 'use strict';
var service = {}; var service = {};
@ -131,7 +132,9 @@ angular.module('portainer.docker')
return deferred.promise; return deferred.promise;
}; };
service.logs = function(id, stdout, stderr, timestamps, tail) { service.logs = function(id, stdout, stderr, timestamps, tail, stripHeaders) {
var deferred = $q.defer();
var parameters = { var parameters = {
id: id, id: id,
stdout: stdout || 0, stdout: stdout || 0,
@ -140,7 +143,16 @@ angular.module('portainer.docker')
tail: tail || 'all' tail: tail || 'all'
}; };
return Container.logs(parameters).$promise; Container.logs(parameters).$promise
.then(function success(data) {
var logs = LogHelper.formatLogs(data.logs, stripHeaders);
deferred.resolve(logs);
})
.catch(function error(err) {
deferred.reject(err);
});
return deferred.promise;
}; };
service.containerStats = function(id) { service.containerStats = function(id) {

View File

@ -1,5 +1,6 @@
angular.module('portainer.docker') angular.module('portainer.docker')
.factory('ServiceService', ['$q', 'Service', 'ServiceHelper', 'TaskService', 'ResourceControlService', function ServiceServiceFactory($q, Service, ServiceHelper, TaskService, ResourceControlService) { .factory('ServiceService', ['$q', 'Service', 'ServiceHelper', 'TaskService', 'ResourceControlService', 'LogHelper',
function ServiceServiceFactory($q, Service, ServiceHelper, TaskService, ResourceControlService, LogHelper) {
'use strict'; 'use strict';
var service = {}; var service = {};
@ -59,6 +60,8 @@ angular.module('portainer.docker')
}; };
service.logs = function(id, stdout, stderr, timestamps, tail) { service.logs = function(id, stdout, stderr, timestamps, tail) {
var deferred = $q.defer();
var parameters = { var parameters = {
id: id, id: id,
stdout: stdout || 0, stdout: stdout || 0,
@ -67,7 +70,16 @@ angular.module('portainer.docker')
tail: tail || 'all' tail: tail || 'all'
}; };
return Service.logs(parameters).$promise; Service.logs(parameters).$promise
.then(function success(data) {
var logs = LogHelper.formatLogs(data.logs, true);
deferred.resolve(logs);
})
.catch(function error(err) {
deferred.reject(err);
});
return deferred.promise;
}; };
return service; return service;

View File

@ -1,5 +1,6 @@
angular.module('portainer.docker') angular.module('portainer.docker')
.factory('TaskService', ['$q', 'Task', function TaskServiceFactory($q, Task) { .factory('TaskService', ['$q', 'Task', 'LogHelper',
function TaskServiceFactory($q, Task, LogHelper) {
'use strict'; 'use strict';
var service = {}; var service = {};
@ -36,6 +37,8 @@ angular.module('portainer.docker')
}; };
service.logs = function(id, stdout, stderr, timestamps, tail) { service.logs = function(id, stdout, stderr, timestamps, tail) {
var deferred = $q.defer();
var parameters = { var parameters = {
id: id, id: id,
stdout: stdout || 0, stdout: stdout || 0,
@ -44,7 +47,16 @@ angular.module('portainer.docker')
tail: tail || 'all' tail: tail || 'all'
}; };
return Task.logs(parameters).$promise; Task.logs(parameters).$promise
.then(function success(data) {
var logs = LogHelper.formatLogs(data.logs, true);
deferred.resolve(logs);
})
.catch(function error(err) {
deferred.reject(err);
});
return deferred.promise;
}; };
return service; return service;

View File

@ -11,7 +11,7 @@ function ($scope, $transition$, $interval, ContainerService, Notifications) {
if (!logCollectionStatus) { if (!logCollectionStatus) {
stopRepeater(); stopRepeater();
} else { } else {
setUpdateRepeater(); setUpdateRepeater(!$scope.container.Config.Tty);
} }
}; };
@ -31,10 +31,10 @@ function ($scope, $transition$, $interval, ContainerService, Notifications) {
$scope.logs = logs; $scope.logs = logs;
} }
function setUpdateRepeater() { function setUpdateRepeater(skipHeaders) {
var refreshRate = $scope.state.refreshRate; var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function() { $scope.repeater = $interval(function() {
ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount) ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount, skipHeaders)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
}) })
@ -45,11 +45,11 @@ function ($scope, $transition$, $interval, ContainerService, Notifications) {
}, refreshRate * 1000); }, refreshRate * 1000);
} }
function startLogPolling() { function startLogPolling(skipHeaders) {
ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount) ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, $scope.state.lineCount, skipHeaders)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
setUpdateRepeater(); setUpdateRepeater(skipHeaders);
}) })
.catch(function error(err) { .catch(function error(err) {
stopRepeater(); stopRepeater();
@ -60,8 +60,9 @@ function ($scope, $transition$, $interval, ContainerService, Notifications) {
function initView() { function initView() {
ContainerService.container($transition$.params().id) ContainerService.container($transition$.params().id)
.then(function success(data) { .then(function success(data) {
$scope.container = data; var container = data;
startLogPolling(); $scope.container = container;
startLogPolling(!container.Config.Tty);
}) })
.catch(function error(err) { .catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve container information'); Notifications.error('Failure', err, 'Unable to retrieve container information');