lazy load client stats in UI
parent
626b18d5ca
commit
256b79ae51
|
@ -0,0 +1,42 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.openid.connect.model;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class ClientStat {
|
||||
|
||||
private Integer approvedSiteCount;
|
||||
|
||||
/**
|
||||
* @return the count
|
||||
*/
|
||||
public Integer getApprovedSiteCount() {
|
||||
return approvedSiteCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param count the count to set
|
||||
*/
|
||||
public void setApprovedSiteCount(Integer count) {
|
||||
this.approvedSiteCount = count;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,8 @@ package org.mitre.openid.connect.service;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.mitre.openid.connect.model.ClientStat;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
|
@ -42,15 +44,15 @@ public interface StatsService {
|
|||
*
|
||||
* @return a map of id of client object to number of approvals
|
||||
*/
|
||||
public Map<Long, Integer> getByClientId();
|
||||
public Map<String, Integer> getByClientId();
|
||||
|
||||
/**
|
||||
* Calculate the usage count for a single client
|
||||
*
|
||||
* @param id the id of the client to search on
|
||||
* @param clientId the id of the client to search on
|
||||
* @return
|
||||
*/
|
||||
public Integer getCountForClientId(Long id);
|
||||
public ClientStat getCountForClientId(String clientId);
|
||||
|
||||
/**
|
||||
* Trigger the stats to be recalculated upon next update.
|
||||
|
|
|
@ -299,12 +299,6 @@ var BreadCrumbView = Backbone.View.extend({
|
|||
});
|
||||
|
||||
|
||||
// Stats table
|
||||
|
||||
var StatsModel = Backbone.Model.extend({
|
||||
url: "api/stats/byclientid"
|
||||
});
|
||||
|
||||
// User Profile
|
||||
|
||||
var UserProfileView = Backbone.View.extend({
|
||||
|
@ -430,8 +424,6 @@ var AppRouter = Backbone.Router.extend({
|
|||
|
||||
initialize:function () {
|
||||
|
||||
this.clientStats = new StatsModel();
|
||||
|
||||
this.breadCrumbView = new BreadCrumbView({
|
||||
collection:new Backbone.Collection()
|
||||
});
|
||||
|
|
|
@ -194,6 +194,10 @@ var RegistrationTokenModel = Backbone.Model.extend({
|
|||
urlRoot: 'api/tokens/registration'
|
||||
});
|
||||
|
||||
var ClientStatsModel = Backbone.Model.extend({
|
||||
urlRoot: 'api/stats/byclientid'
|
||||
});
|
||||
|
||||
var ClientCollection = Backbone.Collection.extend({
|
||||
|
||||
initialize: function() {
|
||||
|
@ -216,6 +220,8 @@ var ClientCollection = Backbone.Collection.extend({
|
|||
var ClientView = Backbone.View.extend({
|
||||
|
||||
tagName: 'tr',
|
||||
|
||||
isRendered: false,
|
||||
|
||||
initialize:function (options) {
|
||||
this.options = options;
|
||||
|
@ -236,6 +242,10 @@ var ClientView = Backbone.View.extend({
|
|||
this.registrationTokenTemplate = _.template($('#tmpl-client-registration-token').html());
|
||||
}
|
||||
|
||||
if (!this.countTemplate) {
|
||||
this.countTemplate = _.template($('#tmpl-client-count').html());
|
||||
}
|
||||
|
||||
this.model.bind('change', this.render, this);
|
||||
|
||||
},
|
||||
|
@ -259,7 +269,7 @@ var ClientView = Backbone.View.extend({
|
|||
}
|
||||
|
||||
|
||||
var json = {client: this.model.toJSON(), count: this.options.count, whiteList: this.options.whiteList,
|
||||
var json = {client: this.model.toJSON(), whiteList: this.options.whiteList,
|
||||
displayCreationDate: displayCreationDate, hoverCreationDate: hoverCreationDate};
|
||||
this.$el.html(this.template(json));
|
||||
|
||||
|
@ -273,10 +283,19 @@ var ClientView = Backbone.View.extend({
|
|||
this.$('.allow-introspection').tooltip({title: $.t('client.client-table.allow-introspection-tooltip')});
|
||||
|
||||
this.updateMatched();
|
||||
this.updateStats();
|
||||
|
||||
$(this.el).i18n();
|
||||
|
||||
this.isRendered = true;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
updateStats:function(eventName) {
|
||||
$('.count', this.el).html(this.countTemplate({count: this.options.clientStat.get('approvedSiteCount')}));
|
||||
},
|
||||
|
||||
showRegistrationToken:function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -412,6 +431,8 @@ var ClientListView = Backbone.View.extend({
|
|||
|
||||
tagName: 'span',
|
||||
|
||||
stats: {},
|
||||
|
||||
initialize:function (options) {
|
||||
this.options = options;
|
||||
this.filteredModel = this.model;
|
||||
|
@ -420,7 +441,6 @@ var ClientListView = Backbone.View.extend({
|
|||
load:function(callback) {
|
||||
if (this.model.isFetched &&
|
||||
this.options.whiteListList.isFetched &&
|
||||
this.options.stats.isFetched &&
|
||||
this.options.systemScopeList.isFetched) {
|
||||
callback();
|
||||
return;
|
||||
|
@ -430,13 +450,11 @@ var ClientListView = Backbone.View.extend({
|
|||
$('#loading').html(
|
||||
'<span class="label" id="loading-clients">' + $.t("common.clients") + '</span> ' +
|
||||
'<span class="label" id="loading-whitelist">' + $.t("whitelist.whitelist") + '</span> ' +
|
||||
'<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> ' +
|
||||
'<span class="label" id="loading-stats">' + $.t("common.statistics") + '</span> '
|
||||
'<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.whiteListList.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.stats.fetchIfNeeded({success:function(e) {$('#loading-stats').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
|
@ -471,27 +489,44 @@ var ClientListView = Backbone.View.extend({
|
|||
|
||||
renderInner:function(eventName) {
|
||||
|
||||
// render the rows
|
||||
// set up the rows to render
|
||||
// (note that this doesn't render until visibility is determined in togglePlaceholder)
|
||||
|
||||
_.each(this.filteredModel.models, function (client, index) {
|
||||
var clientStat = this.getStat(client.get('clientId'));
|
||||
var view = new ClientView({
|
||||
model:client,
|
||||
count:this.options.stats.get(client.get('id')),
|
||||
model:client,
|
||||
clientStat:clientStat,
|
||||
systemScopeList: this.options.systemScopeList,
|
||||
whiteList: this.options.whiteListList.getByClientId(client.get('clientId'))
|
||||
});
|
||||
view.parentView = this;
|
||||
var element = view.render().el;
|
||||
//var element = view.render().el;
|
||||
var element = view.el;
|
||||
$("#client-table",this.el).append(element);
|
||||
if (Math.ceil((index + 1) / 10) != 1) {
|
||||
$(element).hide();
|
||||
}
|
||||
this.addView(client.get('id'), view);
|
||||
}, this);
|
||||
|
||||
this.togglePlaceholder();
|
||||
|
||||
|
||||
},
|
||||
|
||||
views:{},
|
||||
|
||||
addView:function(index, view) {
|
||||
this.views[index] = view;
|
||||
},
|
||||
|
||||
getView:function(index) {
|
||||
return this.views[index];
|
||||
},
|
||||
|
||||
getStat:function(index) {
|
||||
if (!this.stats[index]) {
|
||||
this.stats[index] = new ClientStatsModel({id: index});
|
||||
}
|
||||
return this.stats[index];
|
||||
},
|
||||
|
||||
togglePlaceholder:function() {
|
||||
// set up pagination
|
||||
var numPages = Math.ceil(this.filteredModel.length / 10);
|
||||
|
@ -508,6 +543,7 @@ var ClientListView = Backbone.View.extend({
|
|||
}
|
||||
|
||||
if (this.filteredModel.length > 0) {
|
||||
this.changePage(undefined, 1);
|
||||
$('#client-table', this.el).show();
|
||||
$('#client-table-empty', this.el).hide();
|
||||
$('#client-table-search-empty', this.el).hide();
|
||||
|
@ -527,14 +563,54 @@ var ClientListView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
changePage:function(event, num) {
|
||||
console.log('Page changed: ' + num);
|
||||
|
||||
$('.paginator', this.el).bootpag({page:num});
|
||||
var _self = this;
|
||||
|
||||
_.each(this.filteredModel.models, function (client, index) {
|
||||
var view = _self.getView(client.get('id'));
|
||||
if (!view) {
|
||||
console.log('Error: no view for client ' + client.get('id'));
|
||||
return;
|
||||
}
|
||||
|
||||
// only show/render clients on the current page
|
||||
|
||||
console.log(':: ' + index + ' ' + num + ' ' + Math.ceil((index + 1) / 10) != num);
|
||||
|
||||
if (Math.ceil((index + 1) / 10) != num) {
|
||||
$(view.el).hide();
|
||||
} else {
|
||||
if (!view.isRendered) {
|
||||
view.render();
|
||||
var clientStat = view.options.clientStat;
|
||||
|
||||
// load and display the stats
|
||||
$.when(clientStat.fetchIfNeeded({
|
||||
success:function(e) {
|
||||
|
||||
},
|
||||
error:app.errorHandlerView.handleError()}))
|
||||
.done(function(e) {
|
||||
view.updateStats();
|
||||
});
|
||||
}
|
||||
$(view.el).show();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
$('#client-table tbody tr', this.el).each(function(index, element) {
|
||||
if (Math.ceil((index + 1) / 10) != num) {
|
||||
// hide the element
|
||||
$(element).hide();
|
||||
} else {
|
||||
// show the element
|
||||
$(element).show();
|
||||
}
|
||||
});
|
||||
*/
|
||||
},
|
||||
|
||||
refreshTable:function(e) {
|
||||
|
@ -543,14 +619,12 @@ var ClientListView = Backbone.View.extend({
|
|||
$('#loading').html(
|
||||
'<span class="label" id="loading-clients">' + $.t("common.clients") + '</span> ' +
|
||||
'<span class="label" id="loading-whitelist">' + $.t("whitelist.whitelist") + '</span> ' +
|
||||
'<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> ' +
|
||||
'<span class="label" id="loading-stats">' + $.t("common.statistics") + '</span> '
|
||||
'<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> '
|
||||
);
|
||||
|
||||
var _self = this;
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.whiteListList.fetch({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.stats.fetch({success:function(e) {$('#loading-stats').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
|
@ -1210,8 +1284,7 @@ ui.routes.push({path: "admin/clients", name: "listClients", callback:
|
|||
|
||||
this.updateSidebar('admin/clients');
|
||||
|
||||
var view = new ClientListView({model:this.clientList, stats: this.clientStats, systemScopeList: this.systemScopeList, whiteListList: this.whiteListList});
|
||||
|
||||
var view = new ClientListView({model:this.clientList, systemScopeList: this.systemScopeList, whiteListList: this.whiteListList});
|
||||
view.load(function() {
|
||||
$('#content').html(view.render().el);
|
||||
view.delegateEvents();
|
||||
|
|
|
@ -17,14 +17,8 @@
|
|||
<!-- client -->
|
||||
|
||||
<script type="text/html" id="tmpl-client-table-item">
|
||||
<td>
|
||||
<% if (count == 0) { %>
|
||||
<span class="label label-important">0</span>
|
||||
<% } else if (count != null) { %>
|
||||
<span class="label label-info"><%- count %></span>
|
||||
<% } else { %>
|
||||
<span class="label label-warning">?</span>
|
||||
<% } %>
|
||||
<td class="count">
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
@ -900,3 +894,13 @@
|
|||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="tmpl-client-count">
|
||||
<% if (count == 0) { %>
|
||||
<span class="label label-important">0</span>
|
||||
<% } else if (count != null) { %>
|
||||
<span class="label label-info"><%- count %></span>
|
||||
<% } else { %>
|
||||
<span class="label label-warning">?</span>
|
||||
<% } %>
|
||||
</script>
|
|
@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.openid.connect.model.ApprovedSite;
|
||||
import org.mitre.openid.connect.model.ClientStat;
|
||||
import org.mitre.openid.connect.service.ApprovedSiteService;
|
||||
import org.mitre.openid.connect.service.StatsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -65,12 +66,12 @@ public class DefaultStatsService implements StatsService {
|
|||
}, 10, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private Supplier<Map<Long, Integer>> byClientIdCache = createByClientIdCache();
|
||||
private Supplier<Map<String, Integer>> byClientIdCache = createByClientIdCache();
|
||||
|
||||
private Supplier<Map<Long, Integer>> createByClientIdCache() {
|
||||
return Suppliers.memoizeWithExpiration(new Supplier<Map<Long, Integer>>() {
|
||||
private Supplier<Map<String, Integer>> createByClientIdCache() {
|
||||
return Suppliers.memoizeWithExpiration(new Supplier<Map<String, Integer>>() {
|
||||
@Override
|
||||
public Map<Long, Integer> get() {
|
||||
public Map<String, Integer> get() {
|
||||
return computeByClientId();
|
||||
}
|
||||
|
||||
|
@ -107,11 +108,11 @@ public class DefaultStatsService implements StatsService {
|
|||
* @see org.mitre.openid.connect.service.StatsService#calculateByClientId()
|
||||
*/
|
||||
@Override
|
||||
public Map<Long, Integer> getByClientId() {
|
||||
public Map<String, Integer> getByClientId() {
|
||||
return byClientIdCache.get();
|
||||
}
|
||||
|
||||
private Map<Long, Integer> computeByClientId() {
|
||||
private Map<String, Integer> computeByClientId() {
|
||||
// get all approved sites
|
||||
Collection<ApprovedSite> allSites = approvedSiteService.getAll();
|
||||
|
||||
|
@ -120,10 +121,10 @@ public class DefaultStatsService implements StatsService {
|
|||
clientIds.add(approvedSite.getClientId());
|
||||
}
|
||||
|
||||
Map<Long, Integer> counts = getEmptyClientCountMap();
|
||||
Map<String, Integer> counts = getEmptyClientCountMap();
|
||||
for (String clientId : clientIds) {
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
counts.put(client.getId(), clientIds.count(clientId));
|
||||
counts.put(client.getClientId(), clientIds.count(clientId));
|
||||
}
|
||||
|
||||
return counts;
|
||||
|
@ -133,22 +134,24 @@ public class DefaultStatsService implements StatsService {
|
|||
* @see org.mitre.openid.connect.service.StatsService#countForClientId(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Integer getCountForClientId(Long id) {
|
||||
|
||||
Map<Long, Integer> counts = getByClientId();
|
||||
return counts.get(id);
|
||||
public ClientStat getCountForClientId(String id) {
|
||||
|
||||
Map<String, Integer> counts = getByClientId();
|
||||
ClientStat stat = new ClientStat();
|
||||
stat.setApprovedSiteCount(counts.get(id));
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new map of all client ids set to zero
|
||||
* @return
|
||||
*/
|
||||
private Map<Long, Integer> getEmptyClientCountMap() {
|
||||
Map<Long, Integer> counts = new HashMap<>();
|
||||
private Map<String, Integer> getEmptyClientCountMap() {
|
||||
Map<String, Integer> counts = new HashMap<>();
|
||||
Collection<ClientDetailsEntity> clients = clientService.getAllClients();
|
||||
for (ClientDetailsEntity client : clients) {
|
||||
counts.put(client.getId(), 0);
|
||||
counts.put(client.getClientId(), 0);
|
||||
}
|
||||
|
||||
return counts;
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.mitre.openid.connect.web;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.mitre.openid.connect.model.ClientStat;
|
||||
import org.mitre.openid.connect.service.StatsService;
|
||||
import org.mitre.openid.connect.view.JsonEntityView;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -53,20 +54,20 @@ public class StatsAPI {
|
|||
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_USER')")
|
||||
@RequestMapping(value = "byclientid", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String statsByClient(ModelMap m) {
|
||||
Map<Long, Integer> e = statsService.getByClientId();
|
||||
|
||||
m.put(JsonEntityView.ENTITY, e);
|
||||
|
||||
return JsonEntityView.VIEWNAME;
|
||||
}
|
||||
|
||||
// @PreAuthorize("hasRole('ROLE_USER')")
|
||||
// @RequestMapping(value = "byclientid", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
// public String statsByClient(ModelMap m) {
|
||||
// Map<Long, Integer> e = statsService.getByClientId();
|
||||
//
|
||||
// m.put(JsonEntityView.ENTITY, e);
|
||||
//
|
||||
// return JsonEntityView.VIEWNAME;
|
||||
// }
|
||||
//
|
||||
@PreAuthorize("hasRole('ROLE_USER')")
|
||||
@RequestMapping(value = "byclientid/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String statsByClientId(@PathVariable("id") Long id, ModelMap m) {
|
||||
Integer e = statsService.getCountForClientId(id);
|
||||
public String statsByClientId(@PathVariable("id") String clientId, ModelMap m) {
|
||||
ClientStat e = statsService.getCountForClientId(clientId);
|
||||
|
||||
m.put(JsonEntityView.ENTITY, e);
|
||||
|
||||
|
|
Loading…
Reference in New Issue