From 11374a27078266bebe5239d595796589e78bae9d Mon Sep 17 00:00:00 2001 From: stianst Date: Thu, 11 Oct 2018 19:38:29 +0200 Subject: [PATCH] KEYCLOAK-8556 Improvements to profile --- common/pom.xml | 5 + .../java/org/keycloak/common/Profile.java | 201 ++++++++++++------ .../java/org/keycloak/common/ProfileTest.java | 97 +++++++++ .../info/ProfileInfoRepresentation.java | 26 ++- .../keycloak/keycloak-common/main/module.xml | 1 + .../keycloak/keycloak-common/main/module.xml | 1 + .../keycloak/keycloak-common/main/module.xml | 1 + .../keycloak/keycloak-common/main/module.xml | 1 + .../keycloak/keycloak-common/main/module.xml | 1 + .../keycloak/keycloak-common/main/module.xml | 1 + .../resources/account/AccountRestService.java | 12 +- .../account/AccountRestServiceTest.java | 12 +- .../forms/ScriptAuthenticatorTest.java | 6 + .../messages/admin-messages_en.properties | 8 +- .../admin/resources/partials/server-info.html | 39 +++- 15 files changed, 318 insertions(+), 94 deletions(-) create mode 100644 common/src/test/java/org/keycloak/common/ProfileTest.java diff --git a/common/pom.xml b/common/pom.xml index 4ec3062c15..9df751277b 100755 --- a/common/pom.xml +++ b/common/pom.xml @@ -50,6 +50,11 @@ org.bouncycastle bcpkix-jdk15on + + org.jboss.logging + jboss-logging + provided + junit junit diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index 04d4f0b7bb..e7a2a23f97 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -17,12 +17,12 @@ package org.keycloak.common; +import org.jboss.logging.Logger; + import java.io.File; import java.io.FileInputStream; -import java.util.Arrays; -import java.util.Collections; +import java.io.IOException; import java.util.HashSet; -import java.util.List; import java.util.Properties; import java.util.Set; @@ -32,95 +32,101 @@ import java.util.Set; */ public class Profile { + private static final Logger logger = Logger.getLogger(Profile.class); + + public enum Type { + DEFAULT, + DISABLED_BY_DEFAULT, + PREVIEW, + EXPERIMENTAL + } + public enum Feature { - ACCOUNT2, - ADMIN_FINE_GRAINED_AUTHZ, - DOCKER, - IMPERSONATION, - OPENSHIFT_INTEGRATION, - SCRIPTS, - TOKEN_EXCHANGE + ACCOUNT2(Type.EXPERIMENTAL), + ACCOUNT_API(Type.PREVIEW), + ADMIN_FINE_GRAINED_AUTHZ(Type.PREVIEW), + DOCKER(Type.DISABLED_BY_DEFAULT), + IMPERSONATION(Type.DEFAULT), + OPENSHIFT_INTEGRATION(Type.DEFAULT), + SCRIPTS(Type.PREVIEW), + TOKEN_EXCHANGE(Type.PREVIEW); + + private Type type; + + Feature(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } } private enum ProductValue { - KEYCLOAK(), - RHSSO(Feature.ACCOUNT2); - - private List excluded; - - ProductValue(Feature... excluded) { - this.excluded = Arrays.asList(excluded); - } + KEYCLOAK, + RHSSO } private enum ProfileValue { - PRODUCT(Feature.ADMIN_FINE_GRAINED_AUTHZ, Feature.SCRIPTS, Feature.DOCKER, Feature.ACCOUNT2, Feature.TOKEN_EXCHANGE), - PREVIEW(Feature.ACCOUNT2), - COMMUNITY(Feature.DOCKER, Feature.ACCOUNT2); - - private List disabled; - - ProfileValue(Feature... disabled) { - this.disabled = Arrays.asList(disabled); - } + COMMUNITY, + PRODUCT, + PREVIEW } - private static final Profile CURRENT = new Profile(); + private static Profile CURRENT = new Profile(); private final ProductValue product; private final ProfileValue profile; private final Set disabledFeatures = new HashSet<>(); + private final Set previewFeatures = new HashSet<>(); + private final Set experimentalFeatures = new HashSet<>(); private Profile() { + Config config = new Config(); + product = "rh-sso".equals(Version.NAME) ? ProductValue.RHSSO : ProductValue.KEYCLOAK; + profile = ProfileValue.valueOf(config.getProfile().toUpperCase()); - try { - Properties props = new Properties(); + for (Feature f : Feature.values()) { + Boolean enabled = config.getConfig(f); - String jbossServerConfigDir = System.getProperty("jboss.server.config.dir"); - if (jbossServerConfigDir != null) { - File file = new File(jbossServerConfigDir, "profile.properties"); - if (file.isFile()) { - props.load(new FileInputStream(file)); - } - } - - if (System.getProperties().containsKey("keycloak.profile")) { - props.setProperty("profile", System.getProperty("keycloak.profile")); - } - - for (String k : System.getProperties().stringPropertyNames()) { - if (k.startsWith("keycloak.profile.feature.")) { - props.put(k.replace("keycloak.profile.feature.", "feature."), System.getProperty(k)); - } - } - - if (props.containsKey("profile")) { - profile = ProfileValue.valueOf(props.getProperty("profile").toUpperCase()); - } else { - profile = ProfileValue.valueOf(Version.DEFAULT_PROFILE.toUpperCase()); - } - - disabledFeatures.addAll(profile.disabled); - disabledFeatures.removeAll(product.excluded); - - for (String k : props.stringPropertyNames()) { - if (k.startsWith("feature.")) { - Feature f = Feature.valueOf(k.replace("feature.", "").toUpperCase()); - if (props.get(k).equals("enabled")) { - disabledFeatures.remove(f); - } else if (props.get(k).equals("disabled")) { + switch (f.getType()) { + case DEFAULT: + if (enabled != null && !enabled) { disabledFeatures.add(f); } - } + break; + case DISABLED_BY_DEFAULT: + if (enabled == null || !enabled) { + disabledFeatures.add(f); + } + break; + case PREVIEW: + previewFeatures.add(f); + if (enabled == null || !enabled) { + disabledFeatures.add(f); + } else { + logger.info("Preview feature enabled: " + f.name().toLowerCase()); + } + break; + case EXPERIMENTAL: + experimentalFeatures.add(f); + if (enabled == null || !enabled) { + disabledFeatures.add(f); + } else { + logger.warn("Experimental feature enabled: " + f.name().toLowerCase()); + } + break; } - } catch (Exception e) { - throw new RuntimeException(e); } } + public static void init() { + CURRENT = new Profile(); + } + public static String getName() { return CURRENT.profile.name().toLowerCase(); } @@ -129,11 +135,68 @@ public class Profile { return CURRENT.disabledFeatures; } + public static Set getPreviewFeatures() { + return CURRENT.previewFeatures; + } + + public static Set getExperimentalFeatures() { + return CURRENT.experimentalFeatures; + } + public static boolean isFeatureEnabled(Feature feature) { - if (CURRENT.product.excluded.contains(feature)) { - return false; - } return !CURRENT.disabledFeatures.contains(feature); } + private class Config { + + private Properties properties; + + public Config() { + properties = new Properties(); + + try { + String jbossServerConfigDir = System.getProperty("jboss.server.config.dir"); + if (jbossServerConfigDir != null) { + File file = new File(jbossServerConfigDir, "profile.properties"); + if (file.isFile()) { + properties.load(new FileInputStream(file)); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getProfile() { + String profile = System.getProperty("keycloak.profile"); + if (profile != null) { + return profile; + } + + profile = properties.getProperty("profile"); + if (profile != null) { + return profile; + } + + return Version.DEFAULT_PROFILE; + } + + public Boolean getConfig(Feature feature) { + String config = System.getProperty("keycloak.profile.feature." + feature.name().toLowerCase()); + if (config == null) { + config = properties.getProperty("feature." + feature.name().toLowerCase()); + } + + if (config == null) { + return null; + } else if (config.equals("enabled")) { + return Boolean.TRUE; + } else if (config.equals("disabled")) { + return Boolean.FALSE; + } else { + throw new RuntimeException("Invalid value for feature " + config); + } + } + } + } diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java new file mode 100644 index 0000000000..827e08e1e7 --- /dev/null +++ b/common/src/test/java/org/keycloak/common/ProfileTest.java @@ -0,0 +1,97 @@ +package org.keycloak.common; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Properties; +import java.util.Set; + +public class ProfileTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void checkDefaults() { + Assert.assertEquals("community", Profile.getName()); + assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE); + assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE); + assertEquals(Profile.getExperimentalFeatures(), Profile.Feature.ACCOUNT2); + } + + @Test + public void configWithSystemProperties() { + Assert.assertEquals("community", Profile.getName()); + Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER)); + Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION)); + + System.setProperty("keycloak.profile", "preview"); + System.setProperty("keycloak.profile.feature.docker", "enabled"); + System.setProperty("keycloak.profile.feature.impersonation", "disabled"); + + Profile.init(); + + Assert.assertEquals("preview", Profile.getName()); + Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER)); + Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION)); + + System.getProperties().remove("keycloak.profile"); + System.getProperties().remove("keycloak.profile.feature.docker"); + System.getProperties().remove("keycloak.profile.feature.impersonation"); + + Profile.init(); + } + + @Test + public void configWithPropertiesFile() throws IOException { + Assert.assertEquals("community", Profile.getName()); + Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.DOCKER)); + Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION)); + + File d = temporaryFolder.newFolder(); + File f = new File(d, "profile.properties"); + + Properties p = new Properties(); + p.setProperty("profile", "preview"); + p.setProperty("feature.docker", "enabled"); + p.setProperty("feature.impersonation", "disabled"); + PrintWriter pw = new PrintWriter(f); + p.list(pw); + pw.close(); + + System.setProperty("jboss.server.config.dir", d.getAbsolutePath()); + + Profile.init(); + + Assert.assertEquals("preview", Profile.getName()); + Assert.assertTrue(Profile.isFeatureEnabled(Profile.Feature.DOCKER)); + Assert.assertFalse(Profile.isFeatureEnabled(Profile.Feature.IMPERSONATION)); + + System.getProperties().remove("jboss.server.config.dir"); + + Profile.init(); + } + + public static void assertEquals(Set actual, Profile.Feature... expected) { + Profile.Feature[] a = actual.toArray(new Profile.Feature[actual.size()]); + Arrays.sort(a, new FeatureComparator()); + Arrays.sort(expected, new FeatureComparator()); + Assert.assertArrayEquals(a, expected); + } + + private static class FeatureComparator implements Comparator { + @Override + public int compare(Profile.Feature o1, Profile.Feature o2) { + return o1.name().compareTo(o2.name()); + } + } + +} diff --git a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java index c0cbea83f4..52481bcd66 100644 --- a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java @@ -21,6 +21,7 @@ import org.keycloak.common.Profile; import java.util.LinkedList; import java.util.List; +import java.util.Set; /** * @author Stian Thorgersen @@ -29,15 +30,16 @@ public class ProfileInfoRepresentation { private String name; private List disabledFeatures; + private List previewFeatures; + private List experimentalFeatures; public static ProfileInfoRepresentation create() { ProfileInfoRepresentation info = new ProfileInfoRepresentation(); info.name = Profile.getName(); - info.disabledFeatures = new LinkedList<>(); - for (Profile.Feature f : Profile.getDisabledFeatures()) { - info.disabledFeatures.add(f.name()); - } + info.disabledFeatures = names(Profile.getDisabledFeatures()); + info.previewFeatures = names(Profile.getPreviewFeatures()); + info.experimentalFeatures = names(Profile.getExperimentalFeatures()); return info; } @@ -50,4 +52,20 @@ public class ProfileInfoRepresentation { return disabledFeatures; } + public List getPreviewFeatures() { + return previewFeatures; + } + + public List getExperimentalFeatures() { + return experimentalFeatures; + } + + private static List names(Set featureSet) { + List l = new LinkedList(); + for (Profile.Feature f : featureSet) { + l.add(f.name()); + } + return l; + } + } diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml index 4afb80d502..695dc1a9ad 100755 --- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml +++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml @@ -27,6 +27,7 @@ + diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml index 4afb80d502..695dc1a9ad 100755 --- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml +++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml @@ -27,6 +27,7 @@ + diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml index 81578ae8d0..7e964233d8 100755 --- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml +++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml @@ -27,6 +27,7 @@ + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml index 7b024b19e7..ee73fde207 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml @@ -24,6 +24,7 @@ + diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml index 4afb80d502..695dc1a9ad 100755 --- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml +++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml @@ -27,6 +27,7 @@ + diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml index 4afb80d502..695dc1a9ad 100755 --- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml +++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml @@ -27,6 +27,7 @@ + diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java index 0a39dd40bf..72fe53826e 100755 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java @@ -206,7 +206,7 @@ public class AccountRestService { @Produces(MediaType.APPLICATION_JSON) @NoCache public Response sessions() { - checkAccount2Enabled(); + checkAccountApiEnabled(); List reps = new LinkedList<>(); List sessions = session.sessions().getUserSessions(realm, user); @@ -244,7 +244,7 @@ public class AccountRestService { @Produces(MediaType.APPLICATION_JSON) @NoCache public Response sessionsLogout(@QueryParam("current") boolean removeCurrent) { - checkAccount2Enabled(); + checkAccountApiEnabled(); UserSessionModel userSession = auth.getSession(); List userSessions = session.sessions().getUserSessions(realm, user); @@ -268,7 +268,7 @@ public class AccountRestService { @Produces(MediaType.APPLICATION_JSON) @NoCache public Response sessionLogout(@QueryParam("id") String id) { - checkAccount2Enabled(); + checkAccountApiEnabled(); UserSessionModel userSession = session.sessions().getUserSession(realm, id); if (userSession != null && userSession.getUser().equals(user)) { AuthenticationManager.backchannelLogout(session, userSession, true); @@ -278,7 +278,7 @@ public class AccountRestService { @Path("/credentials") public AccountCredentialResource credentials() { - checkAccount2Enabled(); + checkAccountApiEnabled(); return new AccountCredentialResource(session, event, user); } @@ -286,8 +286,8 @@ public class AccountRestService { // TODO Applications // TODO Logs - private static void checkAccount2Enabled() { - if (!Profile.isFeatureEnabled(Profile.Feature.ACCOUNT2)) { + private static void checkAccountApiEnabled() { + if (!Profile.isFeatureEnabled(Profile.Feature.ACCOUNT_API)) { throw new NotFoundException(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java index b24ec81d97..0cbda1bed1 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java @@ -44,7 +44,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.*; import org.keycloak.services.messages.Messages; -import static org.keycloak.common.Profile.Feature.ACCOUNT2; +import static org.keycloak.common.Profile.Feature.ACCOUNT_API; import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled; /** @@ -234,7 +234,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest { @Test public void testGetSessions() throws IOException { - assumeFeatureEnabled(ACCOUNT2); + assumeFeatureEnabled(ACCOUNT_API); List sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(tokenUtil.getToken()).asJson(new TypeReference>() {}); @@ -243,14 +243,14 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest { @Test public void testGetPasswordDetails() throws IOException { - assumeFeatureEnabled(ACCOUNT2); + assumeFeatureEnabled(ACCOUNT_API); getPasswordDetails(); } @Test public void testPostPasswordUpdate() throws IOException { - assumeFeatureEnabled(ACCOUNT2); + assumeFeatureEnabled(ACCOUNT_API); //Get the time of lastUpdate AccountCredentialResource.PasswordDetails initialDetails = getPasswordDetails(); @@ -275,7 +275,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest { @Test public void testPasswordConfirmation() throws IOException { - assumeFeatureEnabled(ACCOUNT2); + assumeFeatureEnabled(ACCOUNT_API); updatePassword("password", "Str0ng3rP4ssw0rd", "confirmationDoesNotMatch", 400); @@ -318,7 +318,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest { @Test public void testDeleteSession() throws IOException { - assumeFeatureEnabled(ACCOUNT2); + assumeFeatureEnabled(ACCOUNT_API); TokenUtil viewToken = new TokenUtil("view-account-access", "password"); String sessionId = oauth.doLogin("view-account-access", "password").getSessionState(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java index 9971da79b1..ec042b293c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java @@ -26,6 +26,7 @@ import org.junit.Rule; import org.junit.Test; import org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory; import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory; +import org.keycloak.common.Profile; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; @@ -64,6 +65,11 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest { public static final String EXECUTION_ID = "scriptAuth"; + @BeforeClass + public static void verifyEnvironment() { + ProfileAssume.assumeFeatureEnabled(Profile.Feature.SCRIPTS); + } + @Override public void configureTestRealm(RealmRepresentation testRealm) { diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 7876630046..4c1c9153f3 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1121,11 +1121,17 @@ include-representation.tooltip=Include JSON representation for create and update clear-admin-events.tooltip=Deletes all admin events in the database. server-version=Server Version server-profile=Server Profile -server-disabled=Server Disabled Features +server-disabled=Disabled Features +server-disabled.tooltip=Features that are not currently enabled. Some features are not enabled by default. This applies to all preview and experimental features. +server-preview=Preview Features +server-preview.tooltip=Preview features are not supported in production use and may be significantly changed or removed in the future. +server-experimental=Experimental Features +server-experimental.tooltip=Experimental features are experimental features that may not be fully function. Never use experimental features in production. info=Info providers=Providers server-time=Server Time server-uptime=Server Uptime +profile=Profile memory=Memory total-memory=Total Memory free-memory=Free Memory diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html b/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html index 4a4b296d90..6d2441971a 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html @@ -14,14 +14,6 @@ {{:: 'server-version' | translate}} {{serverInfo.systemInfo.version}} - - {{:: 'server-profile' | translate}} - {{serverInfo.profileInfo.name | capitalize}} - - - {{:: 'server-disabled' | translate}} - {{serverInfo.profileInfo.disabledFeatures.join(', ').toLowerCase() | capitalize}} - {{:: 'server-time' | translate}} {{serverInfo.systemInfo.serverTime}} @@ -32,6 +24,37 @@ + + {{:: 'profile' | translate}} + + + + + + + + + + + + + + + + + + +
{{:: 'server-profile' | translate}}{{serverInfo.profileInfo.name | capitalize}}
+ {{:: 'server-disabled' | translate}} + {{:: 'server-disabled.tooltip' | translate}} + {{serverInfo.profileInfo.disabledFeatures.sort().join(', ')}}
+ {{:: 'server-preview' | translate}} + {{:: 'server-preview.tooltip' | translate}} + {{serverInfo.profileInfo.previewFeatures.sort().join(', ')}}
+ {{:: 'server-experimental' | translate}} + {{:: 'server-experimental.tooltip' | translate}} + {{serverInfo.profileInfo.experimentalFeatures.sort().join(', ')}}
+
{{:: 'memory' | translate}}