mirror of https://github.com/portainer/portainer
feat(service-details) - add service logs (#671)
parent
bc3d5e97ea
commit
6df5eb3787
16
app/app.js
16
app/app.js
|
@ -25,6 +25,7 @@ angular.module('portainer', [
|
|||
'container',
|
||||
'containerConsole',
|
||||
'containerLogs',
|
||||
'serviceLogs',
|
||||
'containers',
|
||||
'createContainer',
|
||||
'createNetwork',
|
||||
|
@ -166,7 +167,7 @@ angular.module('portainer', [
|
|||
}
|
||||
}
|
||||
})
|
||||
.state('logs', {
|
||||
.state('containerlogs', {
|
||||
url: '^/containers/:id/logs',
|
||||
views: {
|
||||
'content@': {
|
||||
|
@ -179,6 +180,19 @@ angular.module('portainer', [
|
|||
}
|
||||
}
|
||||
})
|
||||
.state('servicelogs', {
|
||||
url: '^/services/:id/logs',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/components/serviceLogs/servicelogs.html',
|
||||
controller: 'ServiceLogsController'
|
||||
},
|
||||
'sidebar@': {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('console', {
|
||||
url: '^/containers/:id/console',
|
||||
views: {
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="logs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="containerlogs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -72,6 +72,13 @@
|
|||
<input type="text" class="form-control" ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" ng-disabled="isUpdating" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="applicationState.endpoint.apiVersion >= 1.30">
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="servicelogs({id: service.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
angular.module('serviceLogs', [])
|
||||
.controller('ServiceLogsController', ['$scope', '$stateParams', '$anchorScroll', 'ServiceLogs', 'Service',
|
||||
function ($scope, $stateParams, $anchorScroll, ServiceLogs, Service) {
|
||||
$scope.state = {};
|
||||
$scope.state.displayTimestampsOut = false;
|
||||
$scope.state.displayTimestampsErr = false;
|
||||
$scope.stdout = '';
|
||||
$scope.stderr = '';
|
||||
$scope.tailLines = 2000;
|
||||
|
||||
function getLogs() {
|
||||
$('#loadingViewSpinner').show();
|
||||
getLogsStdout();
|
||||
getLogsStderr();
|
||||
$('#loadingViewSpinner').hide();
|
||||
}
|
||||
|
||||
function getLogsStderr() {
|
||||
ServiceLogs.get($stateParams.id, {
|
||||
stdout: 0,
|
||||
stderr: 1,
|
||||
timestamps: $scope.state.displayTimestampsErr,
|
||||
tail: $scope.tailLines
|
||||
}, function (data, status, headers, config) {
|
||||
// Replace carriage returns with newlines to clean up output
|
||||
data = data.replace(/[\r]/g, '\n');
|
||||
// Strip 8 byte header from each line of output
|
||||
data = data.substring(8);
|
||||
data = data.replace(/\n(.{8})/g, '\n');
|
||||
$scope.stderr = data;
|
||||
});
|
||||
}
|
||||
|
||||
function getLogsStdout() {
|
||||
ServiceLogs.get($stateParams.id, {
|
||||
stdout: 1,
|
||||
stderr: 0,
|
||||
timestamps: $scope.state.displayTimestampsOut,
|
||||
tail: $scope.tailLines
|
||||
}, function (data, status, headers, config) {
|
||||
// Replace carriage returns with newlines to clean up output
|
||||
data = data.replace(/[\r]/g, '\n');
|
||||
// Strip 8 byte header from each line of output
|
||||
data = data.substring(8);
|
||||
data = data.replace(/\n(.{8})/g, '\n');
|
||||
$scope.stdout = data;
|
||||
});
|
||||
}
|
||||
|
||||
function getService() {
|
||||
$('#loadingViewSpinner').show();
|
||||
Service.get({id: $stateParams.id}, function (d) {
|
||||
$scope.service = d;
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve service info');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
getService();
|
||||
getLogs();
|
||||
|
||||
var logIntervalId = window.setInterval(getLogs, 5000);
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
// clearing interval when view changes
|
||||
clearInterval(logIntervalId);
|
||||
});
|
||||
|
||||
$scope.toggleTimestampsOut = function () {
|
||||
getLogsStdout();
|
||||
};
|
||||
|
||||
$scope.toggleTimestampsErr = function () {
|
||||
getLogsStderr();
|
||||
};
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
}]);
|
|
@ -0,0 +1,56 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Service logs">
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="services">Services</a> > <a ui-sref="service({id: service.ID})">{{ service.Spec.Name }}</a> > Logs
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="widget-icon grey pull-left">
|
||||
<i class="fa fa-list-alt"></i>
|
||||
</div>
|
||||
<div class="title">{{ service.Spec.Name }}</div>
|
||||
<div class="comment">Name</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-info-circle" title="Stdout logs"></rd-widget-header>
|
||||
<rd-widget-taskbar>
|
||||
<input type="checkbox" ng-model="state.displayTimestampsOut" id="displayAllTsOut" ng-change="toggleTimestampsOut()"/>
|
||||
<label for="displayAllTsOut">Display timestamps</label>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="panel-body">
|
||||
<pre id="stdoutLog" class="pre-scrollable pre-x-scrollable">{{stdout}}</pre>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-exclamation-triangle" title="Stderr logs"></rd-widget-header>
|
||||
<rd-widget-taskbar>
|
||||
<input type="checkbox" ng-model="state.displayTimestampsErr" id="displayAllTsErr" ng-change="toggleTimestampsErr()"/>
|
||||
<label for="displayAllTsErr">Display timestamps</label>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="panel-body">
|
||||
<pre id="stderrLog" class="pre-scrollable pre-x-scrollable">{{stderr}}</pre>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('portainer.rest')
|
||||
.factory('ServiceLogs', ['$http', 'DOCKER_ENDPOINT', 'EndpointProvider', function ServiceLogsFactory($http, DOCKER_ENDPOINT, EndpointProvider) {
|
||||
'use strict';
|
||||
return {
|
||||
get: function (id, params, callback) {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: DOCKER_ENDPOINT + '/' + EndpointProvider.endpointID() + '/services/' + id + '/logs',
|
||||
params: {
|
||||
'stdout': params.stdout || 0,
|
||||
'stderr': params.stderr || 0,
|
||||
'timestamps': params.timestamps || 0,
|
||||
'tail': params.tail || 'all'
|
||||
}
|
||||
}).success(callback).error(function (data, status, headers, config) {
|
||||
console.log(error, data);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
Loading…
Reference in New Issue