created bookmarkable tabs component (#296)
This commit is contained in:
parent
45cda0a969
commit
0d9b4ff054
6 changed files with 103 additions and 51 deletions
|
@ -12,7 +12,6 @@ import {
|
|||
SelectVariant,
|
||||
Switch,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabTitleText,
|
||||
TextInput,
|
||||
ValidatedOptions,
|
||||
|
@ -26,6 +25,7 @@ import {
|
|||
useAdminClient,
|
||||
asyncStateFetch,
|
||||
} from "../../context/auth/AdminClient";
|
||||
import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
|
@ -39,7 +39,6 @@ export const ClientScopeForm = () => {
|
|||
>();
|
||||
const history = useHistory();
|
||||
const [clientScope, setClientScope] = useState<ClientScopeRepresentation>();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const providers = useLoginProviders();
|
||||
|
@ -103,13 +102,9 @@ export const ClientScopeForm = () => {
|
|||
/>
|
||||
|
||||
<PageSection variant="light">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onSelect={(_, key) => setActiveTab(key as number)}
|
||||
isBox
|
||||
>
|
||||
<KeycloakTabs isBox>
|
||||
<Tab
|
||||
eventKey={0}
|
||||
eventKey="settings"
|
||||
title={<TabTitleText>{t("common:settings")}</TabTitleText>}
|
||||
>
|
||||
<Form
|
||||
|
@ -312,14 +307,14 @@ export const ClientScopeForm = () => {
|
|||
</Tab>
|
||||
<Tab
|
||||
isHidden={!id}
|
||||
eventKey={1}
|
||||
eventKey="mappers"
|
||||
title={<TabTitleText>{t("mappers")}</TabTitleText>}
|
||||
>
|
||||
{clientScope && (
|
||||
<MapperList clientScope={clientScope} refresh={refresh} />
|
||||
)}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</KeycloakTabs>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
PageSection,
|
||||
Spinner,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabTitleText,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -33,6 +32,7 @@ import {
|
|||
import { ClientScopes } from "./scopes/ClientScopes";
|
||||
import { EvaluateScopes } from "./scopes/EvaluateScopes";
|
||||
import { ServiceAccount } from "./service-account/ServiceAccount";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
|
||||
type ClientDetailHeaderProps = {
|
||||
onChange: (...event: any[]) => void;
|
||||
|
@ -106,8 +106,6 @@ export const ClientDetails = () => {
|
|||
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [activeTab2, setActiveTab2] = useState(30);
|
||||
const [client, setClient] = useState<ClientRepresentation>();
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
|
@ -203,57 +201,49 @@ export const ClientDetails = () => {
|
|||
)}
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onSelect={(_, key) => setActiveTab(key as number)}
|
||||
isBox
|
||||
>
|
||||
<KeycloakTabs isBox>
|
||||
<Tab
|
||||
eventKey={0}
|
||||
eventKey="settings"
|
||||
title={<TabTitleText>{t("common:settings")}</TabTitleText>}
|
||||
>
|
||||
<ClientSettings form={form} save={save} />
|
||||
</Tab>
|
||||
{publicClient && (
|
||||
<Tab
|
||||
eventKey={1}
|
||||
eventKey="credentials"
|
||||
title={<TabTitleText>{t("credentials")}</TabTitleText>}
|
||||
>
|
||||
<Credentials clientId={id} form={form} save={save} />
|
||||
</Tab>
|
||||
)}
|
||||
<Tab
|
||||
eventKey={2}
|
||||
eventKey="clientScopes"
|
||||
title={<TabTitleText>{t("clientScopes")}</TabTitleText>}
|
||||
>
|
||||
<Tabs
|
||||
activeKey={activeTab2}
|
||||
isSecondary
|
||||
onSelect={(_, key) => setActiveTab2(key as number)}
|
||||
>
|
||||
<KeycloakTabs paramName="subtab" isSecondary>
|
||||
<Tab
|
||||
eventKey={30}
|
||||
eventKey="setup"
|
||||
title={<TabTitleText>{t("setup")}</TabTitleText>}
|
||||
>
|
||||
<ClientScopes clientId={id} protocol={client!.protocol!} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey={31}
|
||||
eventKey="evaluate"
|
||||
title={<TabTitleText>{t("evaluate")}</TabTitleText>}
|
||||
>
|
||||
<EvaluateScopes clientId={id} protocol={client!.protocol!} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</KeycloakTabs>
|
||||
</Tab>
|
||||
{client && client.serviceAccountsEnabled && (
|
||||
<Tab
|
||||
eventKey={3}
|
||||
eventKey="serviceAccount"
|
||||
title={<TabTitleText>{t("serviceAccount")}</TabTitleText>}
|
||||
>
|
||||
<ServiceAccount clientId={id} />
|
||||
</Tab>
|
||||
)}
|
||||
</Tabs>
|
||||
</KeycloakTabs>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
|
|
53
src/components/keycloak-tabs/KeycloakTabs.tsx
Normal file
53
src/components/keycloak-tabs/KeycloakTabs.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -5,7 +5,6 @@ import {
|
|||
Label,
|
||||
PageSection,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabTitleText,
|
||||
ToolbarItem,
|
||||
} from "@patternfly/react-core";
|
||||
|
@ -19,13 +18,13 @@ import { RealmContext } from "../context/realm-context/RealmContext";
|
|||
import { InfoCircleIcon } from "@patternfly/react-icons";
|
||||
import { AdminEvents } from "./AdminEvents";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
|
||||
export const EventsSection = () => {
|
||||
const { t } = useTranslation("events");
|
||||
const adminClient = useAdminClient();
|
||||
const { realm } = useContext(RealmContext);
|
||||
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [key, setKey] = useState("");
|
||||
const refresh = () => setKey(`${new Date().getTime()}`);
|
||||
|
||||
|
@ -55,13 +54,9 @@ export const EventsSection = () => {
|
|||
<>
|
||||
<ViewHeader titleKey="events:title" subKey="events:eventExplain" />
|
||||
<PageSection variant="light">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onSelect={(_, key) => setActiveTab(key as number)}
|
||||
isBox
|
||||
>
|
||||
<KeycloakTabs isBox>
|
||||
<Tab
|
||||
eventKey={0}
|
||||
eventKey="userEvents"
|
||||
title={<TabTitleText>{t("userEvents")}</TabTitleText>}
|
||||
>
|
||||
<KeycloakDataTable
|
||||
|
@ -110,12 +105,12 @@ export const EventsSection = () => {
|
|||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey={1}
|
||||
eventKey="adminEvents"
|
||||
title={<TabTitleText>{t("adminEvents")}</TabTitleText>}
|
||||
>
|
||||
<AdminEvents />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</KeycloakTabs>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
DropdownItem,
|
||||
PageSection,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabTitleText,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -20,6 +19,7 @@ import { ViewHeader } from "../components/view-header/ViewHeader";
|
|||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { RealmRoleForm } from "./RealmRoleForm";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
|
||||
const arrayToAttributes = (attributeArray: KeyValueType[]) => {
|
||||
const initValue: { [index: string]: string[] } = {};
|
||||
|
@ -50,7 +50,6 @@ export const RealmRoleTabs = () => {
|
|||
const history = useHistory();
|
||||
const [name, setName] = useState("");
|
||||
const adminClient = useAdminClient();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const { realm } = useRealm();
|
||||
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
@ -145,24 +144,20 @@ export const RealmRoleTabs = () => {
|
|||
/>
|
||||
<PageSection variant="light">
|
||||
{id && (
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onSelect={(_, key) => setActiveTab(key as number)}
|
||||
isBox
|
||||
>
|
||||
<KeycloakTabs isBox>
|
||||
<Tab
|
||||
eventKey={0}
|
||||
eventKey="details"
|
||||
title={<TabTitleText>{t("details")}</TabTitleText>}
|
||||
>
|
||||
<RealmRoleForm form={form} save={save} editMode={true} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey={1}
|
||||
eventKey="attributes"
|
||||
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
||||
>
|
||||
<RoleAttributes form={form} save={save} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</KeycloakTabs>
|
||||
)}
|
||||
{!id && <RealmRoleForm form={form} save={save} editMode={false} />}
|
||||
</PageSection>
|
||||
|
|
|
@ -63,6 +63,12 @@ export const routes: RoutesFn = (t: TFunction) => [
|
|||
breadcrumb: t("clients:clientSettings"),
|
||||
access: "view-clients",
|
||||
},
|
||||
{
|
||||
path: "/:realm/clients/:id/:tab?/:subtab?",
|
||||
component: ClientDetails,
|
||||
breadcrumb: null,
|
||||
access: "view-clients",
|
||||
},
|
||||
{
|
||||
path: "/:realm/client-scopes/new",
|
||||
component: ClientScopeForm,
|
||||
|
@ -75,6 +81,12 @@ export const routes: RoutesFn = (t: TFunction) => [
|
|||
breadcrumb: t("client-scopes:clientScopeDetails"),
|
||||
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",
|
||||
component: RoleMappingForm,
|
||||
|
@ -117,6 +129,12 @@ export const routes: RoutesFn = (t: TFunction) => [
|
|||
breadcrumb: t("roles:roleDetails"),
|
||||
access: "view-realm",
|
||||
},
|
||||
{
|
||||
path: "/:realm/roles/:id/:tab",
|
||||
component: RealmRoleTabs,
|
||||
breadcrumb: null,
|
||||
access: "view-realm",
|
||||
},
|
||||
{
|
||||
path: "/:realm/users",
|
||||
component: UsersSection,
|
||||
|
@ -144,6 +162,12 @@ export const routes: RoutesFn = (t: TFunction) => [
|
|||
breadcrumb: t("events:title"),
|
||||
access: "view-events",
|
||||
},
|
||||
{
|
||||
path: "/:realm/events/:tab",
|
||||
component: EventsSection,
|
||||
breadcrumb: null,
|
||||
access: "view-events",
|
||||
},
|
||||
{
|
||||
path: "/:realm/realm-settings",
|
||||
component: RealmSettingsSection,
|
||||
|
|
Loading…
Reference in a new issue