Use realm name in urls (#265)
* remove circular dependency on realm context * added realm as a param of the url * updated links to include realm * null !== undefined * set realm if realm in url * fixed breadcrumb type * fixed tests * addressed pr review comments
This commit is contained in:
parent
27d9dadee7
commit
b14027ccb7
26 changed files with 697 additions and 589 deletions
|
@ -30,7 +30,7 @@
|
||||||
"react-hook-form": "^6.8.2",
|
"react-hook-form": "^6.8.2",
|
||||||
"react-i18next": "^11.7.0",
|
"react-i18next": "^11.7.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"use-react-router-breadcrumbs": "^1.0.4"
|
"use-react-router-breadcrumbs": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.10.5",
|
"@babel/core": "^7.10.5",
|
||||||
|
|
17
src/App.tsx
17
src/App.tsx
|
@ -1,6 +1,11 @@
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode, useEffect } from "react";
|
||||||
import { Page } from "@patternfly/react-core";
|
import { Page } from "@patternfly/react-core";
|
||||||
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
|
import {
|
||||||
|
BrowserRouter as Router,
|
||||||
|
Route,
|
||||||
|
Switch,
|
||||||
|
useParams,
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
import { Header } from "./PageHeader";
|
import { Header } from "./PageHeader";
|
||||||
import { PageNav } from "./PageNav";
|
import { PageNav } from "./PageNav";
|
||||||
|
@ -13,6 +18,7 @@ import { AccessContextProvider, useAccess } from "./context/access/Access";
|
||||||
import { routes, RouteDef } from "./route-config";
|
import { routes, RouteDef } from "./route-config";
|
||||||
import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs";
|
import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs";
|
||||||
import { ForbiddenSection } from "./ForbiddenSection";
|
import { ForbiddenSection } from "./ForbiddenSection";
|
||||||
|
import { useRealm } from "./context/realm-context/RealmContext";
|
||||||
|
|
||||||
// This must match the id given as scrollableSelector in scroll-form
|
// This must match the id given as scrollableSelector in scroll-form
|
||||||
const mainPageContentId = "kc-main-content-page-container";
|
const mainPageContentId = "kc-main-content-page-container";
|
||||||
|
@ -31,6 +37,13 @@ const AppContexts = ({ children }: { children: ReactNode }) => (
|
||||||
// have access to, show forbidden page.
|
// have access to, show forbidden page.
|
||||||
type SecuredRouteProps = { route: RouteDef };
|
type SecuredRouteProps = { route: RouteDef };
|
||||||
const SecuredRoute = ({ route }: SecuredRouteProps) => {
|
const SecuredRoute = ({ route }: SecuredRouteProps) => {
|
||||||
|
const { setRealm } = useRealm();
|
||||||
|
const { realm } = useParams<{ realm: string }>();
|
||||||
|
useEffect(() => {
|
||||||
|
if (realm) {
|
||||||
|
setRealm(realm);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
const { hasAccess } = useAccess();
|
const { hasAccess } = useAccess();
|
||||||
|
|
||||||
if (hasAccess(route.access)) return <route.component />;
|
if (hasAccess(route.access)) return <route.component />;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
PageSidebar,
|
PageSidebar,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { RealmSelector } from "./components/realm-selector/RealmSelector";
|
import { RealmSelector } from "./components/realm-selector/RealmSelector";
|
||||||
|
import { useRealm } from "./context/realm-context/RealmContext";
|
||||||
import { DataLoader } from "./components/data-loader/DataLoader";
|
import { DataLoader } from "./components/data-loader/DataLoader";
|
||||||
import { useAdminClient } from "./context/auth/AdminClient";
|
import { useAdminClient } from "./context/auth/AdminClient";
|
||||||
import { useAccess } from "./context/access/Access";
|
import { useAccess } from "./context/access/Access";
|
||||||
|
@ -44,13 +45,16 @@ export const PageNav: React.FunctionComponent = () => {
|
||||||
|
|
||||||
type LeftNavProps = { title: string; path: string };
|
type LeftNavProps = { title: string; path: string };
|
||||||
const LeftNav = ({ title, path }: LeftNavProps) => {
|
const LeftNav = ({ title, path }: LeftNavProps) => {
|
||||||
const route = routes(() => {}).find((route) => route.path === path);
|
const { realm } = useRealm();
|
||||||
|
const route = routes(() => {}).find(
|
||||||
|
(route) => route.path.substr("/:realm".length) === path
|
||||||
|
);
|
||||||
if (!route || !hasAccess(route.access)) return <></>;
|
if (!route || !hasAccess(route.access)) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavItem
|
<NavItem
|
||||||
id={"nav-item" + path.replace("/", "-")}
|
id={"nav-item" + path.replace("/", "-")}
|
||||||
to={path}
|
to={`/${realm}${path}`}
|
||||||
isActive={activeItem === path}
|
isActive={activeItem === path}
|
||||||
>
|
>
|
||||||
{t(title)}
|
{t(title)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import { Button, PageSection } from "@patternfly/react-core";
|
import { Button, PageSection } from "@patternfly/react-core";
|
||||||
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable
|
||||||
export const ClientScopesSection = () => {
|
export const ClientScopesSection = () => {
|
||||||
const { t } = useTranslation("client-scopes");
|
const { t } = useTranslation("client-scopes");
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ export const ClientScopesSection = () => {
|
||||||
|
|
||||||
const ClientScopeDetailLink = (clientScope: ClientScopeRepresentation) => (
|
const ClientScopeDetailLink = (clientScope: ClientScopeRepresentation) => (
|
||||||
<>
|
<>
|
||||||
<Link key={clientScope.id} to={`/client-scopes/${clientScope.id}`}>
|
<Link key={clientScope.id} to={`${url}/${clientScope.id}`}>
|
||||||
{clientScope.name}
|
{clientScope.name}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
@ -35,7 +36,7 @@ export const ClientScopesSection = () => {
|
||||||
ariaLabelKey="client-scopes:clientScopeList"
|
ariaLabelKey="client-scopes:clientScopeList"
|
||||||
searchPlaceholderKey="client-scopes:searchFor"
|
searchPlaceholderKey="client-scopes:searchFor"
|
||||||
toolbarItem={
|
toolbarItem={
|
||||||
<Button onClick={() => history.push("/client-scopes/new")}>
|
<Button onClick={() => history.push(`${url}/new`)}>
|
||||||
{t("createClientScope")}
|
{t("createClientScope")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
|
@ -44,6 +44,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
const [filteredData, setFilteredData] = useState<
|
const [filteredData, setFilteredData] = useState<
|
||||||
{ mapper: ProtocolMapperRepresentation; cells: Row }[]
|
{ mapper: ProtocolMapperRepresentation; cells: Row }[]
|
||||||
|
@ -70,7 +71,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (filter === undefined) {
|
if (filter === undefined) {
|
||||||
const mapper = mappers as ProtocolMapperTypeRepresentation;
|
const mapper = mappers as ProtocolMapperTypeRepresentation;
|
||||||
history.push(`/client-scopes/${clientScope.id}/${mapper.id}`);
|
history.push(`${url}/${mapper.id}`);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await adminClient.clientScopes.addMultipleProtocolMappers(
|
await adminClient.clientScopes.addMultipleProtocolMappers(
|
||||||
|
@ -122,7 +123,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
cells: {
|
cells: {
|
||||||
name: (
|
name: (
|
||||||
<>
|
<>
|
||||||
<Link to={`/client-scopes/${clientScope.id}/${mapper.id}`}>
|
<Link to={`${url}/${clientScope.id}/${mapper.id}`}>
|
||||||
{mapper.name}
|
{mapper.name}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { useHistory, useParams, useRouteMatch } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
|
@ -52,6 +52,7 @@ export const MappingDetails = () => {
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const serverInfo = useServerInfo();
|
const serverInfo = useServerInfo();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/;
|
const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -102,7 +103,7 @@ export const MappingDetails = () => {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
|
addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
|
||||||
history.push(`/client-scopes/${scopeId}`);
|
history.push(`${url}/${scopeId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(t("mappingDeletedError", { error }), AlertVariant.danger);
|
addAlert(t("mappingDeletedError", { error }), AlertVariant.danger);
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,7 +307,11 @@ export const ClientScopeForm = () => {
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</Form>
|
</Form>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey={1} title={<TabTitleText>{t("mappers")}</TabTitleText>}>
|
<Tab
|
||||||
|
isHidden={!id}
|
||||||
|
eventKey={1}
|
||||||
|
title={<TabTitleText>{t("mappers")}</TabTitleText>}
|
||||||
|
>
|
||||||
{clientScope && (
|
{clientScope && (
|
||||||
<MapperList clientScope={clientScope} refresh={refresh} />
|
<MapperList clientScope={clientScope} refresh={refresh} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
|
@ -21,6 +21,7 @@ export const ClientsSection = () => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const baseUrl = adminClient.keycloak
|
const baseUrl = adminClient.keycloak
|
||||||
|
@ -51,7 +52,7 @@ export const ClientsSection = () => {
|
||||||
|
|
||||||
const ClientDetailLink = (client: ClientRepresentation) => (
|
const ClientDetailLink = (client: ClientRepresentation) => (
|
||||||
<>
|
<>
|
||||||
<Link key={client.id} to={`/clients/${client.id}`}>
|
<Link key={client.id} to={`${url}/${client.id}`}>
|
||||||
{client.clientId}
|
{client.clientId}
|
||||||
{!client.enabled && <Badge isRead>Disabled</Badge>}
|
{!client.enabled && <Badge isRead>Disabled</Badge>}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -71,11 +72,11 @@ export const ClientsSection = () => {
|
||||||
searchPlaceholderKey="clients:searchForClient"
|
searchPlaceholderKey="clients:searchForClient"
|
||||||
toolbarItem={
|
toolbarItem={
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => history.push("/add-client")}>
|
<Button onClick={() => history.push(`${url}/add-client`)}>
|
||||||
{t("createClient")}
|
{t("createClient")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => history.push("/import-client")}
|
onClick={() => history.push(`${url}/import-client`)}
|
||||||
variant="link"
|
variant="link"
|
||||||
>
|
>
|
||||||
{t("importClient")}
|
{t("importClient")}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
PageSection,
|
PageSection,
|
||||||
Wizard,
|
Wizard,
|
||||||
|
@ -89,6 +89,7 @@ export const NewClientForm = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const title = t("createClient");
|
const title = t("createClient");
|
||||||
|
const { url } = useRouteMatch();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
|
@ -97,7 +98,7 @@ export const NewClientForm = () => {
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
<Wizard
|
<Wizard
|
||||||
onClose={() => history.push("/clients")}
|
onClose={() => history.push(`${url}/clients`)}
|
||||||
navAriaLabel={`${title} steps`}
|
navAriaLabel={`${title} steps`}
|
||||||
mainAriaLabel={`${title} content`}
|
mainAriaLabel={`${title} content`}
|
||||||
steps={[
|
steps={[
|
||||||
|
|
|
@ -4,11 +4,15 @@ import useBreadcrumbs from "use-react-router-breadcrumbs";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core";
|
import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { routes } from "../../route-config";
|
import { routes } from "../../route-config";
|
||||||
|
|
||||||
export const PageBreadCrumbs = () => {
|
export const PageBreadCrumbs = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const crumbs = useBreadcrumbs(routes(t), { excludePaths: ["/"] });
|
const { realm } = useRealm();
|
||||||
|
const crumbs = useBreadcrumbs(routes(t), {
|
||||||
|
excludePaths: ["/", `/${realm}`],
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{crumbs.length > 1 && (
|
{crumbs.length > 1 && (
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { MemoryRouter } from "react-router-dom";
|
||||||
describe("BreadCrumbs tests", () => {
|
describe("BreadCrumbs tests", () => {
|
||||||
it("couple of crumbs", () => {
|
it("couple of crumbs", () => {
|
||||||
const crumbs = mount(
|
const crumbs = mount(
|
||||||
<MemoryRouter initialEntries={["/clients/1234"]}>
|
<MemoryRouter initialEntries={["/master/clients/1234"]}>
|
||||||
<PageBreadCrumbs />
|
<PageBreadCrumbs />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,20 +22,20 @@ exports[`BreadCrumbs tests couple of crumbs 1`] = `
|
||||||
className="pf-c-breadcrumb__item"
|
className="pf-c-breadcrumb__item"
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
to="/clients"
|
to="/master"
|
||||||
>
|
>
|
||||||
<LinkAnchor
|
<LinkAnchor
|
||||||
href="/clients"
|
href="/master"
|
||||||
navigate={[Function]}
|
navigate={[Function]}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="/clients"
|
href="/master"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
key="/clients"
|
key="/master"
|
||||||
>
|
>
|
||||||
Clients
|
Home
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</LinkAnchor>
|
</LinkAnchor>
|
||||||
|
@ -43,7 +43,7 @@ exports[`BreadCrumbs tests couple of crumbs 1`] = `
|
||||||
</li>
|
</li>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
isActive={true}
|
isActive={false}
|
||||||
key=".$1"
|
key=".$1"
|
||||||
showDivider={true}
|
showDivider={true}
|
||||||
>
|
>
|
||||||
|
@ -78,8 +78,65 @@ exports[`BreadCrumbs tests couple of crumbs 1`] = `
|
||||||
</svg>
|
</svg>
|
||||||
</AngleRightIcon>
|
</AngleRightIcon>
|
||||||
</span>
|
</span>
|
||||||
|
<Link
|
||||||
|
to="/master/clients"
|
||||||
|
>
|
||||||
|
<LinkAnchor
|
||||||
|
href="/master/clients"
|
||||||
|
navigate={[Function]}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/master/clients"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
key="/clients/1234"
|
key="/master/clients"
|
||||||
|
>
|
||||||
|
Clients
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</LinkAnchor>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem
|
||||||
|
isActive={true}
|
||||||
|
key=".$2"
|
||||||
|
showDivider={true}
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
className="pf-c-breadcrumb__item"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pf-c-breadcrumb__item-divider"
|
||||||
|
>
|
||||||
|
<AngleRightIcon
|
||||||
|
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 256 512"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</AngleRightIcon>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
key="/master/clients/1234"
|
||||||
>
|
>
|
||||||
Client details
|
Client details
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
FlexItem,
|
FlexItem,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import "./keycloak-card.css";
|
import "./keycloak-card.css";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||||
|
|
||||||
export type KeycloakCardProps = {
|
export type KeycloakCardProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -38,6 +38,7 @@ export const KeycloakCard = ({
|
||||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
const onDropdownToggle = () => {
|
const onDropdownToggle = () => {
|
||||||
setIsDropdownOpen(!isDropdownOpen);
|
setIsDropdownOpen(!isDropdownOpen);
|
||||||
|
@ -48,11 +49,7 @@ export const KeycloakCard = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const openSettings = () => {
|
const openSettings = () => {
|
||||||
if (providerId === "kerberos") {
|
history.push(`${url}/${providerId}/${id}`);
|
||||||
history.push(`/user-federation/Kerberos/${id}`);
|
|
||||||
} else {
|
|
||||||
history.push(`/user-federation/LDAP/${id}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useContext, useEffect } from "react";
|
import React, { useState, useContext, useEffect } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -16,7 +16,7 @@ import {
|
||||||
import { CheckIcon } from "@patternfly/react-icons";
|
import { CheckIcon } from "@patternfly/react-icons";
|
||||||
|
|
||||||
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
import { RealmContext } 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 "./realm-selector.css";
|
import "./realm-selector.css";
|
||||||
|
@ -26,13 +26,14 @@ type RealmSelectorProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
const { realm, setRealm } = useContext(RealmContext);
|
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(realmList);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
const toUpperCase = (realmName: string) =>
|
const toUpperCase = (realmName: string) =>
|
||||||
realmName.charAt(0).toUpperCase() + realmName.slice(1);
|
realmName.charAt(0).toUpperCase() + realmName.slice(1);
|
||||||
|
@ -48,7 +49,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
<Button
|
<Button
|
||||||
component="div"
|
component="div"
|
||||||
isBlock
|
isBlock
|
||||||
onClick={() => history.push("/add-realm")}
|
onClick={() => history.push(`${url}/add-realm"`)}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
{t("createRealm")}
|
{t("createRealm")}
|
||||||
|
@ -74,6 +75,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
||||||
key={`realm-dropdown-item-${r.realm}`}
|
key={`realm-dropdown-item-${r.realm}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRealm(r.realm!);
|
setRealm(r.realm!);
|
||||||
|
history.push(`/${r.realm}`);
|
||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -4,20 +4,23 @@ import { act } from "@testing-library/react";
|
||||||
|
|
||||||
import { RealmSelector } from "../RealmSelector";
|
import { RealmSelector } from "../RealmSelector";
|
||||||
import { RealmContextProvider } from "../../../context/realm-context/RealmContext";
|
import { RealmContextProvider } from "../../../context/realm-context/RealmContext";
|
||||||
|
import { MemoryRouter } from "react-router-dom";
|
||||||
|
|
||||||
it("renders realm selector", async () => {
|
it("renders realm selector", async () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
|
<MemoryRouter>
|
||||||
<RealmContextProvider>
|
<RealmContextProvider>
|
||||||
|
<div id="realm">
|
||||||
<RealmSelector realmList={[{ id: "321", realm: "another" }]} />
|
<RealmSelector realmList={[{ id: "321", realm: "another" }]} />
|
||||||
|
</div>
|
||||||
</RealmContextProvider>
|
</RealmContextProvider>
|
||||||
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.text()).toBe("Master");
|
|
||||||
|
|
||||||
const expandButton = wrapper.find("button");
|
const expandButton = wrapper.find("button");
|
||||||
act(() => {
|
act(() => {
|
||||||
expandButton!.simulate("click");
|
expandButton!.simulate("click");
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper.find("#realm")).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders realm selector 1`] = `
|
exports[`renders realm selector 1`] = `
|
||||||
<RealmContextProvider>
|
<div
|
||||||
|
id="realm"
|
||||||
|
>
|
||||||
<RealmSelector
|
<RealmSelector
|
||||||
realmList={
|
realmList={
|
||||||
Array [
|
Array [
|
||||||
|
@ -34,7 +36,7 @@ exports[`renders realm selector 1`] = `
|
||||||
id="realm-select-toggle"
|
id="realm-select-toggle"
|
||||||
onToggle={[Function]}
|
onToggle={[Function]}
|
||||||
>
|
>
|
||||||
Master
|
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -67,7 +69,7 @@ exports[`renders realm selector 1`] = `
|
||||||
id="realm-select-toggle"
|
id="realm-select-toggle"
|
||||||
onToggle={[Function]}
|
onToggle={[Function]}
|
||||||
>
|
>
|
||||||
Master
|
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -107,11 +109,7 @@ exports[`renders realm selector 1`] = `
|
||||||
id="realm-select-toggle"
|
id="realm-select-toggle"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
class="pf-c-dropdown__toggle-text"
|
|
||||||
>
|
|
||||||
Master
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
class="pf-c-dropdown__toggle-icon"
|
class="pf-c-dropdown__toggle-icon"
|
||||||
>
|
>
|
||||||
|
@ -201,11 +199,7 @@ exports[`renders realm selector 1`] = `
|
||||||
id="realm-select-toggle"
|
id="realm-select-toggle"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
class="pf-c-dropdown__toggle-text"
|
|
||||||
>
|
|
||||||
Master
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
class="pf-c-dropdown__toggle-icon"
|
class="pf-c-dropdown__toggle-icon"
|
||||||
>
|
>
|
||||||
|
@ -272,11 +266,6 @@ exports[`renders realm selector 1`] = `
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
className="pf-c-dropdown__toggle-text"
|
|
||||||
>
|
|
||||||
Master
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
className="pf-c-dropdown__toggle-icon"
|
className="pf-c-dropdown__toggle-icon"
|
||||||
>
|
>
|
||||||
|
@ -312,5 +301,5 @@ exports[`renders realm selector 1`] = `
|
||||||
</DropdownWithContext>
|
</DropdownWithContext>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</RealmSelector>
|
</RealmSelector>
|
||||||
</RealmContextProvider>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import React, { useState, useContext } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
|
||||||
|
|
||||||
export const RealmContext = React.createContext({
|
type RealmContextType = {
|
||||||
|
realm: string;
|
||||||
|
setRealm: (realm: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RealmContext = React.createContext<RealmContextType>({
|
||||||
realm: "",
|
realm: "",
|
||||||
setRealm: (realm: string) => {},
|
setRealm: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
type RealmContextProviderProps = { children: React.ReactNode };
|
type RealmContextProviderProps = { children: React.ReactNode };
|
||||||
|
@ -11,8 +15,7 @@ type RealmContextProviderProps = { children: React.ReactNode };
|
||||||
export const RealmContextProvider = ({
|
export const RealmContextProvider = ({
|
||||||
children,
|
children,
|
||||||
}: RealmContextProviderProps) => {
|
}: RealmContextProviderProps) => {
|
||||||
const homeRealm = useContext(WhoAmIContext).getHomeRealm();
|
const [realm, setRealm] = useState("");
|
||||||
const [realm, setRealm] = useState(homeRealm);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RealmContext.Provider value={{ realm, setRealm }}>
|
<RealmContext.Provider value={{ realm, setRealm }}>
|
||||||
|
@ -20,3 +23,5 @@ export const RealmContextProvider = ({
|
||||||
</RealmContext.Provider>
|
</RealmContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useRealm = () => useContext(RealmContext);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import i18n from "../../i18n";
|
||||||
|
|
||||||
import { DataLoader } from "../../components/data-loader/DataLoader";
|
import { DataLoader } from "../../components/data-loader/DataLoader";
|
||||||
import { AdminClient } from "../auth/AdminClient";
|
import { AdminClient } 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";
|
||||||
|
@ -54,17 +55,23 @@ export const WhoAmIContext = React.createContext(new WhoAmI());
|
||||||
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 = useContext(AdminClient)!;
|
||||||
|
const { realm, setRealm } = useContext(RealmContext);
|
||||||
|
|
||||||
const whoAmILoader = async () => {
|
const whoAmILoader = async () => {
|
||||||
return await adminClient.whoAmI.find();
|
const whoamiResponse = await adminClient.whoAmI.find({
|
||||||
|
realm: adminClient.keycloak?.realm,
|
||||||
|
});
|
||||||
|
const whoAmI = new WhoAmI(adminClient.keycloak?.realm, whoamiResponse);
|
||||||
|
if (!realm) {
|
||||||
|
setRealm(whoAmI.getHomeRealm());
|
||||||
|
}
|
||||||
|
return whoAmI;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataLoader loader={whoAmILoader}>
|
<DataLoader loader={whoAmILoader}>
|
||||||
{(whoamirep) => (
|
{(whoami) => (
|
||||||
<WhoAmIContext.Provider
|
<WhoAmIContext.Provider value={whoami.data}>
|
||||||
value={new WhoAmI(adminClient.keycloak?.realm, whoamirep.data)}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</WhoAmIContext.Provider>
|
</WhoAmIContext.Provider>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||||
|
|
||||||
import "./GroupsSection.css";
|
import "./GroupsSection.css";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useRouteMatch } from "react-router-dom";
|
||||||
|
|
||||||
type GroupTableData = GroupRepresentation & {
|
type GroupTableData = GroupRepresentation & {
|
||||||
membersLength?: number;
|
membersLength?: number;
|
||||||
|
@ -35,6 +35,7 @@ export const GroupsSection = () => {
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [selectedRows, setSelectedRows] = useState<GroupRepresentation[]>([]);
|
const [selectedRows, setSelectedRows] = useState<GroupRepresentation[]>([]);
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
const [key, setKey] = useState("");
|
const [key, setKey] = useState("");
|
||||||
const refresh = () => setKey(`${new Date().getTime()}`);
|
const refresh = () => setKey(`${new Date().getTime()}`);
|
||||||
|
|
||||||
|
@ -82,7 +83,7 @@ export const GroupsSection = () => {
|
||||||
|
|
||||||
const GroupNameCell = (group: GroupTableData) => (
|
const GroupNameCell = (group: GroupTableData) => (
|
||||||
<>
|
<>
|
||||||
<Link key={group.id} to={`/groups/${group.id}`}>
|
<Link key={group.id} to={`${url}/${group.id}`}>
|
||||||
{group.name}
|
{group.name}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { RoleAttributes } from "./RoleAttributes";
|
import { RoleAttributes } from "./RoleAttributes";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
|
||||||
type RoleFormType = {
|
type RoleFormType = {
|
||||||
form?: UseFormMethods;
|
form?: UseFormMethods;
|
||||||
|
@ -37,6 +38,7 @@ type RoleFormType = {
|
||||||
export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { realm } = useRealm();
|
||||||
return (
|
return (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
isHorizontal
|
isHorizontal
|
||||||
|
@ -90,7 +92,7 @@ export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
||||||
<Button variant="primary" type="submit">
|
<Button variant="primary" type="submit">
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link" onClick={() => history.push("/roles/")}>
|
<Button variant="link" onClick={() => history.push(`/${realm}/roles`)}>
|
||||||
{editMode ? t("common:reload") : t("common:cancel")}
|
{editMode ? t("common:reload") : t("common:cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
|
@ -104,6 +106,7 @@ export const RealmRolesForm = () => {
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
@ -143,7 +146,7 @@ export const RealmRolesForm = () => {
|
||||||
const createdRole = await adminClient.roles.findOneByName({
|
const createdRole = await adminClient.roles.findOneByName({
|
||||||
name: role.name!,
|
name: role.name!,
|
||||||
});
|
});
|
||||||
history.push(`/roles/${createdRole.id}`);
|
history.push(`/${realm}/roles/${createdRole.id}`);
|
||||||
}
|
}
|
||||||
addAlert(t(id ? "roleSaveSuccess" : "roleCreated"), AlertVariant.success);
|
addAlert(t(id ? "roleSaveSuccess" : "roleCreated"), AlertVariant.success);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -163,7 +166,7 @@ export const RealmRolesForm = () => {
|
||||||
try {
|
try {
|
||||||
await adminClient.roles.delById({ id });
|
await adminClient.roles.delById({ id });
|
||||||
addAlert(t("roleDeletedSuccess"), AlertVariant.success);
|
addAlert(t("roleDeletedSuccess"), AlertVariant.success);
|
||||||
history.push("/roles");
|
history.push(`/${realm}/roles`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(`${t("roleDeleteError")} ${error}`, AlertVariant.danger);
|
addAlert(`${t("roleDeleteError")} ${error}`, AlertVariant.danger);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
||||||
import { RoleAttributes } from "./RoleAttributes";
|
import { RoleAttributes } from "./RoleAttributes";
|
||||||
import "./RealmRolesSection.css";
|
import "./RealmRolesSection.css";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
|
||||||
export const RolesTabs = () => {
|
export const RolesTabs = () => {
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
|
@ -29,6 +30,7 @@ export const RolesTabs = () => {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
const { realm } = useRealm();
|
||||||
|
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
@ -118,7 +120,10 @@ export const RolesTabs = () => {
|
||||||
<Button variant="primary" type="submit">
|
<Button variant="primary" type="submit">
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link" onClick={() => history.push("/roles/")}>
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={() => history.push(`/${realm}/roles`)}
|
||||||
|
>
|
||||||
{t("common:reload")}
|
{t("common:reload")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
|
@ -133,7 +138,10 @@ export const RolesTabs = () => {
|
||||||
<Button variant="primary" type="submit">
|
<Button variant="primary" type="submit">
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link" onClick={() => history.push("/roles/")}>
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={() => history.push(`/${realm}/roles`)}
|
||||||
|
>
|
||||||
{t("common:reload")}
|
{t("common:reload")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
|
@ -23,6 +23,7 @@ export const RealmRolesSection = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
const [selectedRole, setSelectedRole] = useState<RoleRepresentation>();
|
const [selectedRole, setSelectedRole] = useState<RoleRepresentation>();
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ export const RealmRolesSection = () => {
|
||||||
|
|
||||||
const RoleDetailLink = (role: RoleRepresentation) => (
|
const RoleDetailLink = (role: RoleRepresentation) => (
|
||||||
<>
|
<>
|
||||||
<Link key={role.id} to={`/roles/${role.id}`}>
|
<Link key={role.id} to={`${url}/${role.id}`}>
|
||||||
{role.name}
|
{role.name}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
@ -81,7 +82,7 @@ export const RealmRolesSection = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const goToCreate = () => history.push("/roles/add-role");
|
const goToCreate = () => history.push(`${url}/add-role`);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewHeader titleKey="roles:title" subKey="roles:roleExplain" />
|
<ViewHeader titleKey="roles:title" subKey="roles:roleExplain" />
|
||||||
|
|
|
@ -23,173 +23,178 @@ import { ClientDetails } from "./clients/ClientDetails";
|
||||||
import { UserFederationKerberosSettings } from "./user-federation/UserFederationKerberosSettings";
|
import { UserFederationKerberosSettings } from "./user-federation/UserFederationKerberosSettings";
|
||||||
import { UserFederationLdapSettings } from "./user-federation/UserFederationLdapSettings";
|
import { UserFederationLdapSettings } from "./user-federation/UserFederationLdapSettings";
|
||||||
import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm";
|
import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm";
|
||||||
|
import { BreadcrumbsRoute } from "use-react-router-breadcrumbs";
|
||||||
|
|
||||||
export type RouteDef = {
|
export type RouteDef = BreadcrumbsRoute & {
|
||||||
path: string;
|
|
||||||
component: () => JSX.Element;
|
component: () => JSX.Element;
|
||||||
breadcrumb: TFunction | "";
|
|
||||||
access: AccessType;
|
access: AccessType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RoutesFn = (t: TFunction) => RouteDef[];
|
type RoutesFn = (t: TFunction) => RouteDef[];
|
||||||
|
|
||||||
export const routes: RoutesFn = (t: any) => [
|
export const routes: RoutesFn = (t) => [
|
||||||
{
|
{
|
||||||
path: "/add-realm",
|
path: "/:realm/add-realm",
|
||||||
component: NewRealmForm,
|
component: NewRealmForm,
|
||||||
breadcrumb: t("realm:createRealm"),
|
breadcrumb: t("realm:createRealm"),
|
||||||
access: "manage-realm",
|
access: "manage-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/clients/:id",
|
path: "/:realm/clients",
|
||||||
component: ClientDetails,
|
|
||||||
breadcrumb: t("clients:clientSettings"),
|
|
||||||
access: "view-clients",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/clients",
|
|
||||||
component: ClientsSection,
|
component: ClientsSection,
|
||||||
breadcrumb: t("clients:clientList"),
|
breadcrumb: t("clients:clientList"),
|
||||||
access: "query-clients",
|
access: "query-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/add-client",
|
path: "/:realm/clients/add-client",
|
||||||
component: NewClientForm,
|
component: NewClientForm,
|
||||||
breadcrumb: t("clients:createClient"),
|
breadcrumb: t("clients:createClient"),
|
||||||
access: "manage-clients",
|
access: "manage-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/import-client",
|
path: "/:realm/clients/import-client",
|
||||||
component: ImportForm,
|
component: ImportForm,
|
||||||
breadcrumb: t("clients:importClient"),
|
breadcrumb: t("clients:importClient"),
|
||||||
access: "manage-clients",
|
access: "manage-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/client-scopes/new",
|
path: "/:realm/clients/:id",
|
||||||
|
component: ClientDetails,
|
||||||
|
breadcrumb: t("clients:clientSettings"),
|
||||||
|
access: "view-clients",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/:realm/client-scopes/new",
|
||||||
component: ClientScopeForm,
|
component: ClientScopeForm,
|
||||||
breadcrumb: t("client-scopes:createClientScope"),
|
breadcrumb: t("client-scopes:createClientScope"),
|
||||||
access: "manage-clients",
|
access: "manage-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/client-scopes/:id",
|
path: "/:realm/client-scopes/:id",
|
||||||
component: ClientScopeForm,
|
component: ClientScopeForm,
|
||||||
breadcrumb: t("client-scopes:clientScopeDetails"),
|
breadcrumb: t("client-scopes:clientScopeDetails"),
|
||||||
access: "view-clients",
|
access: "view-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/client-scopes/:scopeId/oidc-role-name-mapper",
|
path: "/:realm/client-scopes/:scopeId/oidc-role-name-mapper",
|
||||||
component: RoleMappingForm,
|
component: RoleMappingForm,
|
||||||
breadcrumb: t("client-scopes:mappingDetails"),
|
breadcrumb: t("client-scopes:mappingDetails"),
|
||||||
access: "view-clients",
|
access: "view-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/client-scopes/:scopeId/:id",
|
path: "/:realm/client-scopes/:scopeId/:id",
|
||||||
component: MappingDetails,
|
component: MappingDetails,
|
||||||
breadcrumb: t("client-scopes:mappingDetails"),
|
breadcrumb: t("client-scopes:mappingDetails"),
|
||||||
access: "view-clients",
|
access: "view-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/client-scopes/:id",
|
path: "/:realm/client-scopes/:id",
|
||||||
component: ClientScopeForm,
|
component: ClientScopeForm,
|
||||||
breadcrumb: t("client-scopes:clientScopeDetails"),
|
breadcrumb: t("client-scopes:clientScopeDetails"),
|
||||||
access: "view-clients",
|
access: "view-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/client-scopes",
|
path: "/:realm/client-scopes",
|
||||||
component: ClientScopesSection,
|
component: ClientScopesSection,
|
||||||
breadcrumb: t("client-scopes:clientScopeList"),
|
breadcrumb: t("client-scopes:clientScopeList"),
|
||||||
access: "view-clients",
|
access: "view-clients",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/roles",
|
path: "/:realm/roles",
|
||||||
component: RealmRolesSection,
|
component: RealmRolesSection,
|
||||||
breadcrumb: t("roles:roleList"),
|
breadcrumb: t("roles:roleList"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/roles/add-role",
|
path: "/:realm/roles/add-role",
|
||||||
component: RealmRolesForm,
|
component: RealmRolesForm,
|
||||||
breadcrumb: t("roles:createRole"),
|
breadcrumb: t("roles:createRole"),
|
||||||
access: "manage-realm",
|
access: "manage-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/roles/:id",
|
path: "/:realm/roles/:id",
|
||||||
component: RealmRolesForm,
|
component: RealmRolesForm,
|
||||||
breadcrumb: t("roles:roleDetails"),
|
breadcrumb: t("roles:roleDetails"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/users",
|
path: "/:realm/users",
|
||||||
component: UsersSection,
|
component: UsersSection,
|
||||||
breadcrumb: t("users:title"),
|
breadcrumb: t("users:title"),
|
||||||
access: "query-users",
|
access: "query-users",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/groups",
|
path: "/:realm/groups",
|
||||||
component: GroupsSection,
|
component: GroupsSection,
|
||||||
breadcrumb: t("groups"),
|
breadcrumb: t("groups"),
|
||||||
access: "query-groups",
|
access: "query-groups",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/sessions",
|
path: "/:realm/sessions",
|
||||||
component: SessionsSection,
|
component: SessionsSection,
|
||||||
breadcrumb: t("sessions:title"),
|
breadcrumb: t("sessions:title"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/events",
|
path: "/:realm/events",
|
||||||
component: EventsSection,
|
component: EventsSection,
|
||||||
breadcrumb: t("events:title"),
|
breadcrumb: t("events:title"),
|
||||||
access: "view-events",
|
access: "view-events",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/realm-settings",
|
path: "/:realm/realm-settings",
|
||||||
component: RealmSettingsSection,
|
component: RealmSettingsSection,
|
||||||
breadcrumb: t("realmSettings"),
|
breadcrumb: t("realmSettings"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/authentication",
|
path: "/:realm/authentication",
|
||||||
component: AuthenticationSection,
|
component: AuthenticationSection,
|
||||||
breadcrumb: t("authentication"),
|
breadcrumb: t("authentication"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/identity-providers",
|
path: "/:realm/identity-providers",
|
||||||
component: IdentityProvidersSection,
|
component: IdentityProvidersSection,
|
||||||
breadcrumb: t("identityProviders"),
|
breadcrumb: t("identityProviders"),
|
||||||
access: "view-identity-providers",
|
access: "view-identity-providers",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/user-federation",
|
path: "/:realm/user-federation",
|
||||||
component: UserFederationSection,
|
component: UserFederationSection,
|
||||||
breadcrumb: t("userFederation"),
|
breadcrumb: t("userFederation"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/user-federation/kerberos",
|
path: "/:realm/user-federation/kerberos",
|
||||||
component: UserFederationSection,
|
component: UserFederationSection,
|
||||||
breadcrumb: null,
|
breadcrumb: null,
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/user-federation/ldap",
|
path: "/:realm/user-federation/ldap",
|
||||||
component: UserFederationSection,
|
component: UserFederationSection,
|
||||||
breadcrumb: null,
|
breadcrumb: null,
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/user-federation/kerberos/:id",
|
path: "/:realm/user-federation/kerberos/:id",
|
||||||
component: UserFederationKerberosSettings,
|
component: UserFederationKerberosSettings,
|
||||||
breadcrumb: t("common:settings"),
|
breadcrumb: t("common:settings"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/user-federation/ldap/:id",
|
path: "/:realm/user-federation/ldap/:id",
|
||||||
component: UserFederationLdapSettings,
|
component: UserFederationLdapSettings,
|
||||||
breadcrumb: t("common:settings"),
|
breadcrumb: t("common:settings"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/:realm/",
|
||||||
|
component: ClientsSection,
|
||||||
|
breadcrumb: t("common:home"),
|
||||||
|
access: "anyone",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
component: ClientsSection,
|
component: ClientsSection,
|
||||||
|
@ -199,7 +204,7 @@ export const routes: RoutesFn = (t: any) => [
|
||||||
{
|
{
|
||||||
path: "*",
|
path: "*",
|
||||||
component: PageNotFoundSection,
|
component: PageNotFoundSection,
|
||||||
breadcrumb: "",
|
breadcrumb: null,
|
||||||
access: "anyone",
|
access: "anyone",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -19357,9 +19357,9 @@ use-latest@^1.0.0:
|
||||||
use-isomorphic-layout-effect "^1.0.0"
|
use-isomorphic-layout-effect "^1.0.0"
|
||||||
|
|
||||||
use-react-router-breadcrumbs@^1.0.4:
|
use-react-router-breadcrumbs@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/use-react-router-breadcrumbs/-/use-react-router-breadcrumbs-1.0.4.tgz#12b67ba27ac7e6a00e6ae10896ea91e178e87ee2"
|
resolved "https://registry.yarnpkg.com/use-react-router-breadcrumbs/-/use-react-router-breadcrumbs-1.0.5.tgz#3b39a2c2a6ab72544c2fc8984f6825d0f1122877"
|
||||||
integrity sha512-SskKm+wFYPD7eiYrg89y1Wn8vMlY+DiZXNFuP4Wt5gMP2aolcahHGR6pRTWsfMW93CEQxdVkXv/ceHL7nfz2Fw==
|
integrity sha512-NDMgWr5MdksqnATRvp84RtZ0ABfuztlsgR4VWlsBV0D3TVV6xhbmkhTdV3cWnyRIZqNlMXZhwJhyRHoC6fbAsQ==
|
||||||
|
|
||||||
use-sidecar@^1.0.1:
|
use-sidecar@^1.0.1:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
|
|
Loading…
Reference in a new issue