608ffd9663
Adds basic labels support to the registry code (V2), and the API. Note that this does not yet add any UI related support.
406 lines
17 KiB
JavaScript
406 lines
17 KiB
JavaScript
/**
|
|
* 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',
|
|
'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.chartLoading = true;
|
|
|
|
$scope.options = {};
|
|
|
|
var datetime = new Date();
|
|
$scope.options.logStartDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate() - 7);
|
|
$scope.options.logEndDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate());
|
|
$scope.options.monthAgo = moment().subtract(1, 'month').calendar();
|
|
$scope.options.now = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate());
|
|
|
|
var getOffsetDate = function(date, days) {
|
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
|
|
};
|
|
|
|
var defaultPermSuffix = function(metadata) {
|
|
if (metadata.activating_username) {
|
|
return ', when creating user is {activating_username}';
|
|
}
|
|
return '';
|
|
};
|
|
|
|
var getServiceKeyTitle = function(metadata) {
|
|
if (metadata.name) {
|
|
return metadata.name;
|
|
}
|
|
|
|
return metadata.kind.substr(0, 12);
|
|
};
|
|
|
|
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': function(metadata) {
|
|
if (metadata.tag) {
|
|
return 'Push of {tag}';
|
|
} else {
|
|
return 'Repository push';
|
|
}
|
|
},
|
|
'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 <b>build worker</b>';
|
|
} 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 <b>build worker</b>';
|
|
} 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}';
|
|
}
|
|
},
|
|
'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}',
|
|
'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}',
|
|
'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}',
|
|
|
|
'service_key_create': function(metadata) {
|
|
if (metadata.preshared) {
|
|
return 'Manual creation of preshared service key {kid} for service {service}';
|
|
} else {
|
|
return 'Creation of service key {kid} for service {service} by {user_agent}';
|
|
}
|
|
},
|
|
|
|
'service_key_approve': 'Approval of service key {kid}',
|
|
'service_key_modify': 'Modification of service key {kid}',
|
|
'service_key_delete': 'Deletion of service key {kid}',
|
|
'service_key_extend': 'Change of expiration of service key {kid} from {old_expiration_date} to {expiration_date}',
|
|
'service_key_rotate': 'Automatic rotation of service key {kid} by {user_agent}',
|
|
|
|
'take_ownership': function(metadata) {
|
|
if (metadata.was_user) {
|
|
return 'Superuser {superuser} took ownership of user namespace {namespace}';
|
|
} else {
|
|
return 'Superuser {superuser} took ownership of organization {namespace}';
|
|
}
|
|
},
|
|
|
|
'manifest_label_add': 'Label {key} added to manifest {manifest_digest}',
|
|
'manifest_label_delete': 'Label {key} deleted from manifest {manifest_digest}',
|
|
|
|
// 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',
|
|
'revert_tag':' Revert 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',
|
|
'service_key_create': 'Create Service Key',
|
|
'service_key_approve': 'Approve Service Key',
|
|
'service_key_modify': 'Modify Service Key',
|
|
'service_key_delete': 'Delete Service Key',
|
|
'service_key_extend': 'Extend Service Key Expiration',
|
|
'service_key_rotate': 'Automatic rotation of Service Key',
|
|
'take_ownership': 'Take Namespace Ownership',
|
|
'manifest_label_add': 'Add Manifest Label',
|
|
'manifest_label_delete': 'Delete Manifest Label',
|
|
|
|
// 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 getUrl = function(suffix) {
|
|
var url = UtilService.getRestUrl('user/' + suffix);
|
|
if ($scope.organization) {
|
|
url = UtilService.getRestUrl('organization', $scope.organization.name, suffix);
|
|
}
|
|
if ($scope.repository) {
|
|
url = UtilService.getRestUrl('repository', $scope.repository.namespace, $scope.repository.name, suffix);
|
|
}
|
|
|
|
if ($scope.allLogs) {
|
|
url = UtilService.getRestUrl('superuser', suffix)
|
|
}
|
|
|
|
url += '?starttime=' + encodeURIComponent(getDateString($scope.options.logStartDate));
|
|
url += '&endtime=' + encodeURIComponent(getDateString($scope.options.logEndDate));
|
|
return url;
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
$scope.chartLoading = true;
|
|
|
|
var aggregateUrl = getUrl('aggregatelogs')
|
|
var loadAggregate = Restangular.one(aggregateUrl);
|
|
loadAggregate.customGET().then(function(resp) {
|
|
$scope.chart = new LogUsageChart(logKinds);
|
|
$($scope.chart).bind('filteringChanged', function(e) {
|
|
$scope.$apply(function() { $scope.kindsAllowed = e.allowed; });
|
|
});
|
|
|
|
$scope.chart.draw('bar-chart', resp.aggregated, $scope.options.logStartDate,
|
|
$scope.options.logEndDate);
|
|
$scope.chartLoading = false;
|
|
});
|
|
|
|
$scope.nextPageToken = null;
|
|
$scope.hasAdditional = true;
|
|
$scope.loading = false;
|
|
$scope.logs = [];
|
|
$scope.nextPage();
|
|
};
|
|
|
|
$scope.nextPage = function() {
|
|
if ($scope.loading || !$scope.hasAdditional) { return; }
|
|
|
|
$scope.loading = true;
|
|
|
|
var logsUrl = getUrl('logs');
|
|
if ($scope.nextPageToken) {
|
|
logsUrl = logsUrl + '&next_page=' + encodeURIComponent($scope.nextPageToken);
|
|
}
|
|
|
|
var loadLogs = Restangular.one(logsUrl);
|
|
loadLogs.customGET().then(function(resp) {
|
|
resp.logs.forEach(function(log) {
|
|
$scope.logs.push(log);
|
|
});
|
|
|
|
$scope.loading = false;
|
|
$scope.nextPageToken = resp.next_page;
|
|
$scope.hasAdditional = !!resp.next_page;
|
|
});
|
|
};
|
|
|
|
$scope.toggleChart = function() {
|
|
$scope.chartVisible = !$scope.chartVisible;
|
|
};
|
|
|
|
$scope.isVisible = function(allowed, kind) {
|
|
return allowed == null || allowed.hasOwnProperty(kind);
|
|
};
|
|
|
|
$scope.getColor = function(kind, chart) {
|
|
if (!chart) { return ''; }
|
|
return 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('options.logStartDate', update);
|
|
$scope.$watch('options.logEndDate', update);
|
|
}
|
|
};
|
|
|
|
return directiveDefinitionObject;
|
|
});
|