Show display name in realm selector (#27259)
Solves #17735 Signed-off-by: Oliver Cremerius <antikalk@users.noreply.github.com>
This commit is contained in:
parent
516d86cda6
commit
bf89d53134
9 changed files with 144 additions and 70 deletions
|
@ -25,9 +25,9 @@ import { AuthWall } from "./root/AuthWall";
|
||||||
const AppContexts = ({ children }: PropsWithChildren) => (
|
const AppContexts = ({ children }: PropsWithChildren) => (
|
||||||
<ErrorBoundaryProvider>
|
<ErrorBoundaryProvider>
|
||||||
<ServerInfoProvider>
|
<ServerInfoProvider>
|
||||||
<RealmsProvider>
|
<RealmContextProvider>
|
||||||
<RealmContextProvider>
|
<WhoAmIContextProvider>
|
||||||
<WhoAmIContextProvider>
|
<RealmsProvider>
|
||||||
<RecentRealmsProvider>
|
<RecentRealmsProvider>
|
||||||
<AccessContextProvider>
|
<AccessContextProvider>
|
||||||
<Help>
|
<Help>
|
||||||
|
@ -37,9 +37,9 @@ const AppContexts = ({ children }: PropsWithChildren) => (
|
||||||
</Help>
|
</Help>
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
</RecentRealmsProvider>
|
</RecentRealmsProvider>
|
||||||
</WhoAmIContextProvider>
|
</RealmsProvider>
|
||||||
</RealmContextProvider>
|
</WhoAmIContextProvider>
|
||||||
</RealmsProvider>
|
</RealmContextProvider>
|
||||||
</ServerInfoProvider>
|
</ServerInfoProvider>
|
||||||
</ErrorBoundaryProvider>
|
</ErrorBoundaryProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,11 +11,14 @@ import {
|
||||||
Spinner,
|
Spinner,
|
||||||
Split,
|
Split,
|
||||||
SplitItem,
|
SplitItem,
|
||||||
|
Stack,
|
||||||
|
StackItem,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { CheckIcon } from "@patternfly/react-icons";
|
import { CheckIcon } from "@patternfly/react-icons";
|
||||||
import { Fragment, useState, useMemo } from "react";
|
import { Fragment, useState, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, To, useHref } from "react-router-dom";
|
import { Link, To, useHref } from "react-router-dom";
|
||||||
|
import { label } from "ui-shared";
|
||||||
|
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { useRealms } from "../../context/RealmsContext";
|
import { useRealms } from "../../context/RealmsContext";
|
||||||
|
@ -47,16 +50,33 @@ const AddRealm = ({ onClick }: AddRealmProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
type RealmTextProps = {
|
type RealmTextProps = {
|
||||||
value: string;
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
showIsRecent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RealmText = ({ value }: RealmTextProps) => {
|
const RealmText = ({ name, displayName, showIsRecent }: RealmTextProps) => {
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Split className="keycloak__realm_selector__list-item-split">
|
<Split className="keycloak__realm_selector__list-item-split">
|
||||||
<SplitItem isFilled>{value}</SplitItem>
|
<SplitItem isFilled>
|
||||||
<SplitItem>{value === realm && <CheckIcon />}</SplitItem>
|
<Stack>
|
||||||
|
{displayName ? (
|
||||||
|
<StackItem className="pf-u-font-weight-bold" isFilled>
|
||||||
|
{label(t, displayName)}
|
||||||
|
</StackItem>
|
||||||
|
) : null}
|
||||||
|
<StackItem isFilled>{name}</StackItem>
|
||||||
|
</Stack>
|
||||||
|
</SplitItem>
|
||||||
|
<SplitItem>{name === realm && <CheckIcon />}</SplitItem>
|
||||||
|
{showIsRecent ? (
|
||||||
|
<SplitItem>
|
||||||
|
<Label>{t("recent")}</Label>
|
||||||
|
</SplitItem>
|
||||||
|
) : null}
|
||||||
</Split>
|
</Split>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -80,7 +100,7 @@ const ContextSelectorItemLink = ({
|
||||||
|
|
||||||
export const RealmSelector = () => {
|
export const RealmSelector = () => {
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const { realms, refresh } = useRealms();
|
const { realms } = useRealms();
|
||||||
const { whoAmI } = useWhoAmI();
|
const { whoAmI } = useWhoAmI();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
@ -89,35 +109,50 @@ export const RealmSelector = () => {
|
||||||
|
|
||||||
const all = useMemo(
|
const all = useMemo(
|
||||||
() =>
|
() =>
|
||||||
recentRealms
|
realms
|
||||||
.filter((r) => r !== realm)
|
.filter((r) => r.name !== realm)
|
||||||
.map((name) => {
|
.map((realm) => {
|
||||||
return { name, used: true };
|
const used = recentRealms.some((name) => name === realm.name);
|
||||||
|
return { realm, used };
|
||||||
})
|
})
|
||||||
.concat(
|
.sort((r1, r2) => {
|
||||||
realms
|
if (r1.used == r2.used) return 0;
|
||||||
.filter((name) => !recentRealms.includes(name) || name === realm)
|
if (r1.used) return -1;
|
||||||
.map((name) => ({ name, used: false })),
|
if (r2.used) return 1;
|
||||||
),
|
return 0;
|
||||||
|
}),
|
||||||
[recentRealms, realm, realms],
|
[recentRealms, realm, realms],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredItems = useMemo(
|
const filteredItems = useMemo(() => {
|
||||||
() =>
|
const normalizedSearch = search.trim().toLowerCase();
|
||||||
search.trim() === ""
|
|
||||||
? all
|
if (normalizedSearch.length === 0) {
|
||||||
: all.filter((r) =>
|
return all;
|
||||||
r.name.toLowerCase().includes(search.toLowerCase()),
|
}
|
||||||
),
|
|
||||||
[search, all],
|
return search.trim() === ""
|
||||||
|
? all
|
||||||
|
: all.filter(
|
||||||
|
(r) =>
|
||||||
|
r.realm.name.toLowerCase().includes(normalizedSearch) ||
|
||||||
|
label(t, r.realm.displayName)
|
||||||
|
?.toLowerCase()
|
||||||
|
.includes(normalizedSearch),
|
||||||
|
);
|
||||||
|
}, [search, all]);
|
||||||
|
|
||||||
|
const realmDisplayName = useMemo(
|
||||||
|
() => realms.find((r) => r.name === realm)?.displayName,
|
||||||
|
[realm, realms],
|
||||||
);
|
);
|
||||||
|
|
||||||
return realms.length > 5 ? (
|
return realms.length > 5 ? (
|
||||||
<ContextSelector
|
<ContextSelector
|
||||||
data-testid="realmSelector"
|
data-testid="realmSelector"
|
||||||
toggleText={realm}
|
toggleText={label(t, realmDisplayName, realm)}
|
||||||
isOpen={open}
|
isOpen={open}
|
||||||
screenReaderLabel={realm}
|
screenReaderLabel={label(t, realmDisplayName, realm)}
|
||||||
onToggle={() => setOpen(!open)}
|
onToggle={() => setOpen(!open)}
|
||||||
searchInputValue={search}
|
searchInputValue={search}
|
||||||
onSearchInputChange={(value) => setSearch(value)}
|
onSearchInputChange={(value) => setSearch(value)}
|
||||||
|
@ -132,12 +167,11 @@ export const RealmSelector = () => {
|
||||||
>
|
>
|
||||||
{filteredItems.map((item) => (
|
{filteredItems.map((item) => (
|
||||||
<ContextSelectorItemLink
|
<ContextSelectorItemLink
|
||||||
key={item.name}
|
key={item.realm.name}
|
||||||
to={toDashboard({ realm: item.name })}
|
to={toDashboard({ realm: item.realm.name })}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
<RealmText value={item.name} />{" "}
|
<RealmText {...item.realm} showIsRecent={item.used} />{" "}
|
||||||
{item.used && <Label>{t("recent")}</Label>}
|
|
||||||
</ContextSelectorItemLink>
|
</ContextSelectorItemLink>
|
||||||
))}
|
))}
|
||||||
</ContextSelector>
|
</ContextSelector>
|
||||||
|
@ -151,24 +185,23 @@ export const RealmSelector = () => {
|
||||||
<DropdownToggle
|
<DropdownToggle
|
||||||
data-testid="realmSelectorToggle"
|
data-testid="realmSelectorToggle"
|
||||||
onToggle={() => {
|
onToggle={() => {
|
||||||
if (realms.length === 0) refresh();
|
|
||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
}}
|
}}
|
||||||
className="keycloak__realm_selector_dropdown__toggle"
|
className="keycloak__realm_selector_dropdown__toggle"
|
||||||
>
|
>
|
||||||
{realm}
|
{label(t, realmDisplayName, realm)}
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
}
|
}
|
||||||
dropdownItems={(realms.length !== 0
|
dropdownItems={(realms.length !== 0
|
||||||
? realms.map((name) => (
|
? realms.map((realm) => (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key={name}
|
key={realm.name}
|
||||||
component={
|
component={
|
||||||
<Link
|
<Link
|
||||||
to={toDashboard({ realm: name })}
|
to={toDashboard({ realm: realm.name })}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
<RealmText value={name} />
|
<RealmText {...realm} />
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,40 +1,47 @@
|
||||||
import { NetworkError } from "@keycloak/keycloak-admin-client";
|
import { NetworkError } from "@keycloak/keycloak-admin-client";
|
||||||
import { PropsWithChildren, useCallback, useMemo, useState } from "react";
|
import { PropsWithChildren, useCallback, useMemo, useState } from "react";
|
||||||
import { createNamedContext, useRequiredContext } from "ui-shared";
|
import { createNamedContext, useRequiredContext, label } from "ui-shared";
|
||||||
|
|
||||||
import { keycloak } from "../keycloak";
|
import { keycloak } from "../keycloak";
|
||||||
import { useFetch } from "../utils/useFetch";
|
import { useFetch } from "../utils/useFetch";
|
||||||
import { fetchAdminUI } from "./auth/admin-ui-endpoint";
|
import { fetchAdminUI } from "./auth/admin-ui-endpoint";
|
||||||
|
import useLocaleSort from "../utils/useLocaleSort";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type RealmsContextProps = {
|
type RealmsContextProps = {
|
||||||
/** A list of all the realms. */
|
/** A list of all the realms. */
|
||||||
realms: string[];
|
realms: RealmNameRepresentation[];
|
||||||
/** Refreshes the realms with the latest information. */
|
/** Refreshes the realms with the latest information. */
|
||||||
refresh: () => Promise<void>;
|
refresh: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface RealmNameRepresentation {
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const RealmsContext = createNamedContext<RealmsContextProps | undefined>(
|
export const RealmsContext = createNamedContext<RealmsContextProps | undefined>(
|
||||||
"RealmsContext",
|
"RealmsContext",
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RealmsProvider = ({ children }: PropsWithChildren) => {
|
export const RealmsProvider = ({ children }: PropsWithChildren) => {
|
||||||
const [realms, setRealms] = useState<string[]>([]);
|
const [realms, setRealms] = useState<RealmNameRepresentation[]>([]);
|
||||||
const [refreshCount, setRefreshCount] = useState(0);
|
const [refreshCount, setRefreshCount] = useState(0);
|
||||||
|
const localeSort = useLocaleSort();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
function updateRealms(realms: string[]) {
|
function updateRealms(realms: RealmNameRepresentation[]) {
|
||||||
setRealms(realms.sort());
|
setRealms(localeSort(realms, (r) => label(t, r.displayName, r.name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
async () => {
|
||||||
// We don't want to fetch until the user has requested it, so let's ignore the initial mount.
|
|
||||||
if (refreshCount === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await fetchAdminUI<string[]>("ui-ext/realms/names", {});
|
return await fetchAdminUI<RealmNameRepresentation[]>(
|
||||||
|
"ui-ext/realms/names",
|
||||||
|
{},
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof NetworkError && error.response.status < 500) {
|
if (error instanceof NetworkError && error.response.status < 500) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
useStoredState,
|
useStoredState,
|
||||||
} from "ui-shared";
|
} from "ui-shared";
|
||||||
import { useRealm } from "./realm-context/RealmContext";
|
import { useRealm } from "./realm-context/RealmContext";
|
||||||
import { useRealms } from "./RealmsContext";
|
import { RealmNameRepresentation, useRealms } from "./RealmsContext";
|
||||||
|
|
||||||
const MAX_REALMS = 4;
|
const MAX_REALMS = 4;
|
||||||
|
|
||||||
|
@ -43,12 +43,17 @@ export const RecentRealmsProvider = ({ children }: PropsWithChildren) => {
|
||||||
|
|
||||||
export const useRecentRealms = () => useRequiredContext(RecentRealmsContext);
|
export const useRecentRealms = () => useRequiredContext(RecentRealmsContext);
|
||||||
|
|
||||||
function filterRealmNames(realms: string[], storedRealms: string[]) {
|
function filterRealmNames(
|
||||||
|
realms: RealmNameRepresentation[],
|
||||||
|
storedRealms: string[],
|
||||||
|
) {
|
||||||
// If no realms have been set yet we can't filter out any non-existent realm names.
|
// If no realms have been set yet we can't filter out any non-existent realm names.
|
||||||
if (realms.length === 0) {
|
if (realms.length === 0) {
|
||||||
return storedRealms;
|
return storedRealms;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only keep realm names that actually still exist.
|
// Only keep realm names that actually still exist.
|
||||||
return storedRealms.filter((realm) => realms.includes(realm));
|
return storedRealms.filter((realm) => {
|
||||||
|
return realms.some((r) => r.name === realm);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ import FeatureRepresentation, {
|
||||||
} from "@keycloak/keycloak-admin-client/lib/defs/featureRepresentation";
|
} 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 { HelpItem } from "ui-shared";
|
import { HelpItem, label } 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, { mapByKey } from "../utils/useLocaleSort";
|
import useLocaleSort, { mapByKey } from "../utils/useLocaleSort";
|
||||||
|
@ -57,7 +57,7 @@ const EmptyDashboard = () => {
|
||||||
const [realmInfo, setRealmInfo] = useState<RealmRepresentation>();
|
const [realmInfo, setRealmInfo] = useState<RealmRepresentation>();
|
||||||
const brandImage = environment.logo ? environment.logo : "/icon.svg";
|
const brandImage = environment.logo ? environment.logo : "/icon.svg";
|
||||||
useFetch(() => adminClient.realms.findOne({ realm }), setRealmInfo, []);
|
useFetch(() => adminClient.realms.findOne({ realm }), setRealmInfo, []);
|
||||||
const realmDisplayInfo = realmInfo?.displayName || realm;
|
const realmDisplayInfo = label(t, realmInfo?.displayName, realm);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
|
@ -133,7 +133,7 @@ const Dashboard = () => {
|
||||||
|
|
||||||
useFetch(() => adminClient.realms.findOne({ realm }), setRealmInfo, []);
|
useFetch(() => adminClient.realms.findOne({ realm }), setRealmInfo, []);
|
||||||
|
|
||||||
const realmDisplayInfo = realmInfo?.displayName || realm;
|
const realmDisplayInfo = label(t, realmInfo?.displayName, realm);
|
||||||
|
|
||||||
const welcomeTab = useTab("welcome");
|
const welcomeTab = useTab("welcome");
|
||||||
const infoTab = useTab("info");
|
const infoTab = useTab("info");
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
.keycloak__page_nav__nav {
|
.keycloak__page_nav__nav {
|
||||||
--pf-c-page__sidebar--Transition: all 50ms cubic-bezier(.42, 0, .58, 1)
|
--pf-c-page__sidebar--Transition: all 50ms cubic-bezier(.42, 0, .58, 1);
|
||||||
|
overflow: inherit;
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@ export const unWrap = (key: string) => key.substring(2, key.length - 1);
|
||||||
export const label = (
|
export const label = (
|
||||||
t: TFunction,
|
t: TFunction,
|
||||||
text: string | undefined,
|
text: string | undefined,
|
||||||
fallback: string | undefined,
|
fallback?: string,
|
||||||
) => (isBundleKey(text) ? t(unWrap(text!)) : text) || fallback;
|
) => (isBundleKey(text) ? t(unWrap(text!)) : text) || fallback;
|
||||||
|
|
||||||
export const labelAttribute = (
|
export const labelAttribute = (
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.keycloak.admin.ui.rest;
|
||||||
|
|
||||||
import static org.keycloak.utils.StreamsUtil.throwIfEmpty;
|
import static org.keycloak.utils.StreamsUtil.throwIfEmpty;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
|
@ -15,6 +14,7 @@ import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||||
import org.jboss.resteasy.reactive.NoCache;
|
import org.jboss.resteasy.reactive.NoCache;
|
||||||
|
import org.keycloak.admin.ui.rest.model.RealmNameRepresentation;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.services.ForbiddenException;
|
import org.keycloak.services.ForbiddenException;
|
||||||
|
@ -37,26 +37,32 @@ public class UIRealmsResource {
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Lists only the names of the realms",
|
summary = "Lists only the names and display names of the realms",
|
||||||
description = "Returns a list of realm names based on what the caller is allowed to view"
|
description = "Returns a list of realms containing only their name and displayName" +
|
||||||
|
" based on what the caller is allowed to view"
|
||||||
)
|
)
|
||||||
@APIResponse(
|
@APIResponse(
|
||||||
responseCode = "200",
|
responseCode = "200",
|
||||||
description = "",
|
description = "",
|
||||||
content = {@Content(
|
content = {@Content(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = String.class,
|
implementation = RealmNameRepresentation.class,
|
||||||
type = SchemaType.ARRAY
|
type = SchemaType.ARRAY
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
public Stream<String> getRealmNames() {
|
public Stream<RealmNameRepresentation> getRealms() {
|
||||||
Stream<String> realms = session.realms().getRealmsStream()
|
Stream<RealmNameRepresentation> realms = session.realms().getRealmsStream()
|
||||||
.filter(realm -> {
|
.filter(realm -> {
|
||||||
RealmsPermissionEvaluator eval = AdminPermissions.realms(session, auth.adminAuth());
|
RealmsPermissionEvaluator eval = AdminPermissions.realms(session, auth.adminAuth());
|
||||||
return eval.canView(realm) || eval.isAdmin(realm);
|
return eval.canView(realm) || eval.isAdmin(realm);
|
||||||
})
|
})
|
||||||
.map(RealmModel::getName);
|
.map((RealmModel realm) -> {
|
||||||
|
RealmNameRepresentation realmNameRep = new RealmNameRepresentation();
|
||||||
|
realmNameRep.setDisplayName(realm.getDisplayName());
|
||||||
|
realmNameRep.setName(realm.getName());
|
||||||
|
return realmNameRep;
|
||||||
|
});
|
||||||
return throwIfEmpty(realms, new ForbiddenException());
|
return throwIfEmpty(realms, new ForbiddenException());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.keycloak.admin.ui.rest.model;
|
||||||
|
|
||||||
|
public class RealmNameRepresentation {
|
||||||
|
private String name;
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return this.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayName(String displayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue