added filter to idp table and manage order (#32889)

* added filter to idp table and manage order

fixes: #32780
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* Update js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties

Co-authored-by: Stefan Guilhen <sguilhen@redhat.com>
Signed-off-by: Erik Jan de Wit <edewit@redhat.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Erik Jan de Wit <edewit@redhat.com>
Co-authored-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Erik Jan de Wit 2024-09-16 13:34:29 +02:00 committed by GitHub
parent 3fe5d4847a
commit 808883c34d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 158 additions and 81 deletions

View file

@ -1481,6 +1481,8 @@ mapperTypeMsadLdsUserAccountControlMapper=msad-user-account-control-mapper
realmSettingsExplain=Realm settings are settings that control the options for users, applications, roles, and groups in the current realm. realmSettingsExplain=Realm settings are settings that control the options for users, applications, roles, and groups in the current realm.
mappingUpdatedError=Could not update mapping\: '{{error}}' mappingUpdatedError=Could not update mapping\: '{{error}}'
manageDisplayOrder=Manage display order manageDisplayOrder=Manage display order
emptyRealmBasedIdps=No realm based identity providers are configured for this realm.
hideOrganizationLinkedIdps=Hide organization linked identity providers
exactSearch=Exact search exactSearch=Exact search
value=Value value=Value
filenamePlaceholder=Upload a PEM file or paste key below filenamePlaceholder=Upload a PEM file or paste key below

View file

@ -1,12 +1,20 @@
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation"; import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import type { IdentityProvidersQuery } from "@keycloak/keycloak-admin-client/lib/resources/identityProviders"; import type { IdentityProvidersQuery } from "@keycloak/keycloak-admin-client/lib/resources/identityProviders";
import { IconMapper, useAlerts, useFetch } from "@keycloak/keycloak-ui-shared"; import {
Action,
IconMapper,
KeycloakDataTable,
ListEmptyState,
useAlerts,
useFetch,
} from "@keycloak/keycloak-ui-shared";
import { import {
AlertVariant, AlertVariant,
Badge, Badge,
Button, Button,
ButtonVariant, ButtonVariant,
CardTitle, CardTitle,
Checkbox,
Dropdown, Dropdown,
DropdownGroup, DropdownGroup,
DropdownItem, DropdownItem,
@ -28,7 +36,6 @@ import { Link, useNavigate } from "react-router-dom";
import { useAdminClient } from "../admin-client"; import { useAdminClient } from "../admin-client";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { ClickableCard } from "../components/keycloak-card/ClickableCard"; import { ClickableCard } from "../components/keycloak-card/ClickableCard";
import { Action, KeycloakDataTable } from "@keycloak/keycloak-ui-shared";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../context/server-info/ServerInfoProvider";
@ -102,6 +109,7 @@ export default function IdentityProvidersSection() {
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(key + 1); const refresh = () => setKey(key + 1);
const [hide, setHide] = useState(false);
const [addProviderOpen, setAddProviderOpen] = useState(false); const [addProviderOpen, setAddProviderOpen] = useState(false);
const [manageDisplayDialog, setManageDisplayDialog] = useState(false); const [manageDisplayDialog, setManageDisplayDialog] = useState(false);
const [hasProviders, setHasProviders] = useState(false); const [hasProviders, setHasProviders] = useState(false);
@ -121,6 +129,7 @@ export default function IdentityProvidersSection() {
const params: IdentityProvidersQuery = { const params: IdentityProvidersQuery = {
first: first!, first: first!,
max: max!, max: max!,
realmOnly: hide,
}; };
if (search) { if (search) {
params.search = search; params.search = search;
@ -184,6 +193,7 @@ export default function IdentityProvidersSection() {
<DeleteConfirm /> <DeleteConfirm />
{manageDisplayDialog && ( {manageDisplayDialog && (
<ManageOrderDialog <ManageOrderDialog
hideRealmBasedIdps={hide}
onClose={() => { onClose={() => {
setManageDisplayDialog(false); setManageDisplayDialog(false);
refresh(); refresh();
@ -243,6 +253,18 @@ export default function IdentityProvidersSection() {
searchPlaceholderKey="searchForProvider" searchPlaceholderKey="searchForProvider"
toolbarItem={ toolbarItem={
<> <>
<ToolbarItem alignSelf="center">
<Checkbox
label={t("hideOrganizationLinkedIdps")}
id="hideOrganizationLinkedIdps"
data-testid="hideOrganizationLinkedIdps"
isChecked={hide}
onChange={(_event, check) => {
setHide(check);
refresh();
}}
/>
</ToolbarItem>
<ToolbarItem> <ToolbarItem>
<Dropdown <Dropdown
data-testid="addProviderDropdown" data-testid="addProviderDropdown"
@ -299,6 +321,23 @@ export default function IdentityProvidersSection() {
cellRenderer: OrganizationLink, cellRenderer: OrganizationLink,
}, },
]} ]}
emptyState={
<ListEmptyState
message={t("identityProviders")}
instructions={t("emptyRealmBasedIdps")}
isSearchVariant
secondaryActions={[
{
text: t("clearAllFilters"),
onClick: () => {
setHide(false);
refresh();
},
type: ButtonVariant.link,
},
]}
/>
}
/> />
)} )}
</PageSection> </PageSection>

View file

@ -1,5 +1,9 @@
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation"; import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import { useAlerts, useFetch } from "@keycloak/keycloak-ui-shared"; import {
KeycloakSpinner,
useAlerts,
useFetch,
} from "@keycloak/keycloak-ui-shared";
import { import {
Button, Button,
ButtonVariant, ButtonVariant,
@ -23,13 +27,18 @@ import { sortBy } from "lodash-es";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAdminClient } from "../admin-client"; import { useAdminClient } from "../admin-client";
import { KeycloakSpinner } from "@keycloak/keycloak-ui-shared";
type ManageOrderDialogProps = { type ManageOrderDialogProps = {
orgId?: string;
hideRealmBasedIdps?: boolean;
onClose: () => void; onClose: () => void;
}; };
export const ManageOrderDialog = ({ onClose }: ManageOrderDialogProps) => { export const ManageOrderDialog = ({
orgId,
hideRealmBasedIdps = false,
onClose,
}: ManageOrderDialogProps) => {
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { t } = useTranslation(); const { t } = useTranslation();
@ -67,7 +76,10 @@ export const ManageOrderDialog = ({ onClose }: ManageOrderDialogProps) => {
}; };
useFetch( useFetch(
() => adminClient.identityProviders.find(), () =>
orgId
? adminClient.organizations.listIdentityProviders({ orgId })
: adminClient.identityProviders.find({ realmOnly: hideRealmBasedIdps }),
(providers) => { (providers) => {
setProviders(providers); setProviders(providers);
setOrder( setOrder(

View file

@ -1,6 +1,7 @@
import IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation"; import IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import { import {
KeycloakDataTable, KeycloakDataTable,
ListEmptyState,
useAlerts, useAlerts,
useFetch, useFetch,
} from "@keycloak/keycloak-ui-shared"; } from "@keycloak/keycloak-ui-shared";
@ -17,7 +18,7 @@ import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useAdminClient } from "../admin-client"; import { useAdminClient } from "../admin-client";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { ListEmptyState } from "@keycloak/keycloak-ui-shared"; import { ManageOrderDialog } from "../identity-providers/ManageOrderDialog";
import useToggle from "../utils/useToggle"; import useToggle from "../utils/useToggle";
import { LinkIdentityProviderModal } from "./LinkIdentityProviderModal"; import { LinkIdentityProviderModal } from "./LinkIdentityProviderModal";
import { EditOrganizationParams } from "./routes/EditOrganization"; import { EditOrganizationParams } from "./routes/EditOrganization";
@ -74,6 +75,7 @@ export const IdentityProviders = () => {
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(key + 1); const refresh = () => setKey(key + 1);
const [manageDisplayDialog, setManageDisplayDialog] = useState(false);
const [hasProviders, setHasProviders] = useState(false); const [hasProviders, setHasProviders] = useState(false);
const [selectedRow, setSelectedRow] = const [selectedRow, setSelectedRow] =
useState<IdentityProviderRepresentation>(); useState<IdentityProviderRepresentation>();
@ -111,88 +113,110 @@ export const IdentityProviders = () => {
}); });
return ( return (
<PageSection variant="light"> <>
<UnlinkConfirm /> {manageDisplayDialog && (
{open && ( <ManageOrderDialog
<LinkIdentityProviderModal
orgId={orgId!} orgId={orgId!}
identityProvider={selectedRow}
onClose={() => { onClose={() => {
toggleOpen(); setManageDisplayDialog(false);
refresh(); refresh();
}} }}
/> />
)} )}
{!hasProviders ? ( <PageSection variant="light">
<ListEmptyState <UnlinkConfirm />
icon={BellIcon} {open && (
message={t("noIdentityProvider")} <LinkIdentityProviderModal
instructions={t("noIdentityProviderInstructions")} orgId={orgId!}
/> identityProvider={selectedRow}
) : ( onClose={() => {
<KeycloakDataTable toggleOpen();
key={key} refresh();
loader={loader} }}
ariaLabelKey="identityProviders" />
searchPlaceholderKey="searchProvider" )}
toolbarItem={ {!hasProviders ? (
<ToolbarItem> <ListEmptyState
<Button icon={BellIcon}
onClick={() => { message={t("noIdentityProvider")}
setSelectedRow(undefined); instructions={t("noIdentityProviderInstructions")}
/>
) : (
<KeycloakDataTable
key={key}
loader={loader}
ariaLabelKey="identityProviders"
searchPlaceholderKey="searchProvider"
toolbarItem={
<>
<ToolbarItem>
<Button
onClick={() => {
setSelectedRow(undefined);
toggleOpen();
}}
>
{t("linkIdentityProvider")}
</Button>
</ToolbarItem>
<ToolbarItem>
<Button
data-testid="manageDisplayOrder"
variant="link"
onClick={() => setManageDisplayDialog(true)}
>
{t("manageDisplayOrder")}
</Button>
</ToolbarItem>
</>
}
actions={[
{
title: t("edit"),
onRowClick: (row) => {
setSelectedRow(row);
toggleOpen(); toggleOpen();
}} },
>
{t("linkIdentityProvider")}
</Button>
</ToolbarItem>
}
actions={[
{
title: t("edit"),
onRowClick: (row) => {
setSelectedRow(row);
toggleOpen();
}, },
}, {
{ title: t("unLinkIdentityProvider"),
title: t("unLinkIdentityProvider"), onRowClick: (row) => {
onRowClick: (row) => { setSelectedRow(row);
setSelectedRow(row); toggleUnlinkDialog();
toggleUnlinkDialog(); },
}, },
}, ]}
]} columns={[
columns={[ {
{ name: "alias",
name: "alias", },
}, {
{ name: "config['kc.org.domain']",
name: "config['kc.org.domain']", displayKey: "domain",
displayKey: "domain", },
}, {
{ name: "providerId",
name: "providerId", displayKey: "providerDetails",
displayKey: "providerDetails", },
}, {
{ name: "config['kc.org.broker.public']",
name: "config['kc.org.broker.public']", displayKey: "shownOnLoginPage",
displayKey: "shownOnLoginPage", cellRenderer: (row) => (
cellRenderer: (row) => ( <ShownOnLoginPageCheck row={row} refresh={refresh} />
<ShownOnLoginPageCheck row={row} refresh={refresh} /> ),
), },
}, ]}
]} emptyState={
emptyState={ <ListEmptyState
<ListEmptyState message={t("emptyIdentityProviderLink")}
message={t("emptyIdentityProviderLink")} instructions={t("emptyIdentityProviderLinkInstructions")}
instructions={t("emptyIdentityProviderLinkInstructions")} primaryActionText={t("linkIdentityProvider")}
primaryActionText={t("linkIdentityProvider")} onPrimaryAction={toggleOpen}
onPrimaryAction={toggleOpen} />
/> }
} />
/> )}
)} </PageSection>
</PageSection> </>
); );
}; };