From 6a4e4909280c2308541d0e52c39213d086b43d4a Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 23 Feb 2022 15:28:59 +0100 Subject: [PATCH] more robust links out from events (#2133) --- src/client-scopes/routes/ClientScope.ts | 4 +- src/events/AdminEvents.tsx | 46 +-------- src/events/ResourceLinks.tsx | 118 ++++++++++++++++++++++++ src/groups/routes/Groups.tsx | 4 +- 4 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 src/events/ResourceLinks.tsx diff --git a/src/client-scopes/routes/ClientScope.ts b/src/client-scopes/routes/ClientScope.ts index c49a15fa26..4fc000303c 100644 --- a/src/client-scopes/routes/ClientScope.ts +++ b/src/client-scopes/routes/ClientScope.ts @@ -8,12 +8,12 @@ export type ClientScopeTab = "settings" | "mappers" | "scope"; export type ClientScopeParams = { realm: string; id: string; - type: string; tab: ClientScopeTab; + type?: string; }; export const ClientScopeRoute: RouteDef = { - path: "/:realm/client-scopes/:id/:type/:tab", + path: "/:realm/client-scopes/:id/:tab/:type?", component: lazy(() => import("../form/ClientScopeForm")), breadcrumb: (t) => t("client-scopes:clientScopeDetails"), access: "view-clients", diff --git a/src/events/AdminEvents.tsx b/src/events/AdminEvents.tsx index 2cc62c0e9e..b38541cf75 100644 --- a/src/events/AdminEvents.tsx +++ b/src/events/AdminEvents.tsx @@ -15,7 +15,6 @@ import { SelectOption, SelectVariant, TextInput, - Tooltip, } from "@patternfly/react-core"; import { cellWidth, @@ -30,7 +29,6 @@ import moment from "moment"; import React, { FunctionComponent, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; import { pickBy } from "lodash-es"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; @@ -38,6 +36,8 @@ import { useAdminClient } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { prettyPrintJSON } from "../util"; +import { CellResourceLinkRenderer } from "./ResourceLinks"; + import "./events.css"; type DisplayDialogProps = { @@ -87,27 +87,6 @@ const DisplayDialog: FunctionComponent = ({ ); }; -const MAX_TEXT_LENGTH = 38; -const Truncate = ({ - text, - children, -}: { - text?: string; - children: (text: string) => any; -}) => { - const definedText = text || ""; - const needsTruncation = definedText.length > MAX_TEXT_LENGTH; - const truncatedText = definedText.substr(0, MAX_TEXT_LENGTH); - return ( - <> - {needsTruncation && ( - {children(truncatedText + "...")} - )} - {!needsTruncation && <>{children(definedText)}} - - ); -}; - export const AdminEvents = () => { const { t } = useTranslation("events"); const adminClient = useAdminClient(); @@ -204,25 +183,6 @@ export const AdminEvents = () => { commitFilters(); } - const LinkResource = (row: AdminEventRepresentation) => ( - - {(text) => ( - <> - {row.resourceType !== "COMPONENT" && ( - - {text} - - )} - {row.resourceType === "COMPONENT" && {text}} - - )} - - ); - const adminEventSearchFormDisplay = () => { return ( { { name: "resourcePath", displayKey: "events:resourcePath", - cellRenderer: LinkResource, + cellRenderer: CellResourceLinkRenderer, }, { name: "resourceType", diff --git a/src/events/ResourceLinks.tsx b/src/events/ResourceLinks.tsx new file mode 100644 index 0000000000..71083ef401 --- /dev/null +++ b/src/events/ResourceLinks.tsx @@ -0,0 +1,118 @@ +import React, { ReactElement } from "react"; +import { Link } from "react-router-dom"; +import { Tooltip } from "@patternfly/react-core"; + +import type AdminEventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/adminEventRepresentation"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { toClient } from "../clients/routes/Client"; +import { toGroups } from "../groups/routes/Groups"; +import { toClientScope } from "../client-scopes/routes/ClientScope"; +import { toUser } from "../user/routes/User"; +import { toRealmRole } from "../realm-roles/routes/RealmRole"; +import { toFlow } from "../authentication/routes/Flow"; + +type ResourceLinkProps = { + event: AdminEventRepresentation; +}; + +const MAX_TEXT_LENGTH = 38; +const Truncate = ({ + text, + children, +}: { + text?: string; + children: (text: string) => ReactElement; +}) => { + const definedText = text || ""; + const needsTruncation = definedText.length > MAX_TEXT_LENGTH; + const truncatedText = definedText.substring(0, MAX_TEXT_LENGTH); + return needsTruncation ? ( + {children(truncatedText + "…")} + ) : ( + children(definedText) + ); +}; + +const isLinkable = (event: AdminEventRepresentation) => { + if (event.operationType === "DELETE") { + return false; + } + return ( + event.resourceType === "USER" || + event.resourceType === "GROUP_MEMBERSHIP" || + event.resourceType === "GROUP" || + event.resourceType === "CLIENT" || + event.resourceType?.startsWith("AUTHORIZATION_RESOURCE") || + event.resourceType === "CLIENT_SCOPE" || + event.resourceType === "AUTH_FLOW" || + event.resourcePath?.startsWith("roles-by-id") + ); +}; + +const idRegex = new RegExp( + /([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})/ +); + +const createLink = (realm: string, event: AdminEventRepresentation) => { + const part = idRegex.exec(event.resourcePath!); + if (!part) { + console.warn("event didn't contain a valid link", event); + return ""; + } + const id = part[1]; + + if ( + event.resourceType === "CLIENT" || + event.resourceType?.startsWith("AUTHORIZATION_RESOURCE") + ) { + return toClient({ + realm, + clientId: id, + tab: event.resourceType === "CLIENT" ? "settings" : "authorization", + }); + } + + if (event.resourceType === "GROUP") { + return toGroups({ realm, id }); + } + + if (event.resourceType === "CLIENT_SCOPE") { + return toClientScope({ realm, id, tab: "settings" }); + } + + if ( + event.resourceType === "USER" || + event.resourceType === "GROUP_MEMBERSHIP" + ) { + return toUser({ realm, id, tab: "settings" }); + } + + if (event.resourceType === "AUTH_FLOW") { + return toFlow({ realm, id, usedBy: "-" }); + } + + if (event.resourcePath?.startsWith("roles-by-id")) { + return toRealmRole({ realm, id }); + } + + return ""; +}; + +export const ResourceLink = ({ event }: ResourceLinkProps) => { + const { realm } = useRealm(); + return ( + + {(text) => + isLinkable(event) ? ( + {text} + ) : ( + {text} + ) + } + + ); +}; + +export const CellResourceLinkRenderer = ( + adminEvent: AdminEventRepresentation +) => ; diff --git a/src/groups/routes/Groups.tsx b/src/groups/routes/Groups.tsx index c2ba2fb63a..93ca8d832c 100644 --- a/src/groups/routes/Groups.tsx +++ b/src/groups/routes/Groups.tsx @@ -3,10 +3,10 @@ import { lazy } from "react"; import { generatePath } from "react-router-dom"; import type { RouteDef } from "../../route-config"; -export type GroupsParams = { realm: string }; +export type GroupsParams = { realm: string; id?: string }; export const GroupsRoute: RouteDef = { - path: "/:realm/groups", + path: "/:realm/groups/:id?", component: lazy(() => import("../GroupsSection")), access: "query-groups", matchOptions: {