Merge pull request #556 from edewit/change-asyncFetch-to-it's-own-hook

changed `asyncStateFetch` to be it's own hook
This commit is contained in:
mfrances17 2021-05-18 10:13:30 -04:00 committed by GitHub
commit c0c3fd6692
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 569 additions and 728 deletions

View file

@ -25,6 +25,7 @@
"@patternfly/react-core": "4.115.1", "@patternfly/react-core": "4.115.1",
"@patternfly/react-icons": "4.10.1", "@patternfly/react-icons": "4.10.1",
"@patternfly/react-table": "4.26.7", "@patternfly/react-table": "4.26.7",
"axios": "^0.21.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next": "^19.6.2", "i18next": "^19.6.2",
"keycloak-admin": "1.14.16", "keycloak-admin": "1.14.16",

View file

@ -1,8 +1,7 @@
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useState } from "react";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useErrorHandler } from "react-error-boundary";
import { import {
FormGroup, FormGroup,
PageSection, PageSection,
@ -24,10 +23,7 @@ import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { RealmContext } from "../../context/realm-context/RealmContext"; import { RealmContext } from "../../context/realm-context/RealmContext";
import { import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { ViewHeader } from "../../components/view-header/ViewHeader"; import { ViewHeader } from "../../components/view-header/ViewHeader";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
@ -36,7 +32,6 @@ import { FormAccess } from "../../components/form-access/FormAccess";
export const RoleMappingForm = () => { export const RoleMappingForm = () => {
const { realm } = useContext(RealmContext); const { realm } = useContext(RealmContext);
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler();
const history = useHistory(); const history = useHistory();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
@ -51,53 +46,49 @@ export const RoleMappingForm = () => {
const [selectedClient, setSelectedClient] = useState<ClientRepresentation>(); const [selectedClient, setSelectedClient] = useState<ClientRepresentation>();
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]); const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
useEffect(() => { useFetch(
return asyncStateFetch( async () => {
async () => { const clients = await adminClient.clients.find();
const clients = await adminClient.clients.find();
const asyncFilter = async ( const asyncFilter = async (
predicate: (client: ClientRepresentation) => Promise<boolean> predicate: (client: ClientRepresentation) => Promise<boolean>
) => { ) => {
const results = await Promise.all(clients.map(predicate)); const results = await Promise.all(clients.map(predicate));
return clients.filter((_, index) => results[index]); return clients.filter((_, index) => results[index]);
}; };
const filteredClients = await asyncFilter( const filteredClients = await asyncFilter(
async (client) => async (client) =>
(await adminClient.clients.listRoles({ id: client.id! })).length > 0 (await adminClient.clients.listRoles({ id: client.id! })).length > 0
); );
filteredClients.map( filteredClients.map(
(client) => (client) =>
(client.toString = function () { (client.toString = function () {
return this.clientId!; return this.clientId!;
}) })
); );
return filteredClients; return filteredClients;
}, },
(filteredClients) => setClients(filteredClients), (filteredClients) => setClients(filteredClients),
handleError []
); );
}, []);
useEffect(() => { useFetch(
return asyncStateFetch( async () => {
async () => { const client = selectedClient as ClientRepresentation;
const client = selectedClient as ClientRepresentation; if (client && client.name !== "realmRoles") {
if (client && client.name !== "realmRoles") { const clientRoles = await adminClient.clients.listRoles({
const clientRoles = await adminClient.clients.listRoles({ id: client.id!,
id: client.id!, });
}); return clientRoles;
return clientRoles; } else {
} else { return await adminClient.roles.find();
return await adminClient.roles.find(); }
} },
}, (clientRoles) => setClientRoles(clientRoles),
(clientRoles) => setClientRoles(clientRoles), [selectedClient]
handleError );
);
}, [selectedClient]);
const save = async (mapping: ProtocolMapperRepresentation) => { const save = async (mapping: ProtocolMapperRepresentation) => {
try { try {

View file

@ -1,7 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useErrorHandler } from "react-error-boundary";
import { import {
ActionGroup, ActionGroup,
AlertVariant, AlertVariant,
@ -24,10 +23,7 @@ import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configProp
import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
import { ViewHeader } from "../../components/view-header/ViewHeader"; import { ViewHeader } from "../../components/view-header/ViewHeader";
import { import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
@ -45,7 +41,6 @@ type Params = {
export const MappingDetails = () => { export const MappingDetails = () => {
const { t } = useTranslation("client-scopes"); const { t } = useTranslation("client-scopes");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const { id, mapperId } = useParams<Params>(); const { id, mapperId } = useParams<Params>();
@ -61,52 +56,50 @@ export const MappingDetails = () => {
const serverInfo = useServerInfo(); const serverInfo = useServerInfo();
const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/; const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/;
useEffect(() => { useFetch(
return asyncStateFetch( async () => {
async () => { if (mapperId.match(isGuid)) {
if (mapperId.match(isGuid)) { const data = await adminClient.clientScopes.findProtocolMapper({
const data = await adminClient.clientScopes.findProtocolMapper({ id,
id, mapperId,
mapperId, });
if (data) {
Object.entries(data).map((entry) => {
convertToFormValues(entry[1], "config", setValue);
}); });
if (data) {
Object.entries(data).map((entry) => {
convertToFormValues(entry[1], "config", setValue);
});
}
const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
const properties = mapperTypes.find(
(type) => type.id === data!.protocolMapper
)?.properties!;
return {
configProperties: properties,
mapping: data,
};
} else {
const scope = await adminClient.clientScopes.findOne({ id });
const protocolMappers = serverInfo.protocolMapperTypes![
scope.protocol!
];
const mapping = protocolMappers.find(
(mapper) => mapper.id === mapperId
)!;
return {
mapping: {
name: mapping.name,
protocol: scope.protocol,
protocolMapper: mapperId,
},
};
} }
}, const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
(result) => { const properties = mapperTypes.find(
setConfigProperties(result.configProperties); (type) => type.id === data!.protocolMapper
setMapping(result.mapping); )?.properties!;
},
handleError return {
); configProperties: properties,
}, []); mapping: data,
};
} else {
const scope = await adminClient.clientScopes.findOne({ id });
const protocolMappers = serverInfo.protocolMapperTypes![
scope.protocol!
];
const mapping = protocolMappers.find(
(mapper) => mapper.id === mapperId
)!;
return {
mapping: {
name: mapping.name,
protocol: scope.protocol,
protocolMapper: mapperId,
},
};
}
},
(result) => {
setConfigProperties(result.configProperties);
setMapping(result.mapping);
},
[]
);
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "common:deleteMappingTitle", titleKey: "common:deleteMappingTitle",

View file

@ -1,6 +1,5 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useErrorHandler } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
AlertVariant, AlertVariant,
@ -11,10 +10,7 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation"; import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
import { import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs"; import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { ViewHeader } from "../../components/view-header/ViewHeader"; import { ViewHeader } from "../../components/view-header/ViewHeader";
@ -30,7 +26,6 @@ export const ClientScopeForm = () => {
const [hide, setHide] = useState(false); const [hide, setHide] = useState(false);
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
@ -38,19 +33,17 @@ export const ClientScopeForm = () => {
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
useEffect(() => { useFetch(
return asyncStateFetch( async () => {
async () => { if (id) {
if (id) { return await adminClient.clientScopes.findOne({ id });
return await adminClient.clientScopes.findOne({ id }); }
} },
}, (clientScope) => {
(clientScope) => { setClientScope(clientScope);
setClientScope(clientScope); },
}, [key, id]
handleError );
);
}, [key, id]);
const loader = async () => { const loader = async () => {
const assignedRoles = hide const assignedRoles = hide

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { import {
Alert, Alert,
AlertVariant, AlertVariant,
@ -12,9 +12,8 @@ import {
TabTitleText, TabTitleText,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import { useErrorHandler } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, FormProvider, useForm } from "react-hook-form"; import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import _ from "lodash"; import _ from "lodash";
@ -26,7 +25,7 @@ import {
} from "../components/confirm-dialog/ConfirmDialog"; } from "../components/confirm-dialog/ConfirmDialog";
import { DownloadDialog } from "../components/download-dialog/DownloadDialog"; import { DownloadDialog } from "../components/download-dialog/DownloadDialog";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { Credentials } from "./credentials/Credentials"; import { Credentials } from "./credentials/Credentials";
import { import {
convertFormValuesToObject, convertFormValuesToObject,
@ -125,7 +124,6 @@ export const ClientDetails = () => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const handleError = useErrorHandler();
const { realm } = useRealm(); const { realm } = useRealm();
const history = useHistory(); const history = useHistory();
@ -140,6 +138,12 @@ export const ClientDetails = () => {
const form = useForm<ClientForm>(); const form = useForm<ClientForm>();
const { clientId } = useParams<{ clientId: string }>(); const { clientId } = useParams<{ clientId: string }>();
const clientAuthenticatorType = useWatch({
control: form.control,
name: "clientAuthenticatorType",
defaultValue: "client-secret",
});
const [client, setClient] = useState<ClientRepresentation>(); const [client, setClient] = useState<ClientRepresentation>();
const loader = async () => { const loader = async () => {
@ -178,16 +182,14 @@ export const ClientDetails = () => {
}); });
}; };
useEffect(() => { useFetch(
return asyncStateFetch( () => adminClient.clients.findOne({ id: clientId }),
() => adminClient.clients.findOne({ id: clientId }), (fetchedClient) => {
(fetchedClient) => { setClient(fetchedClient);
setClient(fetchedClient); setupForm(fetchedClient);
setupForm(fetchedClient); },
}, [clientId]
handleError );
);
}, [clientId]);
const save = async ( const save = async (
{ confirmed = false, messageKey = "clientSaveSuccess" }: SaveOptions = { { confirmed = false, messageKey = "clientSaveSuccess" }: SaveOptions = {
@ -198,8 +200,7 @@ export const ClientDetails = () => {
if (await form.trigger()) { if (await form.trigger()) {
if ( if (
!client?.publicClient && !client?.publicClient &&
client?.clientAuthenticatorType !== client?.clientAuthenticatorType !== clientAuthenticatorType &&
form.getValues("clientAuthenticatorType") &&
!confirmed !confirmed
) { ) {
toggleChangeAuthenticator(); toggleChangeAuthenticator();
@ -241,7 +242,7 @@ export const ClientDetails = () => {
<ConfirmDialogModal <ConfirmDialogModal
continueButtonLabel="common:yes" continueButtonLabel="common:yes"
titleKey={t("changeAuthenticatorConfirmTitle", { titleKey={t("changeAuthenticatorConfirmTitle", {
clientAuthenticatorType: form.getValues("clientAuthenticatorType"), clientAuthenticatorType: clientAuthenticatorType,
})} })}
open={changeAuthenticatorOpen} open={changeAuthenticatorOpen}
toggleDialog={toggleChangeAuthenticator} toggleDialog={toggleChangeAuthenticator}
@ -249,9 +250,9 @@ export const ClientDetails = () => {
> >
<> <>
{t("changeAuthenticatorConfirm", { {t("changeAuthenticatorConfirm", {
clientAuthenticatorType: form.getValues("clientAuthenticatorType"), clientAuthenticatorType: clientAuthenticatorType,
})} })}
{form.getValues("clientAuthenticatorType") === "client-jwt" && ( {clientAuthenticatorType === "client-jwt" && (
<Alert variant="info" isInline title={t("signedJWTConfirm")} /> <Alert variant="info" isInline title={t("signedJWTConfirm")} />
)} )}
</> </>

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { Link, useHistory } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
@ -49,8 +49,6 @@ export const ClientsSection = () => {
return await adminClient.clients.find({ ...params }); return await adminClient.clients.find({ ...params });
}; };
useEffect(refresh, [selectedClient]);
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: t("clientDelete", { clientId: selectedClient?.clientId }), titleKey: t("clientDelete", { clientId: selectedClient?.clientId }),
messageKey: "clients:clientDeleteConfirm", messageKey: "clients:clientDeleteConfirm",
@ -62,7 +60,7 @@ export const ClientsSection = () => {
id: selectedClient!.id!, id: selectedClient!.id!,
}); });
addAlert(t("clientDeletedSuccess"), AlertVariant.success); addAlert(t("clientDeletedSuccess"), AlertVariant.success);
setSelectedClient(undefined); refresh();
} catch (error) { } catch (error) {
addAlert(t("clientDeleteError", { error }), AlertVariant.danger); addAlert(t("clientDeleteError", { error }), AlertVariant.danger);
} }

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { Control, Controller } from "react-hook-form"; import { Control, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import _ from "lodash"; import _ from "lodash";
@ -11,12 +11,8 @@ import {
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
asyncStateFetch,
useAdminClient,
} from "../../context/auth/AdminClient";
import { SaveReset } from "./SaveReset"; import { SaveReset } from "./SaveReset";
import { useErrorHandler } from "react-error-boundary";
type AuthenticationOverridesProps = { type AuthenticationOverridesProps = {
control: Control<Record<string, any>>; control: Control<Record<string, any>>;
@ -34,32 +30,27 @@ export const AuthenticationOverrides = ({
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const [flows, setFlows] = useState<JSX.Element[]>([]); const [flows, setFlows] = useState<JSX.Element[]>([]);
const handleError = useErrorHandler();
const [browserFlowOpen, setBrowserFlowOpen] = useState(false); const [browserFlowOpen, setBrowserFlowOpen] = useState(false);
const [directGrantOpen, setDirectGrantOpen] = useState(false); const [directGrantOpen, setDirectGrantOpen] = useState(false);
useEffect( useFetch(
() => () => adminClient.authenticationManagement.getFlows(),
asyncStateFetch( (flows) => {
() => adminClient.authenticationManagement.getFlows(), let filteredFlows = [
(flows) => { ...flows.filter((flow) => flow.providerId !== "client-flow"),
let filteredFlows = [ ];
...flows.filter((flow) => flow.providerId !== "client-flow"), filteredFlows = _.sortBy(filteredFlows, [(f) => f.alias]);
]; setFlows([
filteredFlows = _.sortBy(filteredFlows, [(f) => f.alias]); <SelectOption key="empty" value="">
setFlows([ {t("common:choose")}
<SelectOption key="empty" value=""> </SelectOption>,
{t("common:choose")} ...filteredFlows.map((flow) => (
</SelectOption>, <SelectOption key={flow.id} value={flow.id}>
...filteredFlows.map((flow) => ( {flow.alias}
<SelectOption key={flow.id} value={flow.id}> </SelectOption>
{flow.alias} )),
</SelectOption> ]);
)), },
]);
},
handleError
),
[] []
); );

View file

@ -24,10 +24,7 @@ import { useAlerts } from "../../components/alert/Alerts";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { ClientSecret } from "./ClientSecret"; import { ClientSecret } from "./ClientSecret";
import { SignedJWT } from "./SignedJWT"; import { SignedJWT } from "./SignedJWT";
@ -55,8 +52,12 @@ export type CredentialsProps = {
export const Credentials = ({ clientId, save }: CredentialsProps) => { export const Credentials = ({ clientId, save }: CredentialsProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const [providers, setProviders] = useState<ClientAuthenticatorProviders[]>(
[]
);
const { const {
control, control,
formState: { isDirty }, formState: { isDirty },
@ -64,37 +65,33 @@ export const Credentials = ({ clientId, save }: CredentialsProps) => {
const clientAuthenticatorType = useWatch({ const clientAuthenticatorType = useWatch({
control: control, control: control,
name: "clientAuthenticatorType", name: "clientAuthenticatorType",
defaultValue: "",
}); });
const [providers, setProviders] = useState<ClientAuthenticatorProviders[]>(
[]
);
const [secret, setSecret] = useState(""); const [secret, setSecret] = useState("");
const [accessToken, setAccessToken] = useState(""); const [accessToken, setAccessToken] = useState("");
const [open, isOpen] = useState(false); const [open, isOpen] = useState(false);
useEffect(() => { useFetch(
return asyncStateFetch( async () => {
async () => { const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders(
const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders( { id: clientId }
{ id: clientId } );
);
const secret = await adminClient.clients.getClientSecret({ const secret = await adminClient.clients.getClientSecret({
id: clientId, id: clientId,
}); });
return { return {
providers, providers,
secret: secret.value!, secret: secret.value!,
}; };
}, },
({ providers, secret }) => { ({ providers, secret }) => {
setProviders(providers); setProviders(providers);
setSecret(secret); setSecret(secret);
}, },
handleError []
); );
}, []);
async function regenerate<T>( async function regenerate<T>(
call: (clientId: string) => Promise<T>, call: (clientId: string) => Promise<T>,
@ -164,6 +161,7 @@ export const Credentials = ({ clientId, save }: CredentialsProps) => {
<Controller <Controller
name="clientAuthenticatorType" name="clientAuthenticatorType"
control={control} control={control}
defaultValue=""
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="kc-client-authenticator-type" toggleId="kc-client-authenticator-type"

View file

@ -25,13 +25,9 @@ import { FormAccess } from "../../components/form-access/FormAccess";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useFormContext, useWatch } from "react-hook-form";
import { ClientForm } from "../ClientDetails"; import { ClientForm } from "../ClientDetails";
import { GenerateKeyDialog } from "./GenerateKeyDialog"; import { GenerateKeyDialog } from "./GenerateKeyDialog";
import { import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
asyncStateFetch,
useAdminClient,
} from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog"; import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog";
import { useErrorHandler } from "react-error-boundary";
type KeysProps = { type KeysProps = {
save: () => void; save: () => void;
@ -48,7 +44,6 @@ export const Keys = ({ clientId, save }: KeysProps) => {
formState: { isDirty }, formState: { isDirty },
} = useFormContext<ClientForm>(); } = useFormContext<ClientForm>();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const errorHandler = useErrorHandler();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const [keyInfo, setKeyInfo] = useState<CertificateRepresentation>(); const [keyInfo, setKeyInfo] = useState<CertificateRepresentation>();
@ -60,13 +55,10 @@ export const Keys = ({ clientId, save }: KeysProps) => {
name: "attributes.use-jwks-url", name: "attributes.use-jwks-url",
defaultValue: "false", defaultValue: "false",
}); });
useEffect(
() => useFetch(
asyncStateFetch( () => adminClient.clients.getKeyInfo({ id: clientId, attr }),
() => adminClient.clients.getKeyInfo({ id: clientId, attr }), (info) => setKeyInfo(info),
(info) => setKeyInfo(info),
errorHandler
),
[] []
); );

View file

@ -1,6 +1,5 @@
import React, { useContext, useEffect, useRef, useState } from "react"; import React, { useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useErrorHandler } from "react-error-boundary";
import { import {
ClipboardCopy, ClipboardCopy,
EmptyState, EmptyState,
@ -31,10 +30,7 @@ import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
import { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation"; import { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
import { import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { RealmContext } from "../../context/realm-context/RealmContext"; import { RealmContext } from "../../context/realm-context/RealmContext";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
@ -149,14 +145,11 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
const tabContent2 = useRef(null); const tabContent2 = useRef(null);
const tabContent3 = useRef(null); const tabContent3 = useRef(null);
const handleError = useErrorHandler(); useFetch(
useEffect(() => { () => adminClient.clients.listOptionalClientScopes({ id: clientId }),
return asyncStateFetch( (optionalClientScopes) => setSelectableScopes(optionalClientScopes),
() => adminClient.clients.listOptionalClientScopes({ id: clientId }), []
(optionalClientScopes) => setSelectableScopes(optionalClientScopes), );
handleError
);
}, []);
const toString = (user: UserRepresentation) => { const toString = (user: UserRepresentation) => {
return ( return (
@ -169,90 +162,82 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
); );
}; };
useEffect(() => { useFetch(
return asyncStateFetch( () => {
() => { if (userSearch.length > 2) {
if (userSearch.length > 2) { return adminClient.users.find({ search: userSearch });
return adminClient.users.find({ search: userSearch }); } else {
} else { return Promise.resolve<UserRepresentation[]>([]);
return Promise.resolve<UserRepresentation[]>([]); }
} },
}, (users) =>
(users) => setUserItems(
setUserItems( users
users .map((user) => {
.map((user) => { user.toString = function () {
user.toString = function () { return toString(this);
return toString(this); };
}; return user;
return user; })
}) .map((user) => <SelectOption key={user.id} value={user} />)
.map((user) => <SelectOption key={user.id} value={user} />) ),
), [userSearch]
handleError );
);
}, [userSearch]);
useEffect(() => { useFetch(
return asyncStateFetch( async () => {
async () => { const scope = selected.join(" ");
const scope = selected.join(" "); const effectiveRoles = await adminClient.clients.evaluatePermission({
const effectiveRoles = await adminClient.clients.evaluatePermission({ id: clientId,
roleContainer: realm,
scope,
type: "granted",
});
const mapperList = (await adminClient.clients.evaluateListProtocolMapper({
id: clientId,
scope,
})) as ({
type: ProtocolMapperTypeRepresentation;
} & ProtocolMapperRepresentation)[];
return {
mapperList,
effectiveRoles,
};
},
({ mapperList, effectiveRoles }) => {
setEffectiveRoles(effectiveRoles);
mapperList.map((mapper) => {
mapper.type = mapperTypes.filter(
(type) => type.id === mapper.protocolMapper
)[0];
});
setProtocolMappers(mapperList);
refresh();
},
[selected]
);
useFetch(
() => {
const scope = selected.join(" ");
if (user) {
return adminClient.clients.evaluateGenerateAccessToken({
id: clientId, id: clientId,
roleContainer: realm, userId: user.id!,
scope, scope,
type: "granted",
}); });
} else {
const mapperList = (await adminClient.clients.evaluateListProtocolMapper( return Promise.resolve({});
{ }
id: clientId, },
scope, (accessToken) => {
} setAccessToken(JSON.stringify(accessToken, undefined, 3));
)) as ({ },
type: ProtocolMapperTypeRepresentation; [user, selected]
} & ProtocolMapperRepresentation)[]; );
return {
mapperList,
effectiveRoles,
};
},
({ mapperList, effectiveRoles }) => {
setEffectiveRoles(effectiveRoles);
mapperList.map((mapper) => {
mapper.type = mapperTypes.filter(
(type) => type.id === mapper.protocolMapper
)[0];
});
setProtocolMappers(mapperList);
refresh();
},
handleError
);
}, [selected]);
useEffect(() => {
return asyncStateFetch(
() => {
const scope = selected.join(" ");
if (user) {
return adminClient.clients.evaluateGenerateAccessToken({
id: clientId,
userId: user.id!,
scope,
});
} else {
return Promise.resolve({});
}
},
(accessToken) => {
setAccessToken(JSON.stringify(accessToken, undefined, 3));
},
handleError
);
}, [user, selected]);
return ( return (
<> <>

View file

@ -1,8 +1,7 @@
import React, { DependencyList, useEffect, useState } from "react"; import React, { DependencyList, useState } from "react";
import { Spinner } from "@patternfly/react-core"; import { Spinner } from "@patternfly/react-core";
import { useErrorHandler } from "react-error-boundary";
import { asyncStateFetch } from "../../context/auth/AdminClient"; import { useFetch } from "../../context/auth/AdminClient";
type DataLoaderProps<T> = { type DataLoaderProps<T> = {
loader: () => Promise<T>; loader: () => Promise<T>;
@ -12,15 +11,12 @@ type DataLoaderProps<T> = {
export function DataLoader<T>(props: DataLoaderProps<T>) { export function DataLoader<T>(props: DataLoaderProps<T>) {
const [data, setData] = useState<T | undefined>(); const [data, setData] = useState<T | undefined>();
const handleError = useErrorHandler();
useEffect(() => { useFetch(
return asyncStateFetch( () => props.loader(),
() => props.loader(), (result) => setData(result),
(result) => setData(result), props.deps || []
handleError );
);
}, props.deps || []);
if (data) { if (data) {
if (props.children instanceof Function) { if (props.children instanceof Function) {

View file

@ -2,6 +2,7 @@ import React from "react";
import { DataLoader } from "../DataLoader"; import { DataLoader } from "../DataLoader";
import { act } from "@testing-library/react"; import { act } from "@testing-library/react";
import { render, unmountComponentAtNode } from "react-dom"; import { render, unmountComponentAtNode } from "react-dom";
import { MockAdminClient } from "../../../stories/MockAdminClient";
let container: HTMLDivElement; let container: HTMLDivElement;
beforeEach(() => { beforeEach(() => {
@ -19,15 +20,17 @@ describe("<DataLoader />", () => {
const loader = () => Promise.resolve(["a", "b"]); const loader = () => Promise.resolve(["a", "b"]);
await act(async () => { await act(async () => {
render( render(
<DataLoader loader={loader}> <MockAdminClient>
{(result) => ( <DataLoader loader={loader}>
<div> {(result) => (
{result.map((d, i) => ( <div>
<i key={i}>{d}</i> {result.map((d, i) => (
))} <i key={i}>{d}</i>
</div> ))}
)} </div>
</DataLoader>, )}
</DataLoader>
</MockAdminClient>,
container container
); );
}); });

View file

@ -1,5 +1,4 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useContext } from "react";
import { useErrorHandler } from "react-error-boundary";
import { import {
Alert, Alert,
AlertVariant, AlertVariant,
@ -19,10 +18,7 @@ import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog";
import { HelpItem } from "../help-enabler/HelpItem"; import { HelpItem } from "../help-enabler/HelpItem";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { HelpContext } from "../help-enabler/HelpHeader"; import { HelpContext } from "../help-enabler/HelpHeader";
type DownloadDialogProps = { type DownloadDialogProps = {
@ -39,7 +35,6 @@ export const DownloadDialog = ({
protocol = "openid-connect", protocol = "openid-connect",
}: DownloadDialogProps) => { }: DownloadDialogProps) => {
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler();
const { t } = useTranslation("common"); const { t } = useTranslation("common");
const { enabled } = useContext(HelpContext); const { enabled } = useContext(HelpContext);
const serverInfo = useServerInfo(); const serverInfo = useServerInfo();
@ -51,23 +46,21 @@ export const DownloadDialog = ({
const [snippet, setSnippet] = useState(""); const [snippet, setSnippet] = useState("");
const [openType, setOpenType] = useState(false); const [openType, setOpenType] = useState(false);
useEffect(() => { useFetch(
return asyncStateFetch( async () => {
async () => { const snippet = await adminClient.clients.getInstallationProviders({
const snippet = await adminClient.clients.getInstallationProviders({ id,
id, providerId: selected,
providerId: selected, });
}); if (typeof snippet === "string") {
if (typeof snippet === "string") { return snippet;
return snippet; } else {
} else { return JSON.stringify(snippet, undefined, 3);
return JSON.stringify(snippet, undefined, 3); }
} },
}, (snippet) => setSnippet(snippet),
(snippet) => setSnippet(snippet), [id, selected]
handleError );
);
}, [id, selected]);
return ( return (
<ConfirmDialogModal <ConfirmDialogModal
titleKey={t("clients:downloadAdaptorTitle")} titleKey={t("clients:downloadAdaptorTitle")}

View file

@ -1,6 +1,5 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useErrorHandler } from "react-error-boundary";
import _ from "lodash"; import _ from "lodash";
import { import {
Badge, Badge,
@ -18,10 +17,7 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { KeycloakDataTable } from "../table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../table-toolbar/KeycloakDataTable";
import { import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
asyncStateFetch,
useAdminClient,
} from "../../context/auth/AdminClient";
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import { FilterIcon } from "@patternfly/react-icons"; import { FilterIcon } from "@patternfly/react-icons";
import { Row, ServiceRole } from "./RoleMapping"; import { Row, ServiceRole } from "./RoleMapping";
@ -54,7 +50,6 @@ export const AddRoleMappingModal = ({
}: AddRoleMappingModalProps) => { }: AddRoleMappingModalProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const errorHandler = useErrorHandler();
const [clients, setClients] = useState<ClientRole[]>([]); const [clients, setClients] = useState<ClientRole[]>([]);
const [searchToggle, setSearchToggle] = useState(false); const [searchToggle, setSearchToggle] = useState(false);
@ -65,48 +60,42 @@ export const AddRoleMappingModal = ({
const [selectedClients, setSelectedClients] = useState<ClientRole[]>([]); const [selectedClients, setSelectedClients] = useState<ClientRole[]>([]);
const [selectedRows, setSelectedRows] = useState<Row[]>([]); const [selectedRows, setSelectedRows] = useState<Row[]>([]);
useEffect( useFetch(
() => async () => {
asyncStateFetch( const clients = await adminClient.clients.find();
async () => { return (
const clients = await adminClient.clients.find(); await Promise.all(
return ( clients.map(async (client) => {
await Promise.all( let roles: RoleRepresentation[] = [];
clients.map(async (client) => { if (type === "service-account") {
let roles: RoleRepresentation[] = []; roles = await adminClient.users.listAvailableClientRoleMappings({
if (type === "service-account") { id: id,
roles = await adminClient.users.listAvailableClientRoleMappings( clientUniqueId: client.id!,
{ });
id: id, } else if (type === "client-scope") {
clientUniqueId: client.id!, roles = await adminClient.clientScopes.listAvailableClientScopeMappings(
} {
); id,
} else if (type === "client-scope") { client: client.id!,
roles = await adminClient.clientScopes.listAvailableClientScopeMappings(
{
id,
client: client.id!,
}
);
} }
return { );
roles, }
client, return {
}; roles,
}) client,
) };
) })
.flat() )
.filter((row) => row.roles.length !== 0) )
.map((row) => { .flat()
return { ...row.client, numberOfRoles: row.roles.length }; .filter((row) => row.roles.length !== 0)
}); .map((row) => {
}, return { ...row.client, numberOfRoles: row.roles.length };
(clients) => { });
setClients(clients); },
}, (clients) => {
errorHandler setClients(clients);
), },
[] []
); );

View file

@ -1,4 +1,4 @@
import React, { Children } from "react"; import React, { Children, Fragment } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
Grid, Grid,
@ -38,22 +38,18 @@ export const ScrollForm = ({
<Grid hasGutter {...rest}> <Grid hasGutter {...rest}>
<GridItem span={8}> <GridItem span={8}>
{sections.map((cat, index) => ( {sections.map((cat, index) => (
<> <Fragment key={cat}>
{!borders && ( {!borders && (
<ScrollPanel <ScrollPanel scrollId={spacesToHyphens(cat)} title={cat}>
scrollId={spacesToHyphens(cat)}
key={cat}
title={cat}
>
{nodes[index]} {nodes[index]}
</ScrollPanel> </ScrollPanel>
)} )}
{borders && ( {borders && (
<FormPanel scrollId={spacesToHyphens(cat)} key={cat} title={cat}> <FormPanel scrollId={spacesToHyphens(cat)} title={cat}>
{nodes[index]} {nodes[index]}
</FormPanel> </FormPanel>
)} )}
</> </Fragment>
))} ))}
</GridItem> </GridItem>
<GridItem span={4}> <GridItem span={4}>

View file

@ -1,6 +1,5 @@
import React, { isValidElement, ReactNode, useEffect, useState } from "react"; import React, { isValidElement, ReactNode, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useErrorHandler } from "react-error-boundary";
import { import {
IAction, IAction,
IActions, IActions,
@ -16,7 +15,7 @@ import { Spinner } from "@patternfly/react-core";
import _ from "lodash"; import _ from "lodash";
import { PaginatingTableToolbar } from "./PaginatingTableToolbar"; import { PaginatingTableToolbar } from "./PaginatingTableToolbar";
import { asyncStateFetch } from "../../context/auth/AdminClient"; import { useFetch } from "../../context/auth/AdminClient";
import { ListEmptyState } from "../list-empty-state/ListEmptyState"; import { ListEmptyState } from "../list-empty-state/ListEmptyState";
import { SVGIconProps } from "@patternfly/react-icons/dist/js/createIcon"; import { SVGIconProps } from "@patternfly/react-icons/dist/js/createIcon";
@ -181,7 +180,6 @@ export function KeycloakDataTable<T>({
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
const handleError = useErrorHandler();
useEffect(() => { useEffect(() => {
if (canSelectAll) { if (canSelectAll) {
@ -199,27 +197,24 @@ export function KeycloakDataTable<T>({
} }
}, [selected]); }, [selected]);
useEffect(() => { useFetch(
setLoading(true); async () => {
return asyncStateFetch( setLoading(true);
async () => { return unPaginatedData || (await loader(first, max, search));
let data = unPaginatedData || (await loader(first, max, search)); },
(data) => {
if (!isPaginated) {
setUnPaginatedData(data);
data = data.slice(first, first + max);
}
if (!isPaginated) { const result = convertToColumns(data);
setUnPaginatedData(data); setRows(result);
data = data.slice(first, first + max); setFilteredData(result);
} setLoading(false);
},
return convertToColumns(data); [key, first, max, search]
}, );
(result) => {
setRows(result);
setFilteredData(result);
setLoading(false);
},
handleError
);
}, [key, first, max, search]);
const getNodeText = (node: Cell<T>): string => { const getNodeText = (node: Cell<T>): string => {
if (["string", "number"].includes(typeof node)) { if (["string", "number"].includes(typeof node)) {
@ -356,6 +351,7 @@ export function KeycloakDataTable<T>({
return ( return (
<> <>
{!rows && loading && <Loading />}
{rows && ( {rows && (
<PaginatingTableToolbar <PaginatingTableToolbar
count={rows.length} count={rows.length}

View file

@ -1,5 +1,8 @@
import { createContext, useContext } from "react"; import { createContext, DependencyList, useContext, useEffect } from "react";
import axios from "axios";
import KeycloakAdminClient from "keycloak-admin"; import KeycloakAdminClient from "keycloak-admin";
import { useErrorHandler } from "react-error-boundary";
export const AdminClient = createContext<KeycloakAdminClient | undefined>( export const AdminClient = createContext<KeycloakAdminClient | undefined>(
undefined undefined
@ -15,37 +18,39 @@ export const useAdminClient = () => {
* It takes 2 functions one you do your adminClient call in and the other to set your state * It takes 2 functions one you do your adminClient call in and the other to set your state
* *
* @example * @example
* useEffect(() => { * useFetch(
* return asyncStateFetch( * () => adminClient.components.findOne({ id }),
* () => adminClient.components.findOne({ id }), * (component) => setupForm(component),
* (component) => setupForm(component) * []
* ); * );
* }, []);
* *
* @param adminClientCall use this to do your adminClient call * @param adminClientCall use this to do your adminClient call
* @param callback when the data is fetched this is where you set your state * @param callback when the data is fetched this is where you set your state
* @param onError custom error handler
*/ */
export function asyncStateFetch<T>( export function useFetch<T>(
adminClientCall: () => Promise<T>, adminClientCall: () => Promise<T>,
callback: (param: T) => void, callback: (param: T) => void,
onError: (error: Error) => void deps?: DependencyList
) { ) {
let canceled = false; const adminClient = useAdminClient();
const onError = useErrorHandler();
adminClientCall() const source = axios.CancelToken.source();
.then((result) => { adminClient.setConfig({ requestConfig: { cancelToken: source.token } });
try {
if (!canceled) { useEffect(() => {
callback(result); adminClientCall()
.then((result) => {
callback(result);
})
.catch((error) => {
if (!axios.isCancel(error)) {
onError(error);
} }
} catch (error) { });
if (onError) onError(error);
}
})
.catch(onError);
return () => { return () => {
canceled = true; source.cancel();
}; };
}, deps);
} }

View file

@ -1,10 +1,9 @@
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useState } from "react";
import _ from "lodash"; import _ from "lodash";
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation"; import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import { RecentUsed } from "../../components/realm-selector/recent-used"; import { RecentUsed } from "../../components/realm-selector/recent-used";
import { useErrorHandler } from "react-error-boundary"; import { useAdminClient, useFetch } from "../auth/AdminClient";
import { asyncStateFetch, useAdminClient } from "../auth/AdminClient";
import { WhoAmIContext } from "../whoami/WhoAmI"; import { WhoAmIContext } from "../whoami/WhoAmI";
type RealmContextType = { type RealmContextType = {
@ -30,7 +29,6 @@ export const RealmContextProvider = ({
const [realm, setRealm] = useState(whoAmI.getHomeRealm()); const [realm, setRealm] = useState(whoAmI.getHomeRealm());
const [realms, setRealms] = useState<RealmRepresentation[]>([]); const [realms, setRealms] = useState<RealmRepresentation[]>([]);
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const errorHandler = useErrorHandler();
const recentUsed = new RecentUsed(); const recentUsed = new RecentUsed();
const updateRealmsList = (realms: RealmRepresentation[]) => { const updateRealmsList = (realms: RealmRepresentation[]) => {
@ -38,13 +36,9 @@ export const RealmContextProvider = ({
recentUsed.clean(realms.map((r) => r.realm!)); recentUsed.clean(realms.map((r) => r.realm!));
}; };
useEffect( useFetch(
() => () => adminClient.realms.find(),
asyncStateFetch( (realms) => updateRealmsList(realms),
() => adminClient.realms.find(),
(realms) => updateRealmsList(realms),
errorHandler
),
[] []
); );

View file

@ -1,11 +1,10 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useErrorHandler } from "react-error-boundary";
import i18n from "../../i18n"; import i18n from "../../i18n";
import { asyncStateFetch, useAdminClient } from "../auth/AdminClient";
import WhoAmIRepresentation, { import WhoAmIRepresentation, {
AccessType, AccessType,
} from "keycloak-admin/lib/defs/whoAmIRepresentation"; } from "keycloak-admin/lib/defs/whoAmIRepresentation";
import { useAdminClient, useFetch } from "../auth/AdminClient";
export class WhoAmI { export class WhoAmI {
constructor( constructor(
@ -62,20 +61,17 @@ export const WhoAmIContext = React.createContext<WhoAmIProps>({
type WhoAmIProviderProps = { children: React.ReactNode }; type WhoAmIProviderProps = { children: React.ReactNode };
export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => { export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => {
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler();
const [whoAmI, setWhoAmI] = useState<WhoAmI>(new WhoAmI()); const [whoAmI, setWhoAmI] = useState<WhoAmI>(new WhoAmI());
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
useEffect(() => { useFetch(
return asyncStateFetch( () => adminClient.whoAmI.find({ realm: "master" }),
() => adminClient.whoAmI.find({ realm: "master" }), (me) => {
(me) => { const whoAmI = new WhoAmI(adminClient.keycloak?.realm, me);
const whoAmI = new WhoAmI(adminClient.keycloak?.realm, me); setWhoAmI(whoAmI);
setWhoAmI(whoAmI); },
}, [key]
handleError );
);
}, [key]);
return ( return (
<WhoAmIContext.Provider value={{ refresh: () => setKey(key + 1), whoAmI }}> <WhoAmIContext.Provider value={{ refresh: () => setKey(key + 1), whoAmI }}>

View file

@ -1,7 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useHistory, useLocation } from "react-router-dom"; import { useHistory, useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useErrorHandler } from "react-error-boundary";
import { import {
DropdownItem, DropdownItem,
PageSection, PageSection,
@ -14,7 +13,7 @@ import {
import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation"; import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
@ -35,7 +34,6 @@ export const GroupsSection = () => {
const { subGroups, setSubGroups, currentGroup } = useSubGroups(); const { subGroups, setSubGroups, currentGroup } = useSubGroups();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const { realm } = useRealm(); const { realm } = useRealm();
const errorHandler = useErrorHandler();
const [rename, setRename] = useState<string>(); const [rename, setRename] = useState<string>();
@ -55,28 +53,24 @@ export const GroupsSection = () => {
return true; return true;
}; };
useEffect( useFetch(
() => async () => {
asyncStateFetch( const ids = getId(location.pathname);
async () => { const isNavigationStateInValid = ids && ids.length > subGroups.length;
const ids = getId(location.pathname);
const isNavigationStateInValid = ids && ids.length > subGroups.length;
if (isNavigationStateInValid) { if (isNavigationStateInValid) {
const groups: GroupRepresentation[] = []; const groups: GroupRepresentation[] = [];
for (const i of ids!) { for (const i of ids!) {
const group = await adminClient.groups.findOne({ id: i }); const group = await adminClient.groups.findOne({ id: i });
if (group) groups.push(group); if (group) groups.push(group);
} }
return groups; return groups;
} }
return []; return [];
}, },
(groups: GroupRepresentation[]) => { (groups: GroupRepresentation[]) => {
if (groups.length) setSubGroups(groups); if (groups.length) setSubGroups(groups);
}, },
errorHandler
),
[id] [id]
); );

View file

@ -1,6 +1,5 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useErrorHandler } from "react-error-boundary";
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,
@ -23,7 +22,7 @@ import {
import { AngleRightIcon, SearchIcon } from "@patternfly/react-icons"; import { AngleRightIcon, SearchIcon } from "@patternfly/react-icons";
import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation"; import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
type MoveGroupDialogProps = { type MoveGroupDialogProps = {
@ -40,7 +39,6 @@ export const MoveGroupDialog = ({
const { t } = useTranslation("groups"); const { t } = useTranslation("groups");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const errorHandler = useErrorHandler();
const [navigation, setNavigation] = useState<GroupRepresentation[]>([]); const [navigation, setNavigation] = useState<GroupRepresentation[]>([]);
const [groups, setGroups] = useState<GroupRepresentation[]>([]); const [groups, setGroups] = useState<GroupRepresentation[]>([]);
@ -50,23 +48,19 @@ export const MoveGroupDialog = ({
const [id, setId] = useState<string>(); const [id, setId] = useState<string>();
const currentGroup = () => navigation[navigation.length - 1]; const currentGroup = () => navigation[navigation.length - 1];
useEffect( useFetch(
() => async () => {
asyncStateFetch( if (id) {
async () => { const group = await adminClient.groups.findOne({ id });
if (id) { return { group, groups: group.subGroups! };
const group = await adminClient.groups.findOne({ id }); } else {
return { group, groups: group.subGroups! }; return { groups: await adminClient.groups.find() };
} else { }
return { groups: await adminClient.groups.find() }; },
} ({ group: selectedGroup, groups }) => {
}, if (selectedGroup) setNavigation([...navigation, selectedGroup]);
({ group: selectedGroup, groups }) => { setGroups(groups.filter((g) => g.id !== group.id));
if (selectedGroup) setNavigation([...navigation, selectedGroup]); },
setGroups(groups.filter((g) => g.id !== group.id));
},
errorHandler
),
[id] [id]
); );

View file

@ -1,6 +1,5 @@
import React, { Fragment, useEffect, useState } from "react"; import React, { Fragment, useState } from "react";
import { Link, useHistory, useRouteMatch } from "react-router-dom"; import { Link, useHistory, useRouteMatch } from "react-router-dom";
import { useErrorHandler } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import _ from "lodash"; import _ from "lodash";
import { import {
@ -26,7 +25,7 @@ import {
import IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProviderRepresentation"; import IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProviderRepresentation";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
@ -58,19 +57,14 @@ export const IdentityProvidersSection = () => {
>(); >();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const errorHandler = useErrorHandler();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
useEffect( useFetch(
() => async () =>
asyncStateFetch( (await adminClient.realms.findOne({ realm })).identityProviders!,
async () => (providers) => {
(await adminClient.realms.findOne({ realm })).identityProviders!, setProviders(providers);
(providers) => { },
setProviders(providers);
},
errorHandler
),
[] []
); );

View file

@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useErrorHandler } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form"; import { Controller, useFormContext } from "react-hook-form";
import { import {
@ -12,10 +11,7 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import AuthenticationFlowRepresentation from "keycloak-admin/lib/defs/authenticationFlowRepresentation"; import AuthenticationFlowRepresentation from "keycloak-admin/lib/defs/authenticationFlowRepresentation";
import { import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
asyncStateFetch,
useAdminClient,
} from "../../context/auth/AdminClient";
import { SwitchField } from "../component/SwitchField"; import { SwitchField } from "../component/SwitchField";
import { TextField } from "../component/TextField"; import { TextField } from "../component/TextField";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
@ -30,19 +26,10 @@ const LoginFlow = ({
const { control } = useFormContext(); const { control } = useFormContext();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const errorHandler = useErrorHandler();
const [flows, setFlows] = useState<AuthenticationFlowRepresentation[]>(); const [flows, setFlows] = useState<AuthenticationFlowRepresentation[]>();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
useEffect( useFetch(() => adminClient.authenticationManagement.getFlows(), setFlows, []);
() =>
asyncStateFetch(
() => adminClient.authenticationManagement.getFlows(),
setFlows,
errorHandler
),
[]
);
return ( return (
<FormGroup <FormGroup

View file

@ -1,6 +1,5 @@
import React, { useEffect } from "react"; import React from "react";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import { useErrorHandler } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, FormProvider, useForm } from "react-hook-form"; import { Controller, FormProvider, useForm } from "react-hook-form";
import { import {
@ -18,10 +17,7 @@ import IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProv
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { ScrollForm } from "../../components/scroll-form/ScrollForm"; import { ScrollForm } from "../../components/scroll-form/ScrollForm";
import { ViewHeader } from "../../components/view-header/ViewHeader"; import { ViewHeader } from "../../components/view-header/ViewHeader";
import { import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
asyncStateFetch,
useAdminClient,
} from "../../context/auth/AdminClient";
import { toUpperCase } from "../../util"; import { toUpperCase } from "../../util";
import { GeneralSettings } from "./GeneralSettings"; import { GeneralSettings } from "./GeneralSettings";
import { AdvancedSettings } from "./AdvancedSettings"; import { AdvancedSettings } from "./AdvancedSettings";
@ -91,17 +87,12 @@ export const DetailSettings = () => {
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const history = useHistory(); const history = useHistory();
const { realm } = useRealm(); const { realm } = useRealm();
const errorHandler = useErrorHandler();
useEffect( useFetch(
() => () => adminClient.identityProviders.findOne({ alias: id }),
asyncStateFetch( (provider) => {
() => adminClient.identityProviders.findOne({ alias: id }), Object.entries(provider).map((entry) => setValue(entry[0], entry[1]));
(provider) => { },
Object.entries(provider).map((entry) => setValue(entry[0], entry[1]));
},
errorHandler
),
[] []
); );

View file

@ -10,13 +10,12 @@ import {
ModalVariant, ModalVariant,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons"; import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons";
import _ from "lodash"; import _ from "lodash";
import { useErrorHandler } from "react-error-boundary";
type Role = RoleRepresentation & { type Role = RoleRepresentation & {
clientId?: string; clientId?: string;
@ -34,7 +33,6 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
const [name, setName] = useState(""); const [name, setName] = useState("");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const [selectedRows, setSelectedRows] = useState<RoleRepresentation[]>([]); const [selectedRows, setSelectedRows] = useState<RoleRepresentation[]>([]);
const errorHandler = useErrorHandler();
const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false);
const [filterType, setFilterType] = useState("roles"); const [filterType, setFilterType] = useState("roles");
@ -129,17 +127,19 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
refresh(); refresh();
}, [filterType]); }, [filterType]);
useEffect(() => { useFetch(
if (id) { async () => {
return asyncStateFetch( if (id) return await adminClient.roles.findOneById({ id });
() => adminClient.roles.findOneById({ id }), },
(fetchedRole) => setName(fetchedRole.name!), (fetchedRole) => {
errorHandler if (fetchedRole) {
); setName(fetchedRole.name!);
} else { } else {
setName(t("createRole")); setName(t("createRole"));
} }
}, []); },
[]
);
const onFilterDropdownToggle = () => { const onFilterDropdownToggle = () => {
setIsFilterDropdownOpen(!isFilterDropdownOpen); setIsFilterDropdownOpen(!isFilterDropdownOpen);

View file

@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, FormProvider, useForm } from "react-hook-form"; import { Controller, FormProvider, useForm } from "react-hook-form";
import { useErrorHandler } from "react-error-boundary";
import { import {
AlertVariant, AlertVariant,
ButtonVariant, ButtonVariant,
@ -17,7 +16,7 @@ import {
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation"; import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import { toUpperCase } from "../util"; import { toUpperCase } from "../util";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
@ -124,7 +123,6 @@ const RealmSettingsHeader = ({
export const RealmSettingsSection = () => { export const RealmSettingsSection = () => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const form = useForm(); const form = useForm();
@ -136,16 +134,14 @@ export const RealmSettingsSection = () => {
ComponentRepresentation[] ComponentRepresentation[]
>([]); >([]);
useEffect(() => { useFetch(
return asyncStateFetch( () => adminClient.realms.findOne({ realm: realmName }),
() => adminClient.realms.findOne({ realm: realmName }), (realm) => {
(realm) => { setupForm(realm);
setupForm(realm); setRealm(realm);
setRealm(realm); },
}, []
handleError );
);
}, []);
useEffect(() => { useEffect(() => {
const update = async () => { const update = async () => {

View file

@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import React from "react";
import { import {
ActionGroup, ActionGroup,
AlertVariant, AlertVariant,
@ -26,7 +26,7 @@ import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresenta
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
@ -35,7 +35,6 @@ import { ScrollForm } from "../components/scroll-form/ScrollForm";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { LdapMapperList } from "./ldap/mappers/LdapMapperList"; import { LdapMapperList } from "./ldap/mappers/LdapMapperList";
import { useErrorHandler } from "react-error-boundary";
type ldapComponentRepresentation = ComponentRepresentation & { type ldapComponentRepresentation = ComponentRepresentation & {
config?: { config?: {
@ -179,24 +178,24 @@ export const UserFederationLdapSettings = () => {
const history = useHistory(); const history = useHistory();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { realm } = useRealm(); const { realm } = useRealm();
const errorHandler = useErrorHandler();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
useEffect(() => { useFetch(
if (id) { async () => {
return asyncStateFetch( if (id) {
() => adminClient.components.findOne({ id }), return await adminClient.components.findOne({ id });
(fetchedComponent) => { }
if (fetchedComponent) { return undefined;
setupForm(fetchedComponent); },
} (fetchedComponent) => {
}, if (fetchedComponent) {
errorHandler setupForm(fetchedComponent);
); }
} },
}, []); []
);
const setupForm = (component: ComponentRepresentation) => { const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => { Object.entries(component).map((entry) => {

View file

@ -1,6 +1,5 @@
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useState } from "react";
import { useHistory, useRouteMatch } from "react-router-dom"; import { useHistory, useRouteMatch } from "react-router-dom";
import { useErrorHandler } from "react-error-boundary";
import { import {
AlertVariant, AlertVariant,
ButtonVariant, ButtonVariant,
@ -24,7 +23,7 @@ import { ViewHeader } from "../components/view-header/ViewHeader";
import { DatabaseIcon } from "@patternfly/react-icons"; import { DatabaseIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { RealmContext } from "../context/realm-context/RealmContext"; import { RealmContext } from "../context/realm-context/RealmContext";
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import "./user-federation.css"; import "./user-federation.css";
@ -40,25 +39,22 @@ export const UserFederationSection = () => {
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
const handleError = useErrorHandler();
const { url } = useRouteMatch(); const { url } = useRouteMatch();
const history = useHistory(); const history = useHistory();
useEffect(() => { useFetch(
return asyncStateFetch( () => {
() => { const testParams: { [name: string]: string | number } = {
const testParams: { [name: string]: string | number } = { parentId: realm,
parentId: realm, type: "org.keycloak.storage.UserStorageProvider",
type: "org.keycloak.storage.UserStorageProvider", };
}; return adminClient.components.find(testParams);
return adminClient.components.find(testParams); },
}, (userFederations) => {
(userFederations) => { setUserFederations(userFederations);
setUserFederations(userFederations); },
}, [key]
handleError );
);
}, [key]);
const ufAddProviderDropdownItems = [ const ufAddProviderDropdownItems = [
<DropdownItem <DropdownItem

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,
@ -20,10 +20,9 @@ import {
ToolbarItem, ToolbarItem,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { AngleRightIcon, SearchIcon } from "@patternfly/react-icons"; import { AngleRightIcon, SearchIcon } from "@patternfly/react-icons";
import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation"; import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
import { useErrorHandler } from "react-error-boundary";
import _ from "lodash"; import _ from "lodash";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
@ -52,8 +51,6 @@ export const JoinGroupDialog = ({
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const [selectedRows, setSelectedRows] = useState<Group[]>([]); const [selectedRows, setSelectedRows] = useState<Group[]>([]);
const errorHandler = useErrorHandler();
const [navigation, setNavigation] = useState<Group[]>([]); const [navigation, setNavigation] = useState<Group[]>([]);
const [groups, setGroups] = useState<Group[]>([]); const [groups, setGroups] = useState<Group[]>([]);
const [filtered, setFiltered] = useState<GroupRepresentation[]>(); const [filtered, setFiltered] = useState<GroupRepresentation[]>();
@ -63,42 +60,38 @@ export const JoinGroupDialog = ({
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
useEffect( useFetch(
() => async () => {
asyncStateFetch( const allGroups = await adminClient.groups.find();
async () => {
const allGroups = await adminClient.groups.find();
if (groupId) { if (groupId) {
const group = await adminClient.groups.findOne({ id: groupId }); const group = await adminClient.groups.findOne({ id: groupId });
return { group, groups: group.subGroups! }; return { group, groups: group.subGroups! };
} else if (id) { } else if (id) {
const existingUserGroups = await adminClient.users.listGroups({ const existingUserGroups = await adminClient.users.listGroups({
id, id,
}); });
return { return {
groups: _.differenceBy(allGroups, existingUserGroups, "id"), groups: _.differenceBy(allGroups, existingUserGroups, "id"),
}; };
} else } else
return { return {
groups: allGroups, groups: allGroups,
}; };
}, },
async ({ group: selectedGroup, groups }) => { async ({ group: selectedGroup, groups }) => {
if (selectedGroup) { if (selectedGroup) {
setNavigation([...navigation, selectedGroup]); setNavigation([...navigation, selectedGroup]);
} }
groups.forEach((group: Group) => { groups.forEach((group: Group) => {
group.checked = !!selectedRows.find((r) => r.id === group.id); group.checked = !!selectedRows.find((r) => r.id === group.id);
}); });
id id
? setGroups(groups) ? setGroups(groups)
: setGroups([...groups.filter((row) => !chips.includes(row.name))]); : setGroups([...groups.filter((row) => !chips.includes(row.name))]);
}, },
errorHandler
),
[groupId] [groupId]
); );

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { import {
ActionGroup, ActionGroup,
AlertVariant, AlertVariant,
@ -19,8 +19,7 @@ import { FormAccess } from "../components/form-access/FormAccess";
import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation"; import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { useErrorHandler } from "react-error-boundary";
import moment from "moment"; import moment from "moment";
import { JoinGroupDialog } from "./JoinGroupDialog"; import { JoinGroupDialog } from "./JoinGroupDialog";
import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation"; import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
@ -51,7 +50,6 @@ export const UserForm = ({
const history = useHistory(); const history = useHistory();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const handleError = useErrorHandler();
const watchUsernameInput = watch("username"); const watchUsernameInput = watch("username");
const [timestamp, setTimestamp] = useState(null); const [timestamp, setTimestamp] = useState(null);
@ -64,17 +62,15 @@ export const UserForm = ({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
useEffect(() => { useFetch(
if (editMode) { async () => {
return asyncStateFetch( if (editMode) return await adminClient.users.findOne({ id: id });
() => adminClient.users.findOne({ id: id }), },
(user) => { (user) => {
setupForm(user); if (user) setupForm(user);
}, },
handleError [chips]
); );
}
}, [chips]);
const setupForm = (user: UserRepresentation) => { const setupForm = (user: UserRepresentation) => {
reset(); reset();

View file

@ -14,10 +14,9 @@ import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { emptyFormatter } from "../util"; import { emptyFormatter } from "../util";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation"; import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
import { cellWidth } from "@patternfly/react-table"; import { cellWidth } from "@patternfly/react-table";
import { useErrorHandler } from "react-error-boundary";
import _ from "lodash"; import _ from "lodash";
import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation"; import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import { JoinGroupDialog } from "./JoinGroupDialog"; import { JoinGroupDialog } from "./JoinGroupDialog";
@ -43,7 +42,6 @@ export const UserGroups = () => {
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
const handleError = useErrorHandler();
const [selectedGroup, setSelectedGroup] = useState<GroupRepresentation>(); const [selectedGroup, setSelectedGroup] = useState<GroupRepresentation>();
const [list, setList] = useState(false); const [list, setList] = useState(false);
@ -177,17 +175,13 @@ export const UserGroups = () => {
return alphabetize(filterDupesfromGroups); return alphabetize(filterDupesfromGroups);
}; };
useEffect(() => { useFetch(
return asyncStateFetch( () => adminClient.users.listGroups({ id }),
() => { (response) => {
return Promise.resolve(adminClient.users.listGroups({ id })); setListGroups(!!(response && response.length > 0));
}, },
(response) => { []
setListGroups(!!(response && response.length > 0)); );
},
handleError
);
}, []);
useEffect(() => { useEffect(() => {
refresh(); refresh();

View file

@ -1,5 +1,4 @@
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useState } from "react";
import { useErrorHandler } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
AlertVariant, AlertVariant,
@ -17,7 +16,7 @@ import {
} from "@patternfly/react-icons"; } from "@patternfly/react-icons";
import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation"; import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
@ -36,7 +35,6 @@ type BruteUser = UserRepresentation & {
export const UsersSection = () => { export const UsersSection = () => {
const { t } = useTranslation("users"); const { t } = useTranslation("users");
const handleError = useErrorHandler();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const { realm: realmName } = useContext(RealmContext); const { realm: realmName } = useContext(RealmContext);
@ -50,27 +48,25 @@ export const UsersSection = () => {
const [key, setKey] = useState(""); const [key, setKey] = useState("");
const refresh = () => setKey(`${new Date().getTime()}`); const refresh = () => setKey(`${new Date().getTime()}`);
useEffect(() => { useFetch(
return asyncStateFetch( () => {
() => { const testParams = {
const testParams = { type: "org.keycloak.storage.UserStorageProvider",
type: "org.keycloak.storage.UserStorageProvider", };
};
return Promise.all([ return Promise.all([
adminClient.components.find(testParams), adminClient.components.find(testParams),
adminClient.users.count(), adminClient.users.count(),
]); ]);
}, },
(response) => { (response) => {
//should *only* list users when no user federation is configured and uses count > 100 //should *only* list users when no user federation is configured and uses count > 100
setListUsers( setListUsers(
!((response[0] && response[0].length > 0) || response[1] > 100) !((response[0] && response[0].length > 0) || response[1] > 100)
); );
}, },
handleError []
); );
}, []);
const UserDetailLink = (user: UserRepresentation) => ( const UserDetailLink = (user: UserRepresentation) => (
<> <>

View file

@ -5974,7 +5974,7 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
axios@^0.21.0: axios@^0.21.0, axios@^0.21.1:
version "0.21.1" version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==