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);
|
||||
|
||||
private final Type type;
|
||||
private String label;
|
||||
private final String label;
|
||||
|
||||
private Set<Feature> dependencies;
|
||||
Feature(String label, Type type) {
|
||||
|
@ -133,7 +133,7 @@ public class Profile {
|
|||
EXPERIMENTAL("Experimental"),
|
||||
DEPRECATED("Deprecated");
|
||||
|
||||
private String label;
|
||||
private final String label;
|
||||
|
||||
Type(String label) {
|
||||
this.label = label;
|
||||
|
@ -248,7 +248,7 @@ public class Profile {
|
|||
case DEFAULT:
|
||||
return true;
|
||||
case PREVIEW:
|
||||
return profile.equals(ProfileName.PREVIEW) ? true : false;
|
||||
return profile.equals(ProfileName.PREVIEW);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -268,12 +268,12 @@ public class Profile {
|
|||
}
|
||||
|
||||
private void logUnsupportedFeatures() {
|
||||
logUnsuportedFeatures(Feature.Type.PREVIEW, getPreviewFeatures(), Logger.Level.INFO);
|
||||
logUnsuportedFeatures(Feature.Type.EXPERIMENTAL, getExperimentalFeatures(), Logger.Level.WARN);
|
||||
logUnsuportedFeatures(Feature.Type.DEPRECATED, getDeprecatedFeatures(), Logger.Level.WARN);
|
||||
logUnsupportedFeatures(Feature.Type.PREVIEW, getPreviewFeatures(), Logger.Level.INFO);
|
||||
logUnsupportedFeatures(Feature.Type.EXPERIMENTAL, getExperimentalFeatures(), 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()
|
||||
.map(Feature::getType)
|
||||
.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 ProfileInfoRepresentation profileInfo;
|
||||
|
||||
private List<FeatureRepresentation> features;
|
||||
|
||||
private CryptoInfoRepresentation cryptoInfo;
|
||||
|
||||
private Map<String, List<ThemeInfoRepresentation>> themes;
|
||||
|
@ -77,6 +79,14 @@ public class ServerInfoRepresentation {
|
|||
this.profileInfo = profileInfo;
|
||||
}
|
||||
|
||||
public List<FeatureRepresentation> getFeatures() {
|
||||
return features;
|
||||
}
|
||||
|
||||
public void setFeatures(List<FeatureRepresentation> features) {
|
||||
this.features = features;
|
||||
}
|
||||
|
||||
public CryptoInfoRepresentation getCryptoInfo() {
|
||||
return cryptoInfo;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { union, filter } from "lodash-es";
|
||||
import {
|
||||
Brand,
|
||||
Card,
|
||||
|
@ -26,13 +25,16 @@ import {
|
|||
Title,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import FeatureRepresentation, {
|
||||
FeatureType,
|
||||
} from "@keycloak/keycloak-admin-client/lib/defs/featureRepresentation";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { toUpperCase } from "../util";
|
||||
import { HelpItem } from "ui-shared";
|
||||
import environment from "../environment";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import useLocaleSort from "../utils/useLocaleSort";
|
||||
import useLocaleSort, { mapByKey } from "../utils/useLocaleSort";
|
||||
import {
|
||||
RoutableTabs,
|
||||
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 { t } = useTranslation();
|
||||
const { realm } = useRealm();
|
||||
const serverInfo = useServerInfo();
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const isDeprecatedFeature = (feature: string) =>
|
||||
disabledFeatures.includes(feature);
|
||||
|
||||
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 sortedFeatures = useMemo(
|
||||
() => localeSort(serverInfo.features ?? [], mapByKey("name")),
|
||||
[serverInfo.features],
|
||||
);
|
||||
|
||||
const disabledFeatures = useMemo(
|
||||
() =>
|
||||
localeSort(
|
||||
serverInfo.profileInfo?.disabledFeatures ?? [],
|
||||
(item) => item,
|
||||
),
|
||||
[serverInfo.profileInfo],
|
||||
() => sortedFeatures.filter((f) => !f.enabled) || [],
|
||||
[serverInfo.features],
|
||||
);
|
||||
|
||||
const enabledFeatures = useMemo(
|
||||
() =>
|
||||
localeSort(
|
||||
filter(
|
||||
union(
|
||||
serverInfo.profileInfo?.experimentalFeatures,
|
||||
serverInfo.profileInfo?.previewFeatures,
|
||||
),
|
||||
(feature) => {
|
||||
return !isDeprecatedFeature(feature);
|
||||
},
|
||||
),
|
||||
(item) => item,
|
||||
),
|
||||
[serverInfo.profileInfo],
|
||||
() => sortedFeatures.filter((f) => f.enabled) || [],
|
||||
[serverInfo.features],
|
||||
);
|
||||
|
||||
const useTab = (tab: DashboardTab) =>
|
||||
|
@ -215,17 +216,10 @@ const Dashboard = () => {
|
|||
<DescriptionListDescription>
|
||||
<List variant={ListVariant.inline}>
|
||||
{enabledFeatures.map((feature) => (
|
||||
<ListItem key={feature} className="pf-u-mb-sm">
|
||||
{feature}{" "}
|
||||
{isExperimentalFeature(feature) ? (
|
||||
<Label color="orange">
|
||||
{t("experimental")}
|
||||
</Label>
|
||||
) : null}
|
||||
{isPreviewFeature(feature) ? (
|
||||
<Label color="blue">{t("preview")}</Label>
|
||||
) : null}
|
||||
</ListItem>
|
||||
<FeatureItem
|
||||
key={feature.name}
|
||||
feature={feature}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</DescriptionListDescription>
|
||||
|
@ -241,22 +235,10 @@ const Dashboard = () => {
|
|||
<DescriptionListDescription>
|
||||
<List variant={ListVariant.inline}>
|
||||
{disabledFeatures.map((feature) => (
|
||||
<ListItem key={feature} className="pf-u-mb-sm">
|
||||
{feature}{" "}
|
||||
{isExperimentalFeature(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>
|
||||
<FeatureItem
|
||||
key={feature.name}
|
||||
feature={feature}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</DescriptionListDescription>
|
||||
|
|
|
@ -10,10 +10,15 @@ export enum Feature {
|
|||
}
|
||||
|
||||
export default function useIsFeatureEnabled() {
|
||||
const { profileInfo } = useServerInfo();
|
||||
const disabledFilters = profileInfo?.disabledFeatures ?? [];
|
||||
const { features } = useServerInfo();
|
||||
|
||||
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 { ConfigPropertyRepresentation } from "./configPropertyRepresentation.js";
|
||||
import FeatureRepresentation from "./featureRepresentation.js";
|
||||
import type PasswordPolicyTypeRepresentation from "./passwordPolicyTypeRepresentation.js";
|
||||
import type ProfileInfoRepresentation from "./profileInfoRepresentation.js";
|
||||
import type ProtocolMapperRepresentation from "./protocolMapperRepresentation.js";
|
||||
|
@ -12,6 +13,7 @@ export interface ServerInfoRepresentation {
|
|||
systemInfo?: SystemInfoRepresentation;
|
||||
memoryInfo?: MemoryInfoRepresentation;
|
||||
profileInfo?: ProfileInfoRepresentation;
|
||||
features?: FeatureRepresentation[];
|
||||
cryptoInfo?: CryptoInfoRepresentation;
|
||||
themes?: { [index: string]: ThemeInfoRepresentation[] };
|
||||
socialProviders?: { [index: string]: string }[];
|
||||
|
|
|
@ -49,6 +49,7 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
|||
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
||||
import org.keycloak.representations.info.ClientInstallationRepresentation;
|
||||
import org.keycloak.representations.info.CryptoInfoRepresentation;
|
||||
import org.keycloak.representations.info.FeatureRepresentation;
|
||||
import org.keycloak.representations.info.MemoryInfoRepresentation;
|
||||
import org.keycloak.representations.info.ProfileInfoRepresentation;
|
||||
import org.keycloak.representations.info.ProviderRepresentation;
|
||||
|
@ -104,6 +105,7 @@ public class ServerInfoAdminResource {
|
|||
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp()));
|
||||
info.setMemoryInfo(MemoryInfoRepresentation.create());
|
||||
info.setProfileInfo(ProfileInfoRepresentation.create());
|
||||
info.setFeatures(FeatureRepresentation.create());
|
||||
|
||||
// True - asymmetric algorithms, false - symmetric algorithms
|
||||
Map<Boolean, List<String>> algorithms = session.getAllProviders(ClientSignatureVerifierProvider.class).stream()
|
||||
|
|
Loading…
Reference in a new issue