diff --git a/data/model.py b/data/model.py index 5c1d3a214..f95abb8c5 100644 --- a/data/model.py +++ b/data/model.py @@ -459,12 +459,13 @@ def get_organization_team(orgname, teamname): return result[0] -def get_organization_members_with_teams(organization): +def get_organization_members_with_teams(organization, membername = None): joined = TeamMember.select().annotate(Team).annotate(User) query = joined.where(Team.organization == organization) + if membername: + query = query.where(User.username == membername) return query - def get_organization_team_members(teamid): joined = User.select().join(TeamMember).join(Team) query = joined.where(Team.id == teamid) @@ -1058,11 +1059,14 @@ def delete_webhook(namespace_name, repository_name, public_id): webhook.delete_instance() return webhook -def list_logs(user_or_organization_name, start_time, repository = None): +def list_logs(user_or_organization_name, start_time, performer = None, repository = None): joined = LogEntry.select().join(User) if repository: joined = joined.where(LogEntry.repository == repository) + if performer: + joined = joined.where(LogEntry.performer == performer) + return joined.where(User.username == user_or_organization_name, LogEntry.datetime >= start_time).order_by(LogEntry.datetime.desc()) def log_action(kind_name, user_or_organization_name, performer=None, repository=None, diff --git a/endpoints/api.py b/endpoints/api.py index e6bb02746..937b9b898 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -444,6 +444,35 @@ def get_organization_members(orgname): abort(403) + +@app.route('/api/organization//members/', methods=['GET']) +@api_login_required +def get_organization_member(orgname, membername): + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + try: + org = model.get_organization(orgname) + except model.InvalidOrganizationException: + abort(404) + + member_dict = None + member_teams = model.get_organization_members_with_teams(org, membername = membername) + for member in member_teams: + if not member_dict: + member_dict = {'username': member.user.username, + 'is_robot': member.user.robot, + 'teams': []} + + member_dict['teams'].append(member.team.name) + + if not member_dict: + abort(404) + + return jsonify({'member': member_dict}) + + abort(403) + + @app.route('/api/organization//private', methods=['GET']) @api_login_required def get_organization_private_allowed(orgname): @@ -1727,8 +1756,13 @@ def log_view(log): def org_logs_api(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): + performer_name = request.args.get('performer', None) + performer = None + if performer_name: + performer = model.get_user(performer_name) + week_ago = datetime.today() - timedelta(7) # One week - logs = model.list_logs(orgname, week_ago) + logs = model.list_logs(orgname, week_ago, performer = performer) return jsonify({ 'start_time': week_ago, 'logs': [log_view(log) for log in logs] diff --git a/static/directives/logs-view.html b/static/directives/logs-view.html index 2ed8f60bd..104129543 100644 --- a/static/directives/logs-view.html +++ b/static/directives/logs-view.html @@ -4,7 +4,11 @@
- Usage Logs For the last seven days + + Usage Logs + + For the last seven days + diff --git a/static/js/app.js b/static/js/app.js index dd1b228d1..11212cb1a 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -451,6 +451,7 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an when('/organization/:orgname', {templateUrl: '/static/partials/org-view.html', controller: OrgViewCtrl}). when('/organization/:orgname/admin', {templateUrl: '/static/partials/org-admin.html', controller: OrgAdminCtrl}). when('/organization/:orgname/teams/:teamname', {templateUrl: '/static/partials/team-view.html', controller: TeamViewCtrl}). + when('/organization/:orgname/logs/:membername', {templateUrl: '/static/partials/org-member-logs.html', controller: OrgMemberLogsCtrl}). when('/v1/', {title: 'Activation information', templateUrl: '/static/partials/v1-page.html', controller: V1Ctrl}). when('/', {title: 'Hosted Private Docker Registry', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}). otherwise({redirectTo: '/'}); @@ -679,7 +680,8 @@ quayApp.directive('logsView', function () { 'organization': '=organization', 'user': '=user', 'visible': '=visible', - 'repository': '=repository' + 'repository': '=repository', + 'performer': '=performer' }, controller: function($scope, $element, $sce, Restangular) { $scope.loading = true; @@ -782,6 +784,10 @@ quayApp.directive('logsView', function () { if ($scope.repository) { url = getRestUrl('repository', $scope.repository.namespace, $scope.repository.name, 'logs'); } + + if ($scope.performer) { + url += '?performer=' + encodeURIComponent($scope.performer.username); + } var loadLogs = Restangular.one(url); loadLogs.customGET().then(function(resp) { @@ -851,6 +857,7 @@ quayApp.directive('logsView', function () { $scope.$watch('user', update); $scope.$watch('repository', update); $scope.$watch('visible', update); + $scope.$watch('performer', update); } }; diff --git a/static/js/controllers.js b/static/js/controllers.js index 85e9278a7..0654450a5 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -1318,7 +1318,7 @@ function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, Plan $scope.loading = false; }, true); - requested = $routeParams['plan']; + requested = $routeParams['plan']; // Load the list of plans. PlanService.getPlans(function(plans) { @@ -1380,4 +1380,49 @@ function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, Plan }); }); }; +} + + +function OrgMemberLogsCtrl($scope, $routeParams, $timeout, Restangular) { + var orgname = $routeParams.orgname; + var membername = $routeParams.membername; + + $scope.orgname = orgname; + $scope.loading = true; + $scope.memberInfo = null; + $scope.ready = false; + + var checkReady = function() { + $scope.loading = !$scope.organization || !$scope.memberInfo; + if (!$scope.loading) { + $timeout(function() { + $scope.ready = true; + }); + } + }; + + var loadOrganization = function() { + var getOrganization = Restangular.one(getRestUrl('organization', orgname)) + getOrganization.get().then(function(resp) { + $scope.organization = resp; + checkReady(); + }, function() { + $scope.organization = null; + $scope.loading = false; + }); + }; + + var loadMemberInfo = function() { + var getMemberInfo = Restangular.one(getRestUrl('organization', orgname, 'members', membername)) + getMemberInfo.get().then(function(resp) { + $scope.memberInfo = resp.member; + checkReady(); + }, function() { + $scope.memberInfo = null; + $scope.loading = false; + }); + }; + + loadOrganization(); + loadMemberInfo(); } \ No newline at end of file diff --git a/static/partials/org-admin.html b/static/partials/org-admin.html index 45360ec76..7a8bcdb3e 100644 --- a/static/partials/org-admin.html +++ b/static/partials/org-admin.html @@ -121,8 +121,9 @@ User/Robot Account Teams + - + @@ -133,6 +134,11 @@ {{ team }} + + + + +
diff --git a/static/partials/org-member-logs.html b/static/partials/org-member-logs.html new file mode 100644 index 000000000..7750de305 --- /dev/null +++ b/static/partials/org-member-logs.html @@ -0,0 +1,16 @@ +
+ +
+ +
+ Organization not found +
+ +
+ Member not found +
+ +
+
+
+