diff --git a/data/model/log.py b/data/model/log.py index d927f2ad7..eb52b96ef 100644 --- a/data/model/log.py +++ b/data/model/log.py @@ -62,13 +62,22 @@ def get_aggregated_logs(start_time, end_time, performer=None, repository=None, n def get_logs_query(start_time, end_time, performer=None, repository=None, namespace=None, ignore=None): Performer = User.alias() + Account = User.alias() selections = [LogEntry, Performer] + if namespace is None and repository is None: + selections.append(Account) + query = _logs_query(selections, start_time, end_time, performer, repository, namespace, ignore) query = (query.switch(LogEntry) .join(Performer, JOIN_LEFT_OUTER, on=(LogEntry.performer == Performer.id).alias('performer'))) + if namespace is None and repository is None: + query = (query.switch(LogEntry) + .join(Account, JOIN_LEFT_OUTER, + on=(LogEntry.account == Account.id).alias('account'))) + return query diff --git a/endpoints/api/logs.py b/endpoints/api/logs.py index 8722ad298..276d7f453 100644 --- a/endpoints/api/logs.py +++ b/endpoints/api/logs.py @@ -20,7 +20,7 @@ LOGS_PER_PAGE = 20 SERVICE_LEVEL_LOG_KINDS = set(['service_key_create', 'service_key_approve', 'service_key_delete', 'service_key_modify', 'service_key_extend', 'service_key_rotate']) -def log_view(log, kinds): +def log_view(log, kinds, include_namespace): view = { 'kind': kinds[log.kind_id], 'metadata': json.loads(log.metadata_json), @@ -33,9 +33,24 @@ def log_view(log, kinds): 'kind': 'user', 'name': log.performer.username, 'is_robot': log.performer.robot, - 'avatar': avatar.get_data_for_user(log.performer) + 'avatar': avatar.get_data_for_user(log.performer), } + if include_namespace: + if log.account and log.account.username: + if log.account.organization: + view['namespace'] = { + 'kind': 'org', + 'name': log.account.username, + 'avatar': avatar.get_data_for_org(log.account), + } + else: + view['namespace'] = { + 'kind': 'user', + 'name': log.account.username, + 'avatar': avatar.get_data_for_user(log.account), + } + return view def aggregated_log_view(log, kinds, start_time): @@ -92,10 +107,11 @@ def get_logs(start_time, end_time, performer_name=None, repository=None, namespa logs, next_page_token = model.modelutil.paginate(logs_query, database.LogEntry, descending=True, page_token=page_token, limit=LOGS_PER_PAGE) + include_namespace = namespace is None and repository is None return { 'start_time': format_date(start_time), 'end_time': format_date(end_time), - 'logs': [log_view(log, kinds) for log in logs], + 'logs': [log_view(log, kinds, include_namespace) for log in logs], }, next_page_token diff --git a/endpoints/api/manifest.py b/endpoints/api/manifest.py index 0307ecaba..019c3dad2 100644 --- a/endpoints/api/manifest.py +++ b/endpoints/api/manifest.py @@ -98,6 +98,8 @@ class RepositoryManifestLabels(RepositoryParamResource): 'value': label_data['value'], 'manifest_digest': manifestref, 'media_type': label_data['media_type'], + 'namespace': namespace, + 'repo': repository, } log_action('manifest_label_add', namespace, metadata, repo=tag_manifest.tag.repository) @@ -150,7 +152,9 @@ class ManageRepositoryManifestLabel(RepositoryParamResource): 'id': labelid, 'key': deleted.key, 'value': deleted.value, - 'manifest_digest': manifestref + 'manifest_digest': manifestref, + 'namespace': namespace, + 'repo': repository, } log_action('manifest_label_delete', namespace, metadata, repo=tag_manifest.tag.repository) diff --git a/endpoints/api/permission.py b/endpoints/api/permission.py index f07d87b2f..259e9c9d6 100644 --- a/endpoints/api/permission.py +++ b/endpoints/api/permission.py @@ -194,6 +194,7 @@ class RepositoryUserPermission(RepositoryParamResource): log_action('change_repo_permission', namespace, {'username': username, 'repo': repository, + 'namespace': namespace, 'role': new_permission['role']}, repo=model.repository.get_repository(namespace, repository)) @@ -209,7 +210,7 @@ class RepositoryUserPermission(RepositoryParamResource): raise request_error(exception=ex) log_action('delete_repo_permission', namespace, - {'username': username, 'repo': repository}, + {'username': username, 'repo': repository, 'namespace': namespace}, repo=model.repository.get_repository(namespace, repository)) return '', 204 diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py index 1ae77b6a2..2f8baed5b 100644 --- a/endpoints/api/repository.py +++ b/endpoints/api/repository.py @@ -340,7 +340,7 @@ class Repository(RepositoryParamResource): repo.save() log_action('set_repo_description', namespace, - {'repo': repository, 'description': values['description']}, + {'repo': repository, 'namespace': namespace, 'description': values['description']}, repo=repo) return { 'success': True @@ -404,6 +404,6 @@ class RepositoryVisibility(RepositoryParamResource): model.repository.set_repository_visibility(repo, visibility) log_action('change_repo_visibility', namespace, - {'repo': repository, 'visibility': values['visibility']}, + {'repo': repository, 'namespace': namespace, 'visibility': values['visibility']}, repo=repo) return {'success': True} diff --git a/endpoints/api/repositorynotification.py b/endpoints/api/repositorynotification.py index a9828d518..1a68ec20d 100644 --- a/endpoints/api/repositorynotification.py +++ b/endpoints/api/repositorynotification.py @@ -102,7 +102,8 @@ class RepositoryNotificationList(RepositoryParamResource): resp = notification_view(new_notification) log_action('add_repo_notification', namespace, - {'repo': repository, 'notification_id': new_notification.uuid, + {'repo': repository, 'namespace': namespace, + 'notification_id': new_notification.uuid, 'event': parsed['event'], 'method': parsed['method']}, repo=repo) return resp, 201 @@ -143,7 +144,7 @@ class RepositoryNotification(RepositoryParamResource): """ Deletes the specified notification. """ deleted = model.notification.delete_repo_notification(namespace, repository, uuid) log_action('delete_repo_notification', namespace, - {'repo': repository, 'notification_id': uuid, + {'repo': repository, 'namespace': namespace, 'notification_id': uuid, 'event': deleted.event.name, 'method': deleted.method.name}, repo=model.repository.get_repository(namespace, repository)) diff --git a/endpoints/api/tag.py b/endpoints/api/tag.py index 51ace9d9c..3a24200b8 100644 --- a/endpoints/api/tag.py +++ b/endpoints/api/tag.py @@ -106,7 +106,8 @@ class RepositoryTag(RepositoryParamResource): username = get_authenticated_user().username log_action('move_tag' if original_image_id else 'create_tag', namespace, {'username': username, 'repo': repository, 'tag': tag, - 'image': image_id, 'original_image': original_image_id}, + 'namespace': namespace, 'image': image_id, + 'original_image': original_image_id}, repo=model.repository.get_repository(namespace, repository)) return 'Updated', 201 @@ -119,7 +120,7 @@ class RepositoryTag(RepositoryParamResource): username = get_authenticated_user().username log_action('delete_tag', namespace, - {'username': username, 'repo': repository, 'tag': tag}, + {'username': username, 'repo': repository, 'namespace': namespace, 'tag': tag}, repo=model.repository.get_repository(namespace, repository)) return '', 204 diff --git a/static/directives/entity-reference.html b/static/directives/entity-reference.html index 5e83b1040..5f2d9c242 100644 --- a/static/directives/entity-reference.html +++ b/static/directives/entity-reference.html @@ -16,6 +16,7 @@ + {{ entity.name }} diff --git a/static/directives/logs-view.html b/static/directives/logs-view.html index c5d30b6aa..977100498 100644 --- a/static/directives/logs-view.html +++ b/static/directives/logs-view.html @@ -44,14 +44,20 @@ infinite-scroll-distance="2"> + - + +
Namespace Description Date/TimeUser/Token/AppPerforming User/Token/App IP Address
+ + + + diff --git a/static/js/directives/ui/logs-view.js b/static/js/directives/ui/logs-view.js index 2f186bc25..c9bbb713e 100644 --- a/static/js/directives/ui/logs-view.js +++ b/static/js/directives/ui/logs-view.js @@ -55,20 +55,20 @@ angular.module('quay').directive('logsView', function () { 'account_change_cc': 'Update credit card', 'account_change_password': 'Change password', 'account_convert': 'Convert account to organization', - 'create_robot': 'Create Robot Account: {robot}', - 'delete_robot': 'Delete Robot Account: {robot}', - 'create_repo': 'Create Repository: {repo}', + 'create_robot': 'Create Robot Account {robot}', + 'delete_robot': 'Delete Robot Account {robot}', + 'create_repo': 'Create Repository {namespace}/{repo}', 'push_repo': function(metadata) { if (metadata.tag) { - return 'Push of {tag}'; + return 'Push of {tag} to repository {namespace}/{repo}'; } else { - return 'Repository push'; + return 'Repository push to {namespace}/{repo}'; } }, 'repo_verb': function(metadata) { var prefix = ''; if (metadata.verb == 'squash') { - prefix = 'Pull of squashed tag {tag}' + prefix = 'Pull of squashed tag {tag} from {namespace}/{repo}' } if (metadata.token) { @@ -86,11 +86,11 @@ angular.module('quay').directive('logsView', function () { return prefix; }, 'pull_repo': function(metadata) { - var description = 'repository'; + var description = 'repository {namespace}/{repo}'; if (metadata.tag) { - description = 'tag {tag}'; + description = 'tag {tag} from repository {namespace}/{repo}'; } else if (metadata.manifest_digest) { - description = 'digest {manifest_digest}'; + description = 'digest {manifest_digest} from repository {namespace}/{repo}'; } if (metadata.token) { @@ -109,40 +109,40 @@ angular.module('quay').directive('logsView', function () { 'delete_repo': 'Delete repository: {repo}', 'change_repo_permission': function(metadata) { if (metadata.username) { - return 'Change permission for user {username} in repository {repo} to {role}'; + return 'Change permission for user {username} in repository {namespace}/{repo} to {role}'; } else if (metadata.team) { - return 'Change permission for team {team} in repository {repo} to {role}'; + return 'Change permission for team {team} in repository {namespace}/{repo} to {role}'; } else if (metadata.token) { - return 'Change permission for token {token} in repository {repo} to {role}'; + return 'Change permission for token {token} in repository {namespace}/{repo} to {role}'; } }, 'delete_repo_permission': function(metadata) { if (metadata.username) { - return 'Remove permission for user {username} from repository {repo}'; + return 'Remove permission for user {username} from repository {namespace}/{repo}'; } else if (metadata.team) { - return 'Remove permission for team {team} from repository {repo}'; + return 'Remove permission for team {team} from repository {namespace}/{repo}'; } else if (metadata.token) { - return 'Remove permission for token {token} from repository {repo}'; + return 'Remove permission for token {token} from repository {namespace}/{repo}'; } }, 'revert_tag': 'Tag {tag} reverted to image {image} from image {original_image}', - 'delete_tag': 'Tag {tag} deleted in repository {repo} by user {username}', - 'create_tag': 'Tag {tag} created in repository {repo} on image {image} by user {username}', - 'move_tag': 'Tag {tag} moved from image {original_image} to image {image} in repository {repo} by user {username}', - 'change_repo_visibility': 'Change visibility for repository {repo} to {visibility}', + 'delete_tag': 'Tag {tag} deleted in repository {namespace}/{repo} by user {username}', + 'create_tag': 'Tag {tag} created in repository {namespace}/{repo} on image {image} by user {username}', + 'move_tag': 'Tag {tag} moved from image {original_image} to image {image} in repository {namespace}/{repo} by user {username}', + 'change_repo_visibility': 'Change visibility for repository {namespace}/{repo} to {visibility}', 'add_repo_accesstoken': 'Create access token {token} in repository {repo}', 'delete_repo_accesstoken': 'Delete access token {token} in repository {repo}', - 'set_repo_description': 'Change description for repository {repo}', + 'set_repo_description': 'Change description for repository {namespace}/{repo}', 'build_dockerfile': function(metadata) { if (metadata.trigger_id) { var triggerDescription = TriggerService.getDescription( metadata['service'], metadata['config']); - return 'Build image from Dockerfile for repository {repo} triggered by ' + triggerDescription; + return 'Build image from Dockerfile for repository {namespace}/{repo} triggered by ' + triggerDescription; } - return 'Build image from Dockerfile for repository {repo}'; + return 'Build image from Dockerfile for repository {namespace}/{repo}'; }, - 'org_create_team': 'Create team: {team}', - 'org_delete_team': 'Delete team: {team}', + 'org_create_team': 'Create team {team}', + 'org_delete_team': 'Delete team {team}', 'org_add_team_member': 'Add member {member} to team {team}', 'org_remove_team_member': 'Remove member {member} from team {team}', 'org_invite_team_member': function(metadata) { @@ -204,12 +204,12 @@ angular.module('quay').directive('logsView', function () { 'add_repo_notification': function(metadata) { var eventData = ExternalNotificationData.getEventInfo(metadata.event); - return 'Add notification of event "' + eventData['title'] + '" for repository {repo}'; + return 'Add notification of event "' + eventData['title'] + '" for repository {namespace}/{repo}'; }, 'delete_repo_notification': function(metadata) { var eventData = ExternalNotificationData.getEventInfo(metadata.event); - return 'Delete notification of event "' + eventData['title'] + '" for repository {repo}'; + return 'Delete notification of event "' + eventData['title'] + '" for repository {namespace}/{repo}'; }, 'regenerate_robot_token': 'Regenerated token for robot {robot}', @@ -236,8 +236,8 @@ angular.module('quay').directive('logsView', function () { } }, - 'manifest_label_add': 'Label {key} added to manifest {manifest_digest}', - 'manifest_label_delete': 'Label {key} deleted from manifest {manifest_digest}', + 'manifest_label_add': 'Label {key} added to manifest {manifest_digest} under repository {namespace}/{repo}', + 'manifest_label_delete': 'Label {key} deleted from manifest {manifest_digest} under repository {namespace}/{repo}', // Note: These are deprecated. 'add_repo_webhook': 'Add webhook in repository {repo}', @@ -400,6 +400,15 @@ angular.module('quay').directive('logsView', function () { $scope.getDescription = function(log) { log.metadata['_ip'] = log.ip ? log.ip : null; + + // Note: This is for back-compat for logs that previously did not have namespaces. + // TODO(jschorr): Remove this after a month or two (~April 2017). + var namespace = ''; + if (log.namespace) { + namespace = log.namespace.username || log.namespace.name; + } + + log.metadata['namespace'] = log.metadata['namespace'] || namespace || ''; return StringBuilderService.buildString(logDescriptions[log.kind] || log.kind, log.metadata); };