Add credential table (#3710)
This commit is contained in:
parent
25c66e6901
commit
2ae520fb8a
7 changed files with 157 additions and 102 deletions
|
@ -46,7 +46,7 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@keycloak/keycloak-admin-client": "^19.0.3",
|
||||
"@keycloak/keycloak-admin-client": "^21.0.0-dev.3",
|
||||
"@patternfly/patternfly": "^4.219.2",
|
||||
"@patternfly/react-code-editor": "^4.82.55",
|
||||
"@patternfly/react-core": "^4.258.3",
|
||||
|
|
|
@ -150,6 +150,7 @@
|
|||
"type": "Type",
|
||||
"userLabel": "User label",
|
||||
"data": "Data",
|
||||
"providedBy": "Provided by",
|
||||
"passwordDataTitle": "Password data",
|
||||
"updateCredentialUserLabelSuccess": "The user label has been changed successfully.",
|
||||
"updateCredentialUserLabelError": "Error changing user label: {{error}}",
|
||||
|
|
|
@ -94,6 +94,10 @@ export const ResourcesPolicySelect = ({
|
|||
])
|
||||
)
|
||||
.flat()
|
||||
.filter(
|
||||
(r): r is PolicyRepresentation | ResourceRepresentation =>
|
||||
typeof r !== "string"
|
||||
)
|
||||
.map(convert)
|
||||
.filter(
|
||||
({ id }, index, self) =>
|
||||
|
|
|
@ -134,24 +134,13 @@ export default function NewAttributeSettings() {
|
|||
flatten<any, any>({ permissions, selector, required }, { safe: true })
|
||||
).map(([key, value]) => form.setValue(key, value));
|
||||
form.setValue("annotations", convert(annotations));
|
||||
form.setValue("validations", convert(validations));
|
||||
form.setValue("validations", validations);
|
||||
form.setValue("isRequired", required !== undefined);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const save = async (profileConfig: UserProfileAttributeType) => {
|
||||
const validations = profileConfig.validations?.reduce(
|
||||
(prevValidations: any, currentValidations: any) => {
|
||||
prevValidations[currentValidations.key] =
|
||||
currentValidations.value?.length === 0
|
||||
? {}
|
||||
: currentValidations.value;
|
||||
return prevValidations;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const annotations = (profileConfig.annotations! as KeyValueType[]).reduce(
|
||||
(obj, item) => Object.assign(obj, { [item.key]: item.value }),
|
||||
{}
|
||||
|
@ -169,7 +158,6 @@ export default function NewAttributeSettings() {
|
|||
...attribute,
|
||||
name: attributeName,
|
||||
displayName: profileConfig.displayName!,
|
||||
validations,
|
||||
selector: profileConfig.selector,
|
||||
permissions: profileConfig.permissions!,
|
||||
annotations,
|
||||
|
@ -188,7 +176,6 @@ export default function NewAttributeSettings() {
|
|||
name: profileConfig.name,
|
||||
displayName: profileConfig.displayName!,
|
||||
required: profileConfig.isRequired ? profileConfig.required : {},
|
||||
validations,
|
||||
selector: profileConfig.selector,
|
||||
permissions: profileConfig.permissions!,
|
||||
annotations,
|
||||
|
|
|
@ -37,6 +37,7 @@ import { CredentialRow } from "./user-credentials/CredentialRow";
|
|||
import { toUpperCase } from "../util";
|
||||
|
||||
import "./user-credentials.css";
|
||||
import { FederatedCredentials } from "./user-credentials/FederatedCredentials";
|
||||
|
||||
type UserCredentialsProps = {
|
||||
user: UserRepresentation;
|
||||
|
@ -361,7 +362,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
|
|||
<Divider />
|
||||
</>
|
||||
)}
|
||||
{groupedUserCredentials.length !== 0 ? (
|
||||
{groupedUserCredentials.length !== 0 && (
|
||||
<>
|
||||
{user.email && (
|
||||
<Button
|
||||
|
@ -374,10 +375,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
|
|||
</Button>
|
||||
)}
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<TableComposable
|
||||
aria-label="userCredentials-table"
|
||||
variant={"compact"}
|
||||
>
|
||||
<TableComposable variant={"compact"}>
|
||||
<Thead>
|
||||
<Tr className="kc-table-header">
|
||||
<Th>
|
||||
|
@ -490,9 +488,14 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
|
|||
</TableComposable>
|
||||
</PageSection>
|
||||
</>
|
||||
) : (
|
||||
)}
|
||||
{(user.federationLink || user.origin) && (
|
||||
<FederatedCredentials user={user} onSetPassword={toggleModal} />
|
||||
)}
|
||||
{groupedUserCredentials.length === 0 &&
|
||||
!(user.federationLink || user.origin) && (
|
||||
<ListEmptyState
|
||||
hasIcon={true}
|
||||
hasIcon
|
||||
message={t("noCredentials")}
|
||||
instructions={t("noCredentialsText")}
|
||||
primaryActionText={t("setPassword")}
|
||||
|
|
112
apps/admin-ui/src/user/user-credentials/FederatedCredentials.tsx
Normal file
112
apps/admin-ui/src/user/user-credentials/FederatedCredentials.tsx
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
} from "@patternfly/react-core";
|
||||
import {
|
||||
TableComposable,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
|
||||
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { useAccess } from "../../context/access/Access";
|
||||
import { toUserFederationLdap } from "../../user-federation/routes/UserFederationLdap";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
type FederatedCredentialsProps = {
|
||||
user: UserRepresentation;
|
||||
onSetPassword: () => void;
|
||||
};
|
||||
|
||||
export const FederatedCredentials = ({
|
||||
user,
|
||||
onSetPassword,
|
||||
}: FederatedCredentialsProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const { adminClient } = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
|
||||
const [credentialTypes, setCredentialTypes] = useState<string[]>();
|
||||
const [component, setComponent] = useState<ComponentRepresentation>();
|
||||
const access = useAccess();
|
||||
|
||||
useFetch(
|
||||
() =>
|
||||
Promise.all([
|
||||
adminClient.users.getUserStorageCredentialTypes({ id: user.id! }),
|
||||
access.hasAccess("view-realm")
|
||||
? adminClient.components.findOne({
|
||||
id: (user.federationLink || user.origin)!,
|
||||
})
|
||||
: adminClient.userStorageProvider.name({
|
||||
id: (user.federationLink || user.origin)!,
|
||||
}),
|
||||
]),
|
||||
([credentialTypes, component]) => {
|
||||
setCredentialTypes(credentialTypes);
|
||||
setComponent(component);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
if (!credentialTypes || !component) {
|
||||
return <KeycloakSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<TableComposable variant={"compact"}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t("type")}</Th>
|
||||
<Th>{t("providedBy")}</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{credentialTypes.map((credential) => (
|
||||
<Tr key={credential}>
|
||||
<Td>
|
||||
<b>{credential}</b>
|
||||
</Td>
|
||||
<Td>
|
||||
<Button
|
||||
variant="link"
|
||||
isDisabled={!access.hasAccess("view-realm")}
|
||||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={toUserFederationLdap({
|
||||
id: component.id!,
|
||||
realm,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{component.name}
|
||||
</Button>
|
||||
</Td>
|
||||
{credential === "password" && (
|
||||
<Td modifier="fitContent">
|
||||
<Button variant="secondary" onClick={onSetPassword}>
|
||||
{t("setPassword")}
|
||||
</Button>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
</PageSection>
|
||||
);
|
||||
};
|
74
package-lock.json
generated
74
package-lock.json
generated
|
@ -140,7 +140,7 @@
|
|||
},
|
||||
"apps/admin-ui": {
|
||||
"dependencies": {
|
||||
"@keycloak/keycloak-admin-client": "^19.0.3",
|
||||
"@keycloak/keycloak-admin-client": "^21.0.0-dev.3",
|
||||
"@patternfly/patternfly": "^4.219.2",
|
||||
"@patternfly/react-code-editor": "^4.82.55",
|
||||
"@patternfly/react-core": "^4.258.3",
|
||||
|
@ -2953,13 +2953,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@keycloak/keycloak-admin-client": {
|
||||
"version": "19.0.3",
|
||||
"license": "Apache-2.0",
|
||||
"version": "21.0.0-dev.3",
|
||||
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-21.0.0-dev.3.tgz",
|
||||
"integrity": "sha512-wFvxZXrVGiHgd3OCab4YWAwcinykPYhuNlO8BokyP6XClKKL5qCQgNm+iWxgDB/njUdY3ovqDBTnY9KUKI3qWA==",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"camelize-ts": "^2.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"query-string": "^7.0.1",
|
||||
"url-join": "^5.0.0",
|
||||
"url-template": "^3.0.0"
|
||||
},
|
||||
|
@ -6822,6 +6822,7 @@
|
|||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
|
@ -8354,13 +8355,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir": {
|
||||
"version": "3.3.2",
|
||||
"dev": true,
|
||||
|
@ -11726,22 +11720,6 @@
|
|||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/query-string": {
|
||||
"version": "7.1.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"filter-obj": "^1.1.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/querystring": {
|
||||
"version": "0.2.0",
|
||||
"dev": true,
|
||||
|
@ -12856,13 +12834,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/split-string": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
|
@ -13054,13 +13025,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/strict-uri-encode": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"dev": true,
|
||||
|
@ -16898,12 +16862,13 @@
|
|||
}
|
||||
},
|
||||
"@keycloak/keycloak-admin-client": {
|
||||
"version": "19.0.3",
|
||||
"version": "21.0.0-dev.3",
|
||||
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-21.0.0-dev.3.tgz",
|
||||
"integrity": "sha512-wFvxZXrVGiHgd3OCab4YWAwcinykPYhuNlO8BokyP6XClKKL5qCQgNm+iWxgDB/njUdY3ovqDBTnY9KUKI3qWA==",
|
||||
"requires": {
|
||||
"axios": "^0.27.2",
|
||||
"camelize-ts": "^2.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"query-string": "^7.0.1",
|
||||
"url-join": "^5.0.0",
|
||||
"url-template": "^3.0.0"
|
||||
}
|
||||
|
@ -18311,7 +18276,7 @@
|
|||
"@babel/preset-env": "^7.19.4",
|
||||
"@cypress/webpack-batteries-included-preprocessor": "^2.2.3",
|
||||
"@cypress/webpack-preprocessor": "^5.15.3",
|
||||
"@keycloak/keycloak-admin-client": "^19.0.3",
|
||||
"@keycloak/keycloak-admin-client": "^21.0.0-dev.3",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@patternfly/patternfly": "^4.219.2",
|
||||
"@patternfly/react-code-editor": "^4.82.55",
|
||||
|
@ -19725,7 +19690,8 @@
|
|||
"dev": true
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0"
|
||||
"version": "0.2.0",
|
||||
"dev": true
|
||||
},
|
||||
"decompress": {
|
||||
"version": "4.2.1",
|
||||
|
@ -20742,9 +20708,6 @@
|
|||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"filter-obj": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"find-cache-dir": {
|
||||
"version": "3.3.2",
|
||||
"dev": true,
|
||||
|
@ -22943,15 +22906,6 @@
|
|||
"version": "6.5.3",
|
||||
"dev": true
|
||||
},
|
||||
"query-string": {
|
||||
"version": "7.1.1",
|
||||
"requires": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"filter-obj": "^1.1.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"dev": true
|
||||
|
@ -23686,9 +23640,6 @@
|
|||
"version": "1.4.8",
|
||||
"dev": true
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"split-string": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
|
@ -23830,9 +23781,6 @@
|
|||
"version": "1.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"dev": true,
|
||||
|
|
Loading…
Reference in a new issue