mirror of https://github.com/portainer/portainer
Merge branch 'feat71-events-view' into internal
commit
5ef6b536ac
|
@ -12,6 +12,7 @@ angular.module('uifordocker', [
|
||||||
'containers',
|
'containers',
|
||||||
'createContainer',
|
'createContainer',
|
||||||
'docker',
|
'docker',
|
||||||
|
'events',
|
||||||
'images',
|
'images',
|
||||||
'image',
|
'image',
|
||||||
'containerLogs',
|
'containerLogs',
|
||||||
|
@ -84,6 +85,11 @@ angular.module('uifordocker', [
|
||||||
templateUrl: 'app/components/docker/docker.html',
|
templateUrl: 'app/components/docker/docker.html',
|
||||||
controller: 'DockerController'
|
controller: 'DockerController'
|
||||||
})
|
})
|
||||||
|
.state('events', {
|
||||||
|
url: '/events/',
|
||||||
|
templateUrl: 'app/components/events/events.html',
|
||||||
|
controller: 'EventsController'
|
||||||
|
})
|
||||||
.state('images', {
|
.state('images', {
|
||||||
url: '/images/',
|
url: '/images/',
|
||||||
templateUrl: 'app/components/images/images.html',
|
templateUrl: 'app/components/images/images.html',
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<rd-header>
|
||||||
|
<rd-header-title title="Event list">
|
||||||
|
<a data-toggle="tooltip" title="Refresh" ui-sref="events" ui-sref-opts="{reload: true}">
|
||||||
|
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</rd-header-title>
|
||||||
|
<rd-header-content>Events</rd-header-content>
|
||||||
|
</rd-header>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
|
<rd-widget>
|
||||||
|
<rd-widget-header icon="fa-history" title="Events">
|
||||||
|
<div class="pull-right">
|
||||||
|
<i id="loadEventsSpinner" class="fa fa-cog fa-2x fa-spin" style="margin-top: 5px;"></i>
|
||||||
|
</div>
|
||||||
|
</rd-widget-header>
|
||||||
|
<rd-widget-taskbar classes="col-lg-12">
|
||||||
|
<div class="pull-right">
|
||||||
|
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||||
|
</div>
|
||||||
|
</rd-widget-taskbar>
|
||||||
|
<rd-widget-body classes="no-padding">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<a ui-sref="events" ng-click="order('Time')">
|
||||||
|
Date
|
||||||
|
<span ng-show="sortType == 'Time' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||||
|
<span ng-show="sortType == 'Time' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a ui-sref="events" ng-click="order('Type')">
|
||||||
|
Category
|
||||||
|
<span ng-show="sortType == 'Type' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||||
|
<span ng-show="sortType == 'Type' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a ui-sref="events" ng-click="order('Details')">
|
||||||
|
Details
|
||||||
|
<span ng-show="sortType == 'Details' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||||
|
<span ng-show="sortType == 'Details' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="event in (events | filter:state.filter | orderBy:sortType:sortReverse)">
|
||||||
|
<td>{{ event.Time|getdatefromtimestamp }}</td>
|
||||||
|
<td>{{ event.Type }}</td>
|
||||||
|
<td>{{ event.Details }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,27 @@
|
||||||
|
angular.module('events', [])
|
||||||
|
.controller('EventsController', ['$scope', 'Settings', 'Messages', 'Events',
|
||||||
|
function ($scope, Settings, Messages, Events) {
|
||||||
|
$scope.state = {};
|
||||||
|
$scope.sortType = 'Time';
|
||||||
|
$scope.sortReverse = true;
|
||||||
|
|
||||||
|
$scope.order = function(sortType) {
|
||||||
|
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||||
|
$scope.sortType = sortType;
|
||||||
|
};
|
||||||
|
|
||||||
|
var from = moment().subtract(24, 'hour').unix();
|
||||||
|
var to = moment().unix();
|
||||||
|
|
||||||
|
Events.query({since: from, until: to},
|
||||||
|
function(d) {
|
||||||
|
$scope.events = d.map(function (item) {
|
||||||
|
return new EventViewModel(item);
|
||||||
|
});
|
||||||
|
$('#loadEventsSpinner').hide();
|
||||||
|
},
|
||||||
|
function (e) {
|
||||||
|
Messages.error("Unable to load events", e.data);
|
||||||
|
$('#loadEventsSpinner').hide();
|
||||||
|
});
|
||||||
|
}]);
|
|
@ -163,6 +163,12 @@ angular.module('dockerui.filters', [])
|
||||||
return date.toDateString();
|
return date.toDateString();
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.filter('getdatefromtimestamp', function () {
|
||||||
|
'use strict';
|
||||||
|
return function (timestamp) {
|
||||||
|
return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
};
|
||||||
|
})
|
||||||
.filter('errorMsg', function () {
|
.filter('errorMsg', function () {
|
||||||
return function (object) {
|
return function (object) {
|
||||||
var idx = 0;
|
var idx = 0;
|
||||||
|
|
|
@ -98,6 +98,16 @@ angular.module('dockerui.services', ['ngResource', 'ngSanitize'])
|
||||||
inspect: {method: 'GET', params: {id: '@id', action: 'json'}}
|
inspect: {method: 'GET', params: {id: '@id', action: 'json'}}
|
||||||
});
|
});
|
||||||
}])
|
}])
|
||||||
|
.factory('Events', ['$resource', 'Settings', function EventFactory($resource, Settings) {
|
||||||
|
'use strict';
|
||||||
|
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/monitor-docker-s-events
|
||||||
|
return $resource(Settings.url + '/events', {}, {
|
||||||
|
query: {method: 'GET', params: {since: '@since', until: '@until'}, isArray: true, transformResponse: [function f(data) {
|
||||||
|
var str = "[" + data.replace(/\n/g, " ").replace(/\}\s*\{/g, "}, {") + "]";
|
||||||
|
return angular.fromJson(str);
|
||||||
|
}]}
|
||||||
|
});
|
||||||
|
}])
|
||||||
.factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) {
|
.factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) {
|
||||||
'use strict';
|
'use strict';
|
||||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#show-the-docker-version-information
|
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#show-the-docker-version-information
|
||||||
|
|
|
@ -20,3 +20,109 @@ function ContainerViewModel(data) {
|
||||||
this.Command = data.Command;
|
this.Command = data.Command;
|
||||||
this.Checked = false;
|
this.Checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function EventViewModel(data) {
|
||||||
|
// Type, Action, Actor unavailable in Docker < 1.10
|
||||||
|
this.Time = data.time;
|
||||||
|
if (data.Type) {
|
||||||
|
this.Type = data.Type;
|
||||||
|
this.Details = createEventDetails(data);
|
||||||
|
} else {
|
||||||
|
this.Type = data.status;
|
||||||
|
this.Details = data.from;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEventDetails(event) {
|
||||||
|
var eventAttr = event.Actor.Attributes;
|
||||||
|
var details = '';
|
||||||
|
switch (event.Type) {
|
||||||
|
case 'container':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'stop':
|
||||||
|
details = 'Container ' + eventAttr.name + ' stopped';
|
||||||
|
break;
|
||||||
|
case 'destroy':
|
||||||
|
details = 'Container ' + eventAttr.name + ' deleted';
|
||||||
|
break;
|
||||||
|
case 'create':
|
||||||
|
details = 'Container ' + eventAttr.name + ' created';
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
details = 'Container ' + eventAttr.name + ' started';
|
||||||
|
break;
|
||||||
|
case 'kill':
|
||||||
|
details = 'Container ' + eventAttr.name + ' killed';
|
||||||
|
break;
|
||||||
|
case 'die':
|
||||||
|
details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode;
|
||||||
|
break;
|
||||||
|
case 'commit':
|
||||||
|
details = 'Container ' + eventAttr.name + ' committed';
|
||||||
|
break;
|
||||||
|
case 'restart':
|
||||||
|
details = 'Container ' + eventAttr.name + ' restarted';
|
||||||
|
break;
|
||||||
|
case 'pause':
|
||||||
|
details = 'Container ' + eventAttr.name + ' paused';
|
||||||
|
break;
|
||||||
|
case 'unpause':
|
||||||
|
details = 'Container ' + eventAttr.name + ' unpaused';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'delete':
|
||||||
|
details = 'Image deleted';
|
||||||
|
break;
|
||||||
|
case 'tag':
|
||||||
|
details = 'New tag created for ' + eventAttr.name;
|
||||||
|
break;
|
||||||
|
case 'untag':
|
||||||
|
details = 'Image untagged';
|
||||||
|
break;
|
||||||
|
case 'pull':
|
||||||
|
details = 'Image ' + event.Actor.ID + ' pulled';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'network':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'create':
|
||||||
|
details = 'Network ' + eventAttr.name + ' created';
|
||||||
|
break;
|
||||||
|
case 'destroy':
|
||||||
|
details = 'Network ' + eventAttr.name + ' deleted';
|
||||||
|
break;
|
||||||
|
case 'connect':
|
||||||
|
details = 'Container connected to ' + eventAttr.name + ' network';
|
||||||
|
break;
|
||||||
|
case 'disconnect':
|
||||||
|
details = 'Container disconnected from ' + eventAttr.name + ' network';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'volume':
|
||||||
|
switch (event.Action) {
|
||||||
|
case 'create':
|
||||||
|
details = 'Volume ' + event.Actor.ID + ' created';
|
||||||
|
break;
|
||||||
|
case 'destroy':
|
||||||
|
details = 'Volume ' + event.Actor.ID + ' deleted';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
details = 'Unsupported event';
|
||||||
|
}
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
"angular-ui-router": "^0.2.15",
|
"angular-ui-router": "^0.2.15",
|
||||||
"angular-sanitize": "~1.5.0",
|
"angular-sanitize": "~1.5.0",
|
||||||
"angular-mocks": "~1.5.0",
|
"angular-mocks": "~1.5.0",
|
||||||
"angular-oboe": "*",
|
|
||||||
"angular-resource": "~1.5.0",
|
"angular-resource": "~1.5.0",
|
||||||
"angular-ui-select": "~0.17.1",
|
"angular-ui-select": "~0.17.1",
|
||||||
"bootstrap": "~3.3.6",
|
"bootstrap": "~3.3.6",
|
||||||
|
@ -39,6 +38,7 @@
|
||||||
"jquery.gritter": "1.7.4",
|
"jquery.gritter": "1.7.4",
|
||||||
"lodash": "4.12.0",
|
"lodash": "4.12.0",
|
||||||
"rdash-ui": "1.0.*",
|
"rdash-ui": "1.0.*",
|
||||||
|
"moment": "~2.14.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"angular": "1.5.5"
|
"angular": "1.5.5"
|
||||||
|
|
|
@ -68,11 +68,11 @@ module.exports = function (grunt) {
|
||||||
jsTpl: ['<%= distdir %>/templates/**/*.js'],
|
jsTpl: ['<%= distdir %>/templates/**/*.js'],
|
||||||
jsVendor: [
|
jsVendor: [
|
||||||
'bower_components/jquery/dist/jquery.min.js',
|
'bower_components/jquery/dist/jquery.min.js',
|
||||||
'assets/js/jquery.gritter.js', // Using custom version to fix error in minified build due to "use strict"
|
|
||||||
'bower_components/bootstrap/dist/js/bootstrap.min.js',
|
'bower_components/bootstrap/dist/js/bootstrap.min.js',
|
||||||
'bower_components/Chart.js/Chart.min.js',
|
'bower_components/Chart.js/Chart.min.js',
|
||||||
'bower_components/lodash/dist/lodash.min.js',
|
'bower_components/lodash/dist/lodash.min.js',
|
||||||
'bower_components/oboe/dist/oboe-browser.js',
|
'bower_components/moment/min/moment.min.js',
|
||||||
|
'assets/js/jquery.gritter.js', // Using custom version to fix error in minified build due to "use strict"
|
||||||
'assets/js/legend.js' // Not a bower package
|
'assets/js/legend.js' // Not a bower package
|
||||||
],
|
],
|
||||||
specs: ['test/**/*.spec.js'],
|
specs: ['test/**/*.spec.js'],
|
||||||
|
@ -156,7 +156,6 @@ module.exports = function (grunt) {
|
||||||
'bower_components/angular-ui-router/release/angular-ui-router.min.js',
|
'bower_components/angular-ui-router/release/angular-ui-router.min.js',
|
||||||
'bower_components/angular-resource/angular-resource.min.js',
|
'bower_components/angular-resource/angular-resource.min.js',
|
||||||
'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js',
|
'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js',
|
||||||
'bower_components/angular-oboe/dist/angular-oboe.min.js',
|
|
||||||
'bower_components/angular-ui-select/dist/select.min.js'],
|
'bower_components/angular-ui-select/dist/select.min.js'],
|
||||||
dest: '<%= distdir %>/js/angular.js'
|
dest: '<%= distdir %>/js/angular.js'
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
<li class="sidebar-list">
|
<li class="sidebar-list">
|
||||||
<a ui-sref="volumes">Volumes <span class="menu-icon fa fa-cubes"></span></a>
|
<a ui-sref="volumes">Volumes <span class="menu-icon fa fa-cubes"></span></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="sidebar-list" ng-if="!config.swarm">
|
||||||
|
<a ui-sref="events">Events <span class="menu-icon fa fa-history"></span></a>
|
||||||
|
</li>
|
||||||
<li class="sidebar-list" ng-if="config.swarm">
|
<li class="sidebar-list" ng-if="config.swarm">
|
||||||
<a ui-sref="swarm">Swarm <span class="menu-icon fa fa-object-group"></span></a>
|
<a ui-sref="swarm">Swarm <span class="menu-icon fa fa-object-group"></span></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue