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',
|
'container',
|
||||||
'containerConsole',
|
'containerConsole',
|
||||||
'containerLogs',
|
'containerLogs',
|
||||||
|
'serviceLogs',
|
||||||
'containers',
|
'containers',
|
||||||
'createContainer',
|
'createContainer',
|
||||||
'createNetwork',
|
'createNetwork',
|
||||||
|
@ -166,7 +167,7 @@ angular.module('portainer', [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('logs', {
|
.state('containerlogs', {
|
||||||
url: '^/containers/:id/logs',
|
url: '^/containers/:id/logs',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'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', {
|
.state('console', {
|
||||||
url: '^/containers/:id/console',
|
url: '^/containers/:id/console',
|
||||||
views: {
|
views: {
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="btn-group" role="group" aria-label="...">
|
<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="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>
|
<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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -72,6 +72,13 @@
|
||||||
<input type="text" class="form-control" ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" ng-disabled="isUpdating" />
|
<input type="text" class="form-control" ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" ng-disabled="isUpdating" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</rd-widget-body>
|
</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