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:
parent
03e7ec760c
commit
27d9dadee7
22 changed files with 346 additions and 291 deletions
|
@ -24,7 +24,7 @@ import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
|||
import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
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 { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
|
@ -47,40 +47,49 @@ export const RoleMappingForm = () => {
|
|||
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const clients = await adminClient.clients.find();
|
||||
return useFetch(
|
||||
async () => {
|
||||
const clients = await adminClient.clients.find();
|
||||
|
||||
const asyncFilter = async (
|
||||
predicate: (client: ClientRepresentation) => Promise<boolean>
|
||||
) => {
|
||||
const results = await Promise.all(clients.map(predicate));
|
||||
return clients.filter((_, index) => results[index]);
|
||||
};
|
||||
const asyncFilter = async (
|
||||
predicate: (client: ClientRepresentation) => Promise<boolean>
|
||||
) => {
|
||||
const results = await Promise.all(clients.map(predicate));
|
||||
return clients.filter((_, index) => results[index]);
|
||||
};
|
||||
|
||||
const filteredClients = await asyncFilter(
|
||||
async (client) =>
|
||||
(await adminClient.clients.listRoles({ id: client.id! })).length > 0
|
||||
);
|
||||
const filteredClients = await asyncFilter(
|
||||
async (client) =>
|
||||
(await adminClient.clients.listRoles({ id: client.id! })).length > 0
|
||||
);
|
||||
|
||||
filteredClients.map(
|
||||
(client) =>
|
||||
(client.toString = function () {
|
||||
return this.clientId!;
|
||||
})
|
||||
);
|
||||
setClients(filteredClients);
|
||||
})();
|
||||
filteredClients.map(
|
||||
(client) =>
|
||||
(client.toString = function () {
|
||||
return this.clientId!;
|
||||
})
|
||||
);
|
||||
return filteredClients;
|
||||
},
|
||||
(filteredClients) => setClients(filteredClients)
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const client = selectedClient as ClientRepresentation;
|
||||
if (client && client.name !== "realmRoles") {
|
||||
setClientRoles(await adminClient.clients.listRoles({ id: client.id! }));
|
||||
} else {
|
||||
setClientRoles(await adminClient.roles.find());
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
async () => {
|
||||
const client = selectedClient as ClientRepresentation;
|
||||
if (client && client.name !== "realmRoles") {
|
||||
const clientRoles = await adminClient.clients.listRoles({
|
||||
id: client.id!,
|
||||
});
|
||||
return clientRoles;
|
||||
} else {
|
||||
return await adminClient.roles.find();
|
||||
}
|
||||
},
|
||||
(clientRoles) => setClientRoles(clientRoles)
|
||||
);
|
||||
}, [selectedClient]);
|
||||
|
||||
const save = async (mapping: ProtocolMapperRepresentation) => {
|
||||
|
|
|
@ -24,7 +24,7 @@ import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configProp
|
|||
import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
|
||||
|
||||
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 { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
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}[}]?$/;
|
||||
|
||||
useEffect(() => {
|
||||
if (id.match(isGuid)) {
|
||||
(async () => {
|
||||
const data = await adminClient.clientScopes.findProtocolMapper({
|
||||
id: scopeId,
|
||||
mapperId: id,
|
||||
});
|
||||
if (data) {
|
||||
Object.entries(data).map((entry) => {
|
||||
convertToFormValues(entry[1], "config", setValue);
|
||||
return useFetch(
|
||||
async () => {
|
||||
if (id.match(isGuid)) {
|
||||
const data = await adminClient.clientScopes.findProtocolMapper({
|
||||
id: scopeId,
|
||||
mapperId: id,
|
||||
});
|
||||
}
|
||||
const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
|
||||
const properties = mapperTypes.find(
|
||||
(type) => type.id === data!.protocolMapper
|
||||
)?.properties!;
|
||||
setConfigProperties(properties);
|
||||
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!;
|
||||
|
||||
setMapping(data);
|
||||
})();
|
||||
} else {
|
||||
(async () => {
|
||||
const scope = await adminClient.clientScopes.findOne({ id: scopeId });
|
||||
setMapping({ protocol: scope.protocol, protocolMapper: id });
|
||||
})();
|
||||
}
|
||||
return {
|
||||
configProperties: properties,
|
||||
mapping: data,
|
||||
};
|
||||
} else {
|
||||
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({
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||
|
||||
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 { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
|
@ -45,25 +45,29 @@ export const ClientScopeForm = () => {
|
|||
const [open, isOpen] = useState(false);
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const load = 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]);
|
||||
});
|
||||
}
|
||||
|
||||
setClientScope(data);
|
||||
}
|
||||
};
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
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) => {
|
||||
try {
|
||||
|
@ -305,7 +309,7 @@ export const ClientScopeForm = () => {
|
|||
</Tab>
|
||||
<Tab eventKey={1} title={<TabTitleText>{t("mappers")}</TabTitleText>}>
|
||||
{clientScope && (
|
||||
<MapperList clientScope={clientScope} refresh={load} />
|
||||
<MapperList clientScope={clientScope} refresh={refresh} />
|
||||
)}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
|
|
@ -19,7 +19,7 @@ import { useAlerts } from "../components/alert/Alerts";
|
|||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { useDownloadDialog } from "../components/download-dialog/DownloadDialog";
|
||||
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 {
|
||||
convertFormValuesToObject,
|
||||
|
@ -144,13 +144,13 @@ export const ClientDetails = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedClient = await adminClient.clients.findOne({ id });
|
||||
if (fetchedClient) {
|
||||
return useFetch(
|
||||
() => adminClient.clients.findOne({ id }),
|
||||
(fetchedClient) => {
|
||||
setClient(fetchedClient);
|
||||
setupForm(fetchedClient);
|
||||
}
|
||||
})();
|
||||
);
|
||||
}, []);
|
||||
|
||||
const save = async () => {
|
||||
|
|
|
@ -22,7 +22,7 @@ import { useAlerts } from "../../components/alert/Alerts";
|
|||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
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 { SignedJWT } from "./SignedJWT";
|
||||
import { X509 } from "./X509";
|
||||
|
@ -64,17 +64,25 @@ export const Credentials = ({ clientId, form, save }: CredentialsProps) => {
|
|||
const [open, isOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders(
|
||||
{ id: clientId }
|
||||
);
|
||||
setProviders(providers);
|
||||
return useFetch(
|
||||
async () => {
|
||||
const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders(
|
||||
{ id: clientId }
|
||||
);
|
||||
|
||||
const secret = await adminClient.clients.getClientSecret({
|
||||
id: clientId,
|
||||
});
|
||||
setSecret(secret.value!);
|
||||
})();
|
||||
const secret = await adminClient.clients.getClientSecret({
|
||||
id: clientId,
|
||||
});
|
||||
return {
|
||||
providers,
|
||||
secret: secret.value!,
|
||||
};
|
||||
},
|
||||
({ providers, secret }) => {
|
||||
setProviders(providers);
|
||||
setSecret(secret);
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
async function regenerate<T>(
|
||||
|
|
|
@ -23,7 +23,7 @@ import { FilterIcon } from "@patternfly/react-icons";
|
|||
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||
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 { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||
import { AddScopeDialog } from "./AddScopeDialog";
|
||||
|
@ -133,52 +133,57 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
const [rows, setRows] = useState<TableRow[]>();
|
||||
const [rest, setRest] = useState<ClientScopeRepresentation[]>();
|
||||
|
||||
const loader = 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 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)
|
||||
);
|
||||
};
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
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) => {
|
||||
if (!data) {
|
||||
|
@ -199,7 +204,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
value
|
||||
);
|
||||
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
||||
await loader();
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
||||
}
|
||||
|
@ -237,7 +242,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
)
|
||||
);
|
||||
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
||||
loader();
|
||||
refresh();
|
||||
} catch (error) {
|
||||
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
||||
}
|
||||
|
@ -316,7 +321,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
})
|
||||
);
|
||||
setAddToggle(false);
|
||||
await loader();
|
||||
await refresh();
|
||||
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
|
@ -363,7 +368,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
t("clientScopeRemoveSuccess"),
|
||||
AlertVariant.success
|
||||
);
|
||||
loader();
|
||||
refresh();
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
t("clientScopeRemoveError", { error }),
|
||||
|
@ -416,7 +421,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
t("clientScopeRemoveSuccess"),
|
||||
AlertVariant.success
|
||||
);
|
||||
loader();
|
||||
refresh();
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
t("clientScopeRemoveError", { error }),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Spinner } from "@patternfly/react-core";
|
||||
import { useFetch } from "../../context/auth/AdminClient";
|
||||
|
||||
type DataLoaderProps<T> = {
|
||||
loader: () => Promise<T>;
|
||||
|
@ -14,20 +15,22 @@ type Result<T> = {
|
|||
|
||||
export function DataLoader<T>(props: DataLoaderProps<T>) {
|
||||
const [data, setData] = useState<{ result: T } | undefined>(undefined);
|
||||
const loadData = async () => {
|
||||
const result = await props.loader();
|
||||
setData({ result });
|
||||
};
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
useEffect(() => {
|
||||
setData(undefined);
|
||||
loadData();
|
||||
}, [props]);
|
||||
return useFetch(
|
||||
() => props.loader(),
|
||||
(result) => setData({ result })
|
||||
);
|
||||
}, [key]);
|
||||
|
||||
if (data) {
|
||||
if (props.children instanceof Function) {
|
||||
return props.children({
|
||||
data: data.result,
|
||||
refresh: () => loadData(),
|
||||
refresh: refresh,
|
||||
});
|
||||
}
|
||||
return props.children;
|
||||
|
|
|
@ -18,7 +18,7 @@ import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog";
|
|||
import { HelpItem } from "../help-enabler/HelpItem";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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";
|
||||
|
||||
export type DownloadDialogProps = {
|
||||
|
@ -65,23 +65,20 @@ export const DownloadDialog = ({
|
|||
const [openType, setOpenType] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
(async () => {
|
||||
const snippet = await adminClient.clients.getInstallationProviders({
|
||||
id,
|
||||
providerId: selected,
|
||||
});
|
||||
if (isMounted) {
|
||||
return useFetch(
|
||||
async () => {
|
||||
const snippet = await adminClient.clients.getInstallationProviders({
|
||||
id,
|
||||
providerId: selected,
|
||||
});
|
||||
if (typeof snippet === "string") {
|
||||
setSnippet(snippet);
|
||||
return snippet;
|
||||
} else {
|
||||
setSnippet(JSON.stringify(snippet, undefined, 3));
|
||||
return JSON.stringify(snippet, undefined, 3);
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
},
|
||||
(snippet) => setSnippet(snippet)
|
||||
);
|
||||
}, [selected]);
|
||||
return (
|
||||
<ConfirmDialogModal
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Children, useState } from "react";
|
||||
import React, { Children } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
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
|
||||
const mainPageContentId = "#kc-main-content-page-container";
|
||||
|
||||
let spacesToHyphens = (string: string): string => {
|
||||
const spacesToHyphens = (string: string): string => {
|
||||
return string.replace(/\s+/g, "-");
|
||||
};
|
||||
|
||||
export const ScrollForm = ({ sections, children }: ScrollFormProps) => {
|
||||
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);
|
||||
return (
|
||||
<Grid hasGutter>
|
||||
<GridItem span={8}>
|
||||
{sections.map((cat, index) => (
|
||||
<FormPanel scrollId={spacesToHyphens(cat)} key={cat} title={cat}>
|
||||
{/* <FormPanel scrollId={cat.replace(/\s+/g, "-")} key={cat} title={cat}> */}
|
||||
{nodes[index]}
|
||||
</FormPanel>
|
||||
))}
|
||||
</GridItem>
|
||||
<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>
|
||||
</Grid>
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import _ from "lodash";
|
|||
|
||||
import { PaginatingTableToolbar } from "./PaginatingTableToolbar";
|
||||
import { TableToolbar } from "./TableToolbar";
|
||||
import { useFetch } from "../../context/auth/AdminClient";
|
||||
|
||||
type Row<T> = {
|
||||
data: T;
|
||||
|
@ -127,30 +128,35 @@ export function KeycloakDataTable<T>({
|
|||
const [first, setFirst] = useState(0);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
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);
|
||||
};
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [first, max]);
|
||||
return useFetch(
|
||||
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 => {
|
||||
if (["string", "number"].includes(typeof node)) {
|
||||
|
@ -184,7 +190,7 @@ export function KeycloakDataTable<T>({
|
|||
action.onClick = async (_, rowIndex) => {
|
||||
const result = await actions[index].onRowClick!(rows![rowIndex].data);
|
||||
if (result) {
|
||||
load();
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
return action;
|
||||
|
@ -235,7 +241,7 @@ export function KeycloakDataTable<T>({
|
|||
}}
|
||||
inputGroupName={`${ariaLabelKey}input`}
|
||||
inputGroupOnChange={searchOnChange}
|
||||
inputGroupOnClick={load}
|
||||
inputGroupOnClick={refresh}
|
||||
inputGroupPlaceholder={t(searchPlaceholderKey)}
|
||||
toolbarItem={toolbarItem}
|
||||
>
|
||||
|
|
|
@ -16,3 +16,20 @@ export const useAdminClient = () => {
|
|||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import { FormAccess } from "../components/form-access/FormAccess";
|
|||
import { useAlerts } from "../components/alert/Alerts";
|
||||
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 { RoleAttributes } from "./RoleAttributes";
|
||||
|
||||
|
@ -110,15 +110,22 @@ export const RealmRolesForm = () => {
|
|||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (id) {
|
||||
const fetchedRole = await adminClient.roles.findOneById({ id });
|
||||
setName(fetchedRole.name!);
|
||||
setupForm(fetchedRole);
|
||||
} else {
|
||||
setName(t("createRole"));
|
||||
return useFetch(
|
||||
async () => {
|
||||
if (id) {
|
||||
const role = await adminClient.roles.findOneById({ id });
|
||||
return { role, name: role.name };
|
||||
} else {
|
||||
return { name: t("createRole") };
|
||||
}
|
||||
},
|
||||
({ role, name }) => {
|
||||
setName(name!);
|
||||
if (role) {
|
||||
setupForm(role);
|
||||
}
|
||||
}
|
||||
})();
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setupForm = (role: RoleRepresentation) => {
|
||||
|
|
|
@ -17,7 +17,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
|
||||
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 { RoleAttributes } from "./RoleAttributes";
|
||||
import "./RealmRolesSection.css";
|
||||
|
@ -35,11 +35,13 @@ export const RolesTabs = () => {
|
|||
const { addAlert } = useAlerts();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedRole = await adminClient.roles.findOneById({ id });
|
||||
setName(fetchedRole.name!);
|
||||
setupForm(fetchedRole);
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.roles.findOneById({ id }),
|
||||
(fetchedRole) => {
|
||||
setName(fetchedRole.name!);
|
||||
setupForm(fetchedRole);
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setupForm = (role: RoleRepresentation) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useParams } from "react-router-dom";
|
|||
import { Button, ButtonVariant, TextInput } from "@patternfly/react-core";
|
||||
import { useForm } from "react-hook-form";
|
||||
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 { useTranslation } from "react-i18next";
|
||||
|
||||
|
@ -46,11 +46,13 @@ export const RoleAttributes = () => {
|
|||
];
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedRole = await adminClient.roles.findOneById({ id });
|
||||
setName(fetchedRole.name!);
|
||||
setupForm(fetchedRole);
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.roles.findOneById({ id }),
|
||||
(fetchedRole) => {
|
||||
setName(fetchedRole.name!);
|
||||
setupForm(fetchedRole);
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setupForm = (role: RoleRepresentation) => {
|
||||
|
|
|
@ -22,7 +22,7 @@ import { ViewHeader } from "../components/view-header/ViewHeader";
|
|||
import { DatabaseIcon } from "@patternfly/react-icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 "./user-federation.css";
|
||||
|
||||
|
@ -34,19 +34,23 @@ export const UserFederationSection = () => {
|
|||
const { t } = useTranslation("user-federation");
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
|
||||
const loader = async () => {
|
||||
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);
|
||||
};
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
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 = [
|
||||
<DropdownItem key="itemLDAP">LDAP</DropdownItem>,
|
||||
|
@ -54,7 +58,7 @@ export const UserFederationSection = () => {
|
|||
];
|
||||
|
||||
const learnMoreLinkProps = {
|
||||
title: `${t("common:learnMore")}`,
|
||||
title: t("common:learnMore"),
|
||||
href:
|
||||
"https://www.keycloak.org/docs/latest/server_admin/index.html#_user-storage-federation",
|
||||
};
|
||||
|
@ -70,7 +74,7 @@ export const UserFederationSection = () => {
|
|||
onConfirm: async () => {
|
||||
try {
|
||||
await adminClient.components.del({ id: currentCard });
|
||||
await loader();
|
||||
refresh();
|
||||
addAlert(t("userFedDeletedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(t("userFedDeleteError", { error }), AlertVariant.danger);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { convertToFormValues } from "../../util";
|
|||
import { useForm, Controller, useWatch } from "react-hook-form";
|
||||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
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 _ from "lodash";
|
||||
|
||||
|
@ -41,12 +41,10 @@ export const KerberosSettingsCache = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedComponent = await adminClient.components.findOne({ id });
|
||||
if (fetchedComponent) {
|
||||
setupForm(fetchedComponent);
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.components.findOne({ id }),
|
||||
(component) => setupForm(component)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const [isCachePolicyDropdownOpen, setIsCachePolicyDropdownOpen] = useState(
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useForm, Controller } from "react-hook-form";
|
|||
import { convertToFormValues } from "../../util";
|
||||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
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";
|
||||
|
||||
export const LdapSettingsAdvanced = () => {
|
||||
|
@ -27,12 +27,10 @@ export const LdapSettingsAdvanced = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedComponent = await adminClient.components.findOne({ id });
|
||||
if (fetchedComponent) {
|
||||
setupForm(fetchedComponent);
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.components.findOne({ id }),
|
||||
(fetchedComponent) => setupForm(fetchedComponent)
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -16,7 +16,7 @@ import { convertToFormValues } from "../../util";
|
|||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
import { EyeIcon } from "@patternfly/react-icons";
|
||||
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";
|
||||
|
||||
export const LdapSettingsConnection = () => {
|
||||
|
@ -62,12 +62,10 @@ export const LdapSettingsConnection = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedComponent = await adminClient.components.findOne({ id });
|
||||
if (fetchedComponent) {
|
||||
setupForm(fetchedComponent);
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.components.findOne({ id }),
|
||||
(fetchedComponent) => setupForm(fetchedComponent)
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useForm, Controller } from "react-hook-form";
|
|||
import { convertToFormValues } from "../../util";
|
||||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
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";
|
||||
|
||||
export const LdapSettingsGeneral = () => {
|
||||
|
@ -37,12 +37,10 @@ export const LdapSettingsGeneral = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedComponent = await adminClient.components.findOne({ id });
|
||||
if (fetchedComponent) {
|
||||
setupForm(fetchedComponent);
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.components.findOne({ id }),
|
||||
(fetchedComponent) => setupForm(fetchedComponent)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const convertVendorNames = (vendorName: string) => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useForm, Controller } from "react-hook-form";
|
|||
import { convertToFormValues } from "../../util";
|
||||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
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";
|
||||
|
||||
export const LdapSettingsKerberosIntegration = () => {
|
||||
|
@ -28,12 +28,10 @@ export const LdapSettingsKerberosIntegration = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedComponent = await adminClient.components.findOne({ id });
|
||||
if (fetchedComponent) {
|
||||
setupForm(fetchedComponent);
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.components.findOne({ id }),
|
||||
(fetchedComponent) => setupForm(fetchedComponent)
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -12,7 +12,7 @@ import { HelpItem } from "../../components/help-enabler/HelpItem";
|
|||
import { useForm, Controller } from "react-hook-form";
|
||||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
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 { convertToFormValues } from "../../util";
|
||||
|
||||
|
@ -54,12 +54,10 @@ export const LdapSettingsSearching = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedComponent = await adminClient.components.findOne({ id });
|
||||
if (fetchedComponent) {
|
||||
setupForm(fetchedComponent);
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.components.findOne({ id }),
|
||||
(fetchedComponent) => setupForm(fetchedComponent)
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useForm, Controller } from "react-hook-form";
|
|||
import { convertToFormValues } from "../../util";
|
||||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
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";
|
||||
|
||||
export const LdapSettingsSynchronization = () => {
|
||||
|
@ -27,12 +27,10 @@ export const LdapSettingsSynchronization = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedComponent = await adminClient.components.findOne({ id });
|
||||
if (fetchedComponent) {
|
||||
setupForm(fetchedComponent);
|
||||
}
|
||||
})();
|
||||
return useFetch(
|
||||
() => adminClient.components.findOne({ id }),
|
||||
(fetchedComponent) => setupForm(fetchedComponent)
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
Loading…
Reference in a new issue