KEYCLOAK-1542 - rewrote showing info from providers to be generic

This commit is contained in:
Vlastimil Elias 2015-07-23 16:39:27 +02:00
parent 1c142cb170
commit 99f74acb94
17 changed files with 555 additions and 440 deletions

View file

@ -6,20 +6,19 @@ import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderOperationalInfo;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@ -33,7 +32,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
private Config.Scope config;
private DatabaseInfo databaseInfo;
private Map<String,String> operationalInfo;
@Override
public JpaConnectionProvider create(KeycloakSession session) {
@ -127,7 +126,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
connection = getConnection();
try{
prepareDatabaseInfo(connection);
prepareOperationalInfo(connection);
if (databaseSchema != null) {
logger.trace("Updating database");
@ -180,16 +179,16 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
}
protected void prepareDatabaseInfo(Connection connection) {
protected void prepareOperationalInfo(Connection connection) {
try {
databaseInfo = new DatabaseInfo();
operationalInfo = new LinkedHashMap<>();
DatabaseMetaData md = connection.getMetaData();
databaseInfo.databaseDriver = md.getDriverName() + " " + md.getDriverVersion();
databaseInfo.databaseProduct = md.getDatabaseProductName() + " " + md.getDatabaseProductVersion();
databaseInfo.databaseUser = md.getUserName();
databaseInfo.jdbcUrl = md.getURL();
operationalInfo.put("databaseUrl",md.getURL());
operationalInfo.put("databaseUser", md.getUserName());
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
} catch (SQLException e) {
logger.warn("Unable to get database info due " + e.getMessage());
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
}
}
@ -209,31 +208,8 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
@Override
public DatabaseInfo getOperationalInfo() {
return databaseInfo;
}
public static class DatabaseInfo implements ProviderOperationalInfo {
protected String jdbcUrl;
protected String databaseUser;
protected String databaseProduct;
protected String databaseDriver;
public String getJdbcUrl() {
return jdbcUrl;
}
public String getDatabaseDriver() {
return databaseDriver;
}
public String getDatabaseUser() {
return databaseUser;
}
public String getDatabaseProduct() {
return databaseProduct;
}
public Map<String,String> getOperationalInfo() {
return operationalInfo;
}
}

View file

@ -1,10 +1,10 @@
package org.keycloak.connections.jpa;
import org.keycloak.provider.MonitorableProviderFactory;
import org.keycloak.provider.ServerInfoAwareProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface JpaConnectionProviderFactory extends MonitorableProviderFactory<JpaConnectionProvider> {
public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory<JpaConnectionProvider> {
}

View file

@ -14,13 +14,13 @@ import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocati
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderOperationalInfo;
import javax.net.ssl.SSLSocketFactory;
import java.lang.reflect.Method;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -61,7 +61,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
private DB db;
protected Config.Scope config;
private MongoDbInfo mongoDbInfo;
private Map<String,String> operationalInfo;
@Override
public MongoConnectionProvider create(KeycloakSession session) {
@ -165,11 +165,11 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
client = new MongoClient(new ServerAddress(host, port), clientOptions);
}
mongoDbInfo = new MongoDbInfo();
mongoDbInfo.driverVersion = client.getVersion();
mongoDbInfo.address = client.getAddress().toString();
mongoDbInfo.database = dbName;
mongoDbInfo.user = user;
operationalInfo = new LinkedHashMap<>();
operationalInfo.put("mongoServerAddress", client.getAddress().toString());
operationalInfo.put("mongoDatabaseName", dbName);
operationalInfo.put("mongoUser", user);
operationalInfo.put("mongoDriverVersion", client.getVersion());
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
return client;
@ -219,31 +219,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
}
@Override
public ProviderOperationalInfo getOperationalInfo() {
return mongoDbInfo;
public Map<String,String> getOperationalInfo() {
return operationalInfo;
}
public static class MongoDbInfo implements ProviderOperationalInfo {
public String address;
public String database;
public String driverVersion;
public String user;
public String getAddress() {
return address;
}
public String getDatabase() {
return database;
}
public String getDriverVersion() {
return driverVersion;
}
public String getUser() {
return user;
}
}
}

View file

@ -1,9 +1,9 @@
package org.keycloak.connections.mongo;
import org.keycloak.provider.MonitorableProviderFactory;
import org.keycloak.provider.ServerInfoAwareProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface MongoConnectionProviderFactory extends MonitorableProviderFactory<MongoConnectionProvider> {
public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory<MongoConnectionProvider> {
}

View file

@ -1112,7 +1112,13 @@ module.config([ '$routeProvider', function($routeProvider) {
controller : 'AuthenticationConfigCreateCtrl'
})
.when('/server-info', {
templateUrl : resourceUrl + '/partials/server-info.html'
templateUrl : resourceUrl + '/partials/server-info.html',
resolve : {
serverInfoPage : function(ServerInfoPageLoader) {
return ServerInfoPageLoader();
}
},
controller : 'ServerInfoPageCtrl'
})
.when('/logout', {
templateUrl : resourceUrl + '/partials/home.html',

View file

@ -113,6 +113,13 @@ module.controller('HomeCtrl', function(Realm, Auth, $location) {
});
});
module.controller('ServerInfoPageCtrl', function($scope, ServerInfoPage) {
$scope.serverInfoPage = ServerInfoPage.get();
$scope.serverInfoPageUpdate = function() {
$scope.serverInfoPage = ServerInfoPage.get();
};
});
module.controller('RealmListCtrl', function($scope, Realm, Current) {
$scope.realms = Realm.query();
Current.realms = $scope.realms;

View file

@ -39,6 +39,10 @@ module.factory('ServerInfoLoader', function(Loader, ServerInfo, $q) {
return Loader.get(ServerInfo);
});
module.factory('ServerInfoPageLoader', function(Loader, ServerInfoPage, $q) {
return Loader.get(ServerInfoPage);
});
module.factory('RealmLoader', function(Loader, Realm, $route, $q) {
return Loader.get(Realm, function() {
return {

View file

@ -205,6 +205,9 @@ module.factory('ServerInfo', function($resource) {
return $resource(authUrl + '/admin/serverinfo');
});
module.factory('ServerInfoPage', function($resource) {
return $resource(authUrl + '/admin/serverinfopage');
});
module.factory('ClientProtocolMapper', function($resource) {

View file

@ -4,15 +4,15 @@
<table class="table table-striped table-bordered">
<tr>
<td width="20%">Keycloak Version</td>
<td>{{serverInfo.version}}</td>
<td>{{serverInfoPage.version}}</td>
</tr>
<tr>
<td>Server Time</td>
<td>{{serverInfo.serverTime}} (<a style="cursor: pointer" data-ng-click="serverInfoUpdate()">update</a>)</td>
<td>{{serverInfoPage.serverTime}} (<a style="cursor: pointer" data-ng-click="serverInfoPageUpdate()">update</a>)</td>
</tr>
<tr>
<td>Server Uptime</td>
<td>{{serverInfo.serverUptime}}</td>
<td>{{serverInfoPage.serverUptime}}</td>
</tr>
</table>
@ -22,15 +22,15 @@
<table class="table table-striped table-bordered" style="margin-top: 0px;">
<tr>
<td width="20%">Total Memory</td>
<td>{{serverInfo.memoryInfo.totalFormated}}</td>
<td>{{serverInfoPage.memoryInfo.totalFormated}}</td>
</tr>
<tr>
<td>Free Memory</td>
<td>{{serverInfo.memoryInfo.freeFormated}} ({{serverInfo.memoryInfo.freePercentage}}%)</td>
<td>{{serverInfoPage.memoryInfo.freeFormated}} ({{serverInfoPage.memoryInfo.freePercentage}}%)</td>
</tr>
<tr>
<td>Used Memory</td>
<td>{{serverInfo.memoryInfo.usedFormated}}</td>
<td>{{serverInfoPage.memoryInfo.usedFormated}}</td>
</tr>
</table>
</div>
@ -42,109 +42,73 @@
<table class="table table-striped table-bordered" style="margin-top: 0px;">
<tr>
<td width="20%">Current Working Directory</td>
<td>{{serverInfo.systemInfo.userDir}}</td>
<td>{{serverInfoPage.systemInfo.userDir}}</td>
</tr>
<tr>
<td>Java Version</td>
<td>{{serverInfo.systemInfo.javaVersion}}</td>
<td>{{serverInfoPage.systemInfo.javaVersion}}</td>
</tr>
<tr>
<td>Java Vendor</td>
<td>{{serverInfo.systemInfo.javaVendor}}</td>
<td>{{serverInfoPage.systemInfo.javaVendor}}</td>
</tr>
<tr>
<td>Java Runtime</td>
<td>{{serverInfo.systemInfo.javaRuntime}}</td>
<td>{{serverInfoPage.systemInfo.javaRuntime}}</td>
</tr>
<tr>
<td>Java VM</td>
<td>{{serverInfo.systemInfo.javaVm}}</td>
<td>{{serverInfoPage.systemInfo.javaVm}}</td>
</tr>
<tr>
<td>Java VM Version</td>
<td>{{serverInfo.systemInfo.javaVmVersion}}</td>
<td>{{serverInfoPage.systemInfo.javaVmVersion}}</td>
</tr>
<tr>
<td>Java Home</td>
<td>{{serverInfo.systemInfo.javaHome}}</td>
<td>{{serverInfoPage.systemInfo.javaHome}}</td>
</tr>
<tr>
<td>User Name</td>
<td>{{serverInfo.systemInfo.userName}}</td>
<td>{{serverInfoPage.systemInfo.userName}}</td>
</tr>
<tr>
<td>User Timezone</td>
<td>{{serverInfo.systemInfo.userTimezone}}</td>
<td>{{serverInfoPage.systemInfo.userTimezone}}</td>
</tr>
<tr>
<td>User Locale</td>
<td>{{serverInfo.systemInfo.userLocale}}</td>
<td>{{serverInfoPage.systemInfo.userLocale}}</td>
</tr>
<tr>
<td>System Encoding</td>
<td>{{serverInfo.systemInfo.fileEncoding}}</td>
<td>{{serverInfoPage.systemInfo.fileEncoding}}</td>
</tr>
<tr>
<td>Operating System</td>
<td>{{serverInfo.systemInfo.osName}} {{serverInfo.systemInfo.osVersion}}</td>
<td>{{serverInfoPage.systemInfo.osName}} {{serverInfoPage.systemInfo.osVersion}}</td>
</tr>
<tr>
<td>OS Architecture</td>
<td>{{serverInfo.systemInfo.osArchitecture}}</td>
<td>{{serverInfoPage.systemInfo.osArchitecture}}</td>
</tr>
</table>
</div>
</fieldset>
<fieldset ng-show="serverInfo.jpaInfo">
<fieldset ng-show="serverInfoPage.jpaInfo">
<legend collapsed>Database Info</legend>
<div class="form-group">
<table class="table table-striped table-bordered" style="margin-top: 0px;">
<tr>
<td width="20%">Database URL</td>
<td>{{serverInfo.jpaInfo.jdbcUrl}}</td>
</tr>
<tr>
<td>Database User</td>
<td>{{serverInfo.jpaInfo.databaseUser}}</td>
</tr>
<tr>
<td>Database Type</td>
<td>{{serverInfo.jpaInfo.databaseProduct}}</td>
</tr>
<tr>
<td>Database Driver</td>
<td>{{serverInfo.jpaInfo.databaseDriver}}</td>
<tr ng-repeat="(key, value) in serverInfoPage.jpaInfo">
<td width="20%">{{key}}</td>
<td>{{value}}</td>
</tr>
</table>
</div>
</fieldset>
<fieldset ng-show="serverInfo.mongoDbInfo">
<legend collapsed>Mongo DB Info</legend>
<div class="form-group">
<table class="table table-striped table-bordered" style="margin-top: 0px;">
<tr width="20%">
<td>Address</td>
<td>{{serverInfo.mongoDbInfo.address}}</td>
</tr>
<tr>
<td>Database</td>
<td>{{serverInfo.mongoDbInfo.database}}</td>
</tr>
<tr>
<td>User</td>
<td>{{serverInfo.mongoDbInfo.user}}</td>
</tr>
<tr>
<td>Driver Version</td>
<td>{{serverInfo.mongoDbInfo.driverVersion}}</td>
</tr>
</table>
</div>
</fieldset>
<fieldset>
<legend collapsed>Providers</legend>
@ -160,11 +124,23 @@
</tr>
</thead>
<tbody>
<tr data-ng-repeat="spi in (serverInfo.providers | filter:{internal:false} | orderBy:'name')">
<tr data-ng-repeat="spi in (serverInfoPage.providers | filter:{internal:false} | orderBy:'name')">
<td>{{spi.name}}</td>
<td>
<div data-ng-repeat="provider in (spi.implementations | orderBy:'toString()')">
{{provider}}
<div data-ng-repeat="provider in (spi.implementations | orderBy:'name')">
{{provider.name}}
<span ng-show="provider.operationalInfo">
<button type="button" class="btn btn-default btn-xs" ng-click="collapseRep = !collapseRep">
<span class="glyphicon glyphicon-plus" data-ng-show="!collapseRep"></span>
<span class="glyphicon glyphicon-minus" data-ng-show="collapseRep"></span>
</button>
<table ng-show="collapseRep" class="table table-striped table-bordered" style="margin-top: 0px;">
<tr ng-repeat="(key, value) in provider.operationalInfo">
<td width="20%">{{key}}</td>
<td>{{value}}</td>
</tr>
</table>
</span>
</div>
</td>
</tr>
@ -184,11 +160,23 @@
</tr>
</thead>
<tbody>
<tr data-ng-repeat="spi in (serverInfo.providers | filter:{internal:true} | orderBy:'name')">
<tr data-ng-repeat="spi in (serverInfoPage.providers | filter:{internal:true} | orderBy:'name')">
<td>{{spi.name}}</td>
<td>
<div data-ng-repeat="provider in (spi.implementations | orderBy:'toString()')">
{{provider}}
<div data-ng-repeat="provider in (spi.implementations | orderBy:'name')">
{{provider.name}}
<span ng-show="provider.operationalInfo">
<button type="button" class="btn btn-default btn-xs" ng-click="collapseRep = !collapseRep">
<span class="glyphicon glyphicon-plus" data-ng-show="!collapseRep"></span>
<span class="glyphicon glyphicon-minus" data-ng-show="collapseRep"></span>
</button>
<table ng-show="collapseRep" class="table table-striped table-bordered" style="margin-top: 0px;">
<tr ng-repeat="(key, value) in provider.operationalInfo">
<td width="20%">{{key}}</td>
<td>{{value}}</td>
</tr>
</table>
</span>
</div>
</td>
</tr>

View file

@ -1,18 +0,0 @@
package org.keycloak.provider;
/**
* Provider factory for provider which is monitorable. It means some info about it can be shown on "Server Info" page or accessed over Operational monitoring endpoint.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public interface MonitorableProviderFactory<T extends Provider> extends ProviderFactory<T> {
/**
* Get operational info about given provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc).
* Is used to be shown on "Server Info" page or in Operational monitoring endpoint.
*
* @return extendion of {@link ProviderOperationalInfo}
*/
public ProviderOperationalInfo getOperationalInfo();
}

View file

@ -1,14 +0,0 @@
package org.keycloak.provider;
import java.io.Serializable;
/**
* Operational info about given Provider.
* Contains info about Provider that can be shown on "Server Info" page or accessed over Operational monitoring endpoint.
*
* @author Vlastimil Elias (velias at redhat dot com)
* @see MonitorableProviderFactory
*/
public interface ProviderOperationalInfo extends Serializable {
}

View file

@ -0,0 +1,20 @@
package org.keycloak.provider;
import java.util.Map;
/**
* Marker interface for ProviderFactory of Provider which wants to show some info on "Server Info" page in Admin console.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public interface ServerInfoAwareProviderFactory<T extends Provider> extends ProviderFactory<T> {
/**
* Get operational info about given provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
* shown on "Server Info" page.
*
* @return Map with keys describing value and relevant values itself
*/
public Map<String, String> getOperationalInfo();
}

View file

@ -39,21 +39,6 @@
<artifactId>keycloak-connections-http-client</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-jpa</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-mongo</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-forms-common-freemarker</artifactId>

View file

@ -218,6 +218,32 @@ public class AdminRoot {
return adminResource;
}
/**
* Operational information about the server for "Server Info" page
*
* @param headers
* @return
*/
@Path("serverinfopage")
public ServerInfoPageAdminResource getServerInfoPage(@Context final HttpHeaders headers) {
handlePreflightRequest();
AdminAuth auth = authenticateRealmAdminRequest(headers);
if (!isAdmin(auth)) {
throw new ForbiddenException();
}
if (auth != null) {
logger.debug("authenticated admin access for: " + auth.getUser().getUsername());
}
Cors.add(request).allowedOrigins(auth.getToken()).allowedMethods("GET", "PUT", "POST", "DELETE").auth().build(response);
ServerInfoPageAdminResource adminResource = new ServerInfoPageAdminResource();
ResteasyProviderFactory.getInstance().injectProperties(adminResource);
return adminResource;
}
protected boolean isAdmin(AdminAuth auth) {
RealmManager realmManager = new RealmManager(session);

View file

@ -1,11 +1,8 @@
package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger;
import org.keycloak.Version;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.connections.mongo.MongoConnectionProvider;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType;
@ -19,10 +16,8 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.provider.MonitorableProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderOperationalInfo;
import org.keycloak.provider.Spi;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@ -31,13 +26,11 @@ import org.keycloak.social.SocialIdentityProvider;
import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
@ -46,8 +39,6 @@ import java.util.Set;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ServerInfoAdminResource {
private static final Logger logger = Logger.getLogger(ServerInfoAdminResource.class);
private static final Map<String, List<String>> ENUMS = createEnumsMap(EventType.class, OperationType.class);
@ -64,9 +55,6 @@ public class ServerInfoAdminResource {
ServerInfoRepresentation info = new ServerInfoRepresentation();
info.version = Version.VERSION;
info.serverTime = new Date().toString();
info.serverStartupTime = session.getKeycloakSessionFactory().getServerStartupTimestamp();
info.memoryInfo = (new MemoryInfo()).init(Runtime.getRuntime());
info.systemInfo = (new SystemInfo()).init();
setSocialProviders(info);
setIdentityProviders(info);
setThemes(info);
@ -77,21 +65,6 @@ public class ServerInfoAdminResource {
setProtocolMapperTypes(info);
setBuiltinProtocolMappers(info);
info.setEnums(ENUMS);
ProviderFactory<JpaConnectionProvider> jpf = session.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
if(jpf!=null && jpf instanceof MonitorableProviderFactory){
info.jpaInfo = ((MonitorableProviderFactory<?>)jpf).getOperationalInfo();
} else {
logger.debug("JPA provider not found or is not monitorable");
}
ProviderFactory<MongoConnectionProvider> mpf = session.getKeycloakSessionFactory().getProviderFactory(MongoConnectionProvider.class);
if(mpf!=null && mpf instanceof MonitorableProviderFactory){
info.mongoDbInfo = ((MonitorableProviderFactory<?>)mpf).getOperationalInfo();
} else {
logger.debug("Mongo provider not found or is not monitorable");
}
return info;
}
@ -214,170 +187,12 @@ public class ServerInfoAdminResource {
info.clientImporters.add(data);
}
}
public static class MemoryInfo implements Serializable {
protected long total;
protected long used;
public MemoryInfo(){
}
/**
* Fill object fwith info.
* @param runtime used to get memory info from.
* @return itself for chaining
*/
public MemoryInfo init(Runtime runtime){
total = runtime.maxMemory();
used = runtime.totalMemory() - runtime.freeMemory();
return this;
}
public long getTotal(){
return total;
}
public String getTotalFormated(){
return formatMemory(getTotal());
}
public long getFree(){
return getTotal() - getUsed();
}
public String getFreeFormated(){
return formatMemory(getFree());
}
public long getUsed(){
return used;
}
public String getUsedFormated(){
return formatMemory(getUsed());
}
public long getFreePercentage(){
return getFree() * 100 / getTotal();
}
private String formatMemory(long bytes){
if(bytes > 1024L*1024L){
return bytes/(1024L *1024L) + " MB";
} else if(bytes > 1024L){
return bytes/(1024L) + " kB";
} else {
return bytes + " B";
}
}
}
public static class SystemInfo implements Serializable {
protected String javaVersion;
protected String javaVendor;
protected String javaVm;
protected String javaVmVersion;
protected String javaRuntime;
protected String javaHome;
protected String osName;
protected String osArchitecture;
protected String osVersion;
protected String fileEncoding;
protected String userName;
protected String userDir;
protected String userTimezone;
protected String userLocale;
public SystemInfo() {
}
/**
* Fill object with info about current system loaded from {@link System} properties.
* @return object itself for chaining
*/
protected SystemInfo init(){
javaVersion = System.getProperty("java.version");
javaVendor = System.getProperty("java.vendor");
javaVm = System.getProperty("java.vm.name");
javaVmVersion = System.getProperty("java.vm.version");
javaRuntime = System.getProperty("java.runtime.name");
javaHome = System.getProperty("java.home");
osName = System.getProperty("os.name");
osArchitecture = System.getProperty("os.arch");
osVersion = System.getProperty("os.version");
fileEncoding = System.getProperty("file.encoding");
userName = System.getProperty("user.name");
userDir = System.getProperty("user.dir");
userTimezone = System.getProperty("user.timezone");
userLocale = (new Locale(System.getProperty("user.country"),System.getProperty("user.language")).toString());
return this;
}
public String getJavaVersion(){
return javaVersion;
}
public String getJavaVendor(){
return javaVendor;
}
public String getJavaVm(){
return javaVm;
}
public String getJavaVmVersion(){
return javaVmVersion;
}
public String getJavaRuntime(){
return javaRuntime;
}
public String getJavaHome(){
return javaHome;
}
public String getOsName(){
return osName;
}
public String getOsArchitecture(){
return osArchitecture;
}
public String getOsVersion(){
return osVersion;
}
public String getFileEncoding(){
return fileEncoding;
}
public String getUserName(){
return userName;
}
public String getUserDir(){
return userDir;
}
public String getUserTimezone(){
return userTimezone;
}
public String getUserLocale(){
return userLocale;
}
}
public static class ServerInfoRepresentation implements Serializable {
public static class ServerInfoRepresentation {
private String version;
private String serverTime;
private long serverStartupTime;
private Map<String, List<String>> themes;
@ -393,76 +208,13 @@ public class ServerInfoAdminResource {
private Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers;
private Map<String, List<String>> enums;
private MemoryInfo memoryInfo;
private SystemInfo systemInfo;
private ProviderOperationalInfo jpaInfo;
private ProviderOperationalInfo mongoDbInfo;
public ServerInfoRepresentation() {
}
public SystemInfo getSystemInfo(){
return systemInfo;
}
public MemoryInfo getMemoryInfo(){
return memoryInfo;
}
public ProviderOperationalInfo getJpaInfo() {
return jpaInfo;
}
public ProviderOperationalInfo getMongoDbInfo() {
return mongoDbInfo;
}
public String getServerTime() {
return serverTime;
}
public long getServerStartupTime() {
return serverStartupTime;
}
/**
* @return server startup time formatted
*/
public String getServerStartupTimeFormatted() {
return (new Date(serverStartupTime)).toString();
}
/**
* @return server uptime in millis
*/
public long getServerUptimeMillis(){
return System.currentTimeMillis() - serverStartupTime;
}
/**
* @return server uptime formatted like "0 days, 10 hours, 24 minutes, 55 seconds"
*/
public String getServerUptime(){
long diffInSeconds = getServerUptimeMillis()/1000;
long diff[] = new long[] { 0, 0, 0, 0 };
/* sec */diff[3] = (diffInSeconds >= 60 ? diffInSeconds % 60 : diffInSeconds);
/* min */diff[2] = (diffInSeconds = (diffInSeconds / 60)) >= 60 ? diffInSeconds % 60 : diffInSeconds;
/* hours */diff[1] = (diffInSeconds = (diffInSeconds / 60)) >= 24 ? diffInSeconds % 24 : diffInSeconds;
/* days */diff[0] = (diffInSeconds = (diffInSeconds / 24));
return String.format(
"%d day%s, %d hour%s, %d minute%s, %d second%s",
diff[0],
diff[0] != 1 ? "s" : "",
diff[1],
diff[1] != 1 ? "s" : "",
diff[2],
diff[2] != 1 ? "s" : "",
diff[3],
diff[3] != 1 ? "s" : "");
}
public String getVersion() {
return version;
@ -517,7 +269,7 @@ public class ServerInfoAdminResource {
}
}
public static class SpiInfoRepresentation implements Serializable {
public static class SpiInfoRepresentation {
private String name;
private boolean internal;
private Set<String> implementations;

View file

@ -0,0 +1,375 @@
package org.keycloak.services.resources.admin;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
import org.jboss.logging.Logger;
import org.keycloak.Version;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ServerInfoAwareProviderFactory;
import org.keycloak.provider.Spi;
/**
* REST endpoint which return info for "Server Info" page.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class ServerInfoPageAdminResource {
private static final Logger logger = Logger.getLogger(ServerInfoPageAdminResource.class);
@Context
private KeycloakSession session;
/**
* Returns a list of providers and other operational info about the page.
*
* @return
*/
@GET
public ServerInfoRepresentation getInfo() {
ServerInfoRepresentation info = new ServerInfoRepresentation();
info.version = Version.VERSION;
info.serverTime = new Date().toString();
info.serverStartupTime = session.getKeycloakSessionFactory().getServerStartupTimestamp();
info.memoryInfo = (new MemoryInfo()).init(Runtime.getRuntime());
info.systemInfo = (new SystemInfo()).init();
setProviders(info);
return info;
}
private void setProviders(ServerInfoRepresentation info) {
List<SpiInfoRepresentation> providers = new LinkedList<>();
for (Spi spi : ServiceLoader.load(Spi.class)) {
SpiInfoRepresentation spiRep = new SpiInfoRepresentation();
spiRep.setName(spi.getName());
spiRep.setInternal(spi.isInternal());
spiRep.setSystemInfo(ServerInfoAwareProviderFactory.class.isAssignableFrom(spi.getProviderFactoryClass()));
Set<String> s = session.listProviderIds(spi.getProviderClass());
Set<SpiImplementationRepresentation> srs = new HashSet<>();
if(s!=null){
for(String name: s){
SpiImplementationRepresentation sr = new SpiImplementationRepresentation(name);
if(spiRep.isSystemInfo()){
sr.setOperationalInfo(((ServerInfoAwareProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name)).getOperationalInfo());
}
srs.add(sr);
}
}
spiRep.setImplementations(srs);
providers.add(spiRep);
}
info.providers = providers;
}
public static class MemoryInfo implements Serializable {
protected long total;
protected long used;
public MemoryInfo(){
}
/**
* Fill object fwith info.
* @param runtime used to get memory info from.
* @return itself for chaining
*/
public MemoryInfo init(Runtime runtime){
total = runtime.maxMemory();
used = runtime.totalMemory() - runtime.freeMemory();
return this;
}
public long getTotal(){
return total;
}
public String getTotalFormated(){
return formatMemory(getTotal());
}
public long getFree(){
return getTotal() - getUsed();
}
public String getFreeFormated(){
return formatMemory(getFree());
}
public long getUsed(){
return used;
}
public String getUsedFormated(){
return formatMemory(getUsed());
}
public long getFreePercentage(){
return getFree() * 100 / getTotal();
}
private String formatMemory(long bytes){
if(bytes > 1024L*1024L){
return bytes/(1024L *1024L) + " MB";
} else if(bytes > 1024L){
return bytes/(1024L) + " kB";
} else {
return bytes + " B";
}
}
}
public static class SystemInfo implements Serializable {
protected String javaVersion;
protected String javaVendor;
protected String javaVm;
protected String javaVmVersion;
protected String javaRuntime;
protected String javaHome;
protected String osName;
protected String osArchitecture;
protected String osVersion;
protected String fileEncoding;
protected String userName;
protected String userDir;
protected String userTimezone;
protected String userLocale;
public SystemInfo() {
}
/**
* Fill object with info about current system loaded from {@link System} properties.
* @return object itself for chaining
*/
protected SystemInfo init(){
javaVersion = System.getProperty("java.version");
javaVendor = System.getProperty("java.vendor");
javaVm = System.getProperty("java.vm.name");
javaVmVersion = System.getProperty("java.vm.version");
javaRuntime = System.getProperty("java.runtime.name");
javaHome = System.getProperty("java.home");
osName = System.getProperty("os.name");
osArchitecture = System.getProperty("os.arch");
osVersion = System.getProperty("os.version");
fileEncoding = System.getProperty("file.encoding");
userName = System.getProperty("user.name");
userDir = System.getProperty("user.dir");
userTimezone = System.getProperty("user.timezone");
userLocale = (new Locale(System.getProperty("user.country"),System.getProperty("user.language")).toString());
return this;
}
public String getJavaVersion(){
return javaVersion;
}
public String getJavaVendor(){
return javaVendor;
}
public String getJavaVm(){
return javaVm;
}
public String getJavaVmVersion(){
return javaVmVersion;
}
public String getJavaRuntime(){
return javaRuntime;
}
public String getJavaHome(){
return javaHome;
}
public String getOsName(){
return osName;
}
public String getOsArchitecture(){
return osArchitecture;
}
public String getOsVersion(){
return osVersion;
}
public String getFileEncoding(){
return fileEncoding;
}
public String getUserName(){
return userName;
}
public String getUserDir(){
return userDir;
}
public String getUserTimezone(){
return userTimezone;
}
public String getUserLocale(){
return userLocale;
}
}
public static class ServerInfoRepresentation implements Serializable {
private String version;
private String serverTime;
private long serverStartupTime;
private List<SpiInfoRepresentation> providers;
private MemoryInfo memoryInfo;
private SystemInfo systemInfo;
public ServerInfoRepresentation() {
}
public SystemInfo getSystemInfo(){
return systemInfo;
}
public MemoryInfo getMemoryInfo(){
return memoryInfo;
}
public String getServerTime() {
return serverTime;
}
public long getServerStartupTime() {
return serverStartupTime;
}
/**
* @return server startup time formatted
*/
public String getServerStartupTimeFormatted() {
return (new Date(serverStartupTime)).toString();
}
/**
* @return server uptime in millis
*/
public long getServerUptimeMillis(){
return System.currentTimeMillis() - serverStartupTime;
}
/**
* @return server uptime formatted like "0 days, 10 hours, 24 minutes, 55 seconds"
*/
public String getServerUptime(){
long diffInSeconds = getServerUptimeMillis()/1000;
long diff[] = new long[] { 0, 0, 0, 0 };
/* sec */diff[3] = (diffInSeconds >= 60 ? diffInSeconds % 60 : diffInSeconds);
/* min */diff[2] = (diffInSeconds = (diffInSeconds / 60)) >= 60 ? diffInSeconds % 60 : diffInSeconds;
/* hours */diff[1] = (diffInSeconds = (diffInSeconds / 60)) >= 24 ? diffInSeconds % 24 : diffInSeconds;
/* days */diff[0] = (diffInSeconds = (diffInSeconds / 24));
return String.format(
"%d day%s, %d hour%s, %d minute%s, %d second%s",
diff[0],
diff[0] != 1 ? "s" : "",
diff[1],
diff[1] != 1 ? "s" : "",
diff[2],
diff[2] != 1 ? "s" : "",
diff[3],
diff[3] != 1 ? "s" : "");
}
public String getVersion() {
return version;
}
public List<SpiInfoRepresentation> getProviders() {
return providers;
}
}
public static class SpiInfoRepresentation implements Serializable {
private String name;
private boolean internal;
private boolean systemInfo;
private Set<SpiImplementationRepresentation> implementations;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isInternal() {
return internal;
}
public void setInternal(boolean internal) {
this.internal = internal;
}
public Set<SpiImplementationRepresentation> getImplementations() {
return implementations;
}
public boolean isSystemInfo() {
return systemInfo;
}
public void setSystemInfo(boolean systemInfo) {
this.systemInfo = systemInfo;
}
public void setImplementations(Set<SpiImplementationRepresentation> implementations) {
this.implementations = implementations;
}
}
public static class SpiImplementationRepresentation implements Serializable {
private String name;
private Map<String, String> operationalInfo;
public SpiImplementationRepresentation(String name) {
super();
this.name = name;
}
public Map<String, String> getOperationalInfo() {
return operationalInfo;
}
public void setOperationalInfo(Map<String, String> operationalInfo) {
this.operationalInfo = operationalInfo;
}
public String getName() {
return name;
}
}
}

View file

@ -317,13 +317,41 @@ public class AdminAPITest {
Assert.assertNotNull(response);
Assert.assertEquals(Version.VERSION, response.get("version"));
Assert.assertNotNull(response.get("serverTime"));
Assert.assertNotNull(response.get("providers"));
Assert.assertNotNull(response.get("themes"));
Assert.assertNotNull(response.get("enums"));
// System.out.println(response);
}
@Test
public void testServerInfoPage() {
String token = createToken();
final String authHeader = "Bearer " + token;
ClientRequestFilter authFilter = new ClientRequestFilter() {
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
}
};
Client client = ClientBuilder.newBuilder().register(authFilter).build();
UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
WebTarget target = client.target(AdminRoot.adminBaseUrl(authBase).path("serverinfopage"));
Map<?, ?> response = target.request().accept("application/json").get(Map.class);
Assert.assertNotNull(response);
Assert.assertEquals(Version.VERSION, response.get("version"));
Assert.assertNotNull(response.get("serverTime"));
Assert.assertNotNull(response.get("providers"));
Assert.assertNotNull(response.get("serverStartupTime"));
Assert.assertNotNull(response.get("memoryInfo"));
Assert.assertNotNull(response.get("jpaInfo"));
Assert.assertNull(response.get("mongoDbInfo"));
Assert.assertNotNull(response.get("systemInfo"));
System.out.println(response);
// System.out.println(response);
}