diff --git a/data/model/legacy.py b/data/model/legacy.py index 3a1e55526..e0e50cb67 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -2210,6 +2210,14 @@ def confirm_team_invite(code, user): found.delete_instance() return (team, inviter) + +def get_repository_usage(): + repo_pull = LogEntryKind.get(name = 'pull_repo') + return (LogEntry.select().where(LogEntry.kind == repo_pull, ~(LogEntry.repository >> None)) + .group_by(LogEntry.ip) + .group_by(LogEntry.repository) + .count()) + def archivable_buildlogs_query(): presumed_dead_date = datetime.utcnow() - PRESUMED_DEAD_BUILD_AGE return (RepositoryBuild.select() diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index c41c6a46c..24472b1b8 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -52,6 +52,25 @@ def user_view(user): 'super_user': user.username in app.config['SUPER_USERS'] } +@resource('/v1/superuser/usage/') +@internal_only +@show_if(features.SUPER_USERS) +class UsageInformation(ApiResource): + """ Resource for returning the usage information for enterprise customers. """ + @require_fresh_login + @nickname('getSystemUsage') + def get(self): + """ Returns the number of repository handles currently held. """ + if SuperUserPermission().can(): + return { + 'usage': model.get_repository_usage(), + 'allowed': 0 + } + + abort(403) + + + @resource('/v1/superuser/users/') @internal_only @show_if(features.SUPER_USERS) diff --git a/static/css/quay.css b/static/css/quay.css index 7f711f79a..25934010b 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -3131,38 +3131,38 @@ p.editable:hover i { stroke-width: 1.5px; } -.usage-chart { +.usage-chart-element { display: inline-block; vertical-align: middle; width: 200px; height: 200px; } -.usage-chart .count-text { +.usage-chart-element .count-text { font-size: 22px; } -.usage-chart.limit-at path.arc-0 { +.usage-chart-element.limit-at path.arc-0 { fill: #c09853; } -.usage-chart.limit-over path.arc-0 { +.usage-chart-element.limit-over path.arc-0 { fill: #b94a48; } -.usage-chart.limit-near path.arc-0 { +.usage-chart-element.limit-near path.arc-0 { fill: #468847; } -.usage-chart.limit-over path.arc-1 { +.usage-chart-element.limit-over path.arc-1 { fill: #fcf8e3; } -.usage-chart.limit-at path.arc-1 { +.usage-chart-element.limit-at path.arc-1 { fill: #f2dede; } -.usage-chart.limit-near path.arc-1 { +.usage-chart-element.limit-near path.arc-1 { fill: #dff0d8; } diff --git a/static/directives/plan-manager.html b/static/directives/plan-manager.html index af4c3c016..e15e887be 100644 --- a/static/directives/plan-manager.html +++ b/static/directives/plan-manager.html @@ -24,10 +24,11 @@ -
-
- Repository Usage -
+
diff --git a/static/directives/usage-chart.html b/static/directives/usage-chart.html new file mode 100644 index 000000000..9b9782138 --- /dev/null +++ b/static/directives/usage-chart.html @@ -0,0 +1,2 @@ + +{{ usageTitle }} diff --git a/static/js/app.js b/static/js/app.js index f751cdd98..d76fcc549 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -4540,23 +4540,6 @@ quayApp.directive('planManager', function () { $scope.planChanged({ 'plan': subscribedPlan }); } - if (sub.usedPrivateRepos > $scope.subscribedPlan.privateRepos) { - $scope.limit = 'over'; - } else if (sub.usedPrivateRepos == $scope.subscribedPlan.privateRepos) { - $scope.limit = 'at'; - } else if (sub.usedPrivateRepos >= $scope.subscribedPlan.privateRepos * 0.7) { - $scope.limit = 'near'; - } else { - $scope.limit = 'none'; - } - - if (!$scope.chart) { - $scope.chart = new UsageChart(); - $scope.chart.draw('repository-usage-chart'); - } - - $scope.chart.update(sub.usedPrivateRepos || 0, $scope.subscribedPlan.privateRepos || 0); - $scope.planChanging = false; $scope.planLoading = false; }); @@ -5929,6 +5912,54 @@ quayApp.directive('notificationsBubble', function () { }); +quayApp.directive('usageChart', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/usage-chart.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'current': '=current', + 'total': '=total', + 'limit': '=limit', + 'usageTitle': '@usageTitle' + }, + controller: function($scope, $element) { + $scope.limit = ""; + + var chart = null; + + var update = function() { + if ($scope.current == null || $scope.total == null) { return; } + if (!chart) { + chart = new UsageChart(); + chart.draw('usage-chart-element'); + } + + var current = $scope.current || 0; + var total = $scope.total || 0; + if (current > total) { + $scope.limit = 'over'; + } else if (current == total) { + $scope.limit = 'at'; + } else if (current >= total * 0.7) { + $scope.limit = 'near'; + } else { + $scope.limit = 'none'; + } + + chart.update($scope.current, $scope.total); + }; + + $scope.$watch('current', update); + $scope.$watch('total', update); + } + }; + return directiveDefinitionObject; +}); + + quayApp.directive('notificationView', function () { var directiveDefinitionObject = { priority: 0, diff --git a/static/js/controllers.js b/static/js/controllers.js index ffec020b7..d28181b7c 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -2788,6 +2788,15 @@ function SuperUserAdminCtrl($scope, ApiService, Features, UserService) { $scope.logsCounter = 0; $scope.newUser = {}; $scope.createdUsers = []; + $scope.systemUsage = null; + + $scope.getUsage = function() { + if ($scope.systemUsage) { return; } + + ApiService.getSystemUsage().then(function(resp) { + $scope.systemUsage = resp; + }, ApiService.errorDisplay('Cannot load system usage. Please contact support.')) + } $scope.loadLogs = function() { $scope.logsCounter++; diff --git a/static/partials/super-user.html b/static/partials/super-user.html index a12f9f5b2..0340bffc3 100644 --- a/static/partials/super-user.html +++ b/static/partials/super-user.html @@ -13,6 +13,9 @@
  • Create User
  • +
  • + System Usage +
  • System Logs
  • @@ -27,6 +30,13 @@
    + +
    +
    +
    +
    +