/** * Element which displays usage logs for the given entity. */ angular.module('quay').directive('logsView', function () { var directiveDefinitionObject = { priority: 0, templateUrl: '/static/directives/logs-view.html', replace: false, transclude: false, restrict: 'C', scope: { 'organization': '=organization', 'user': '=user', 'makevisible': '=makevisible', 'repository': '=repository', 'performer': '=performer', 'allLogs': '@allLogs' }, controller: function($scope, $element, $sce, Restangular, ApiService, TriggerService, StringBuilderService, ExternalNotificationData, UtilService) { $scope.loading = true; $scope.logs = null; $scope.kindsAllowed = null; $scope.chartVisible = true; $scope.logsPath = ''; var datetime = new Date(); $scope.logStartDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate() - 7); $scope.logEndDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate()); var defaultPermSuffix = function(metadata) { if (metadata.activating_username) { return ', when creating user is {activating_username}'; } return ''; }; var logDescriptions = { 'account_change_plan': 'Change plan', '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}', 'push_repo': 'Push to repository: {repo}', 'repo_verb': function(metadata) { var prefix = ''; if (metadata.verb == 'squash') { prefix = 'Pull of squashed tag {tag}' } if (metadata.token) { if (metadata.token_type == 'build-worker') { prefix += ' by build worker'; } else { prefix += ' via token'; } } else if (metadata.username) { prefix += ' by {username}'; } else { prefix += ' by {_ip}'; } return prefix; }, 'pull_repo': function(metadata) { if (metadata.token) { var prefix = 'Pull of repository' if (metadata.token_type == 'build-worker') { prefix += ' by build worker'; } else { prefix += ' via token'; } return prefix; } else if (metadata.username) { return 'Pull repository {repo} by {username}'; } else { return 'Public pull of repository {repo} by {_ip}'; } }, 'delete_repo': 'Delete repository: {repo}', 'change_repo_permission': function(metadata) { if (metadata.username) { return 'Change permission for user {username} in repository {repo} to {role}'; } else if (metadata.team) { return 'Change permission for team {team} in repository {repo} to {role}'; } else if (metadata.token) { return 'Change permission for token {token} in repository {repo} to {role}'; } }, 'delete_repo_permission': function(metadata) { if (metadata.username) { return 'Remove permission for user {username} from repository {repo}'; } else if (metadata.team) { return 'Remove permission for team {team} from repository {repo}'; } else if (metadata.token) { return 'Remove permission for token {token} from repository {repo}'; } }, '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}', '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}: {description}', '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 {repo}'; }, '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) { if (metadata.user) { return 'Invite {user} to team {team}'; } else { return 'Invite {email} to team {team}'; } }, 'org_delete_team_member_invite': function(metadata) { if (metadata.user) { return 'Rescind invite of {user} to team {team}'; } else { return 'Rescind invite of {email} to team {team}'; } }, 'org_team_member_invite_accepted': 'User {member}, invited by {inviter}, joined team {team}', 'org_team_member_invite_declined': 'User {member}, invited by {inviter}, declined to join team {team}', 'org_set_team_description': 'Change description of team {team}: {description}', 'org_set_team_role': 'Change permission of team {team} to {role}', 'create_prototype_permission': function(metadata) { if (metadata.delegate_user) { return 'Create default permission: {role} for {delegate_user}' + defaultPermSuffix(metadata); } else if (metadata.delegate_team) { return 'Create default permission: {role} for {delegate_team}' + defaultPermSuffix(metadata); } }, 'modify_prototype_permission': function(metadata) { if (metadata.delegate_user) { return 'Modify default permission: {role} (from {original_role}) for {delegate_user}' + defaultPermSuffix(metadata); } else if (metadata.delegate_team) { return 'Modify default permission: {role} (from {original_role}) for {delegate_team}' + defaultPermSuffix(metadata); } }, 'delete_prototype_permission': function(metadata) { if (metadata.delegate_user) { return 'Delete default permission: {role} for {delegate_user}' + defaultPermSuffix(metadata); } else if (metadata.delegate_team) { return 'Delete default permission: {role} for {delegate_team}' + defaultPermSuffix(metadata); } }, 'setup_repo_trigger': function(metadata) { var triggerDescription = TriggerService.getDescription( metadata['service'], metadata['config']); return 'Setup build trigger - ' + triggerDescription; }, 'delete_repo_trigger': function(metadata) { var triggerDescription = TriggerService.getDescription( metadata['service'], metadata['config']); return 'Delete build trigger - ' + triggerDescription; }, 'create_application': 'Create application {application_name} with client ID {client_id}', 'update_application': 'Update application to {application_name} for client ID {client_id}', 'delete_application': 'Delete application {application_name} with client ID {client_id}', 'reset_application_client_secret': 'Reset the Client Secret of application {application_name} ' + 'with client ID {client_id}', 'add_repo_notification': function(metadata) { var eventData = ExternalNotificationData.getEventInfo(metadata.event); return 'Add notification of event "' + eventData['title'] + '" for repository {repo}'; }, 'delete_repo_notification': function(metadata) { var eventData = ExternalNotificationData.getEventInfo(metadata.event); return 'Delete notification of event "' + eventData['title'] + '" for repository {repo}'; }, 'regenerate_robot_token': 'Regenerated token for robot {robot}', // Note: These are deprecated. 'add_repo_webhook': 'Add webhook in repository {repo}', 'delete_repo_webhook': 'Delete webhook in repository {repo}' }; var logKinds = { 'account_change_plan': 'Change plan', 'account_change_cc': 'Update credit card', 'account_change_password': 'Change password', 'account_convert': 'Convert account to organization', 'create_robot': 'Create Robot Account', 'delete_robot': 'Delete Robot Account', 'create_repo': 'Create Repository', 'push_repo': 'Push to repository', 'repo_verb': 'Pull Repo Verb', 'pull_repo': 'Pull repository', 'delete_repo': 'Delete repository', 'change_repo_permission': 'Change repository permission', 'delete_repo_permission': 'Remove user permission from repository', 'change_repo_visibility': 'Change repository visibility', 'add_repo_accesstoken': 'Create access token', 'delete_repo_accesstoken': 'Delete access token', 'set_repo_description': 'Change repository description', 'build_dockerfile': 'Build image from Dockerfile', 'delete_tag': 'Delete Tag', 'create_tag': 'Create Tag', 'move_tag': 'Move Tag', 'org_create_team': 'Create team', 'org_delete_team': 'Delete team', 'org_add_team_member': 'Add team member', 'org_invite_team_member': 'Invite team member', 'org_delete_team_member_invite': 'Rescind team member invitation', 'org_remove_team_member': 'Remove team member', 'org_team_member_invite_accepted': 'Team invite accepted', 'org_team_member_invite_declined': 'Team invite declined', 'org_set_team_description': 'Change team description', 'org_set_team_role': 'Change team permission', 'create_prototype_permission': 'Create default permission', 'modify_prototype_permission': 'Modify default permission', 'delete_prototype_permission': 'Delete default permission', 'setup_repo_trigger': 'Setup build trigger', 'delete_repo_trigger': 'Delete build trigger', 'create_application': 'Create Application', 'update_application': 'Update Application', 'delete_application': 'Delete Application', 'reset_application_client_secret': 'Reset Client Secret', 'add_repo_notification': 'Add repository notification', 'delete_repo_notification': 'Delete repository notification', 'regenerate_robot_token': 'Regenerate Robot Token', // Note: these are deprecated. 'add_repo_webhook': 'Add webhook', 'delete_repo_webhook': 'Delete webhook' }; var getDateString = function(date) { return (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear(); }; var getOffsetDate = function(date, days) { return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days); }; var update = function() { var hasValidUser = !!$scope.user; var hasValidOrg = !!$scope.organization; var hasValidRepo = $scope.repository && $scope.repository.namespace; var isValid = hasValidUser || hasValidOrg || hasValidRepo || $scope.allLogs; if (!$scope.makevisible || !isValid) { return; } var twoWeeksAgo = getOffsetDate($scope.logEndDate, -14); if ($scope.logStartDate > $scope.logEndDate || $scope.logStartDate < twoWeeksAgo) { $scope.logStartDate = twoWeeksAgo; } $scope.loading = true; // Note: We construct the URLs here manually because we also use it for the download // path. var url = UtilService.getRestUrl('user/logs'); if ($scope.organization) { url = UtilService.getRestUrl('organization', $scope.organization.name, 'logs'); } if ($scope.repository) { url = UtilService.getRestUrl('repository', $scope.repository.namespace, $scope.repository.name, 'logs'); } if ($scope.allLogs) { url = UtilService.getRestUrl('superuser', 'logs') } url += '?starttime=' + encodeURIComponent(getDateString($scope.logStartDate)); url += '&endtime=' + encodeURIComponent(getDateString($scope.logEndDate)); if ($scope.performer) { url += '&performer=' + encodeURIComponent($scope.performer.name); } var loadLogs = Restangular.one(url); loadLogs.customGET().then(function(resp) { $scope.logsPath = '/api/v1/' + url; if (!$scope.chart) { $scope.chart = new LogUsageChart(logKinds); $($scope.chart).bind('filteringChanged', function(e) { $scope.$apply(function() { $scope.kindsAllowed = e.allowed; }); }); } $scope.chart.draw('bar-chart', resp.logs, $scope.logStartDate, $scope.logEndDate); $scope.kindsAllowed = null; $scope.logs = resp.logs; $scope.loading = false; }); }; $scope.toggleChart = function() { $scope.chartVisible = !$scope.chartVisible; }; $scope.isVisible = function(allowed, kind) { return allowed == null || allowed.hasOwnProperty(kind); }; $scope.getColor = function(kind) { return $scope.chart.getColor(kind); }; $scope.getDescription = function(log) { log.metadata['_ip'] = log.ip ? log.ip : null; return StringBuilderService.buildString(logDescriptions[log.kind] || log.kind, log.metadata); }; $scope.$watch('organization', update); $scope.$watch('user', update); $scope.$watch('repository', update); $scope.$watch('makevisible', update); $scope.$watch('performer', update); $scope.$watch('logStartDate', update); $scope.$watch('logEndDate', update); } }; return directiveDefinitionObject; });