From a3ffbb439dab8983239054348cd59bb5f0e0b9d7 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Fri, 16 Aug 2024 08:30:02 +0200 Subject: [PATCH] initial version organization table for users Signed-off-by: Erik Jan de Wit --- .../admin/messages/messages_en.properties | 6 +- .../src/organizations/OrganizationTable.tsx | 107 ++++++++++++++++++ .../organizations/OrganizationsSection.tsx | 105 +++-------------- js/apps/admin-ui/src/user/EditUser.tsx | 29 ++++- js/apps/admin-ui/src/user/Organizations.tsx | 66 +++++++++++ js/apps/admin-ui/src/user/routes/User.tsx | 1 + .../keycloak-server/scripts/start-server.js | 2 +- .../src/resources/organizations.ts | 9 ++ 8 files changed, 231 insertions(+), 94 deletions(-) create mode 100644 js/apps/admin-ui/src/organizations/OrganizationTable.tsx create mode 100644 js/apps/admin-ui/src/user/Organizations.tsx diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index f735d0e9f8..4c43e74953 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -3216,4 +3216,8 @@ emailVerificationHelp=Specifies independent timeout for email verification. idpAccountEmailVerificationHelp=Specifies independent timeout for IdP account email verification. forgotPasswordHelp=Specifies independent timeout for forgot password. executeActionsHelp=Specifies independent timeout for execute actions. -validatingX509CertsHelp=The public certificates Keycloak uses to validate the signatures of SAML requests and responses from the external IDP when Use metadata descriptor URL is OFF. Multiple certificates can be entered separated by comma (,). The certificates can be re-imported from the Metadata descriptor URL clicking the Import Keys action in the identity provider page. The action downloads the current certificates in the metadata endpoint and assigns them to the config in this same option. You need to click Save to definitely store the re-imported certificates. \ No newline at end of file +validatingX509CertsHelp=The public certificates Keycloak uses to validate the signatures of SAML requests and responses from the external IDP when Use metadata descriptor URL is OFF. Multiple certificates can be entered separated by comma (,). The certificates can be re-imported from the Metadata descriptor URL clicking the Import Keys action in the identity provider page. The action downloads the current certificates in the metadata endpoint and assigns them to the config in this same option. You need to click Save to definitely store the re-imported certificates. +emptyUserOrganizations=No organizations +emptyUserOrganizationsInstructions=There is no organization yet. Please join an organization or send an invitation to join an organization. +joinOrganization=Join organization +sendInvitation=Send invitation \ No newline at end of file diff --git a/js/apps/admin-ui/src/organizations/OrganizationTable.tsx b/js/apps/admin-ui/src/organizations/OrganizationTable.tsx new file mode 100644 index 0000000000..ab31eacc40 --- /dev/null +++ b/js/apps/admin-ui/src/organizations/OrganizationTable.tsx @@ -0,0 +1,107 @@ +import OrganizationRepresentation from "@keycloak/keycloak-admin-client/lib/defs/organizationRepresentation"; +import { Badge, Chip, ChipGroup } from "@patternfly/react-core"; +import { TableText } from "@patternfly/react-table"; +import { PropsWithChildren, ReactNode } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { + KeycloakDataTable, + LoaderFunction, +} from "../components/table-toolbar/KeycloakDataTable"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { toEditOrganization } from "./routes/EditOrganization"; + +const OrgDetailLink = (organization: OrganizationRepresentation) => { + const { t } = useTranslation(); + const { realm } = useRealm(); + return ( + + + {organization.name} + {!organization.enabled && ( + + {t("disabled")} + + )} + + + ); +}; + +const Domains = (org: OrganizationRepresentation) => { + const { t } = useTranslation(); + return ( + + {org.domains?.map((dn) => ( + + {dn.name} + + ))} + + ); +}; + +type OrganizationTableProps = PropsWithChildren & { + loader: + | LoaderFunction + | OrganizationRepresentation[]; + onDelete?: (org: OrganizationRepresentation) => void; + toolbarItem?: ReactNode; +}; + +export const OrganizationTable = ({ + loader, + toolbarItem, + onDelete, + children, +}: OrganizationTableProps) => { + const { t } = useTranslation(); + + return ( + + ); +}; diff --git a/js/apps/admin-ui/src/organizations/OrganizationsSection.tsx b/js/apps/admin-ui/src/organizations/OrganizationsSection.tsx index 678587e644..563163b023 100644 --- a/js/apps/admin-ui/src/organizations/OrganizationsSection.tsx +++ b/js/apps/admin-ui/src/organizations/OrganizationsSection.tsx @@ -1,71 +1,21 @@ import OrganizationRepresentation from "@keycloak/keycloak-admin-client/lib/defs/organizationRepresentation"; +import { useAlerts } from "@keycloak/keycloak-ui-shared"; import { - Badge, Button, ButtonVariant, - Chip, - ChipGroup, PageSection, ToolbarItem, } from "@patternfly/react-core"; -import { TableText } from "@patternfly/react-table"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link, useNavigate } from "react-router-dom"; import { useAdminClient } from "../admin-client"; -import { useAlerts } from "@keycloak/keycloak-ui-shared"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; -import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useRealm } from "../context/realm-context/RealmContext"; +import { OrganizationTable } from "./OrganizationTable"; import { toAddOrganization } from "./routes/AddOrganization"; -import { toEditOrganization } from "./routes/EditOrganization"; - -const OrgDetailLink = (organization: any) => { - const { t } = useTranslation(); - const { realm } = useRealm(); - return ( - - - {organization.name} - {!organization.enabled && ( - - {t("disabled")} - - )} - - - ); -}; - -const Domains = (org: OrganizationRepresentation) => { - const { t } = useTranslation(); - return ( - - {org.domains?.map((dn) => ( - - {dn.name} - - ))} - - ); -}; export default function OrganizationSection() { const { adminClient } = useAdminClient(); @@ -110,12 +60,9 @@ export default function OrganizationSection() { /> - + + + + + + } + > + alert("join organization"), + }, + { + text: t("sendInvitation"), + onClick: () => alert("send invitation"), + }, + ]} + /> + + ); +}; diff --git a/js/apps/admin-ui/src/user/routes/User.tsx b/js/apps/admin-ui/src/user/routes/User.tsx index 808e42ef9d..f72ae3c23f 100644 --- a/js/apps/admin-ui/src/user/routes/User.tsx +++ b/js/apps/admin-ui/src/user/routes/User.tsx @@ -6,6 +6,7 @@ import type { AppRouteObject } from "../../routes"; export type UserTab = | "settings" | "groups" + | "organizations" | "consents" | "attributes" | "sessions" diff --git a/js/apps/keycloak-server/scripts/start-server.js b/js/apps/keycloak-server/scripts/start-server.js index d1cf03a931..b3e08bba51 100755 --- a/js/apps/keycloak-server/scripts/start-server.js +++ b/js/apps/keycloak-server/scripts/start-server.js @@ -56,7 +56,7 @@ async function startServer() { path.join(SERVER_DIR, `bin/kc${SCRIPT_EXTENSION}`), [ "start-dev", - `--features="login2,account3,admin-fine-grained-authz,transient-users,oid4vc-vci"`, + `--features="login2,account3,admin-fine-grained-authz,transient-users,oid4vc-vci,organization"`, ...keycloakArgs, ], { diff --git a/js/libs/keycloak-admin-client/src/resources/organizations.ts b/js/libs/keycloak-admin-client/src/resources/organizations.ts index 8ca0c9fce9..3a07e17537 100644 --- a/js/libs/keycloak-admin-client/src/resources/organizations.ts +++ b/js/libs/keycloak-admin-client/src/resources/organizations.ts @@ -96,6 +96,15 @@ export class Organizations extends Resource<{ realm?: string }> { urlParamKeys: ["orgId", "userId"], }); + public memberOrganizations = this.makeRequest< + { userId: string }, + OrganizationRepresentation[] + >({ + method: "GET", + path: "/members/{userId}/organizations", + urlParamKeys: ["userId"], + }); + public invite = this.makeUpdateRequest<{ orgId: string }, FormData>({ method: "POST", path: "/{orgId}/members/invite-user",