more robust links out from events (#2133)
This commit is contained in:
parent
bda591fb0d
commit
6a4e490928
4 changed files with 125 additions and 47 deletions
|
@ -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",
|
||||
|
|
|
@ -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<DisplayDialogProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
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 && (
|
||||
<Tooltip content={text}>{children(truncatedText + "...")}</Tooltip>
|
||||
)}
|
||||
{!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) => (
|
||||
<Truncate text={row.resourcePath}>
|
||||
{(text) => (
|
||||
<>
|
||||
{row.resourceType !== "COMPONENT" && (
|
||||
<Link
|
||||
to={`/${realm}/${row.resourcePath}${
|
||||
row.resourceType !== "GROUP" ? "/settings" : ""
|
||||
}`}
|
||||
>
|
||||
{text}
|
||||
</Link>
|
||||
)}
|
||||
{row.resourceType === "COMPONENT" && <span>{text}</span>}
|
||||
</>
|
||||
)}
|
||||
</Truncate>
|
||||
);
|
||||
|
||||
const adminEventSearchFormDisplay = () => {
|
||||
return (
|
||||
<Flex
|
||||
|
@ -606,7 +566,7 @@ export const AdminEvents = () => {
|
|||
{
|
||||
name: "resourcePath",
|
||||
displayKey: "events:resourcePath",
|
||||
cellRenderer: LinkResource,
|
||||
cellRenderer: CellResourceLinkRenderer,
|
||||
},
|
||||
{
|
||||
name: "resourceType",
|
||||
|
|
118
src/events/ResourceLinks.tsx
Normal file
118
src/events/ResourceLinks.tsx
Normal file
|
@ -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 ? (
|
||||
<Tooltip content={text}>{children(truncatedText + "…")}</Tooltip>
|
||||
) : (
|
||||
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 (
|
||||
<Truncate text={event.resourcePath}>
|
||||
{(text) =>
|
||||
isLinkable(event) ? (
|
||||
<Link to={createLink(realm, event)}>{text}</Link>
|
||||
) : (
|
||||
<span>{text}</span>
|
||||
)
|
||||
}
|
||||
</Truncate>
|
||||
);
|
||||
};
|
||||
|
||||
export const CellResourceLinkRenderer = (
|
||||
adminEvent: AdminEventRepresentation
|
||||
) => <ResourceLink event={adminEvent} />;
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue