Fixes with realm list (#576)
* only query realms endpoint once currently the realm list query is expensive and we only need it once. * fixed refresh issues * fixed tests * fixed imports * fixed types * removed strange non code * reload instead * added more realm tests
This commit is contained in:
parent
e1d53c640e
commit
78f843cdcc
23 changed files with 219 additions and 802 deletions
|
@ -4,16 +4,18 @@ import CreateRealmPage from "../support/pages/admin_console/CreateRealmPage";
|
||||||
import Masthead from "../support/pages/admin_console/Masthead";
|
import Masthead from "../support/pages/admin_console/Masthead";
|
||||||
import AdminClient from "../support/util/AdminClient";
|
import AdminClient from "../support/util/AdminClient";
|
||||||
import { keycloakBefore } from "../support/util/keycloak_before";
|
import { keycloakBefore } from "../support/util/keycloak_before";
|
||||||
|
import RealmSelector from "../support/pages/admin_console/RealmSelector";
|
||||||
|
|
||||||
const masthead = new Masthead();
|
const masthead = new Masthead();
|
||||||
const loginPage = new LoginPage();
|
const loginPage = new LoginPage();
|
||||||
const sidebarPage = new SidebarPage();
|
const sidebarPage = new SidebarPage();
|
||||||
const createRealmPage = new CreateRealmPage();
|
const createRealmPage = new CreateRealmPage();
|
||||||
|
const realmSelector = new RealmSelector();
|
||||||
|
|
||||||
describe("Realms test", function () {
|
describe("Realms test", () => {
|
||||||
const testRealmName = "Test realm";
|
const testRealmName = "Test realm";
|
||||||
describe("Realm creation", function () {
|
describe("Realm creation", () => {
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
keycloakBefore();
|
keycloakBefore();
|
||||||
loginPage.logIn();
|
loginPage.logIn();
|
||||||
});
|
});
|
||||||
|
@ -23,7 +25,7 @@ describe("Realms test", function () {
|
||||||
await client.deleteRealm(testRealmName);
|
await client.deleteRealm(testRealmName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail creating Master realm", function () {
|
it("should fail creating Master realm", () => {
|
||||||
sidebarPage.goToCreateRealm();
|
sidebarPage.goToCreateRealm();
|
||||||
createRealmPage.fillRealmName("master").createRealm();
|
createRealmPage.fillRealmName("master").createRealm();
|
||||||
|
|
||||||
|
@ -32,14 +34,14 @@ describe("Realms test", function () {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create Test realm", function () {
|
it("should create Test realm", () => {
|
||||||
sidebarPage.goToCreateRealm();
|
sidebarPage.goToCreateRealm();
|
||||||
createRealmPage.fillRealmName(testRealmName).createRealm();
|
createRealmPage.fillRealmName(testRealmName).createRealm();
|
||||||
|
|
||||||
masthead.checkNotificationMessage("Realm created");
|
masthead.checkNotificationMessage("Realm created");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should change to Test realm", function () {
|
it("should change to Test realm", () => {
|
||||||
sidebarPage.getCurrentRealm().should("eq", "Master");
|
sidebarPage.getCurrentRealm().should("eq", "Master");
|
||||||
|
|
||||||
sidebarPage
|
sidebarPage
|
||||||
|
@ -48,4 +50,29 @@ describe("Realms test", function () {
|
||||||
.should("eq", testRealmName);
|
.should("eq", testRealmName);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("More then 5 realms", () => {
|
||||||
|
const realmNames = ["One", "Two", "Three", "Four", "Five"];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
keycloakBefore();
|
||||||
|
loginPage.logIn();
|
||||||
|
for (const realmName of realmNames) {
|
||||||
|
sidebarPage.goToCreateRealm();
|
||||||
|
createRealmPage.fillRealmName(realmName).createRealm();
|
||||||
|
sidebarPage.goToClients();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const client = new AdminClient();
|
||||||
|
for (const realmName of realmNames) {
|
||||||
|
await client.deleteRealm(realmName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("switch to searchable realm selector", () => {
|
||||||
|
realmSelector.openRealmContextSelector().shouldContainAll(realmNames);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
27
cypress/support/pages/admin_console/RealmSelector.ts
Normal file
27
cypress/support/pages/admin_console/RealmSelector.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
export default class RealmSelector {
|
||||||
|
private realmSelector = "realmSelector";
|
||||||
|
private realmContextSelector = ".keycloak__realm_selector__context_selector";
|
||||||
|
|
||||||
|
shouldContainAll(realmsList: string[]) {
|
||||||
|
cy.getId(this.realmSelector)
|
||||||
|
.scrollIntoView()
|
||||||
|
.get("ul")
|
||||||
|
.should((realms) => {
|
||||||
|
for (let index = 0; index < realmsList.length; index++) {
|
||||||
|
const realmName = realmsList[index];
|
||||||
|
expect(realms).to.contain(realmName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
openRealmContextSelector() {
|
||||||
|
cy.getId(this.realmSelector).scrollIntoView();
|
||||||
|
cy.get(this.realmContextSelector).click();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,64 +1,47 @@
|
||||||
export default class SidebarPage {
|
export default class SidebarPage {
|
||||||
realmsDrpDwn: string;
|
private realmsDrpDwn = "realmSelectorToggle";
|
||||||
realmsList: string;
|
private realmsList = "realmSelector";
|
||||||
createRealmBtn: string;
|
private createRealmBtn = "add-realm";
|
||||||
clientsBtn: string;
|
|
||||||
clientScopesBtn: string;
|
|
||||||
realmRolesBtn: string;
|
|
||||||
usersBtn: string;
|
|
||||||
groupsBtn: string;
|
|
||||||
sessionsBtn: string;
|
|
||||||
eventsBtn: string;
|
|
||||||
realmSettingsBtn: string;
|
|
||||||
authenticationBtn: string;
|
|
||||||
identityProvidersBtn: string;
|
|
||||||
userFederationBtn: string;
|
|
||||||
|
|
||||||
constructor() {
|
private clientsBtn = "#nav-item-clients";
|
||||||
this.realmsDrpDwn = "#realm-select-toggle";
|
private clientScopesBtn = "#nav-item-client-scopes";
|
||||||
this.realmsList = "#realm-select ul";
|
private realmRolesBtn = "#nav-item-roles";
|
||||||
this.createRealmBtn = "#realm-select li:last-child a";
|
private usersBtn = "#nav-item-users";
|
||||||
|
private groupsBtn = "#nav-item-groups";
|
||||||
|
private sessionsBtn = "#nav-item-sessions";
|
||||||
|
private eventsBtn = "#nav-item-events";
|
||||||
|
|
||||||
this.clientsBtn = "#nav-item-clients";
|
private realmSettingsBtn = "#nav-item-realm-settings";
|
||||||
this.clientScopesBtn = "#nav-item-client-scopes";
|
private authenticationBtn = "#nav-item-authentication";
|
||||||
this.realmRolesBtn = "#nav-item-roles";
|
private identityProvidersBtn = "#nav-item-identity-providers";
|
||||||
this.usersBtn = "#nav-item-users";
|
private userFederationBtn = "#nav-item-user-federation";
|
||||||
this.groupsBtn = "#nav-item-groups";
|
|
||||||
this.sessionsBtn = "#nav-item-sessions";
|
|
||||||
this.eventsBtn = "#nav-item-events";
|
|
||||||
|
|
||||||
this.realmSettingsBtn = "#nav-item-realm-settings";
|
|
||||||
this.authenticationBtn = "#nav-item-authentication";
|
|
||||||
this.identityProvidersBtn = "#nav-item-identity-providers";
|
|
||||||
this.userFederationBtn = "#nav-item-user-federation";
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentRealm() {
|
getCurrentRealm() {
|
||||||
return cy.get(this.realmsDrpDwn).invoke("text");
|
return cy.getId(this.realmsDrpDwn).scrollIntoView().invoke("text");
|
||||||
}
|
}
|
||||||
|
|
||||||
goToRealm(realmName: string) {
|
goToRealm(realmName: string) {
|
||||||
cy.get(this.realmsDrpDwn).click();
|
cy.getId(this.realmsDrpDwn).scrollIntoView().click();
|
||||||
cy.get(this.realmsList).contains(realmName).click();
|
cy.getId(this.realmsList).get("ul").contains(realmName).click();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
goToCreateRealm() {
|
goToCreateRealm() {
|
||||||
cy.get(this.realmsDrpDwn).click();
|
cy.getId(this.realmsDrpDwn).scrollIntoView().click();
|
||||||
cy.get(this.createRealmBtn).click();
|
cy.getId(this.createRealmBtn).click();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
goToClients() {
|
goToClients() {
|
||||||
cy.get(this.clientsBtn).click();
|
cy.get(this.clientsBtn).scrollIntoView().click();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
goToClientScopes() {
|
goToClientScopes() {
|
||||||
cy.get(this.clientScopesBtn).click();
|
cy.get(this.clientScopesBtn).scrollIntoView().click();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
27
src/App.tsx
27
src/App.tsx
|
@ -6,7 +6,7 @@ import {
|
||||||
Switch,
|
Switch,
|
||||||
useParams,
|
useParams,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { ErrorBoundary, useErrorHandler } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
|
||||||
import { Header } from "./PageHeader";
|
import { Header } from "./PageHeader";
|
||||||
import { PageNav } from "./PageNav";
|
import { PageNav } from "./PageNav";
|
||||||
|
@ -21,9 +21,7 @@ import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs";
|
||||||
import { ForbiddenSection } from "./ForbiddenSection";
|
import { ForbiddenSection } from "./ForbiddenSection";
|
||||||
import { SubGroups } from "./groups/SubGroupsContext";
|
import { SubGroups } from "./groups/SubGroupsContext";
|
||||||
import { useRealm } from "./context/realm-context/RealmContext";
|
import { useRealm } from "./context/realm-context/RealmContext";
|
||||||
import { useAdminClient, asyncStateFetch } from "./context/auth/AdminClient";
|
|
||||||
import { ErrorRenderer } from "./components/error/ErrorRenderer";
|
import { ErrorRenderer } from "./components/error/ErrorRenderer";
|
||||||
import { RecentUsed } from "./components/realm-selector/recent-used";
|
|
||||||
|
|
||||||
export const mainPageContentId = "kc-main-content-page-container";
|
export const mainPageContentId = "kc-main-content-page-container";
|
||||||
|
|
||||||
|
@ -39,28 +37,11 @@ const AppContexts = ({ children }: { children: ReactNode }) => (
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
// set the realm form the path if it's one of the know realms
|
// set the realm form the path
|
||||||
const RealmPathSelector = ({ children }: { children: ReactNode }) => {
|
const RealmPathSelector = ({ children }: { children: ReactNode }) => {
|
||||||
const { setRealm } = useRealm();
|
const { setRealm } = useRealm();
|
||||||
const { realm } = useParams<{ realm: string }>();
|
const { realm } = useParams<{ realm: string }>();
|
||||||
const adminClient = useAdminClient();
|
useEffect(() => setRealm(realm), []);
|
||||||
const handleError = useErrorHandler();
|
|
||||||
const recentUsed = new RecentUsed();
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
asyncStateFetch(
|
|
||||||
() => adminClient.realms.find(),
|
|
||||||
(realms) => {
|
|
||||||
recentUsed.clean(realms.map((r) => r.realm!));
|
|
||||||
if (realms.findIndex((r) => r.realm == realm) !== -1) {
|
|
||||||
setRealm(realm);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleError
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
@ -88,7 +69,7 @@ export const App = () => {
|
||||||
>
|
>
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
FallbackComponent={ErrorRenderer}
|
FallbackComponent={ErrorRenderer}
|
||||||
onReset={() => (location.href = "/")}
|
onReset={window.location.reload}
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
{routes(() => {}).map((route, i) => (
|
{routes(() => {}).map((route, i) => (
|
||||||
|
|
|
@ -14,12 +14,12 @@ export const KeycloakAdminConsole = ({
|
||||||
adminClient,
|
adminClient,
|
||||||
}: KeycloakAdminConsoleProps) => {
|
}: KeycloakAdminConsoleProps) => {
|
||||||
return (
|
return (
|
||||||
<RealmContextProvider>
|
|
||||||
<AdminClient.Provider value={adminClient}>
|
<AdminClient.Provider value={adminClient}>
|
||||||
<WhoAmIContextProvider>
|
<WhoAmIContextProvider>
|
||||||
|
<RealmContextProvider>
|
||||||
<App />
|
<App />
|
||||||
|
</RealmContextProvider>
|
||||||
</WhoAmIContextProvider>
|
</WhoAmIContextProvider>
|
||||||
</AdminClient.Provider>
|
</AdminClient.Provider>
|
||||||
</RealmContextProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,6 @@ import { WhoAmIContext } from "./context/whoami/WhoAmI";
|
||||||
import { HelpContext, HelpHeader } from "./components/help-enabler/HelpHeader";
|
import { HelpContext, HelpHeader } from "./components/help-enabler/HelpHeader";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory } from "react-router-dom";
|
||||||
import { useAdminClient } from "./context/auth/AdminClient";
|
import { useAdminClient } from "./context/auth/AdminClient";
|
||||||
import { useRealm } from "./context/realm-context/RealmContext";
|
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
@ -59,13 +58,11 @@ export const Header = () => {
|
||||||
const ServerInfoDropdownItem = () => {
|
const ServerInfoDropdownItem = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { setRealm } = useRealm();
|
|
||||||
return (
|
return (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="server info"
|
key="server info"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
history.push("/master/");
|
history.push("/master/");
|
||||||
setRealm("master");
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("realmInfo")}
|
{t("realmInfo")}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import _ from "lodash";
|
|
||||||
import {
|
import {
|
||||||
Nav,
|
Nav,
|
||||||
NavItem,
|
NavItem,
|
||||||
|
@ -12,8 +11,6 @@ import {
|
||||||
|
|
||||||
import { RealmSelector } from "./components/realm-selector/RealmSelector";
|
import { RealmSelector } from "./components/realm-selector/RealmSelector";
|
||||||
import { useRealm } from "./context/realm-context/RealmContext";
|
import { useRealm } from "./context/realm-context/RealmContext";
|
||||||
import { DataLoader } from "./components/data-loader/DataLoader";
|
|
||||||
import { useAdminClient } from "./context/auth/AdminClient";
|
|
||||||
import { useAccess } from "./context/access/Access";
|
import { useAccess } from "./context/access/Access";
|
||||||
import { routes } from "./route-config";
|
import { routes } from "./route-config";
|
||||||
|
|
||||||
|
@ -21,10 +18,6 @@ export const PageNav: React.FunctionComponent = () => {
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
||||||
const { hasAccess, hasSomeAccess } = useAccess();
|
const { hasAccess, hasSomeAccess } = useAccess();
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const adminClient = useAdminClient();
|
|
||||||
const realmLoader = async () => {
|
|
||||||
return _.sortBy(await adminClient.realms.find(), "realm");
|
|
||||||
};
|
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
@ -83,13 +76,9 @@ export const PageNav: React.FunctionComponent = () => {
|
||||||
nav={
|
nav={
|
||||||
<Nav onSelect={onSelect}>
|
<Nav onSelect={onSelect}>
|
||||||
<NavList>
|
<NavList>
|
||||||
<DataLoader loader={realmLoader} deps={[realm]}>
|
|
||||||
{(realmList) => (
|
|
||||||
<NavItem className="keycloak__page_nav__nav_item__realm-selector">
|
<NavItem className="keycloak__page_nav__nav_item__realm-selector">
|
||||||
<RealmSelector realmList={realmList || []} />
|
<RealmSelector />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
)}
|
|
||||||
</DataLoader>
|
|
||||||
</NavList>
|
</NavList>
|
||||||
{isOnAddRealm() && (
|
{isOnAddRealm() && (
|
||||||
<NavGroup title="">
|
<NavGroup title="">
|
||||||
|
|
|
@ -26,15 +26,15 @@ import {
|
||||||
AllClientScopes,
|
AllClientScopes,
|
||||||
AllClientScopeType,
|
AllClientScopeType,
|
||||||
} from "../components/client-scope/ClientScopeTypes";
|
} from "../components/client-scope/ClientScopeTypes";
|
||||||
|
import KeycloakAdminClient from "keycloak-admin";
|
||||||
|
import { ChangeTypeDialog } from "./ChangeTypeDialog";
|
||||||
|
|
||||||
|
import "./client-scope.css";
|
||||||
|
|
||||||
type ClientScopeDefaultOptionalType = ClientScopeRepresentation & {
|
type ClientScopeDefaultOptionalType = ClientScopeRepresentation & {
|
||||||
type: AllClientScopeType;
|
type: AllClientScopeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
import "./client-scope.css";
|
|
||||||
import KeycloakAdminClient from "keycloak-admin";
|
|
||||||
import { ChangeTypeDialog } from "./ChangeTypeDialog";
|
|
||||||
|
|
||||||
const castAdminClient = (adminClient: KeycloakAdminClient) =>
|
const castAdminClient = (adminClient: KeycloakAdminClient) =>
|
||||||
(adminClient.clientScopes as unknown) as {
|
(adminClient.clientScopes as unknown) as {
|
||||||
[index: string]: Function;
|
[index: string]: Function;
|
||||||
|
|
|
@ -18,6 +18,8 @@ import {
|
||||||
} from "../../components/client-scope/ClientScopeTypes";
|
} from "../../components/client-scope/ClientScopeTypes";
|
||||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||||
|
|
||||||
|
import "./client-scopes.css";
|
||||||
|
|
||||||
export type AddScopeDialogProps = {
|
export type AddScopeDialogProps = {
|
||||||
clientScopes: ClientScopeRepresentation[];
|
clientScopes: ClientScopeRepresentation[];
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -27,8 +29,6 @@ export type AddScopeDialogProps = {
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
import "./client-scopes.css";
|
|
||||||
|
|
||||||
export const AddScopeDialog = ({
|
export const AddScopeDialog = ({
|
||||||
clientScopes,
|
clientScopes,
|
||||||
open,
|
open,
|
||||||
|
|
|
@ -32,7 +32,7 @@ exports[`Group BreadCrumbs tests couple of crumbs 1`] = `
|
||||||
href="//groups"
|
href="//groups"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
groups
|
Groups
|
||||||
</a>
|
</a>
|
||||||
</LinkAnchor>
|
</LinkAnchor>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -17,7 +17,14 @@ describe("<FormAccess />", () => {
|
||||||
<WhoAmIContext.Provider
|
<WhoAmIContext.Provider
|
||||||
value={{ refresh: () => {}, whoAmI: new WhoAmI("master", whoami) }}
|
value={{ refresh: () => {}, whoAmI: new WhoAmI("master", whoami) }}
|
||||||
>
|
>
|
||||||
<RealmContext.Provider value={{ realm, setRealm: () => {} }}>
|
<RealmContext.Provider
|
||||||
|
value={{
|
||||||
|
realm,
|
||||||
|
setRealm: () => {},
|
||||||
|
realms: [],
|
||||||
|
refresh: () => Promise.resolve(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<AccessContextProvider>
|
<AccessContextProvider>
|
||||||
<FormAccess role="manage-clients">
|
<FormAccess role="manage-clients">
|
||||||
<FormGroup label="test" fieldId="field">
|
<FormGroup label="test" fieldId="field">
|
||||||
|
|
|
@ -16,24 +16,20 @@ import {
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { CheckIcon } from "@patternfly/react-icons";
|
import { CheckIcon } from "@patternfly/react-icons";
|
||||||
|
|
||||||
import { toUpperCase } from "../../util";
|
|
||||||
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
|
import { toUpperCase } from "../../util";
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
||||||
import { RecentUsed } from "./recent-used";
|
import { RecentUsed } from "./recent-used";
|
||||||
|
|
||||||
import "./realm-selector.css";
|
import "./realm-selector.css";
|
||||||
|
|
||||||
type RealmSelectorProps = {
|
export const RealmSelector = () => {
|
||||||
realmList: RealmRepresentation[];
|
const { realm, setRealm, realms } = useRealm();
|
||||||
};
|
|
||||||
|
|
||||||
export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
|
||||||
const { realm, setRealm } = useRealm();
|
|
||||||
const { whoAmI } = useContext(WhoAmIContext);
|
const { whoAmI } = useContext(WhoAmIContext);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [filteredItems, setFilteredItems] = useState(realmList);
|
const [filteredItems, setFilteredItems] = useState<RealmRepresentation[]>();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
||||||
const recentUsed = new RecentUsed();
|
const recentUsed = new RecentUsed();
|
||||||
|
@ -47,6 +43,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
|
|
||||||
const AddRealm = () => (
|
const AddRealm = () => (
|
||||||
<Button
|
<Button
|
||||||
|
data-testid="add-realm"
|
||||||
component="div"
|
component="div"
|
||||||
isBlock
|
isBlock
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -59,30 +56,31 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const onFilter = () => {
|
const onFilter = () => {
|
||||||
const filtered =
|
if (search === "") {
|
||||||
search === ""
|
setFilteredItems(undefined);
|
||||||
? realmList
|
} else {
|
||||||
: realmList.filter(
|
const filtered = realms.filter(
|
||||||
(r) => r.realm!.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
(r) => r.realm!.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||||
);
|
);
|
||||||
setFilteredItems(filtered || []);
|
setFilteredItems(filtered);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectRealm = (realm: string) => {
|
const selectRealm = (realm: string) => {
|
||||||
setRealm(realm);
|
setRealm(realm);
|
||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
|
history.push(`/${realm}/`);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onFilter();
|
onFilter();
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
const dropdownItems = realmList.map((r) => (
|
const dropdownItems = realms.map((r) => (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key={`realm-dropdown-item-${r.realm}`}
|
key={`realm-dropdown-item-${r.realm}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
selectRealm(r.realm!);
|
selectRealm(r.realm!);
|
||||||
history.push(`/${realm}/`);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RealmText value={r.realm!} />
|
<RealmText value={r.realm!} />
|
||||||
|
@ -104,8 +102,9 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{realmList.length > 5 && (
|
{realms.length > 5 && (
|
||||||
<ContextSelector
|
<ContextSelector
|
||||||
|
data-testid="realmSelector"
|
||||||
toggleText={toUpperCase(realm)}
|
toggleText={toUpperCase(realm)}
|
||||||
isOpen={open}
|
isOpen={open}
|
||||||
screenReaderLabel={toUpperCase(realm)}
|
screenReaderLabel={toUpperCase(realm)}
|
||||||
|
@ -117,8 +116,10 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
} else {
|
} else {
|
||||||
element = r as ReactElement;
|
element = r as ReactElement;
|
||||||
}
|
}
|
||||||
const value = element.props.value || "master";
|
const value = element.props.value;
|
||||||
|
if (value) {
|
||||||
selectRealm(value);
|
selectRealm(value);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
searchInputValue={search}
|
searchInputValue={search}
|
||||||
onSearchInputChange={(value) => setSearch(value)}
|
onSearchInputChange={(value) => setSearch(value)}
|
||||||
|
@ -130,7 +131,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
<RealmText value={realm} /> <Label>{t("recent")}</Label>
|
<RealmText value={realm} /> <Label>{t("recent")}</Label>
|
||||||
</ContextSelectorItem>
|
</ContextSelectorItem>
|
||||||
))}
|
))}
|
||||||
{filteredItems
|
{(filteredItems || realms)
|
||||||
.filter((r) => !recentUsed.used.includes(r.realm!))
|
.filter((r) => !recentUsed.used.includes(r.realm!))
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<ContextSelectorItem key={item.id}>
|
<ContextSelectorItem key={item.id}>
|
||||||
|
@ -142,14 +143,15 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
</ContextSelectorItem>
|
</ContextSelectorItem>
|
||||||
</ContextSelector>
|
</ContextSelector>
|
||||||
)}
|
)}
|
||||||
{realmList.length <= 5 && (
|
{realms.length <= 5 && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
id="realm-select"
|
id="realm-select"
|
||||||
|
data-testid="realmSelector"
|
||||||
className="keycloak__realm_selector__dropdown"
|
className="keycloak__realm_selector__dropdown"
|
||||||
isOpen={open}
|
isOpen={open}
|
||||||
toggle={
|
toggle={
|
||||||
<DropdownToggle
|
<DropdownToggle
|
||||||
id="realm-select-toggle"
|
data-testid="realmSelectorToggle"
|
||||||
onToggle={() => setOpen(!open)}
|
onToggle={() => setOpen(!open)}
|
||||||
className="keycloak__realm_selector_dropdown__toggle"
|
className="keycloak__realm_selector_dropdown__toggle"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { mount } from "enzyme";
|
|
||||||
import { act } from "@testing-library/react";
|
|
||||||
|
|
||||||
import { RealmSelector } from "../RealmSelector";
|
|
||||||
import { RealmContextProvider } from "../../../context/realm-context/RealmContext";
|
|
||||||
import { MemoryRouter } from "react-router-dom";
|
|
||||||
|
|
||||||
it("renders realm selector", async () => {
|
|
||||||
const wrapper = mount(
|
|
||||||
<MemoryRouter>
|
|
||||||
<RealmContextProvider>
|
|
||||||
<div id="realm">
|
|
||||||
<RealmSelector realmList={[{ id: "321", realm: "another" }]} />
|
|
||||||
</div>
|
|
||||||
</RealmContextProvider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
|
|
||||||
const expandButton = wrapper.find("button");
|
|
||||||
act(() => {
|
|
||||||
expandButton!.simulate("click");
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.find("#realm")).toMatchSnapshot();
|
|
||||||
});
|
|
|
@ -1,305 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`renders realm selector 1`] = `
|
|
||||||
<div
|
|
||||||
id="realm"
|
|
||||||
>
|
|
||||||
<RealmSelector
|
|
||||||
realmList={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"id": "321",
|
|
||||||
"realm": "another",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Dropdown
|
|
||||||
className="keycloak__realm_selector__dropdown"
|
|
||||||
dropdownItems={
|
|
||||||
Array [
|
|
||||||
<DropdownItem
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<RealmText
|
|
||||||
value="another"
|
|
||||||
/>
|
|
||||||
</DropdownItem>,
|
|
||||||
<React.Fragment />,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
id="realm-select"
|
|
||||||
isOpen={false}
|
|
||||||
toggle={
|
|
||||||
<DropdownToggle
|
|
||||||
className="keycloak__realm_selector_dropdown__toggle"
|
|
||||||
id="realm-select-toggle"
|
|
||||||
onToggle={[Function]}
|
|
||||||
>
|
|
||||||
|
|
||||||
</DropdownToggle>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DropdownWithContext
|
|
||||||
autoFocus={true}
|
|
||||||
className="keycloak__realm_selector__dropdown"
|
|
||||||
direction="down"
|
|
||||||
dropdownItems={
|
|
||||||
Array [
|
|
||||||
<DropdownItem
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<RealmText
|
|
||||||
value="another"
|
|
||||||
/>
|
|
||||||
</DropdownItem>,
|
|
||||||
<React.Fragment />,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
id="realm-select"
|
|
||||||
isGrouped={false}
|
|
||||||
isOpen={false}
|
|
||||||
isPlain={false}
|
|
||||||
menuAppendTo="inline"
|
|
||||||
onSelect={[Function]}
|
|
||||||
position="left"
|
|
||||||
toggle={
|
|
||||||
<DropdownToggle
|
|
||||||
className="keycloak__realm_selector_dropdown__toggle"
|
|
||||||
id="realm-select-toggle"
|
|
||||||
onToggle={[Function]}
|
|
||||||
>
|
|
||||||
|
|
||||||
</DropdownToggle>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="pf-c-dropdown keycloak__realm_selector__dropdown"
|
|
||||||
data-ouia-component-id="OUIA-Generated-Dropdown-1"
|
|
||||||
data-ouia-component-type="PF4/Dropdown"
|
|
||||||
data-ouia-safe={true}
|
|
||||||
id="realm-select"
|
|
||||||
>
|
|
||||||
<DropdownToggle
|
|
||||||
aria-haspopup={true}
|
|
||||||
className="keycloak__realm_selector_dropdown__toggle"
|
|
||||||
getMenuRef={[Function]}
|
|
||||||
id="realm-select-toggle"
|
|
||||||
isOpen={false}
|
|
||||||
isPlain={false}
|
|
||||||
key=".0"
|
|
||||||
onEnter={[Function]}
|
|
||||||
onToggle={[Function]}
|
|
||||||
parentRef={
|
|
||||||
Object {
|
|
||||||
"current": <div
|
|
||||||
class="pf-c-dropdown pf-m-expanded keycloak__realm_selector__dropdown"
|
|
||||||
data-ouia-component-id="OUIA-Generated-Dropdown-1"
|
|
||||||
data-ouia-component-type="PF4/Dropdown"
|
|
||||||
data-ouia-safe="true"
|
|
||||||
id="realm-select"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-expanded="true"
|
|
||||||
aria-haspopup="true"
|
|
||||||
class="pf-c-dropdown__toggle keycloak__realm_selector_dropdown__toggle"
|
|
||||||
data-ouia-component-id="OUIA-Generated-DropdownToggle-1"
|
|
||||||
data-ouia-component-type="PF4/DropdownToggle"
|
|
||||||
data-ouia-safe="true"
|
|
||||||
id="realm-select-toggle"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="pf-c-dropdown__toggle-icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="1em"
|
|
||||||
role="img"
|
|
||||||
style="vertical-align: -0.125em;"
|
|
||||||
viewBox="0 0 320 512"
|
|
||||||
width="1em"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<ul
|
|
||||||
aria-labelledby="realm-select-toggle"
|
|
||||||
class="pf-c-dropdown__menu"
|
|
||||||
role="menu"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
role="menuitem"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
aria-disabled="false"
|
|
||||||
class="pf-c-dropdown__menu-item"
|
|
||||||
data-ouia-component-id="OUIA-Generated-DropdownItem-1"
|
|
||||||
data-ouia-component-type="PF4/DropdownItem"
|
|
||||||
data-ouia-safe="true"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="pf-l-split keycloak__realm_selector__list-item-split"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="pf-l-split__item pf-m-fill"
|
|
||||||
>
|
|
||||||
Another
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="pf-l-split__item"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Toggle
|
|
||||||
aria-haspopup={true}
|
|
||||||
bubbleEvent={false}
|
|
||||||
className="keycloak__realm_selector_dropdown__toggle"
|
|
||||||
data-ouia-component-id="OUIA-Generated-DropdownToggle-1"
|
|
||||||
data-ouia-component-type="PF4/DropdownToggle"
|
|
||||||
data-ouia-safe={true}
|
|
||||||
getMenuRef={[Function]}
|
|
||||||
id="realm-select-toggle"
|
|
||||||
isActive={false}
|
|
||||||
isDisabled={false}
|
|
||||||
isOpen={false}
|
|
||||||
isPlain={false}
|
|
||||||
isPrimary={false}
|
|
||||||
isSplitButton={false}
|
|
||||||
onEnter={[Function]}
|
|
||||||
onToggle={[Function]}
|
|
||||||
parentRef={
|
|
||||||
Object {
|
|
||||||
"current": <div
|
|
||||||
class="pf-c-dropdown pf-m-expanded keycloak__realm_selector__dropdown"
|
|
||||||
data-ouia-component-id="OUIA-Generated-Dropdown-1"
|
|
||||||
data-ouia-component-type="PF4/Dropdown"
|
|
||||||
data-ouia-safe="true"
|
|
||||||
id="realm-select"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-expanded="true"
|
|
||||||
aria-haspopup="true"
|
|
||||||
class="pf-c-dropdown__toggle keycloak__realm_selector_dropdown__toggle"
|
|
||||||
data-ouia-component-id="OUIA-Generated-DropdownToggle-1"
|
|
||||||
data-ouia-component-type="PF4/DropdownToggle"
|
|
||||||
data-ouia-safe="true"
|
|
||||||
id="realm-select-toggle"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="pf-c-dropdown__toggle-icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="1em"
|
|
||||||
role="img"
|
|
||||||
style="vertical-align: -0.125em;"
|
|
||||||
viewBox="0 0 320 512"
|
|
||||||
width="1em"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<ul
|
|
||||||
aria-labelledby="realm-select-toggle"
|
|
||||||
class="pf-c-dropdown__menu"
|
|
||||||
role="menu"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
role="menuitem"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
aria-disabled="false"
|
|
||||||
class="pf-c-dropdown__menu-item"
|
|
||||||
data-ouia-component-id="OUIA-Generated-DropdownItem-1"
|
|
||||||
data-ouia-component-type="PF4/DropdownItem"
|
|
||||||
data-ouia-safe="true"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="pf-l-split keycloak__realm_selector__list-item-split"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="pf-l-split__item pf-m-fill"
|
|
||||||
>
|
|
||||||
Another
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="pf-l-split__item"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-expanded={false}
|
|
||||||
aria-haspopup={true}
|
|
||||||
className="pf-c-dropdown__toggle keycloak__realm_selector_dropdown__toggle"
|
|
||||||
data-ouia-component-id="OUIA-Generated-DropdownToggle-1"
|
|
||||||
data-ouia-component-type="PF4/DropdownToggle"
|
|
||||||
data-ouia-safe={true}
|
|
||||||
disabled={false}
|
|
||||||
id="realm-select-toggle"
|
|
||||||
onClick={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="pf-c-dropdown__toggle-icon"
|
|
||||||
>
|
|
||||||
<CaretDownIcon
|
|
||||||
color="currentColor"
|
|
||||||
noVerticalAlign={false}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden={true}
|
|
||||||
aria-labelledby={null}
|
|
||||||
fill="currentColor"
|
|
||||||
height="1em"
|
|
||||||
role="img"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"verticalAlign": "-0.125em",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewBox="0 0 320 512"
|
|
||||||
width="1em"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</CaretDownIcon>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</Toggle>
|
|
||||||
</DropdownToggle>
|
|
||||||
</div>
|
|
||||||
</DropdownWithContext>
|
|
||||||
</Dropdown>
|
|
||||||
</RealmSelector>
|
|
||||||
</div>
|
|
||||||
`;
|
|
|
@ -1,307 +0,0 @@
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useErrorHandler } from "react-error-boundary";
|
|
||||||
import _ from "lodash";
|
|
||||||
import {
|
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
Chip,
|
|
||||||
ChipGroup,
|
|
||||||
Divider,
|
|
||||||
Modal,
|
|
||||||
ModalVariant,
|
|
||||||
Select,
|
|
||||||
SelectGroup,
|
|
||||||
SelectOption,
|
|
||||||
SelectVariant,
|
|
||||||
ToolbarItem,
|
|
||||||
} from "@patternfly/react-core";
|
|
||||||
|
|
||||||
import { KeycloakDataTable } from "../table-toolbar/KeycloakDataTable";
|
|
||||||
import {
|
|
||||||
asyncStateFetch,
|
|
||||||
useAdminClient,
|
|
||||||
} from "../../context/auth/AdminClient";
|
|
||||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
|
||||||
import { FilterIcon } from "@patternfly/react-icons";
|
|
||||||
import { Row, ServiceRole } from "./RoleMapping";
|
|
||||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
|
||||||
|
|
||||||
export type MappingType = "service-account" | "client-scope";
|
|
||||||
|
|
||||||
type AddRoleMappingModalProps = {
|
|
||||||
id: string;
|
|
||||||
type: MappingType;
|
|
||||||
name: string;
|
|
||||||
onAssign: (rows: Row[]) => void;
|
|
||||||
onClose: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ClientRole = ClientRepresentation & {
|
|
||||||
numberOfRoles: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const realmRole = {
|
|
||||||
name: "realmRoles",
|
|
||||||
} as ClientRepresentation;
|
|
||||||
|
|
||||||
export const AddRoleMappingModal = ({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
onAssign,
|
|
||||||
onClose,
|
|
||||||
}: AddRoleMappingModalProps) => {
|
|
||||||
const { t } = useTranslation("clients");
|
|
||||||
const adminClient = useAdminClient();
|
|
||||||
const errorHandler = useErrorHandler();
|
|
||||||
|
|
||||||
const [clients, setClients] = useState<ClientRole[]>([]);
|
|
||||||
const [name, setName] = useState<string>();
|
|
||||||
const [searchToggle, setSearchToggle] = useState(false);
|
|
||||||
|
|
||||||
const [key, setKey] = useState(0);
|
|
||||||
const refresh = () => setKey(new Date().getTime());
|
|
||||||
|
|
||||||
const [selectedClients, setSelectedClients] = useState<ClientRole[]>([]);
|
|
||||||
const [selectedRows, setSelectedRows] = useState<Row[]>([]);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
asyncStateFetch(
|
|
||||||
async () => {
|
|
||||||
const clients = await adminClient.clients.find();
|
|
||||||
setName(clients.find((client) => client.id === clientId)?.clientId);
|
|
||||||
return (
|
|
||||||
await Promise.all(
|
|
||||||
clients.map(async (client) => {
|
|
||||||
let roles: RoleRepresentation[] = [];
|
|
||||||
if (type === "service-account") {
|
|
||||||
roles = await adminClient.users.listAvailableClientRoleMappings(
|
|
||||||
{
|
|
||||||
id: id,
|
|
||||||
clientUniqueId: client.id!,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (type === "client-scope") {
|
|
||||||
roles = await adminClient.clientScopes.listAvailableClientScopeMappings(
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
client: client.id!,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
roles,
|
|
||||||
client,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.flat()
|
|
||||||
.filter((row) => row.roles.length !== 0)
|
|
||||||
.map((row) => {
|
|
||||||
return { ...row.client, numberOfRoles: row.roles.length };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(clients) => {
|
|
||||||
setClients(clients);
|
|
||||||
},
|
|
||||||
errorHandler
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(refresh, [searchToggle]);
|
|
||||||
|
|
||||||
const removeClient = (client: ClientRole) => {
|
|
||||||
setSelectedClients(selectedClients.filter((item) => item.id !== client.id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const loader = async () => {
|
|
||||||
const realmRolesSelected = _.findIndex(
|
|
||||||
selectedClients,
|
|
||||||
(client) => client.name === "realmRoles"
|
|
||||||
);
|
|
||||||
let selected = selectedClients;
|
|
||||||
if (realmRolesSelected !== -1) {
|
|
||||||
selected = selectedClients.filter(
|
|
||||||
(client) => client.name !== "realmRoles"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let availableRoles: RoleRepresentation[] = [];
|
|
||||||
if (type === "service-account") {
|
|
||||||
availableRoles = await adminClient.users.listAvailableRealmRoleMappings({
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
} else if (type === "client-scope") {
|
|
||||||
availableRoles = await adminClient.clientScopes.listAvailableRealmScopeMappings(
|
|
||||||
{ id }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const realmRoles = availableRoles.map((role) => {
|
|
||||||
return {
|
|
||||||
role,
|
|
||||||
client: undefined,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const allClients =
|
|
||||||
selectedClients.length !== 0
|
|
||||||
? selected
|
|
||||||
: await adminClient.clients.find();
|
|
||||||
|
|
||||||
const roles = (
|
|
||||||
await Promise.all(
|
|
||||||
allClients.map(async (client) => {
|
|
||||||
let clientAvailableRoles: RoleRepresentation[] = [];
|
|
||||||
if (type === "service-account") {
|
|
||||||
clientAvailableRoles = await adminClient.users.listAvailableClientRoleMappings(
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
clientUniqueId: client.id!,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (type === "client-scope") {
|
|
||||||
clientAvailableRoles = await adminClient.clientScopes.listAvailableClientScopeMappings(
|
|
||||||
{ id, client: client.id! }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return clientAvailableRoles.map((role) => {
|
|
||||||
return {
|
|
||||||
role,
|
|
||||||
client,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).flat();
|
|
||||||
|
|
||||||
return [
|
|
||||||
...(realmRolesSelected !== -1 || selected.length === 0 ? realmRoles : []),
|
|
||||||
...roles,
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const createSelectGroup = (clients: ClientRepresentation[]) => [
|
|
||||||
<SelectGroup key="role" label={t("realmRoles")}>
|
|
||||||
<SelectOption key="realmRoles" value={realmRole}>
|
|
||||||
{t("realmRoles")}
|
|
||||||
</SelectOption>
|
|
||||||
</SelectGroup>,
|
|
||||||
<Divider key="divider" />,
|
|
||||||
<SelectGroup key="group" label={t("clients")}>
|
|
||||||
{clients.map((client) => (
|
|
||||||
<SelectOption key={client.id} value={client}>
|
|
||||||
{client.clientId}
|
|
||||||
</SelectOption>
|
|
||||||
))}
|
|
||||||
</SelectGroup>,
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
variant={ModalVariant.large}
|
|
||||||
<<<<<<< HEAD:src/clients/service-account/AddServiceAccountModal.tsx
|
|
||||||
title={t("assignRolesTo", {
|
|
||||||
client: name,
|
|
||||||
})}
|
|
||||||
=======
|
|
||||||
title={t("assignRolesTo", { client: name })}
|
|
||||||
>>>>>>> 0f6f6ab (fixed assign):src/components/role-mapping/AddRoleMappingModal.tsx
|
|
||||||
isOpen={true}
|
|
||||||
onClose={onClose}
|
|
||||||
actions={[
|
|
||||||
<Button
|
|
||||||
data-testid="assign"
|
|
||||||
key="confirm"
|
|
||||||
isDisabled={selectedRows?.length === 0}
|
|
||||||
variant="primary"
|
|
||||||
onClick={() => {
|
|
||||||
onAssign(selectedRows);
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("assign")}
|
|
||||||
</Button>,
|
|
||||||
<Button
|
|
||||||
data-testid="cancel"
|
|
||||||
key="cancel"
|
|
||||||
variant="link"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
{t("common:cancel")}
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
toggleId="role"
|
|
||||||
onToggle={() => setSearchToggle(!searchToggle)}
|
|
||||||
isOpen={searchToggle}
|
|
||||||
variant={SelectVariant.checkbox}
|
|
||||||
hasInlineFilter
|
|
||||||
menuAppendTo="parent"
|
|
||||||
placeholderText={
|
|
||||||
<>
|
|
||||||
<FilterIcon /> {t("filterByOrigin")}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
isGrouped
|
|
||||||
onFilter={(evt) => {
|
|
||||||
const value = evt?.target.value || "";
|
|
||||||
return createSelectGroup(
|
|
||||||
clients.filter((client) => client.clientId?.includes(value))
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
selections={selectedClients}
|
|
||||||
onClear={() => setSelectedClients([])}
|
|
||||||
onSelect={(_, selection) => {
|
|
||||||
const client = selection as ClientRole;
|
|
||||||
if (selectedClients.includes(client)) {
|
|
||||||
removeClient(client);
|
|
||||||
} else {
|
|
||||||
setSelectedClients([...selectedClients, client]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{createSelectGroup(clients)}
|
|
||||||
</Select>
|
|
||||||
<ToolbarItem variant="chip-group">
|
|
||||||
<ChipGroup>
|
|
||||||
{selectedClients.map((client) => (
|
|
||||||
<Chip
|
|
||||||
key={`chip-${client.id}`}
|
|
||||||
onClick={() => {
|
|
||||||
removeClient(client);
|
|
||||||
refresh();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{client.clientId || t("realmRoles")}
|
|
||||||
<Badge isRead={true}>{client.numberOfRoles}</Badge>
|
|
||||||
</Chip>
|
|
||||||
))}
|
|
||||||
</ChipGroup>
|
|
||||||
</ToolbarItem>
|
|
||||||
|
|
||||||
<KeycloakDataTable
|
|
||||||
key={key}
|
|
||||||
onSelect={(rows) => setSelectedRows([...rows])}
|
|
||||||
searchPlaceholderKey="clients:searchByRoleName"
|
|
||||||
canSelectAll={false}
|
|
||||||
loader={loader}
|
|
||||||
ariaLabelKey="clients:roles"
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
name: "name",
|
|
||||||
cellRenderer: ServiceRole,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "role.description",
|
|
||||||
displayKey: t("description"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -290,7 +290,6 @@ export function KeycloakDataTable<T>({
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
setSearch;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertAction = () =>
|
const convertAction = () =>
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
import KeycloakAdminClient from "keycloak-admin";
|
import KeycloakAdminClient from "keycloak-admin";
|
||||||
import { RealmContext } from "../realm-context/RealmContext";
|
|
||||||
|
|
||||||
export const AdminClient = createContext<KeycloakAdminClient | undefined>(
|
export const AdminClient = createContext<KeycloakAdminClient | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useAdminClient = () => {
|
export const useAdminClient = () => {
|
||||||
const adminClient = useContext(AdminClient)!;
|
return useContext(AdminClient)!;
|
||||||
const { realm } = useContext(RealmContext);
|
|
||||||
|
|
||||||
adminClient.setConfig({
|
|
||||||
realmName: realm,
|
|
||||||
});
|
|
||||||
|
|
||||||
return adminClient;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
import { RecentUsed } from "../../components/realm-selector/recent-used";
|
import { RecentUsed } from "../../components/realm-selector/recent-used";
|
||||||
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
|
import { asyncStateFetch, useAdminClient } from "../auth/AdminClient";
|
||||||
|
import { WhoAmIContext } from "../whoami/WhoAmI";
|
||||||
|
|
||||||
type RealmContextType = {
|
type RealmContextType = {
|
||||||
realm: string;
|
realm: string;
|
||||||
setRealm: (realm: string) => void;
|
setRealm: (realm: string) => void;
|
||||||
|
realms: RealmRepresentation[];
|
||||||
|
refresh: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RealmContext = React.createContext<RealmContextType>({
|
export const RealmContext = React.createContext<RealmContextType>({
|
||||||
realm: "",
|
realm: "",
|
||||||
setRealm: () => {},
|
setRealm: () => {},
|
||||||
|
realms: [],
|
||||||
|
refresh: () => Promise.resolve(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type RealmContextProviderProps = { children: React.ReactNode };
|
type RealmContextProviderProps = { children: React.ReactNode };
|
||||||
|
@ -16,16 +26,52 @@ type RealmContextProviderProps = { children: React.ReactNode };
|
||||||
export const RealmContextProvider = ({
|
export const RealmContextProvider = ({
|
||||||
children,
|
children,
|
||||||
}: RealmContextProviderProps) => {
|
}: RealmContextProviderProps) => {
|
||||||
const [realm, setRealm] = useState("");
|
const { whoAmI } = useContext(WhoAmIContext);
|
||||||
|
const [realm, setRealm] = useState(whoAmI.getHomeRealm());
|
||||||
|
const [realms, setRealms] = useState<RealmRepresentation[]>([]);
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const errorHandler = useErrorHandler();
|
||||||
const recentUsed = new RecentUsed();
|
const recentUsed = new RecentUsed();
|
||||||
|
|
||||||
const set = (realm: string) => {
|
const updateRealmsList = (realms: RealmRepresentation[]) => {
|
||||||
recentUsed.setRecentUsed(realm);
|
setRealms(_.sortBy(realms, "realm"));
|
||||||
setRealm(realm);
|
recentUsed.clean(realms.map((r) => r.realm!));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
asyncStateFetch(
|
||||||
|
() => adminClient.realms.find(),
|
||||||
|
(realms) => updateRealmsList(realms),
|
||||||
|
errorHandler
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const set = (realm: string) => {
|
||||||
|
if (
|
||||||
|
realms.length === 0 ||
|
||||||
|
realms.findIndex((r) => r.realm == realm) !== -1
|
||||||
|
) {
|
||||||
|
recentUsed.setRecentUsed(realm);
|
||||||
|
setRealm(realm);
|
||||||
|
adminClient.setConfig({
|
||||||
|
realmName: realm,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<RealmContext.Provider value={{ realm, setRealm: set }}>
|
<RealmContext.Provider
|
||||||
|
value={{
|
||||||
|
realm,
|
||||||
|
setRealm: set,
|
||||||
|
realms,
|
||||||
|
refresh: async () => {
|
||||||
|
const list = await adminClient.realms.find();
|
||||||
|
updateRealmsList(list);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</RealmContext.Provider>
|
</RealmContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useErrorHandler } from "react-error-boundary";
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
import i18n from "../../i18n";
|
import i18n from "../../i18n";
|
||||||
|
|
||||||
import { AdminClient, asyncStateFetch } from "../auth/AdminClient";
|
import { asyncStateFetch, useAdminClient } from "../auth/AdminClient";
|
||||||
import { RealmContext } from "../realm-context/RealmContext";
|
|
||||||
import WhoAmIRepresentation, {
|
import WhoAmIRepresentation, {
|
||||||
AccessType,
|
AccessType,
|
||||||
} from "keycloak-admin/lib/defs/whoAmIRepresentation";
|
} from "keycloak-admin/lib/defs/whoAmIRepresentation";
|
||||||
|
@ -62,23 +61,16 @@ export const WhoAmIContext = React.createContext<WhoAmIProps>({
|
||||||
|
|
||||||
type WhoAmIProviderProps = { children: React.ReactNode };
|
type WhoAmIProviderProps = { children: React.ReactNode };
|
||||||
export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => {
|
export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => {
|
||||||
const adminClient = useContext(AdminClient)!;
|
const adminClient = useAdminClient();
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
const { realm, setRealm } = useContext(RealmContext);
|
|
||||||
const [whoAmI, setWhoAmI] = useState<WhoAmI>(new WhoAmI());
|
const [whoAmI, setWhoAmI] = useState<WhoAmI>(new WhoAmI());
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return asyncStateFetch(
|
return asyncStateFetch(
|
||||||
() =>
|
() => adminClient.whoAmI.find({ realm: "master" }),
|
||||||
adminClient.whoAmI.find({
|
|
||||||
realm: adminClient.keycloak?.realm,
|
|
||||||
}),
|
|
||||||
(me) => {
|
(me) => {
|
||||||
const whoAmI = new WhoAmI(adminClient.keycloak?.realm, me);
|
const whoAmI = new WhoAmI(adminClient.keycloak?.realm, me);
|
||||||
if (!realm) {
|
|
||||||
setRealm(whoAmI.getHomeRealm());
|
|
||||||
}
|
|
||||||
setWhoAmI(whoAmI);
|
setWhoAmI(whoAmI);
|
||||||
},
|
},
|
||||||
handleError
|
handleError
|
||||||
|
|
|
@ -43,7 +43,7 @@ const RealmSettingsHeader = ({
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { setRealm } = useRealm();
|
const { refresh } = useRealm();
|
||||||
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
||||||
|
|
||||||
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
||||||
|
@ -65,8 +65,8 @@ const RealmSettingsHeader = ({
|
||||||
try {
|
try {
|
||||||
await adminClient.realms.del({ realm: realmName });
|
await adminClient.realms.del({ realm: realmName });
|
||||||
addAlert(t("deletedSuccess"), AlertVariant.success);
|
addAlert(t("deletedSuccess"), AlertVariant.success);
|
||||||
setRealm("master");
|
|
||||||
history.push("/master/");
|
history.push("/master/");
|
||||||
|
refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(t("deleteError", { error }), AlertVariant.danger);
|
addAlert(t("deleteError", { error }), AlertVariant.danger);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,13 @@ import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
||||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
|
||||||
export const NewRealmForm = () => {
|
export const NewRealmForm = () => {
|
||||||
const { t } = useTranslation("realm");
|
const { t } = useTranslation("realm");
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { refresh } = useContext(WhoAmIContext);
|
const { refresh } = useContext(WhoAmIContext);
|
||||||
|
const { refresh: realmRefresh } = useRealm();
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
|
|
||||||
|
@ -56,10 +58,12 @@ export const NewRealmForm = () => {
|
||||||
try {
|
try {
|
||||||
await adminClient.realms.create(realm);
|
await adminClient.realms.create(realm);
|
||||||
addAlert(t("saveRealmSuccess"), AlertVariant.success);
|
addAlert(t("saveRealmSuccess"), AlertVariant.success);
|
||||||
refresh();
|
|
||||||
//force token update
|
//force token update
|
||||||
|
refresh();
|
||||||
await adminClient.keycloak?.updateToken(Number.MAX_VALUE);
|
await adminClient.keycloak?.updateToken(Number.MAX_VALUE);
|
||||||
history.push(`/${realm.realm}/`);
|
await realmRefresh();
|
||||||
|
history.push(`/${realm.realm}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(
|
addAlert(
|
||||||
t("saveRealmError", {
|
t("saveRealmError", {
|
||||||
|
|
|
@ -43,7 +43,12 @@ export const MockAdminClient = (props: {
|
||||||
>
|
>
|
||||||
<WhoAmIContextProvider>
|
<WhoAmIContextProvider>
|
||||||
<RealmContext.Provider
|
<RealmContext.Provider
|
||||||
value={{ realm: "master", setRealm: () => {} }}
|
value={{
|
||||||
|
realm: "master",
|
||||||
|
setRealm: () => {},
|
||||||
|
realms: [],
|
||||||
|
refresh: () => Promise.resolve(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AccessContextProvider>{props.children}</AccessContextProvider>
|
<AccessContextProvider>{props.children}</AccessContextProvider>
|
||||||
</RealmContext.Provider>
|
</RealmContext.Provider>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import { Meta } from "@storybook/react";
|
import { Meta } from "@storybook/react";
|
||||||
|
|
||||||
import { RealmSelector } from "../components/realm-selector/RealmSelector";
|
import { RealmSelector } from "../components/realm-selector/RealmSelector";
|
||||||
import { RealmContextProvider } from "../context/realm-context/RealmContext";
|
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||||
import { HashRouter } from "react-router-dom";
|
import { HashRouter } from "react-router-dom";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -20,20 +20,24 @@ export default {
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<RealmContextProvider>
|
<RealmContext.Provider
|
||||||
|
value={{
|
||||||
|
realm: "master",
|
||||||
|
setRealm: () => {},
|
||||||
|
realms: [
|
||||||
|
{ id: "master", realm: "Master" },
|
||||||
|
{ id: "photoz", realm: "Photoz" },
|
||||||
|
],
|
||||||
|
refresh: () => Promise.resolve(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Page
|
<Page
|
||||||
sidebar={
|
sidebar={
|
||||||
<PageSidebar
|
<PageSidebar
|
||||||
nav={
|
nav={
|
||||||
<Nav>
|
<Nav>
|
||||||
<NavList>
|
<NavList>
|
||||||
<RealmSelector
|
<RealmSelector />
|
||||||
realmList={[
|
|
||||||
{ id: "master", realm: "Master" },
|
|
||||||
{ id: "photoz", realm: "Photoz" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<NavItem id="default-link1" to="#default-link1" itemId={0}>
|
<NavItem id="default-link1" to="#default-link1" itemId={0}>
|
||||||
Link 1
|
Link 1
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
@ -57,7 +61,7 @@ export const Header = () => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</RealmContextProvider>
|
</RealmContext.Provider>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue