created bookmarkable tabs component (#296)

This commit is contained in:
Erik Jan de Wit 2021-01-13 21:47:40 +01:00 committed by GitHub
parent 45cda0a969
commit 0d9b4ff054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 51 deletions

View file

@ -12,7 +12,6 @@ import {
SelectVariant, SelectVariant,
Switch, Switch,
Tab, Tab,
Tabs,
TabTitleText, TabTitleText,
TextInput, TextInput,
ValidatedOptions, ValidatedOptions,
@ -26,6 +25,7 @@ import {
useAdminClient, useAdminClient,
asyncStateFetch, asyncStateFetch,
} from "../../context/auth/AdminClient"; } from "../../context/auth/AdminClient";
import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider"; import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
import { ViewHeader } from "../../components/view-header/ViewHeader"; import { ViewHeader } from "../../components/view-header/ViewHeader";
@ -39,7 +39,6 @@ export const ClientScopeForm = () => {
>(); >();
const history = useHistory(); const history = useHistory();
const [clientScope, setClientScope] = useState<ClientScopeRepresentation>(); const [clientScope, setClientScope] = useState<ClientScopeRepresentation>();
const [activeTab, setActiveTab] = useState(0);
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const providers = useLoginProviders(); const providers = useLoginProviders();
@ -103,13 +102,9 @@ export const ClientScopeForm = () => {
/> />
<PageSection variant="light"> <PageSection variant="light">
<Tabs <KeycloakTabs isBox>
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(key as number)}
isBox
>
<Tab <Tab
eventKey={0} eventKey="settings"
title={<TabTitleText>{t("common:settings")}</TabTitleText>} title={<TabTitleText>{t("common:settings")}</TabTitleText>}
> >
<Form <Form
@ -312,14 +307,14 @@ export const ClientScopeForm = () => {
</Tab> </Tab>
<Tab <Tab
isHidden={!id} isHidden={!id}
eventKey={1} eventKey="mappers"
title={<TabTitleText>{t("mappers")}</TabTitleText>} title={<TabTitleText>{t("mappers")}</TabTitleText>}
> >
{clientScope && ( {clientScope && (
<MapperList clientScope={clientScope} refresh={refresh} /> <MapperList clientScope={clientScope} refresh={refresh} />
)} )}
</Tab> </Tab>
</Tabs> </KeycloakTabs>
</PageSection> </PageSection>
</> </>
); );

View file

@ -6,7 +6,6 @@ import {
PageSection, PageSection,
Spinner, Spinner,
Tab, Tab,
Tabs,
TabTitleText, TabTitleText,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -33,6 +32,7 @@ import {
import { ClientScopes } from "./scopes/ClientScopes"; import { ClientScopes } from "./scopes/ClientScopes";
import { EvaluateScopes } from "./scopes/EvaluateScopes"; import { EvaluateScopes } from "./scopes/EvaluateScopes";
import { ServiceAccount } from "./service-account/ServiceAccount"; import { ServiceAccount } from "./service-account/ServiceAccount";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
type ClientDetailHeaderProps = { type ClientDetailHeaderProps = {
onChange: (...event: any[]) => void; onChange: (...event: any[]) => void;
@ -106,8 +106,6 @@ export const ClientDetails = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [activeTab, setActiveTab] = useState(0);
const [activeTab2, setActiveTab2] = useState(30);
const [client, setClient] = useState<ClientRepresentation>(); const [client, setClient] = useState<ClientRepresentation>();
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
@ -203,57 +201,49 @@ export const ClientDetails = () => {
)} )}
/> />
<PageSection variant="light"> <PageSection variant="light">
<Tabs <KeycloakTabs isBox>
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(key as number)}
isBox
>
<Tab <Tab
eventKey={0} eventKey="settings"
title={<TabTitleText>{t("common:settings")}</TabTitleText>} title={<TabTitleText>{t("common:settings")}</TabTitleText>}
> >
<ClientSettings form={form} save={save} /> <ClientSettings form={form} save={save} />
</Tab> </Tab>
{publicClient && ( {publicClient && (
<Tab <Tab
eventKey={1} eventKey="credentials"
title={<TabTitleText>{t("credentials")}</TabTitleText>} title={<TabTitleText>{t("credentials")}</TabTitleText>}
> >
<Credentials clientId={id} form={form} save={save} /> <Credentials clientId={id} form={form} save={save} />
</Tab> </Tab>
)} )}
<Tab <Tab
eventKey={2} eventKey="clientScopes"
title={<TabTitleText>{t("clientScopes")}</TabTitleText>} title={<TabTitleText>{t("clientScopes")}</TabTitleText>}
> >
<Tabs <KeycloakTabs paramName="subtab" isSecondary>
activeKey={activeTab2}
isSecondary
onSelect={(_, key) => setActiveTab2(key as number)}
>
<Tab <Tab
eventKey={30} eventKey="setup"
title={<TabTitleText>{t("setup")}</TabTitleText>} title={<TabTitleText>{t("setup")}</TabTitleText>}
> >
<ClientScopes clientId={id} protocol={client!.protocol!} /> <ClientScopes clientId={id} protocol={client!.protocol!} />
</Tab> </Tab>
<Tab <Tab
eventKey={31} eventKey="evaluate"
title={<TabTitleText>{t("evaluate")}</TabTitleText>} title={<TabTitleText>{t("evaluate")}</TabTitleText>}
> >
<EvaluateScopes clientId={id} protocol={client!.protocol!} /> <EvaluateScopes clientId={id} protocol={client!.protocol!} />
</Tab> </Tab>
</Tabs> </KeycloakTabs>
</Tab> </Tab>
{client && client.serviceAccountsEnabled && ( {client && client.serviceAccountsEnabled && (
<Tab <Tab
eventKey={3} eventKey="serviceAccount"
title={<TabTitleText>{t("serviceAccount")}</TabTitleText>} title={<TabTitleText>{t("serviceAccount")}</TabTitleText>}
> >
<ServiceAccount clientId={id} /> <ServiceAccount clientId={id} />
</Tab> </Tab>
)} )}
</Tabs> </KeycloakTabs>
</PageSection> </PageSection>
</> </>
); );

View file

@ -0,0 +1,53 @@
import React, { Children, isValidElement } from "react";
import { useHistory, useRouteMatch } from "react-router-dom";
import { TabProps, Tabs, TabsProps } from "@patternfly/react-core";
type KeycloakTabsProps = Omit<TabsProps, "ref" | "activeKey" | "onSelect"> & {
paramName?: string;
};
const createUrl = (
path: string,
params: { [index: string]: string }
): string => {
let url = path;
for (const key in params) {
const value = params[key];
if (url.indexOf(key) !== -1) {
url = url.replace(new RegExp(`:${key}\\??`), value || "");
} else {
url += `/${value}`;
}
}
return url;
};
export const KeycloakTabs = ({
paramName = "tab",
children,
...rest
}: KeycloakTabsProps) => {
const match = useRouteMatch();
const params = match.params as { [index: string]: string };
const history = useHistory();
const firstTab = Children.toArray(children)[0];
const tab =
params[paramName] ||
(isValidElement<TabProps>(firstTab) && firstTab.props.eventKey) ||
"";
return (
<Tabs
activeKey={tab}
onSelect={(_, key) =>
history.push(
createUrl(match.path, { ...params, [paramName]: key as string })
)
}
{...rest}
>
{children}
</Tabs>
);
};

View file

@ -5,7 +5,6 @@ import {
Label, Label,
PageSection, PageSection,
Tab, Tab,
Tabs,
TabTitleText, TabTitleText,
ToolbarItem, ToolbarItem,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
@ -19,13 +18,13 @@ import { RealmContext } from "../context/realm-context/RealmContext";
import { InfoCircleIcon } from "@patternfly/react-icons"; import { InfoCircleIcon } from "@patternfly/react-icons";
import { AdminEvents } from "./AdminEvents"; import { AdminEvents } from "./AdminEvents";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
export const EventsSection = () => { export const EventsSection = () => {
const { t } = useTranslation("events"); const { t } = useTranslation("events");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { realm } = useContext(RealmContext); const { realm } = useContext(RealmContext);
const [activeTab, setActiveTab] = useState(0);
const [key, setKey] = useState(""); const [key, setKey] = useState("");
const refresh = () => setKey(`${new Date().getTime()}`); const refresh = () => setKey(`${new Date().getTime()}`);
@ -55,13 +54,9 @@ export const EventsSection = () => {
<> <>
<ViewHeader titleKey="events:title" subKey="events:eventExplain" /> <ViewHeader titleKey="events:title" subKey="events:eventExplain" />
<PageSection variant="light"> <PageSection variant="light">
<Tabs <KeycloakTabs isBox>
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(key as number)}
isBox
>
<Tab <Tab
eventKey={0} eventKey="userEvents"
title={<TabTitleText>{t("userEvents")}</TabTitleText>} title={<TabTitleText>{t("userEvents")}</TabTitleText>}
> >
<KeycloakDataTable <KeycloakDataTable
@ -110,12 +105,12 @@ export const EventsSection = () => {
/> />
</Tab> </Tab>
<Tab <Tab
eventKey={1} eventKey="adminEvents"
title={<TabTitleText>{t("adminEvents")}</TabTitleText>} title={<TabTitleText>{t("adminEvents")}</TabTitleText>}
> >
<AdminEvents /> <AdminEvents />
</Tab> </Tab>
</Tabs> </KeycloakTabs>
</PageSection> </PageSection>
</> </>
); );

View file

@ -6,7 +6,6 @@ import {
DropdownItem, DropdownItem,
PageSection, PageSection,
Tab, Tab,
Tabs,
TabTitleText, TabTitleText,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -20,6 +19,7 @@ import { ViewHeader } from "../components/view-header/ViewHeader";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { RealmRoleForm } from "./RealmRoleForm"; import { RealmRoleForm } from "./RealmRoleForm";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
const arrayToAttributes = (attributeArray: KeyValueType[]) => { const arrayToAttributes = (attributeArray: KeyValueType[]) => {
const initValue: { [index: string]: string[] } = {}; const initValue: { [index: string]: string[] } = {};
@ -50,7 +50,6 @@ export const RealmRoleTabs = () => {
const history = useHistory(); const history = useHistory();
const [name, setName] = useState(""); const [name, setName] = useState("");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const [activeTab, setActiveTab] = useState(0);
const { realm } = useRealm(); const { realm } = useRealm();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@ -145,24 +144,20 @@ export const RealmRoleTabs = () => {
/> />
<PageSection variant="light"> <PageSection variant="light">
{id && ( {id && (
<Tabs <KeycloakTabs isBox>
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(key as number)}
isBox
>
<Tab <Tab
eventKey={0} eventKey="details"
title={<TabTitleText>{t("details")}</TabTitleText>} title={<TabTitleText>{t("details")}</TabTitleText>}
> >
<RealmRoleForm form={form} save={save} editMode={true} /> <RealmRoleForm form={form} save={save} editMode={true} />
</Tab> </Tab>
<Tab <Tab
eventKey={1} eventKey="attributes"
title={<TabTitleText>{t("attributes")}</TabTitleText>} title={<TabTitleText>{t("attributes")}</TabTitleText>}
> >
<RoleAttributes form={form} save={save} /> <RoleAttributes form={form} save={save} />
</Tab> </Tab>
</Tabs> </KeycloakTabs>
)} )}
{!id && <RealmRoleForm form={form} save={save} editMode={false} />} {!id && <RealmRoleForm form={form} save={save} editMode={false} />}
</PageSection> </PageSection>

View file

@ -63,6 +63,12 @@ export const routes: RoutesFn = (t: TFunction) => [
breadcrumb: t("clients:clientSettings"), breadcrumb: t("clients:clientSettings"),
access: "view-clients", access: "view-clients",
}, },
{
path: "/:realm/clients/:id/:tab?/:subtab?",
component: ClientDetails,
breadcrumb: null,
access: "view-clients",
},
{ {
path: "/:realm/client-scopes/new", path: "/:realm/client-scopes/new",
component: ClientScopeForm, component: ClientScopeForm,
@ -75,6 +81,12 @@ export const routes: RoutesFn = (t: TFunction) => [
breadcrumb: t("client-scopes:clientScopeDetails"), breadcrumb: t("client-scopes:clientScopeDetails"),
access: "view-clients", access: "view-clients",
}, },
{
path: "/:realm/client-scopes/:id/:tab",
component: ClientScopeForm,
breadcrumb: null,
access: "view-clients",
},
{ {
path: "/:realm/client-scopes/:scopeId/oidc-role-name-mapper", path: "/:realm/client-scopes/:scopeId/oidc-role-name-mapper",
component: RoleMappingForm, component: RoleMappingForm,
@ -117,6 +129,12 @@ export const routes: RoutesFn = (t: TFunction) => [
breadcrumb: t("roles:roleDetails"), breadcrumb: t("roles:roleDetails"),
access: "view-realm", access: "view-realm",
}, },
{
path: "/:realm/roles/:id/:tab",
component: RealmRoleTabs,
breadcrumb: null,
access: "view-realm",
},
{ {
path: "/:realm/users", path: "/:realm/users",
component: UsersSection, component: UsersSection,
@ -144,6 +162,12 @@ export const routes: RoutesFn = (t: TFunction) => [
breadcrumb: t("events:title"), breadcrumb: t("events:title"),
access: "view-events", access: "view-events",
}, },
{
path: "/:realm/events/:tab",
component: EventsSection,
breadcrumb: null,
access: "view-events",
},
{ {
path: "/:realm/realm-settings", path: "/:realm/realm-settings",
component: RealmSettingsSection, component: RealmSettingsSection,