parent
ebc9faea79
commit
0789d3c1cc
8 changed files with 174 additions and 70 deletions
|
@ -95,7 +95,7 @@ public class Profile {
|
||||||
LINKEDIN_OAUTH("LinkedIn Social Identity Provider based on OAuth", Type.DEPRECATED);
|
LINKEDIN_OAUTH("LinkedIn Social Identity Provider based on OAuth", Type.DEPRECATED);
|
||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
private String label;
|
private final String label;
|
||||||
|
|
||||||
private Set<Feature> dependencies;
|
private Set<Feature> dependencies;
|
||||||
Feature(String label, Type type) {
|
Feature(String label, Type type) {
|
||||||
|
@ -133,7 +133,7 @@ public class Profile {
|
||||||
EXPERIMENTAL("Experimental"),
|
EXPERIMENTAL("Experimental"),
|
||||||
DEPRECATED("Deprecated");
|
DEPRECATED("Deprecated");
|
||||||
|
|
||||||
private String label;
|
private final String label;
|
||||||
|
|
||||||
Type(String label) {
|
Type(String label) {
|
||||||
this.label = label;
|
this.label = label;
|
||||||
|
@ -248,7 +248,7 @@ public class Profile {
|
||||||
case DEFAULT:
|
case DEFAULT:
|
||||||
return true;
|
return true;
|
||||||
case PREVIEW:
|
case PREVIEW:
|
||||||
return profile.equals(ProfileName.PREVIEW) ? true : false;
|
return profile.equals(ProfileName.PREVIEW);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -268,12 +268,12 @@ public class Profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logUnsupportedFeatures() {
|
private void logUnsupportedFeatures() {
|
||||||
logUnsuportedFeatures(Feature.Type.PREVIEW, getPreviewFeatures(), Logger.Level.INFO);
|
logUnsupportedFeatures(Feature.Type.PREVIEW, getPreviewFeatures(), Logger.Level.INFO);
|
||||||
logUnsuportedFeatures(Feature.Type.EXPERIMENTAL, getExperimentalFeatures(), Logger.Level.WARN);
|
logUnsupportedFeatures(Feature.Type.EXPERIMENTAL, getExperimentalFeatures(), Logger.Level.WARN);
|
||||||
logUnsuportedFeatures(Feature.Type.DEPRECATED, getDeprecatedFeatures(), Logger.Level.WARN);
|
logUnsupportedFeatures(Feature.Type.DEPRECATED, getDeprecatedFeatures(), Logger.Level.WARN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logUnsuportedFeatures(Feature.Type type, Set<Feature> checkedFeatures, Logger.Level level) {
|
private void logUnsupportedFeatures(Feature.Type type, Set<Feature> checkedFeatures, Logger.Level level) {
|
||||||
Set<Feature.Type> checkedFeatureTypes = checkedFeatures.stream()
|
Set<Feature.Type> checkedFeatureTypes = checkedFeatures.stream()
|
||||||
.map(Feature::getType)
|
.map(Feature::getType)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.keycloak.representations.info;
|
||||||
|
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class FeatureRepresentation {
|
||||||
|
private String name;
|
||||||
|
private String label;
|
||||||
|
private Type type;
|
||||||
|
private boolean isEnabled;
|
||||||
|
private Set<String> dependencies;
|
||||||
|
|
||||||
|
public FeatureRepresentation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeatureRepresentation(Profile.Feature feature, boolean isEnabled) {
|
||||||
|
this.name = feature.name();
|
||||||
|
this.label = feature.getLabel();
|
||||||
|
this.type = Type.valueOf(feature.getType().name());
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
this.dependencies = feature.getDependencies() != null ?
|
||||||
|
feature.getDependencies().stream().map(Enum::name).collect(Collectors.toSet()) : Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<FeatureRepresentation> create() {
|
||||||
|
List<FeatureRepresentation> featureRepresentationList = new ArrayList<>();
|
||||||
|
Profile profile = Profile.getInstance();
|
||||||
|
final Map<Profile.Feature, Boolean> features = profile.getFeatures();
|
||||||
|
features.forEach((f, enabled) -> featureRepresentationList.add(new FeatureRepresentation(f, enabled)));
|
||||||
|
return featureRepresentationList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLabel(String label) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(Type type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
isEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getDependencies() {
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDependencies(Set<String> dependencies) {
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
DEFAULT,
|
||||||
|
DISABLED_BY_DEFAULT,
|
||||||
|
PREVIEW,
|
||||||
|
PREVIEW_DISABLED_BY_DEFAULT,
|
||||||
|
EXPERIMENTAL,
|
||||||
|
DEPRECATED;
|
||||||
|
}
|
|
@ -34,6 +34,8 @@ public class ServerInfoRepresentation {
|
||||||
private MemoryInfoRepresentation memoryInfo;
|
private MemoryInfoRepresentation memoryInfo;
|
||||||
private ProfileInfoRepresentation profileInfo;
|
private ProfileInfoRepresentation profileInfo;
|
||||||
|
|
||||||
|
private List<FeatureRepresentation> features;
|
||||||
|
|
||||||
private CryptoInfoRepresentation cryptoInfo;
|
private CryptoInfoRepresentation cryptoInfo;
|
||||||
|
|
||||||
private Map<String, List<ThemeInfoRepresentation>> themes;
|
private Map<String, List<ThemeInfoRepresentation>> themes;
|
||||||
|
@ -77,6 +79,14 @@ public class ServerInfoRepresentation {
|
||||||
this.profileInfo = profileInfo;
|
this.profileInfo = profileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<FeatureRepresentation> getFeatures() {
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFeatures(List<FeatureRepresentation> features) {
|
||||||
|
this.features = features;
|
||||||
|
}
|
||||||
|
|
||||||
public CryptoInfoRepresentation getCryptoInfo() {
|
public CryptoInfoRepresentation getCryptoInfo() {
|
||||||
return cryptoInfo;
|
return cryptoInfo;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { union, filter } from "lodash-es";
|
|
||||||
import {
|
import {
|
||||||
Brand,
|
Brand,
|
||||||
Card,
|
Card,
|
||||||
|
@ -26,13 +25,16 @@ import {
|
||||||
Title,
|
Title,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import FeatureRepresentation, {
|
||||||
|
FeatureType,
|
||||||
|
} from "@keycloak/keycloak-admin-client/lib/defs/featureRepresentation";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||||
import { toUpperCase } from "../util";
|
import { toUpperCase } from "../util";
|
||||||
import { HelpItem } from "ui-shared";
|
import { HelpItem } from "ui-shared";
|
||||||
import environment from "../environment";
|
import environment from "../environment";
|
||||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||||
import useLocaleSort from "../utils/useLocaleSort";
|
import useLocaleSort, { mapByKey } from "../utils/useLocaleSort";
|
||||||
import {
|
import {
|
||||||
RoutableTabs,
|
RoutableTabs,
|
||||||
useRoutableTab,
|
useRoutableTab,
|
||||||
|
@ -67,48 +69,47 @@ const EmptyDashboard = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type FeatureItemProps = {
|
||||||
|
feature: FeatureRepresentation;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FeatureItem = ({ feature }: FeatureItemProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<ListItem className="pf-u-mb-sm">
|
||||||
|
{feature.name}
|
||||||
|
{feature.type === FeatureType.Experimental && (
|
||||||
|
<Label color="orange">{t("experimental")}</Label>
|
||||||
|
)}
|
||||||
|
{feature.type === FeatureType.Preview && (
|
||||||
|
<Label color="blue">{t("preview")}</Label>
|
||||||
|
)}
|
||||||
|
{feature.type === FeatureType.Default && (
|
||||||
|
<Label color="green">{t("supported")}</Label>
|
||||||
|
)}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const serverInfo = useServerInfo();
|
const serverInfo = useServerInfo();
|
||||||
const localeSort = useLocaleSort();
|
const localeSort = useLocaleSort();
|
||||||
|
|
||||||
const isDeprecatedFeature = (feature: string) =>
|
const sortedFeatures = useMemo(
|
||||||
disabledFeatures.includes(feature);
|
() => localeSort(serverInfo.features ?? [], mapByKey("name")),
|
||||||
|
[serverInfo.features],
|
||||||
const isExperimentalFeature = (feature: string) =>
|
);
|
||||||
serverInfo.profileInfo?.experimentalFeatures?.includes(feature);
|
|
||||||
|
|
||||||
const isPreviewFeature = (feature: string) =>
|
|
||||||
serverInfo.profileInfo?.previewFeatures?.includes(feature);
|
|
||||||
|
|
||||||
const isSupportedFeature = (feature: string) =>
|
|
||||||
!isExperimentalFeature(feature) && !isPreviewFeature(feature);
|
|
||||||
|
|
||||||
const disabledFeatures = useMemo(
|
const disabledFeatures = useMemo(
|
||||||
() =>
|
() => sortedFeatures.filter((f) => !f.enabled) || [],
|
||||||
localeSort(
|
[serverInfo.features],
|
||||||
serverInfo.profileInfo?.disabledFeatures ?? [],
|
|
||||||
(item) => item,
|
|
||||||
),
|
|
||||||
[serverInfo.profileInfo],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const enabledFeatures = useMemo(
|
const enabledFeatures = useMemo(
|
||||||
() =>
|
() => sortedFeatures.filter((f) => f.enabled) || [],
|
||||||
localeSort(
|
[serverInfo.features],
|
||||||
filter(
|
|
||||||
union(
|
|
||||||
serverInfo.profileInfo?.experimentalFeatures,
|
|
||||||
serverInfo.profileInfo?.previewFeatures,
|
|
||||||
),
|
|
||||||
(feature) => {
|
|
||||||
return !isDeprecatedFeature(feature);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(item) => item,
|
|
||||||
),
|
|
||||||
[serverInfo.profileInfo],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const useTab = (tab: DashboardTab) =>
|
const useTab = (tab: DashboardTab) =>
|
||||||
|
@ -215,17 +216,10 @@ const Dashboard = () => {
|
||||||
<DescriptionListDescription>
|
<DescriptionListDescription>
|
||||||
<List variant={ListVariant.inline}>
|
<List variant={ListVariant.inline}>
|
||||||
{enabledFeatures.map((feature) => (
|
{enabledFeatures.map((feature) => (
|
||||||
<ListItem key={feature} className="pf-u-mb-sm">
|
<FeatureItem
|
||||||
{feature}{" "}
|
key={feature.name}
|
||||||
{isExperimentalFeature(feature) ? (
|
feature={feature}
|
||||||
<Label color="orange">
|
/>
|
||||||
{t("experimental")}
|
|
||||||
</Label>
|
|
||||||
) : null}
|
|
||||||
{isPreviewFeature(feature) ? (
|
|
||||||
<Label color="blue">{t("preview")}</Label>
|
|
||||||
) : null}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</DescriptionListDescription>
|
</DescriptionListDescription>
|
||||||
|
@ -241,22 +235,10 @@ const Dashboard = () => {
|
||||||
<DescriptionListDescription>
|
<DescriptionListDescription>
|
||||||
<List variant={ListVariant.inline}>
|
<List variant={ListVariant.inline}>
|
||||||
{disabledFeatures.map((feature) => (
|
{disabledFeatures.map((feature) => (
|
||||||
<ListItem key={feature} className="pf-u-mb-sm">
|
<FeatureItem
|
||||||
{feature}{" "}
|
key={feature.name}
|
||||||
{isExperimentalFeature(feature) ? (
|
feature={feature}
|
||||||
<Label color="orange">
|
/>
|
||||||
{t("experimental")}
|
|
||||||
</Label>
|
|
||||||
) : null}
|
|
||||||
{isPreviewFeature(feature) ? (
|
|
||||||
<Label color="blue">{t("preview")}</Label>
|
|
||||||
) : null}
|
|
||||||
{isSupportedFeature(feature) ? (
|
|
||||||
<Label color="green">
|
|
||||||
{t("supported")}
|
|
||||||
</Label>
|
|
||||||
) : null}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</DescriptionListDescription>
|
</DescriptionListDescription>
|
||||||
|
|
|
@ -10,10 +10,15 @@ export enum Feature {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useIsFeatureEnabled() {
|
export default function useIsFeatureEnabled() {
|
||||||
const { profileInfo } = useServerInfo();
|
const { features } = useServerInfo();
|
||||||
const disabledFilters = profileInfo?.disabledFeatures ?? [];
|
|
||||||
|
|
||||||
return function isFeatureEnabled(feature: Feature) {
|
return function isFeatureEnabled(feature: Feature) {
|
||||||
return !disabledFilters.includes(feature);
|
if (!features) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return features
|
||||||
|
.filter((f) => f.enabled)
|
||||||
|
.map((f) => f.name)
|
||||||
|
.includes(feature);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
export default interface FeatureRepresentation {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
type: FeatureType;
|
||||||
|
enabled: boolean;
|
||||||
|
dependencies: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FeatureType {
|
||||||
|
Default = "DEFAULT",
|
||||||
|
DisabledByDefault = "DISABLED_BY_DEFAULT",
|
||||||
|
Preview = "PREVIEW",
|
||||||
|
PreviewDisabledByDefault = "PREVIEW_DISABLED_BY_DEFAULT",
|
||||||
|
Experimental = "EXPERIMENTAL",
|
||||||
|
Deprecated = "DEPRECATED",
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import type ComponentTypeRepresentation from "./componentTypeRepresentation.js";
|
import type ComponentTypeRepresentation from "./componentTypeRepresentation.js";
|
||||||
import type { ConfigPropertyRepresentation } from "./configPropertyRepresentation.js";
|
import type { ConfigPropertyRepresentation } from "./configPropertyRepresentation.js";
|
||||||
|
import FeatureRepresentation from "./featureRepresentation.js";
|
||||||
import type PasswordPolicyTypeRepresentation from "./passwordPolicyTypeRepresentation.js";
|
import type PasswordPolicyTypeRepresentation from "./passwordPolicyTypeRepresentation.js";
|
||||||
import type ProfileInfoRepresentation from "./profileInfoRepresentation.js";
|
import type ProfileInfoRepresentation from "./profileInfoRepresentation.js";
|
||||||
import type ProtocolMapperRepresentation from "./protocolMapperRepresentation.js";
|
import type ProtocolMapperRepresentation from "./protocolMapperRepresentation.js";
|
||||||
|
@ -12,6 +13,7 @@ export interface ServerInfoRepresentation {
|
||||||
systemInfo?: SystemInfoRepresentation;
|
systemInfo?: SystemInfoRepresentation;
|
||||||
memoryInfo?: MemoryInfoRepresentation;
|
memoryInfo?: MemoryInfoRepresentation;
|
||||||
profileInfo?: ProfileInfoRepresentation;
|
profileInfo?: ProfileInfoRepresentation;
|
||||||
|
features?: FeatureRepresentation[];
|
||||||
cryptoInfo?: CryptoInfoRepresentation;
|
cryptoInfo?: CryptoInfoRepresentation;
|
||||||
themes?: { [index: string]: ThemeInfoRepresentation[] };
|
themes?: { [index: string]: ThemeInfoRepresentation[] };
|
||||||
socialProviders?: { [index: string]: string }[];
|
socialProviders?: { [index: string]: string }[];
|
||||||
|
|
|
@ -49,6 +49,7 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
||||||
import org.keycloak.representations.info.ClientInstallationRepresentation;
|
import org.keycloak.representations.info.ClientInstallationRepresentation;
|
||||||
import org.keycloak.representations.info.CryptoInfoRepresentation;
|
import org.keycloak.representations.info.CryptoInfoRepresentation;
|
||||||
|
import org.keycloak.representations.info.FeatureRepresentation;
|
||||||
import org.keycloak.representations.info.MemoryInfoRepresentation;
|
import org.keycloak.representations.info.MemoryInfoRepresentation;
|
||||||
import org.keycloak.representations.info.ProfileInfoRepresentation;
|
import org.keycloak.representations.info.ProfileInfoRepresentation;
|
||||||
import org.keycloak.representations.info.ProviderRepresentation;
|
import org.keycloak.representations.info.ProviderRepresentation;
|
||||||
|
@ -104,6 +105,7 @@ public class ServerInfoAdminResource {
|
||||||
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp()));
|
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp()));
|
||||||
info.setMemoryInfo(MemoryInfoRepresentation.create());
|
info.setMemoryInfo(MemoryInfoRepresentation.create());
|
||||||
info.setProfileInfo(ProfileInfoRepresentation.create());
|
info.setProfileInfo(ProfileInfoRepresentation.create());
|
||||||
|
info.setFeatures(FeatureRepresentation.create());
|
||||||
|
|
||||||
// True - asymmetric algorithms, false - symmetric algorithms
|
// True - asymmetric algorithms, false - symmetric algorithms
|
||||||
Map<Boolean, List<String>> algorithms = session.getAllProviders(ClientSignatureVerifierProvider.class).stream()
|
Map<Boolean, List<String>> algorithms = session.getAllProviders(ClientSignatureVerifierProvider.class).stream()
|
||||||
|
|
Loading…
Reference in a new issue