Add scim events view

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-01-05 08:20:58 +01:00
parent cfc514c8e0
commit 9eedf7aa1e
389 changed files with 90616 additions and 7 deletions

View File

@@ -0,0 +1,74 @@
import { graphql, usePaginationFragment } from "react-relay";
import type { SCIMEventListFragment$key } from "/__generated__/iam/SCIMEventListFragment.graphql";
import type { SCIMEventListPaginationQuery } from "/__generated__/iam/SCIMEventListPaginationQuery.graphql";
import { useTranslate } from "@probo/i18n";
import { Thead, Tbody, Tr, Th, Td } from "@probo/ui";
import { SortableTable } from "/components/SortableTable";
import { SCIMEventListItem } from "./SCIMEventListItem";
const SCIMEventListFragment = graphql`
fragment SCIMEventListFragment on SCIMConfiguration
@refetchable(queryName: "SCIMEventListPaginationQuery")
@argumentDefinitions(
first: { type: "Int", defaultValue: 20 }
after: { type: "CursorKey", defaultValue: null }
before: { type: "CursorKey", defaultValue: null }
last: { type: "Int", defaultValue: null }
) {
events(first: $first, after: $after, last: $last, before: $before)
@connection(key: "SCIMEventListFragment_events")
@required(action: THROW) {
edges @required(action: THROW) {
node {
id
...SCIMEventListItemFragment
}
}
}
}
`;
export function SCIMEventList(props: { fKey: SCIMEventListFragment$key }) {
const { fKey } = props;
const { __ } = useTranslate();
const eventsPagination = usePaginationFragment<
SCIMEventListPaginationQuery,
SCIMEventListFragment$key
>(SCIMEventListFragment, fKey);
return (
<SortableTable
{...eventsPagination}
refetch={() => {
eventsPagination.refetch({}, { fetchPolicy: "network-only" });
}}
pageSize={20}
>
<Thead>
<Tr>
<Th>{__("Time")}</Th>
<Th>{__("Method")}</Th>
<Th>{__("Path")}</Th>
<Th>{__("Status")}</Th>
<Th>{__("User")}</Th>
<Th>{__("IP Address")}</Th>
<Th>{__("Error")}</Th>
</Tr>
</Thead>
<Tbody>
{eventsPagination.data.events.edges.length === 0 ? (
<Tr>
<Td colSpan={7} className="text-center text-txt-secondary">
{__("No SCIM events recorded yet.")}
</Td>
</Tr>
) : (
eventsPagination.data.events.edges.map(({ node: event }) => (
<SCIMEventListItem key={event.id} fKey={event} />
))
)}
</Tbody>
</SortableTable>
);
}

View File

@@ -0,0 +1,71 @@
import { useFragment } from "react-relay";
import { graphql } from "relay-runtime";
import type { SCIMEventListItemFragment$key } from "/__generated__/iam/SCIMEventListItemFragment.graphql";
import { Td, Tr, Badge } from "@probo/ui";
import { formatDate } from "@probo/helpers";
const SCIMEventListItemFragment = graphql`
fragment SCIMEventListItemFragment on SCIMEvent {
id
method
path
statusCode
errorMessage
ipAddress
createdAt
membership {
id
profile {
fullName
}
}
}
`;
const getStatusBadge = (statusCode: number) => {
if (statusCode >= 200 && statusCode < 300) {
return <Badge variant="success">{statusCode}</Badge>;
} else if (statusCode >= 400 && statusCode < 500) {
return <Badge variant="warning">{statusCode}</Badge>;
} else if (statusCode >= 500) {
return <Badge variant="danger">{statusCode}</Badge>;
}
return <Badge variant="neutral">{statusCode}</Badge>;
};
const getMethodBadge = (method: string) => {
const variants: Record<string, "neutral" | "info" | "danger" | "highlight"> =
{
GET: "info",
POST: "highlight",
PUT: "highlight",
PATCH: "highlight",
DELETE: "danger",
};
return <Badge variant={variants[method] || "neutral"}>{method}</Badge>;
};
export function SCIMEventListItem(props: {
fKey: SCIMEventListItemFragment$key;
}) {
const { fKey } = props;
const event = useFragment<SCIMEventListItemFragment$key>(
SCIMEventListItemFragment,
fKey
);
return (
<Tr>
<Td className="whitespace-nowrap">{formatDate(event.createdAt)}</Td>
<Td>{getMethodBadge(event.method)}</Td>
<Td className="font-mono text-xs">{event.path}</Td>
<Td>{getStatusBadge(event.statusCode)}</Td>
<Td>{event.membership?.profile?.fullName || "-"}</Td>
<Td className="font-mono text-xs">{event.ipAddress}</Td>
<Td className="max-w-xs truncate text-txt-danger">
{event.errorMessage || "-"}
</Td>
</Tr>
);
}