only update state when component is mounted (#266)

* only update state when component is mounted

* removed unused import

* `useFetch` function to update state when mounted
This commit is contained in:
Erik Jan de Wit 2021-01-05 14:39:27 +01:00 committed by GitHub
parent 03e7ec760c
commit 27d9dadee7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 346 additions and 291 deletions

View file

@ -24,7 +24,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 { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } 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";
@ -47,40 +47,49 @@ export const RoleMappingForm = () => {
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]); const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const clients = await adminClient.clients.find(); async () => {
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!;
}) })
); );
setClients(filteredClients); return filteredClients;
})(); },
(filteredClients) => setClients(filteredClients)
);
}, []); }, []);
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const client = selectedClient as ClientRepresentation; async () => {
if (client && client.name !== "realmRoles") { const client = selectedClient as ClientRepresentation;
setClientRoles(await adminClient.clients.listRoles({ id: client.id! })); if (client && client.name !== "realmRoles") {
} else { const clientRoles = await adminClient.clients.listRoles({
setClientRoles(await adminClient.roles.find()); id: client.id!,
} });
})(); return clientRoles;
} else {
return await adminClient.roles.find();
}
},
(clientRoles) => setClientRoles(clientRoles)
);
}, [selectedClient]); }, [selectedClient]);
const save = async (mapping: ProtocolMapperRepresentation) => { const save = async (mapping: ProtocolMapperRepresentation) => {

View file

@ -24,7 +24,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 { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } 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";
@ -55,31 +55,39 @@ export const MappingDetails = () => {
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(() => { useEffect(() => {
if (id.match(isGuid)) { return useFetch(
(async () => { async () => {
const data = await adminClient.clientScopes.findProtocolMapper({ if (id.match(isGuid)) {
id: scopeId, const data = await adminClient.clientScopes.findProtocolMapper({
mapperId: id, id: scopeId,
}); mapperId: id,
if (data) {
Object.entries(data).map((entry) => {
convertToFormValues(entry[1], "config", setValue);
}); });
} if (data) {
const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; Object.entries(data).map((entry) => {
const properties = mapperTypes.find( convertToFormValues(entry[1], "config", setValue);
(type) => type.id === data!.protocolMapper });
)?.properties!; }
setConfigProperties(properties); const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
const properties = mapperTypes.find(
(type) => type.id === data!.protocolMapper
)?.properties!;
setMapping(data); return {
})(); configProperties: properties,
} else { mapping: data,
(async () => { };
const scope = await adminClient.clientScopes.findOne({ id: scopeId }); } else {
setMapping({ protocol: scope.protocol, protocolMapper: id }); const scope = await adminClient.clientScopes.findOne({ id: scopeId });
})(); return {
} mapping: { protocol: scope.protocol, protocolMapper: id },
};
}
},
(result) => {
setConfigProperties(result.configProperties);
setMapping(result.mapping);
}
);
}, []); }, []);
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({

View file

@ -22,7 +22,7 @@ import { Controller, useForm } from "react-hook-form";
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation"; import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { 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 { useLoginProviders } from "../../context/server-info/ServerInfoProvider"; import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
import { ViewHeader } from "../../components/view-header/ViewHeader"; import { ViewHeader } from "../../components/view-header/ViewHeader";
@ -45,25 +45,29 @@ export const ClientScopeForm = () => {
const [open, isOpen] = useState(false); const [open, isOpen] = useState(false);
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const load = async () => { const [key, setKey] = useState(0);
if (id) { const refresh = () => setKey(new Date().getTime());
const data = await adminClient.clientScopes.findOne({ id });
if (data) {
Object.entries(data).map((entry) => {
if (entry[0] === "attributes") {
convertToFormValues(entry[1], "attributes", setValue);
}
setValue(entry[0], entry[1]);
});
}
setClientScope(data);
}
};
useEffect(() => { useEffect(() => {
load(); return useFetch(
}, []); async () => {
if (id) {
const data = await adminClient.clientScopes.findOne({ id });
if (data) {
Object.entries(data).map((entry) => {
if (entry[0] === "attributes") {
convertToFormValues(entry[1], "attributes", setValue);
}
setValue(entry[0], entry[1]);
});
}
return data;
}
},
(data) => setClientScope(data)
);
}, [key]);
const save = async (clientScopes: ClientScopeRepresentation) => { const save = async (clientScopes: ClientScopeRepresentation) => {
try { try {
@ -305,7 +309,7 @@ export const ClientScopeForm = () => {
</Tab> </Tab>
<Tab eventKey={1} title={<TabTitleText>{t("mappers")}</TabTitleText>}> <Tab eventKey={1} title={<TabTitleText>{t("mappers")}</TabTitleText>}>
{clientScope && ( {clientScope && (
<MapperList clientScope={clientScope} refresh={load} /> <MapperList clientScope={clientScope} refresh={refresh} />
)} )}
</Tab> </Tab>
</Tabs> </Tabs>

View file

@ -19,7 +19,7 @@ import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { useDownloadDialog } from "../components/download-dialog/DownloadDialog"; import { useDownloadDialog } from "../components/download-dialog/DownloadDialog";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient } 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,
@ -144,13 +144,13 @@ export const ClientDetails = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedClient = await adminClient.clients.findOne({ id }); () => adminClient.clients.findOne({ id }),
if (fetchedClient) { (fetchedClient) => {
setClient(fetchedClient); setClient(fetchedClient);
setupForm(fetchedClient); setupForm(fetchedClient);
} }
})(); );
}, []); }, []);
const save = async () => { const save = async () => {

View file

@ -22,7 +22,7 @@ import { useAlerts } from "../../components/alert/Alerts";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { ClientSecret } from "./ClientSecret"; import { ClientSecret } from "./ClientSecret";
import { SignedJWT } from "./SignedJWT"; import { SignedJWT } from "./SignedJWT";
import { X509 } from "./X509"; import { X509 } from "./X509";
@ -64,17 +64,25 @@ export const Credentials = ({ clientId, form, save }: CredentialsProps) => {
const [open, isOpen] = useState(false); const [open, isOpen] = useState(false);
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders( async () => {
{ id: clientId } const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders(
); { id: clientId }
setProviders(providers); );
const secret = await adminClient.clients.getClientSecret({ const secret = await adminClient.clients.getClientSecret({
id: clientId, id: clientId,
}); });
setSecret(secret.value!); return {
})(); providers,
secret: secret.value!,
};
},
({ providers, secret }) => {
setProviders(providers);
setSecret(secret);
}
);
}, []); }, []);
async function regenerate<T>( async function regenerate<T>(

View file

@ -23,7 +23,7 @@ import { FilterIcon } from "@patternfly/react-icons";
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation"; import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
import KeycloakAdminClient from "keycloak-admin"; import KeycloakAdminClient from "keycloak-admin";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { TableToolbar } from "../../components/table-toolbar/TableToolbar"; import { TableToolbar } from "../../components/table-toolbar/TableToolbar";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
import { AddScopeDialog } from "./AddScopeDialog"; import { AddScopeDialog } from "./AddScopeDialog";
@ -133,52 +133,57 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
const [rows, setRows] = useState<TableRow[]>(); const [rows, setRows] = useState<TableRow[]>();
const [rest, setRest] = useState<ClientScopeRepresentation[]>(); const [rest, setRest] = useState<ClientScopeRepresentation[]>();
const loader = async () => { const [key, setKey] = useState(0);
const defaultClientScopes = await adminClient.clients.listDefaultClientScopes( const refresh = () => setKey(new Date().getTime());
{ id: clientId }
);
const optionalClientScopes = await adminClient.clients.listOptionalClientScopes(
{ id: clientId }
);
const clientScopes = await adminClient.clientScopes.find();
const find = (id: string) =>
clientScopes.find((clientScope) => id === clientScope.id)!;
const optional = optionalClientScopes.map((c) => {
const scope = find(c.id!);
return {
selected: false,
clientScope: c,
type: ClientScope.optional,
cells: [c.name, c.id, scope.description],
};
});
const defaultScopes = defaultClientScopes.map((c) => {
const scope = find(c.id!);
return {
selected: false,
clientScope: c,
type: ClientScope.default,
cells: [c.name, c.id, scope.description],
};
});
const data = [...optional, ...defaultScopes];
setRows(data);
const names = data.map((row) => row.cells[0]);
setRest(
clientScopes
.filter((scope) => !names.includes(scope.name))
.filter((scope) => scope.protocol === protocol)
);
};
useEffect(() => { useEffect(() => {
loader(); return useFetch(
}, []); async () => {
const defaultClientScopes = await adminClient.clients.listDefaultClientScopes(
{ id: clientId }
);
const optionalClientScopes = await adminClient.clients.listOptionalClientScopes(
{ id: clientId }
);
const clientScopes = await adminClient.clientScopes.find();
const find = (id: string) =>
clientScopes.find((clientScope) => id === clientScope.id)!;
const optional = optionalClientScopes.map((c) => {
const scope = find(c.id!);
return {
selected: false,
clientScope: c,
type: ClientScope.optional,
cells: [c.name, c.id, scope.description],
};
});
const defaultScopes = defaultClientScopes.map((c) => {
const scope = find(c.id!);
return {
selected: false,
clientScope: c,
type: ClientScope.default,
cells: [c.name, c.id, scope.description],
};
});
const rows = [...optional, ...defaultScopes];
const names = rows.map((row) => row.cells[0]);
const rest = clientScopes
.filter((scope) => !names.includes(scope.name))
.filter((scope) => scope.protocol === protocol);
return { rows, rest };
},
({ rows, rest }) => {
setRows(rows);
setRest(rest);
}
);
}, [key]);
const dropdown = (): IFormatter => (data?: IFormatterValueType) => { const dropdown = (): IFormatter => (data?: IFormatterValueType) => {
if (!data) { if (!data) {
@ -199,7 +204,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
value value
); );
addAlert(t("clientScopeSuccess"), AlertVariant.success); addAlert(t("clientScopeSuccess"), AlertVariant.success);
await loader(); await refresh();
} catch (error) { } catch (error) {
addAlert(t("clientScopeError", { error }), AlertVariant.danger); addAlert(t("clientScopeError", { error }), AlertVariant.danger);
} }
@ -237,7 +242,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
) )
); );
addAlert(t("clientScopeSuccess"), AlertVariant.success); addAlert(t("clientScopeSuccess"), AlertVariant.success);
loader(); refresh();
} catch (error) { } catch (error) {
addAlert(t("clientScopeError", { error }), AlertVariant.danger); addAlert(t("clientScopeError", { error }), AlertVariant.danger);
} }
@ -316,7 +321,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
}) })
); );
setAddToggle(false); setAddToggle(false);
await loader(); await refresh();
addAlert(t("clientScopeSuccess"), AlertVariant.success); addAlert(t("clientScopeSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert( addAlert(
@ -363,7 +368,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
t("clientScopeRemoveSuccess"), t("clientScopeRemoveSuccess"),
AlertVariant.success AlertVariant.success
); );
loader(); refresh();
} catch (error) { } catch (error) {
addAlert( addAlert(
t("clientScopeRemoveError", { error }), t("clientScopeRemoveError", { error }),
@ -416,7 +421,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
t("clientScopeRemoveSuccess"), t("clientScopeRemoveSuccess"),
AlertVariant.success AlertVariant.success
); );
loader(); refresh();
} catch (error) { } catch (error) {
addAlert( addAlert(
t("clientScopeRemoveError", { error }), t("clientScopeRemoveError", { error }),

View file

@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Spinner } from "@patternfly/react-core"; import { Spinner } from "@patternfly/react-core";
import { useFetch } from "../../context/auth/AdminClient";
type DataLoaderProps<T> = { type DataLoaderProps<T> = {
loader: () => Promise<T>; loader: () => Promise<T>;
@ -14,20 +15,22 @@ type Result<T> = {
export function DataLoader<T>(props: DataLoaderProps<T>) { export function DataLoader<T>(props: DataLoaderProps<T>) {
const [data, setData] = useState<{ result: T } | undefined>(undefined); const [data, setData] = useState<{ result: T } | undefined>(undefined);
const loadData = async () => {
const result = await props.loader(); const [key, setKey] = useState(0);
setData({ result }); const refresh = () => setKey(new Date().getTime());
};
useEffect(() => { useEffect(() => {
setData(undefined); return useFetch(
loadData(); () => props.loader(),
}, [props]); (result) => setData({ result })
);
}, [key]);
if (data) { if (data) {
if (props.children instanceof Function) { if (props.children instanceof Function) {
return props.children({ return props.children({
data: data.result, data: data.result,
refresh: () => loadData(), refresh: refresh,
}); });
} }
return props.children; return props.children;

View file

@ -18,7 +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 { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { HelpContext } from "../help-enabler/HelpHeader"; import { HelpContext } from "../help-enabler/HelpHeader";
export type DownloadDialogProps = { export type DownloadDialogProps = {
@ -65,23 +65,20 @@ export const DownloadDialog = ({
const [openType, setOpenType] = useState(false); const [openType, setOpenType] = useState(false);
useEffect(() => { useEffect(() => {
let isMounted = true; return useFetch(
(async () => { async () => {
const snippet = await adminClient.clients.getInstallationProviders({ const snippet = await adminClient.clients.getInstallationProviders({
id, id,
providerId: selected, providerId: selected,
}); });
if (isMounted) {
if (typeof snippet === "string") { if (typeof snippet === "string") {
setSnippet(snippet); return snippet;
} else { } else {
setSnippet(JSON.stringify(snippet, undefined, 3)); return JSON.stringify(snippet, undefined, 3);
} }
} },
})(); (snippet) => setSnippet(snippet)
return () => { );
isMounted = false;
};
}, [selected]); }, [selected]);
return ( return (
<ConfirmDialogModal <ConfirmDialogModal

View file

@ -1,4 +1,4 @@
import React, { Children, useState } from "react"; import React, { Children } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
Grid, Grid,
@ -19,44 +19,41 @@ type ScrollFormProps = {
// This must match the page id created in App.tsx unless another page section has been given hasScrollableContent // This must match the page id created in App.tsx unless another page section has been given hasScrollableContent
const mainPageContentId = "#kc-main-content-page-container"; const mainPageContentId = "#kc-main-content-page-container";
let spacesToHyphens = (string: string): string => { const spacesToHyphens = (string: string): string => {
return string.replace(/\s+/g, "-"); return string.replace(/\s+/g, "-");
}; };
export const ScrollForm = ({ sections, children }: ScrollFormProps) => { export const ScrollForm = ({ sections, children }: ScrollFormProps) => {
const { t } = useTranslation("common"); const { t } = useTranslation("common");
const Nav = () => (
<PageSection className="kc-scroll-form--sticky">
<JumpLinks
isVertical
// scrollableSelector has to point to the id of the element whose scrollTop changes
// to scroll the entire main section, it has to be the pf-c-page__main
scrollableSelector={mainPageContentId}
label={t("jumpToSection")}
offset={76}
>
{sections.map((cat) => (
// note that JumpLinks currently does not work with spaces in the href
<JumpLinksItem href={`#${spacesToHyphens(cat)}`}>{cat}</JumpLinksItem>
))}
</JumpLinks>
</PageSection>
);
const nodes = Children.toArray(children); const nodes = Children.toArray(children);
return ( return (
<Grid hasGutter> <Grid hasGutter>
<GridItem span={8}> <GridItem span={8}>
{sections.map((cat, index) => ( {sections.map((cat, index) => (
<FormPanel scrollId={spacesToHyphens(cat)} key={cat} title={cat}> <FormPanel scrollId={spacesToHyphens(cat)} key={cat} title={cat}>
{/* <FormPanel scrollId={cat.replace(/\s+/g, "-")} key={cat} title={cat}> */}
{nodes[index]} {nodes[index]}
</FormPanel> </FormPanel>
))} ))}
</GridItem> </GridItem>
<GridItem span={4}> <GridItem span={4}>
<Nav /> <PageSection className="kc-scroll-form--sticky">
<JumpLinks
isVertical
// scrollableSelector has to point to the id of the element whose scrollTop changes
// to scroll the entire main section, it has to be the pf-c-page__main
scrollableSelector={mainPageContentId}
label={t("jumpToSection")}
offset={76}
>
{sections.map((cat) => (
// note that JumpLinks currently does not work with spaces in the href
<JumpLinksItem key={cat} href={`#${spacesToHyphens(cat)}`}>
{cat}
</JumpLinksItem>
))}
</JumpLinks>
</PageSection>
</GridItem> </GridItem>
</Grid> </Grid>
); );

View file

@ -14,6 +14,7 @@ import _ from "lodash";
import { PaginatingTableToolbar } from "./PaginatingTableToolbar"; import { PaginatingTableToolbar } from "./PaginatingTableToolbar";
import { TableToolbar } from "./TableToolbar"; import { TableToolbar } from "./TableToolbar";
import { useFetch } from "../../context/auth/AdminClient";
type Row<T> = { type Row<T> = {
data: T; data: T;
@ -127,30 +128,35 @@ export function KeycloakDataTable<T>({
const [first, setFirst] = useState(0); const [first, setFirst] = useState(0);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const load = async () => { const [key, setKey] = useState(0);
setLoading(true); const refresh = () => setKey(new Date().getTime());
const data = await loader(first, max, search);
setRows(
data!.map((value) => {
return {
data: value,
selected: false,
cells: columns.map((col) => {
if (col.cellRenderer) {
return col.cellRenderer(value);
}
return (value as any)[col.name];
}),
};
})
);
setLoading(false);
};
useEffect(() => { useEffect(() => {
load(); return useFetch(
}, [first, max]); async () => {
setLoading(true);
const data = await loader(first, max, search);
const result = data!.map((value) => {
return {
data: value,
selected: false,
cells: columns.map((col) => {
if (col.cellRenderer) {
return col.cellRenderer(value);
}
return (value as any)[col.name];
}),
};
});
return result;
},
(result) => {
setRows(result);
setLoading(false);
}
);
}, [key, first, max]);
const getNodeText = (node: keyof T | JSX.Element): string => { const getNodeText = (node: keyof T | JSX.Element): string => {
if (["string", "number"].includes(typeof node)) { if (["string", "number"].includes(typeof node)) {
@ -184,7 +190,7 @@ export function KeycloakDataTable<T>({
action.onClick = async (_, rowIndex) => { action.onClick = async (_, rowIndex) => {
const result = await actions[index].onRowClick!(rows![rowIndex].data); const result = await actions[index].onRowClick!(rows![rowIndex].data);
if (result) { if (result) {
load(); refresh();
} }
}; };
return action; return action;
@ -235,7 +241,7 @@ export function KeycloakDataTable<T>({
}} }}
inputGroupName={`${ariaLabelKey}input`} inputGroupName={`${ariaLabelKey}input`}
inputGroupOnChange={searchOnChange} inputGroupOnChange={searchOnChange}
inputGroupOnClick={load} inputGroupOnClick={refresh}
inputGroupPlaceholder={t(searchPlaceholderKey)} inputGroupPlaceholder={t(searchPlaceholderKey)}
toolbarItem={toolbarItem} toolbarItem={toolbarItem}
> >

View file

@ -16,3 +16,20 @@ export const useAdminClient = () => {
return adminClient; return adminClient;
}; };
export function useFetch<T>(
adminClientCall: () => Promise<T>,
callback: (param: T) => void
) {
let canceled = false;
adminClientCall().then((result) => {
if (!canceled) {
callback(result);
}
});
return () => {
canceled = true;
};
}

View file

@ -24,7 +24,7 @@ import { FormAccess } from "../components/form-access/FormAccess";
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";
import { useAdminClient } 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 { RoleAttributes } from "./RoleAttributes"; import { RoleAttributes } from "./RoleAttributes";
@ -110,15 +110,22 @@ export const RealmRolesForm = () => {
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
if (id) { async () => {
const fetchedRole = await adminClient.roles.findOneById({ id }); if (id) {
setName(fetchedRole.name!); const role = await adminClient.roles.findOneById({ id });
setupForm(fetchedRole); return { role, name: role.name };
} else { } else {
setName(t("createRole")); return { name: t("createRole") };
}
},
({ role, name }) => {
setName(name!);
if (role) {
setupForm(role);
}
} }
})(); );
}, []); }, []);
const setupForm = (role: RoleRepresentation) => { const setupForm = (role: RoleRepresentation) => {

View file

@ -17,7 +17,7 @@ import { Controller, useForm } from "react-hook-form";
import { FormAccess } from "../components/form-access/FormAccess"; import { FormAccess } from "../components/form-access/FormAccess";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import { RoleAttributes } from "./RoleAttributes"; import { RoleAttributes } from "./RoleAttributes";
import "./RealmRolesSection.css"; import "./RealmRolesSection.css";
@ -35,11 +35,13 @@ export const RolesTabs = () => {
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedRole = await adminClient.roles.findOneById({ id }); () => adminClient.roles.findOneById({ id }),
setName(fetchedRole.name!); (fetchedRole) => {
setupForm(fetchedRole); setName(fetchedRole.name!);
})(); setupForm(fetchedRole);
}
);
}, []); }, []);
const setupForm = (role: RoleRepresentation) => { const setupForm = (role: RoleRepresentation) => {

View file

@ -5,7 +5,7 @@ import { useParams } from "react-router-dom";
import { Button, ButtonVariant, TextInput } from "@patternfly/react-core"; import { Button, ButtonVariant, TextInput } from "@patternfly/react-core";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import "./RealmRolesSection.css"; import "./RealmRolesSection.css";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -46,11 +46,13 @@ export const RoleAttributes = () => {
]; ];
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedRole = await adminClient.roles.findOneById({ id }); () => adminClient.roles.findOneById({ id }),
setName(fetchedRole.name!); (fetchedRole) => {
setupForm(fetchedRole); setName(fetchedRole.name!);
})(); setupForm(fetchedRole);
}
);
}, []); }, []);
const setupForm = (role: RoleRepresentation) => { const setupForm = (role: RoleRepresentation) => {

View file

@ -22,7 +22,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 } 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";
@ -34,19 +34,23 @@ export const UserFederationSection = () => {
const { t } = useTranslation("user-federation"); const { t } = useTranslation("user-federation");
const { realm } = useContext(RealmContext); const { realm } = useContext(RealmContext);
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const [key, setKey] = useState(0);
const loader = async () => { const refresh = () => setKey(new Date().getTime());
const testParams: { [name: string]: string | number } = {
parentId: realm,
type: "org.keycloak.storage.UserStorageProvider", // MF note that this is providerType in the output, but API call is still type
};
const userFederations = await adminClient.components.find(testParams);
setUserFederations(userFederations);
};
useEffect(() => { useEffect(() => {
loader(); return useFetch(
}, []); () => {
const testParams: { [name: string]: string | number } = {
parentId: realm,
type: "org.keycloak.storage.UserStorageProvider", // MF note that this is providerType in the output, but API call is still type
};
return adminClient.components.find(testParams);
},
(userFederations) => {
setUserFederations(userFederations);
}
);
}, [key]);
const ufAddProviderDropdownItems = [ const ufAddProviderDropdownItems = [
<DropdownItem key="itemLDAP">LDAP</DropdownItem>, <DropdownItem key="itemLDAP">LDAP</DropdownItem>,
@ -54,7 +58,7 @@ export const UserFederationSection = () => {
]; ];
const learnMoreLinkProps = { const learnMoreLinkProps = {
title: `${t("common:learnMore")}`, title: t("common:learnMore"),
href: href:
"https://www.keycloak.org/docs/latest/server_admin/index.html#_user-storage-federation", "https://www.keycloak.org/docs/latest/server_admin/index.html#_user-storage-federation",
}; };
@ -70,7 +74,7 @@ export const UserFederationSection = () => {
onConfirm: async () => { onConfirm: async () => {
try { try {
await adminClient.components.del({ id: currentCard }); await adminClient.components.del({ id: currentCard });
await loader(); refresh();
addAlert(t("userFedDeletedSuccess"), AlertVariant.success); addAlert(t("userFedDeletedSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert(t("userFedDeleteError", { error }), AlertVariant.danger); addAlert(t("userFedDeleteError", { error }), AlertVariant.danger);

View file

@ -12,7 +12,7 @@ import { convertToFormValues } from "../../util";
import { useForm, Controller, useWatch } from "react-hook-form"; import { useForm, Controller, useWatch } from "react-hook-form";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import _ from "lodash"; import _ from "lodash";
@ -41,12 +41,10 @@ export const KerberosSettingsCache = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedComponent = await adminClient.components.findOne({ id }); () => adminClient.components.findOne({ id }),
if (fetchedComponent) { (component) => setupForm(component)
setupForm(fetchedComponent); );
}
})();
}, []); }, []);
const [isCachePolicyDropdownOpen, setIsCachePolicyDropdownOpen] = useState( const [isCachePolicyDropdownOpen, setIsCachePolicyDropdownOpen] = useState(

View file

@ -6,7 +6,7 @@ import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util"; import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
export const LdapSettingsAdvanced = () => { export const LdapSettingsAdvanced = () => {
@ -27,12 +27,10 @@ export const LdapSettingsAdvanced = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedComponent = await adminClient.components.findOne({ id }); () => adminClient.components.findOne({ id }),
if (fetchedComponent) { (fetchedComponent) => setupForm(fetchedComponent)
setupForm(fetchedComponent); );
}
})();
}, []); }, []);
return ( return (

View file

@ -16,7 +16,7 @@ import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { EyeIcon } from "@patternfly/react-icons"; import { EyeIcon } from "@patternfly/react-icons";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
export const LdapSettingsConnection = () => { export const LdapSettingsConnection = () => {
@ -62,12 +62,10 @@ export const LdapSettingsConnection = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedComponent = await adminClient.components.findOne({ id }); () => adminClient.components.findOne({ id }),
if (fetchedComponent) { (fetchedComponent) => setupForm(fetchedComponent)
setupForm(fetchedComponent); );
}
})();
}, []); }, []);
return ( return (

View file

@ -12,7 +12,7 @@ import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util"; import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
export const LdapSettingsGeneral = () => { export const LdapSettingsGeneral = () => {
@ -37,12 +37,10 @@ export const LdapSettingsGeneral = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedComponent = await adminClient.components.findOne({ id }); () => adminClient.components.findOne({ id }),
if (fetchedComponent) { (fetchedComponent) => setupForm(fetchedComponent)
setupForm(fetchedComponent); );
}
})();
}, []); }, []);
const convertVendorNames = (vendorName: string) => { const convertVendorNames = (vendorName: string) => {

View file

@ -6,7 +6,7 @@ import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util"; import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
export const LdapSettingsKerberosIntegration = () => { export const LdapSettingsKerberosIntegration = () => {
@ -28,12 +28,10 @@ export const LdapSettingsKerberosIntegration = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedComponent = await adminClient.components.findOne({ id }); () => adminClient.components.findOne({ id }),
if (fetchedComponent) { (fetchedComponent) => setupForm(fetchedComponent)
setupForm(fetchedComponent); );
}
})();
}, []); }, []);
return ( return (

View file

@ -12,7 +12,7 @@ import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { convertToFormValues } from "../../util"; import { convertToFormValues } from "../../util";
@ -54,12 +54,10 @@ export const LdapSettingsSearching = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedComponent = await adminClient.components.findOne({ id }); () => adminClient.components.findOne({ id }),
if (fetchedComponent) { (fetchedComponent) => setupForm(fetchedComponent)
setupForm(fetchedComponent); );
}
})();
}, []); }, []);
return ( return (

View file

@ -6,7 +6,7 @@ import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util"; import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
export const LdapSettingsSynchronization = () => { export const LdapSettingsSynchronization = () => {
@ -27,12 +27,10 @@ export const LdapSettingsSynchronization = () => {
}; };
useEffect(() => { useEffect(() => {
(async () => { return useFetch(
const fetchedComponent = await adminClient.components.findOne({ id }); () => adminClient.components.findOne({ id }),
if (fetchedComponent) { (fetchedComponent) => setupForm(fetchedComponent)
setupForm(fetchedComponent); );
}
})();
}, []); }, []);
return ( return (