Use keycloak-admin with axios instead of fetch
wrapper (#212)
* changed to use the admin client * added helper to always set realm * fixed merge * no need to polyfill anymore * updated to use keycloak-admin-client * updated to release version * fixed types * added user federation * update test * lint
This commit is contained in:
parent
42bb5cfe3f
commit
dcb18c5488
48 changed files with 1074 additions and 1245 deletions
|
@ -22,7 +22,7 @@
|
|||
"@patternfly/react-table": "4.16.20",
|
||||
"file-saver": "^2.0.2",
|
||||
"i18next": "^19.6.2",
|
||||
"keycloak-js": "^11.0.0",
|
||||
"keycloak-admin": "^1.14.1",
|
||||
"react": "^16.8.5",
|
||||
"react-dom": "^16.8.5",
|
||||
"react-hook-form": "^6.8.2",
|
||||
|
|
17
src/App.tsx
17
src/App.tsx
|
@ -6,7 +6,6 @@ import { Header } from "./PageHeader";
|
|||
import { PageNav } from "./PageNav";
|
||||
import { Help } from "./components/help-enabler/HelpHeader";
|
||||
|
||||
import { RealmContextProvider } from "./context/realm-context/RealmContext";
|
||||
import { WhoAmIContextProvider } from "./context/whoami/WhoAmI";
|
||||
import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider";
|
||||
import { AlertProvider } from "./components/alert/Alerts";
|
||||
|
@ -18,15 +17,13 @@ import { ForbiddenSection } from "./ForbiddenSection";
|
|||
|
||||
const AppContexts = ({ children }: { children: ReactNode }) => (
|
||||
<WhoAmIContextProvider>
|
||||
<RealmContextProvider>
|
||||
<AccessContextProvider>
|
||||
<Help>
|
||||
<AlertProvider>
|
||||
<ServerInfoProvider>{children}</ServerInfoProvider>
|
||||
</AlertProvider>
|
||||
</Help>
|
||||
</AccessContextProvider>
|
||||
</RealmContextProvider>
|
||||
<AccessContextProvider>
|
||||
<Help>
|
||||
<AlertProvider>
|
||||
<ServerInfoProvider>{children}</ServerInfoProvider>
|
||||
</AlertProvider>
|
||||
</Help>
|
||||
</AccessContextProvider>
|
||||
</WhoAmIContextProvider>
|
||||
);
|
||||
|
||||
|
|
|
@ -14,12 +14,139 @@ import {
|
|||
PageHeaderToolsGroup,
|
||||
} from "@patternfly/react-core";
|
||||
import { HelpIcon } from "@patternfly/react-icons";
|
||||
import { KeycloakContext } from "./context/auth/KeycloakContext";
|
||||
import { WhoAmIContext } from "./context/whoami/WhoAmI";
|
||||
import { HelpHeader } from "./components/help-enabler/HelpHeader";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAdminClient } from "./context/auth/AdminClient";
|
||||
|
||||
export const Header = () => {
|
||||
const adminClient = useAdminClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ManageAccountDropdownItem = () => {
|
||||
return (
|
||||
<DropdownItem
|
||||
key="manage account"
|
||||
onClick={() => adminClient.keycloak.accountManagement()}
|
||||
>
|
||||
{t("manageAccount")}
|
||||
</DropdownItem>
|
||||
);
|
||||
};
|
||||
|
||||
const SignOutDropdownItem = () => {
|
||||
return (
|
||||
<DropdownItem
|
||||
key="sign out"
|
||||
onClick={() => adminClient.keycloak.logout({ redirectUri: "" })}
|
||||
>
|
||||
{t("signOut")}
|
||||
</DropdownItem>
|
||||
);
|
||||
};
|
||||
|
||||
const ServerInfoDropdownItem = () => {
|
||||
const { t } = useTranslation();
|
||||
return <DropdownItem key="server info">{t("serverInfo")}</DropdownItem>;
|
||||
};
|
||||
|
||||
const HelpDropdownItem = () => {
|
||||
const { t } = useTranslation();
|
||||
return <DropdownItem icon={<HelpIcon />}>{t("help")}</DropdownItem>;
|
||||
};
|
||||
|
||||
const kebabDropdownItems = [
|
||||
<ManageAccountDropdownItem key="kebab Manage Account" />,
|
||||
<ServerInfoDropdownItem key="kebab Server Info" />,
|
||||
<HelpDropdownItem key="kebab Help" />,
|
||||
<DropdownSeparator key="kebab sign out separator" />,
|
||||
<SignOutDropdownItem key="kebab Sign out" />,
|
||||
];
|
||||
|
||||
const userDropdownItems = [
|
||||
<ManageAccountDropdownItem key="Manage Account" />,
|
||||
<ServerInfoDropdownItem key="Server info" />,
|
||||
<DropdownSeparator key="sign out separator" />,
|
||||
<SignOutDropdownItem key="Sign out" />,
|
||||
];
|
||||
|
||||
const headerTools = () => {
|
||||
return (
|
||||
<PageHeaderTools>
|
||||
<PageHeaderToolsGroup
|
||||
visibility={{
|
||||
default: "hidden",
|
||||
md: "visible",
|
||||
}} /** the settings and help icon buttons are only visible on desktop sizes and replaced by a kebab dropdown for other sizes */
|
||||
>
|
||||
<PageHeaderToolsItem>
|
||||
<HelpHeader />
|
||||
</PageHeaderToolsItem>
|
||||
</PageHeaderToolsGroup>
|
||||
|
||||
<PageHeaderToolsGroup>
|
||||
<PageHeaderToolsItem
|
||||
visibility={{
|
||||
md: "hidden",
|
||||
}} /** this kebab dropdown replaces the icon buttons and is hidden for desktop sizes */
|
||||
>
|
||||
<KebabDropdown />
|
||||
</PageHeaderToolsItem>
|
||||
<PageHeaderToolsItem
|
||||
visibility={{
|
||||
default: "hidden",
|
||||
md: "visible",
|
||||
}} /** this user dropdown is hidden on mobile sizes */
|
||||
>
|
||||
<UserDropdown />
|
||||
</PageHeaderToolsItem>
|
||||
</PageHeaderToolsGroup>
|
||||
<Avatar src="/img_avatar.svg" alt="Avatar image" />
|
||||
</PageHeaderTools>
|
||||
);
|
||||
};
|
||||
|
||||
const KebabDropdown = () => {
|
||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const onDropdownToggle = () => {
|
||||
setDropdownOpen(!isDropdownOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
isPlain
|
||||
position="right"
|
||||
toggle={<KebabToggle onToggle={onDropdownToggle} />}
|
||||
isOpen={isDropdownOpen}
|
||||
dropdownItems={kebabDropdownItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const UserDropdown = () => {
|
||||
const whoami = useContext(WhoAmIContext);
|
||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const onDropdownToggle = () => {
|
||||
setDropdownOpen(!isDropdownOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
isPlain
|
||||
position="right"
|
||||
isOpen={isDropdownOpen}
|
||||
toggle={
|
||||
<DropdownToggle onToggle={onDropdownToggle}>
|
||||
{whoami.getDisplayName()}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={userDropdownItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageHeader
|
||||
showNavToggle
|
||||
|
@ -33,126 +160,3 @@ export const Header = () => {
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ManageAccountDropdownItem = () => {
|
||||
const keycloak = useContext(KeycloakContext);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<DropdownItem key="manage account" onClick={() => keycloak?.account()}>
|
||||
{t("manageAccount")}
|
||||
</DropdownItem>
|
||||
);
|
||||
};
|
||||
|
||||
const SignOutDropdownItem = () => {
|
||||
const keycloak = useContext(KeycloakContext);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<DropdownItem key="sign out" onClick={() => keycloak?.logout()}>
|
||||
{t("signOut")}
|
||||
</DropdownItem>
|
||||
);
|
||||
};
|
||||
|
||||
const ServerInfoDropdownItem = () => {
|
||||
const { t } = useTranslation();
|
||||
return <DropdownItem key="server info">{t("serverInfo")}</DropdownItem>;
|
||||
};
|
||||
|
||||
const HelpDropdownItem = () => {
|
||||
const { t } = useTranslation();
|
||||
return <DropdownItem icon={<HelpIcon />}>{t("help")}</DropdownItem>;
|
||||
};
|
||||
|
||||
const kebabDropdownItems = [
|
||||
<ManageAccountDropdownItem key="kebab Manage Account" />,
|
||||
<ServerInfoDropdownItem key="kebab Server Info" />,
|
||||
<HelpDropdownItem key="kebab Help" />,
|
||||
<DropdownSeparator key="kebab sign out seperator" />,
|
||||
<SignOutDropdownItem key="kebab Sign out" />,
|
||||
];
|
||||
|
||||
const userDropdownItems = [
|
||||
<ManageAccountDropdownItem key="Manage Account" />,
|
||||
<ServerInfoDropdownItem key="Server info" />,
|
||||
<DropdownSeparator key="sign out seperator" />,
|
||||
<SignOutDropdownItem key="Sign out" />,
|
||||
];
|
||||
|
||||
const headerTools = () => {
|
||||
return (
|
||||
<PageHeaderTools>
|
||||
<PageHeaderToolsGroup
|
||||
visibility={{
|
||||
default: "hidden",
|
||||
md: "visible",
|
||||
}} /** the settings and help icon buttons are only visible on desktop sizes and replaced by a kebab dropdown for other sizes */
|
||||
>
|
||||
<PageHeaderToolsItem>
|
||||
<HelpHeader />
|
||||
</PageHeaderToolsItem>
|
||||
</PageHeaderToolsGroup>
|
||||
|
||||
<PageHeaderToolsGroup>
|
||||
<PageHeaderToolsItem
|
||||
visibility={{
|
||||
md: "hidden",
|
||||
}} /** this kebab dropdown replaces the icon buttons and is hidden for desktop sizes */
|
||||
>
|
||||
<KebabDropdown />
|
||||
</PageHeaderToolsItem>
|
||||
<PageHeaderToolsItem
|
||||
visibility={{
|
||||
default: "hidden",
|
||||
md: "visible",
|
||||
}} /** this user dropdown is hidden on mobile sizes */
|
||||
>
|
||||
<UserDropdown />
|
||||
</PageHeaderToolsItem>
|
||||
</PageHeaderToolsGroup>
|
||||
<Avatar src="/img_avatar.svg" alt="Avatar image" />
|
||||
</PageHeaderTools>
|
||||
);
|
||||
};
|
||||
|
||||
const KebabDropdown = () => {
|
||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const onDropdownToggle = () => {
|
||||
setDropdownOpen(!isDropdownOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
isPlain
|
||||
position="right"
|
||||
toggle={<KebabToggle onToggle={onDropdownToggle} />}
|
||||
isOpen={isDropdownOpen}
|
||||
dropdownItems={kebabDropdownItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const UserDropdown = () => {
|
||||
const keycloak = useContext(KeycloakContext);
|
||||
const whoami = useContext(WhoAmIContext);
|
||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const onDropdownToggle = () => {
|
||||
setDropdownOpen(!isDropdownOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
isPlain
|
||||
position="right"
|
||||
isOpen={isDropdownOpen}
|
||||
toggle={
|
||||
<DropdownToggle onToggle={onDropdownToggle}>
|
||||
{whoami.getDisplayName()}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={userDropdownItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useContext } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
|
@ -10,20 +10,16 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { RealmSelector } from "./components/realm-selector/RealmSelector";
|
||||
import { DataLoader } from "./components/data-loader/DataLoader";
|
||||
import { HttpClientContext } from "./context/http-service/HttpClientContext";
|
||||
import { useAdminClient } from "./context/auth/AdminClient";
|
||||
import { useAccess } from "./context/access/Access";
|
||||
import { RealmRepresentation } from "./realm/models/Realm";
|
||||
import { routes } from "./route-config";
|
||||
|
||||
export const PageNav: React.FunctionComponent = () => {
|
||||
const { t } = useTranslation("common");
|
||||
const { hasAccess, hasSomeAccess } = useAccess();
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const adminClient = useAdminClient();
|
||||
const realmLoader = async () => {
|
||||
const response = await httpClient.doGet<RealmRepresentation[]>(
|
||||
"/admin/realms"
|
||||
);
|
||||
return response.data;
|
||||
return await adminClient.realms.find();
|
||||
};
|
||||
|
||||
const history = useHistory();
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, PageSection, Spinner } from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { ClientRepresentation } from "../realm/models/Realm";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
import { TableToolbar } from "../components/table-toolbar/TableToolbar";
|
||||
import { ClientScopeList } from "./ClientScopesList";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
|
||||
export const ClientScopesSection = () => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
|
@ -16,25 +15,22 @@ export const ClientScopesSection = () => {
|
|||
const [rawData, setRawData] = useState<ClientRepresentation[]>();
|
||||
const [filteredData, setFilteredData] = useState<ClientRepresentation[]>();
|
||||
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (filteredData) {
|
||||
return filteredData;
|
||||
}
|
||||
const result = await httpClient.doGet<ClientRepresentation[]>(
|
||||
`/admin/realms/${realm}/client-scopes`
|
||||
);
|
||||
setRawData(result.data!);
|
||||
const result = await adminClient.clientScopes.find();
|
||||
setRawData(result);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const filterData = (search: string) => {
|
||||
setFilteredData(
|
||||
rawData!.filter((group) =>
|
||||
group.name.toLowerCase().includes(search.toLowerCase())
|
||||
group.name!.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -19,12 +19,10 @@ import {
|
|||
TableVariant,
|
||||
} from "@patternfly/react-table";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
|
||||
import { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
|
||||
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import {
|
||||
ProtocolMapperRepresentation,
|
||||
ProtocolMapperTypeRepresentation,
|
||||
} from "../../context/server-info/server-info";
|
||||
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||
|
||||
export type AddMapperDialogModalProps = {
|
||||
|
@ -45,8 +43,8 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
|
|||
|
||||
const serverInfo = useServerInfo();
|
||||
const protocol = props.protocol;
|
||||
const protocolMappers = serverInfo.protocolMapperTypes[protocol];
|
||||
const builtInMappers = serverInfo.builtinProtocolMappers[protocol];
|
||||
const protocolMappers = serverInfo.protocolMapperTypes![protocol];
|
||||
const builtInMappers = serverInfo.builtinProtocolMappers![protocol];
|
||||
const [filter, setFilter] = useState<ProtocolMapperRepresentation[]>([]);
|
||||
|
||||
const allRows = builtInMappers.map((mapper) => {
|
||||
|
|
222
src/client-scopes/add/NewClientScopeForm.tsx
Normal file
222
src/client-scopes/add/NewClientScopeForm.tsx
Normal file
|
@ -0,0 +1,222 @@
|
|||
import React, { useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
Form,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
Switch,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { ClientScopeRepresentation } from "../models/client-scope";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||
|
||||
export const NewClientScopeForm = () => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
const helpText = useTranslation("client-scopes-help").t;
|
||||
const { register, control, handleSubmit, errors } = useForm<
|
||||
ClientScopeRepresentation
|
||||
>();
|
||||
const history = useHistory();
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const providers = useLoginProviders();
|
||||
|
||||
const [open, isOpen] = useState(false);
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const save = async (clientScopes: ClientScopeRepresentation) => {
|
||||
try {
|
||||
const keyValues = Object.keys(clientScopes.attributes!).map((key) => {
|
||||
const newKey = key.replace(/_/g, ".");
|
||||
return { [newKey]: clientScopes.attributes![key] };
|
||||
});
|
||||
clientScopes.attributes = Object.assign({}, ...keyValues);
|
||||
|
||||
await adminClient.clientScopes.create({ ...clientScopes });
|
||||
addAlert(t("createClientScopeSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
`${t("createClientScopeError")} '${error}'`,
|
||||
AlertVariant.danger
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader
|
||||
titleKey="client-scopes:createClientScope"
|
||||
subKey="client-scopes:clientScopeExplain"
|
||||
/>
|
||||
|
||||
<PageSection variant="light">
|
||||
<Form isHorizontal onSubmit={handleSubmit(save)}>
|
||||
<FormGroup
|
||||
label={t("name")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={helpText("name")}
|
||||
forLabel={t("name")}
|
||||
forID="kc-name"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-name"
|
||||
isRequired
|
||||
validated={errors.name ? "error" : "default"}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: true })}
|
||||
type="text"
|
||||
id="kc-name"
|
||||
name="name"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("description")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={helpText("description")}
|
||||
forLabel={t("description")}
|
||||
forID="kc-description"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-description"
|
||||
>
|
||||
<TextInput
|
||||
ref={register}
|
||||
type="text"
|
||||
id="kc-description"
|
||||
name="description"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("protocol")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={helpText("protocol")}
|
||||
forLabel="protocol"
|
||||
forID="kc-protocol"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-protocol"
|
||||
>
|
||||
<Controller
|
||||
name="protocol"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-protocol"
|
||||
required
|
||||
onToggle={() => isOpen(!open)}
|
||||
onSelect={(_, value, isPlaceholder) => {
|
||||
onChange(isPlaceholder ? "" : (value as string));
|
||||
isOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("selectEncryptionType")}
|
||||
placeholderText={t("common:selectOne")}
|
||||
isOpen={open}
|
||||
>
|
||||
{providers.map((option) => (
|
||||
<SelectOption
|
||||
selected={option === value}
|
||||
key={option}
|
||||
value={option}
|
||||
/>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
hasNoPaddingTop
|
||||
label={t("displayOnConsentScreen")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={helpText("displayOnConsentScreen")}
|
||||
forLabel={t("displayOnConsentScreen")}
|
||||
forID="kc-display.on.consent.screen"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-display.on.consent.screen"
|
||||
>
|
||||
<Controller
|
||||
name="attributes.display_on_consent_screen"
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-display.on.consent.screen"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("consentScreenText")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={helpText("consentScreenText")}
|
||||
forLabel={t("consentScreenText")}
|
||||
forID="kc-consent-screen-text"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-consent-screen-text"
|
||||
>
|
||||
<TextInput
|
||||
ref={register}
|
||||
type="text"
|
||||
id="kc-consent-screen-text"
|
||||
name="attributes.consent_screen_text"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("guiOrder")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={helpText("guiOrder")}
|
||||
forLabel={t("guiOrder")}
|
||||
forID="kc-gui-order"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-gui-order"
|
||||
>
|
||||
<TextInput
|
||||
ref={register}
|
||||
type="number"
|
||||
id="kc-gui-order"
|
||||
name="attributes.gui_order"
|
||||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button variant="primary" type="submit">
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={() => history.push("..")}>
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</Form>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -19,16 +19,14 @@ import {
|
|||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import {
|
||||
ClientRepresentation,
|
||||
RoleRepresentation,
|
||||
} from "../../realm/models/Realm";
|
||||
import { ProtocolMapperRepresentation } from "../models/client-scope";
|
||||
|
||||
export type RoleMappingFormProps = {
|
||||
|
@ -36,8 +34,8 @@ export type RoleMappingFormProps = {
|
|||
};
|
||||
|
||||
export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => {
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const history = useHistory();
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
|
@ -54,18 +52,13 @@ export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => {
|
|||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await httpClient.doGet<RoleRepresentation[]>(
|
||||
`/admin/realms/${realm}/roles`
|
||||
);
|
||||
setRoles(response.data!);
|
||||
const clientResponse = await httpClient.doGet<ClientRepresentation[]>(
|
||||
`/admin/realms/${realm}/clients`
|
||||
);
|
||||
const clients = clientResponse.data!;
|
||||
const roles = await adminClient.roles.find();
|
||||
setRoles(roles);
|
||||
const clients = await adminClient.clients.find();
|
||||
clients.map(
|
||||
(client) =>
|
||||
(client.toString = function () {
|
||||
return this.name;
|
||||
return this.name!;
|
||||
})
|
||||
);
|
||||
setClients(clients);
|
||||
|
@ -76,19 +69,15 @@ export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => {
|
|||
(async () => {
|
||||
const client = selectedClient as ClientRepresentation;
|
||||
if (client && client.name !== "realmRoles") {
|
||||
const response = await httpClient.doGet<RoleRepresentation[]>(
|
||||
`/admin/realms/master/clients/${client.id}/roles`
|
||||
);
|
||||
|
||||
setClientRoles(response.data!);
|
||||
setClientRoles(await adminClient.clients.listRoles({ id: client.id! }));
|
||||
}
|
||||
})();
|
||||
}, [selectedClient]);
|
||||
|
||||
const save = async (mapping: ProtocolMapperRepresentation) => {
|
||||
try {
|
||||
await httpClient.doPost(
|
||||
`/admin/realms/${realm}/client-scopes/${clientScopeId}/protocol-mappers/models`,
|
||||
await adminClient.clientScopes.addProtocolMapper(
|
||||
{ id: clientScopeId },
|
||||
mapping
|
||||
);
|
||||
addAlert(t("mapperCreateSuccess"));
|
||||
|
@ -219,8 +208,8 @@ export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => {
|
|||
} else {
|
||||
return createSelectGroup(
|
||||
clients.filter((client) =>
|
||||
client.name
|
||||
.toLowerCase()
|
||||
client
|
||||
.name!.toLowerCase()
|
||||
.includes(textInput.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
|
@ -16,22 +16,16 @@ import {
|
|||
} from "@patternfly/react-table";
|
||||
import { CaretDownIcon } from "@patternfly/react-icons";
|
||||
|
||||
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||
import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
|
||||
import { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import {
|
||||
ProtocolMapperRepresentation as ServerInfoProtocolMapper,
|
||||
ProtocolMapperTypeRepresentation,
|
||||
} from "../../context/server-info/server-info";
|
||||
|
||||
import {
|
||||
ClientScopeRepresentation,
|
||||
ProtocolMapperRepresentation,
|
||||
} from "../models/client-scope";
|
||||
import { TableToolbar } from "../../components/table-toolbar/TableToolbar";
|
||||
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { AddMapperDialog } from "../add/MapperDialog";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
|
||||
type MapperListProps = {
|
||||
clientScope: ClientScopeRepresentation;
|
||||
|
@ -47,8 +41,7 @@ type Row = {
|
|||
|
||||
export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const [filteredData, setFilteredData] = useState<
|
||||
|
@ -56,7 +49,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
|||
>();
|
||||
const [mapperAction, setMapperAction] = useState(false);
|
||||
const mapperList = clientScope.protocolMappers!;
|
||||
const mapperTypes = useServerInfo().protocolMapperTypes[
|
||||
const mapperTypes = useServerInfo().protocolMapperTypes![
|
||||
clientScope.protocol!
|
||||
];
|
||||
|
||||
|
@ -67,9 +60,9 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
|||
mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[]
|
||||
) => {
|
||||
try {
|
||||
await httpClient.doPost(
|
||||
`/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/add-models`,
|
||||
mappers
|
||||
await adminClient.clientScopes.addMultipleProtocolMappers(
|
||||
{ id: clientScope.id! },
|
||||
mappers as ProtocolMapperRepresentation[]
|
||||
);
|
||||
refresh();
|
||||
addAlert(t("mappingCreatedSuccess"), AlertVariant.success);
|
||||
|
@ -83,7 +76,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
|||
<>
|
||||
<AddMapperDialog
|
||||
protocol={clientScope.protocol!}
|
||||
filter={(mapperList as ServerInfoProtocolMapper[]) || []}
|
||||
filter={mapperList || []}
|
||||
onConfirm={addMappers}
|
||||
open={builtInDialogOpen}
|
||||
toggleDialog={toggleBuiltInMapperDialog}
|
||||
|
@ -168,7 +161,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
|||
>
|
||||
<AddMapperDialog
|
||||
protocol={clientScope.protocol!}
|
||||
filter={(mapperList as ServerInfoProtocolMapper[]) || []}
|
||||
filter={mapperList || []}
|
||||
onConfirm={addMappers}
|
||||
open={builtInDialogOpen}
|
||||
toggleDialog={toggleBuiltInMapperDialog}
|
||||
|
@ -185,9 +178,10 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
|||
title: t("common:delete"),
|
||||
onClick: async (_, rowId) => {
|
||||
try {
|
||||
await httpClient.doDelete(
|
||||
`/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/models/${data[rowId].mapper.id}`
|
||||
);
|
||||
await adminClient.clientScopes.delProtocolMapper({
|
||||
id: clientScope.id!,
|
||||
mapperId: data[rowId].mapper.id!,
|
||||
});
|
||||
refresh();
|
||||
addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
|
@ -19,23 +19,21 @@ import {
|
|||
Switch,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation";
|
||||
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { ProtocolMapperRepresentation } from "../models/client-scope";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import { ConfigPropertyRepresentation } from "../../context/server-info/server-info";
|
||||
import { convertFormValuesToObject, convertToFormValues } from "../../util";
|
||||
|
||||
export const MappingDetails = () => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const { scopeId, id } = useParams<{ scopeId: string; id: string }>();
|
||||
|
@ -47,28 +45,27 @@ export const MappingDetails = () => {
|
|||
>();
|
||||
|
||||
const serverInfo = useServerInfo();
|
||||
const url = `/admin/realms/${realm}/client-scopes/${scopeId}/protocol-mappers/models/${id}`;
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await httpClient.doGet<ProtocolMapperRepresentation>(
|
||||
url
|
||||
);
|
||||
if (response.data) {
|
||||
Object.entries(response.data).map((entry) => {
|
||||
const data = await adminClient.clientScopes.findProtocolMapper({
|
||||
id: scopeId,
|
||||
mapperId: id,
|
||||
});
|
||||
if (data) {
|
||||
Object.entries(data).map((entry) => {
|
||||
if (entry[0] === "config") {
|
||||
convertToFormValues(entry[1], "config", setValue);
|
||||
}
|
||||
setValue(entry[0], entry[1]);
|
||||
});
|
||||
}
|
||||
setMapping(response.data);
|
||||
const mapperTypes =
|
||||
serverInfo.protocolMapperTypes[response.data!.protocol!];
|
||||
setMapping(data);
|
||||
const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
|
||||
const properties = mapperTypes.find(
|
||||
(type) => type.id === response.data!.protocolMapper
|
||||
)?.properties;
|
||||
(type) => type.id === data.protocolMapper
|
||||
)?.properties!;
|
||||
setConfigProperties(properties);
|
||||
})();
|
||||
}, []);
|
||||
|
@ -78,9 +75,12 @@ export const MappingDetails = () => {
|
|||
messageKey: "client-scopes:deleteMappingConfirm",
|
||||
continueButtonLabel: "common:delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: () => {
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
httpClient.doDelete(url);
|
||||
await adminClient.clientScopes.delClientScopeMappings(
|
||||
{ client: scopeId, id },
|
||||
[]
|
||||
);
|
||||
addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
|
||||
history.push(`/client-scopes/${scopeId}`);
|
||||
} catch (error) {
|
||||
|
@ -93,7 +93,10 @@ export const MappingDetails = () => {
|
|||
const config = convertFormValuesToObject(formMapping.config);
|
||||
const map = { ...mapping, config };
|
||||
try {
|
||||
await httpClient.doPut(url, map);
|
||||
await adminClient.clientScopes.updateProtocolMapper(
|
||||
{ id: scopeId, mapperId: id },
|
||||
map
|
||||
);
|
||||
addAlert(t("mappingUpdatedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(t("mappingUpdatedError", { error }), AlertVariant.danger);
|
||||
|
@ -211,8 +214,8 @@ export const MappingDetails = () => {
|
|||
>
|
||||
{configProperties &&
|
||||
configProperties
|
||||
.find((property) => property.name === "jsonType.label")
|
||||
?.options.map((option) => (
|
||||
.find((property) => property.name! === "jsonType.label")
|
||||
?.options!.map((option) => (
|
||||
<SelectOption
|
||||
selected={option === value}
|
||||
key={option}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import {
|
||||
ActionGroup,
|
||||
|
@ -22,8 +22,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
|
||||
import { ClientScopeRepresentation } from "../models/client-scope";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
|
@ -39,8 +38,7 @@ export const ClientScopeForm = () => {
|
|||
const [clientScope, setClientScope] = useState<ClientScopeRepresentation>();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const providers = useLoginProviders();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
|
@ -49,11 +47,9 @@ export const ClientScopeForm = () => {
|
|||
|
||||
const load = async () => {
|
||||
if (id) {
|
||||
const response = await httpClient.doGet<ClientScopeRepresentation>(
|
||||
`/admin/realms/${realm}/client-scopes/${id}`
|
||||
);
|
||||
if (response.data) {
|
||||
Object.entries(response.data).map((entry) => {
|
||||
const data = await adminClient.clientScopes.findOne({ id });
|
||||
if (data) {
|
||||
Object.entries(data).map((entry) => {
|
||||
if (entry[0] === "attributes") {
|
||||
convertToFormValues(entry[1], "attributes", setValue);
|
||||
}
|
||||
|
@ -61,7 +57,7 @@ export const ClientScopeForm = () => {
|
|||
});
|
||||
}
|
||||
|
||||
setClientScope(response.data);
|
||||
setClientScope(data);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -75,11 +71,10 @@ export const ClientScopeForm = () => {
|
|||
clientScopes.attributes!
|
||||
);
|
||||
|
||||
const url = `/admin/realms/${realm}/client-scopes/`;
|
||||
if (id) {
|
||||
await httpClient.doPut(url + id, clientScopes);
|
||||
await adminClient.clientScopes.update({ id }, clientScopes);
|
||||
} else {
|
||||
await httpClient.doPost(url, clientScopes);
|
||||
await adminClient.clientScopes.create(clientScopes);
|
||||
}
|
||||
addAlert(t((id ? "update" : "create") + "Success"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
|
@ -11,16 +11,15 @@ import {
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { useParams } from "react-router-dom";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
|
||||
import { ClientSettings } from "./ClientSettings";
|
||||
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 { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { Credentials } from "./credentials/Credentials";
|
||||
import { ClientRepresentation } from "../realm/models/Realm";
|
||||
import {
|
||||
convertFormValuesToObject,
|
||||
convertToFormValues,
|
||||
|
@ -33,8 +32,7 @@ import {
|
|||
|
||||
export const ClientDetails = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const form = useForm();
|
||||
|
@ -47,16 +45,15 @@ export const ClientDetails = () => {
|
|||
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [name, setName] = useState("");
|
||||
const url = `/admin/realms/${realm}/clients/${id}`;
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "clients:clientDeleteConfirmTitle",
|
||||
messageKey: "clients:clientDeleteConfirm",
|
||||
continueButtonLabel: "common:delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: () => {
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
httpClient.doDelete(`/admin/realms/${realm}/clients/${id}`);
|
||||
await adminClient.clients.del({ id });
|
||||
addAlert(t("clientDeletedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(`${t("clientDeleteError")} ${error}`, AlertVariant.danger);
|
||||
|
@ -84,10 +81,10 @@ export const ClientDetails = () => {
|
|||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchedClient = await httpClient.doGet<ClientRepresentation>(url);
|
||||
if (fetchedClient.data) {
|
||||
setName(fetchedClient.data.clientId);
|
||||
setupForm(fetchedClient.data);
|
||||
const fetchedClient = await adminClient.clients.findOne({ id });
|
||||
if (fetchedClient) {
|
||||
setName(fetchedClient.clientId!);
|
||||
setupForm(fetchedClient);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
@ -105,7 +102,7 @@ export const ClientDetails = () => {
|
|||
redirectUris,
|
||||
attributes,
|
||||
};
|
||||
await httpClient.doPut(url, client);
|
||||
await adminClient.clients.update({ id }, client);
|
||||
setupForm(client as ClientRepresentation);
|
||||
addAlert(t("clientSaveSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext } from "react";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
|
@ -10,12 +10,11 @@ import {
|
|||
IFormatterValueType,
|
||||
} from "@patternfly/react-table";
|
||||
import { Badge, AlertVariant } from "@patternfly/react-core";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
|
||||
import { ExternalLink } from "../components/external-link/ExternalLink";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { ClientRepresentation } from "./models/client-model";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { exportClient } from "../util";
|
||||
|
||||
type ClientListProps = {
|
||||
|
@ -33,8 +32,7 @@ const columns: (keyof ClientRepresentation)[] = [
|
|||
|
||||
export const ClientList = ({ baseUrl, clients, refresh }: ClientListProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const emptyFormatter = (): IFormatter => (data?: IFormatterValueType) => {
|
||||
|
@ -108,9 +106,9 @@ export const ClientList = ({ baseUrl, clients, refresh }: ClientListProps) => {
|
|||
title: t("common:delete"),
|
||||
onClick: async (_, rowId) => {
|
||||
try {
|
||||
await httpClient.doDelete(
|
||||
`/admin/realms/${realm}/clients/${data[rowId].client.id}`
|
||||
);
|
||||
await adminClient.clients.del({
|
||||
id: data[rowId].client.id!,
|
||||
});
|
||||
refresh();
|
||||
addAlert(t("clientDeletedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import React, { useState, useContext, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, PageSection, Spinner } from "@patternfly/react-core";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
|
||||
import { ClientList } from "./ClientList";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { KeycloakContext } from "../context/auth/KeycloakContext";
|
||||
import { ClientRepresentation } from "./models/client-model";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
|
||||
export const ClientsSection = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
@ -18,10 +16,8 @@ export const ClientsSection = () => {
|
|||
const [max, setMax] = useState(10);
|
||||
const [first, setFirst] = useState(0);
|
||||
const [search, setSearch] = useState("");
|
||||
const adminClient = useAdminClient();
|
||||
const [clients, setClients] = useState<ClientRepresentation[]>();
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const keycloak = useContext(KeycloakContext);
|
||||
const { realm } = useContext(RealmContext);
|
||||
|
||||
const loader = async () => {
|
||||
const params: { [name: string]: string | number } = { first, max };
|
||||
|
@ -29,11 +25,8 @@ export const ClientsSection = () => {
|
|||
params.clientId = search;
|
||||
params.search = "true";
|
||||
}
|
||||
const result = await httpClient.doGet<ClientRepresentation[]>(
|
||||
`/admin/realms/${realm}/clients`,
|
||||
{ params: params }
|
||||
);
|
||||
setClients(result.data);
|
||||
const result = await adminClient.clients.find({ ...params });
|
||||
setClients(result);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -84,7 +77,7 @@ export const ClientsSection = () => {
|
|||
<ClientList
|
||||
clients={clients}
|
||||
refresh={loader}
|
||||
baseUrl={keycloak!.authServerUrl()!}
|
||||
baseUrl={adminClient.keycloak.authServerUrl!}
|
||||
/>
|
||||
</PaginatingTableToolbar>
|
||||
)}
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
import React from "react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { render } from "@testing-library/react";
|
||||
import KeycloakAdminClient from "keycloak-admin";
|
||||
|
||||
import clientMock from "./mock-clients.json";
|
||||
import { ClientList } from "../ClientList";
|
||||
import { AdminClient } from "../../context/auth/AdminClient";
|
||||
|
||||
test("renders ClientList", () => {
|
||||
const container = render(
|
||||
<MemoryRouter>
|
||||
<ClientList
|
||||
clients={clientMock}
|
||||
baseUrl="http://blog.nerdin.ch"
|
||||
refresh={() => {}}
|
||||
/>
|
||||
<AdminClient.Provider
|
||||
value={
|
||||
({
|
||||
setConfig: () => {},
|
||||
} as unknown) as KeycloakAdminClient
|
||||
}
|
||||
>
|
||||
<ClientList
|
||||
clients={clientMock}
|
||||
baseUrl="http://blog.nerdin.ch"
|
||||
refresh={() => {}}
|
||||
/>
|
||||
</AdminClient.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useContext } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {
|
||||
PageSection,
|
||||
|
@ -11,18 +11,16 @@ import {
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { GeneralSettings } from "./GeneralSettings";
|
||||
import { CapabilityConfig } from "./CapabilityConfig";
|
||||
import { ClientRepresentation } from "../models/client-model";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
|
||||
export const NewClientForm = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const history = useHistory();
|
||||
|
||||
const [client, setClient] = useState<ClientRepresentation>({
|
||||
|
@ -42,7 +40,7 @@ export const NewClientForm = () => {
|
|||
|
||||
const save = async () => {
|
||||
try {
|
||||
await httpClient.doPost(`/admin/realms/${realm}/clients`, client);
|
||||
await adminClient.clients.create({ ...client });
|
||||
addAlert(t("createSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(t("createError", { error }), AlertVariant.danger);
|
||||
|
|
|
@ -14,15 +14,15 @@ import {
|
|||
Split,
|
||||
SplitItem,
|
||||
} from "@patternfly/react-core";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import CredentialRepresentation from "keycloak-admin/lib/defs/credentialRepresentation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Controller, UseFormMethods, useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { ClientSecret } from "./ClientSecret";
|
||||
import { SignedJWT } from "./SignedJWT";
|
||||
import { X509 } from "./X509";
|
||||
|
@ -49,8 +49,7 @@ export type CredentialsProps = {
|
|||
|
||||
export const Credentials = ({ clientId, form, save }: CredentialsProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
const clientAuthenticatorType = useWatch({
|
||||
control: form.control,
|
||||
|
@ -66,36 +65,37 @@ export const Credentials = ({ clientId, form, save }: CredentialsProps) => {
|
|||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await httpClient.doGet<ClientAuthenticatorProviders[]>(
|
||||
`/admin/realms/${realm}/authentication/client-authenticator-providers`
|
||||
const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders(
|
||||
{ id: clientId }
|
||||
);
|
||||
setProviders(response.data!);
|
||||
setProviders(providers);
|
||||
|
||||
const secretResponse = await httpClient.doGet<Secret>(
|
||||
`/admin/realms/${realm}/clients/${clientId}/client-secret`
|
||||
);
|
||||
setSecret(secretResponse.data!.value);
|
||||
const secret = await adminClient.clients.getClientSecret({
|
||||
id: clientId,
|
||||
});
|
||||
setSecret(secret.value!);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
async function regenerate<T>(
|
||||
endpoint: string,
|
||||
call: (clientId: string) => Promise<T>,
|
||||
message: string
|
||||
): Promise<T | undefined> {
|
||||
try {
|
||||
const response = await httpClient.doPost<T>(
|
||||
`/admin/realms/${realm}/clients/${clientId}/${endpoint}`,
|
||||
{ client: clientId, realm }
|
||||
);
|
||||
const data = await call(clientId);
|
||||
addAlert(t(`${message}Success`), AlertVariant.success);
|
||||
return response.data!;
|
||||
return data;
|
||||
} catch (error) {
|
||||
addAlert(t(`${message}Error`, { error }), AlertVariant.danger);
|
||||
}
|
||||
}
|
||||
|
||||
const regenerateClientSecret = async () => {
|
||||
const secret = await regenerate<Secret>("client-secret", "clientSecret");
|
||||
const secret = await regenerate<CredentialRepresentation>(
|
||||
(clientId) =>
|
||||
adminClient.clients.generateNewClientSecret({ id: clientId }),
|
||||
"clientSecret"
|
||||
);
|
||||
setSecret(secret?.value || "");
|
||||
};
|
||||
|
||||
|
@ -109,7 +109,8 @@ export const Credentials = ({ clientId, form, save }: CredentialsProps) => {
|
|||
|
||||
const regenerateAccessToken = async () => {
|
||||
const accessToken = await regenerate<AccessToken>(
|
||||
"registration-access-token",
|
||||
(clientId) =>
|
||||
adminClient.clients.generateRegistrationAccessToken({ id: clientId }),
|
||||
"accessToken"
|
||||
);
|
||||
setAccessToken(accessToken?.registrationAccessToken || "");
|
||||
|
|
|
@ -18,7 +18,7 @@ export type SignedJWTProps = {
|
|||
|
||||
export const SignedJWT = ({ form }: SignedJWTProps) => {
|
||||
const providers = sortProviders(
|
||||
useServerInfo().providers.clientSignature.providers
|
||||
useServerInfo().providers!.clientSignature.providers
|
||||
);
|
||||
const { t } = useTranslation("clients");
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
PageSection,
|
||||
Form,
|
||||
|
@ -11,18 +11,16 @@ import {
|
|||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ClientRepresentation } from "../models/client-model";
|
||||
import { ClientDescription } from "../ClientDescription";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
|
||||
export const ImportForm = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const form = useForm<ClientRepresentation>();
|
||||
const { register, handleSubmit, setValue } = form;
|
||||
|
||||
|
@ -44,7 +42,7 @@ export const ImportForm = () => {
|
|||
|
||||
const save = async (client: ClientRepresentation) => {
|
||||
try {
|
||||
await httpClient.doPost(`/admin/realms/${realm}/clients`, client);
|
||||
await adminClient.clients.create({ ...client });
|
||||
addAlert(t("clientImportSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(`${t("clientImportError")} '${error}'`, AlertVariant.danger);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useState, useEffect, ReactElement } from "react";
|
||||
import React, { useState, useEffect, ReactElement, useContext } from "react";
|
||||
import {
|
||||
Alert,
|
||||
AlertVariant,
|
||||
|
@ -15,11 +15,10 @@ import {
|
|||
import FileSaver from "file-saver";
|
||||
|
||||
import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
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 { HelpContext } from "../help-enabler/HelpHeader";
|
||||
|
||||
export type DownloadDialogProps = {
|
||||
|
@ -53,13 +52,12 @@ export const DownloadDialog = ({
|
|||
toggleDialog,
|
||||
protocol = "openid-connect",
|
||||
}: DownloadDialogModalProps) => {
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { t } = useTranslation("common");
|
||||
const { enabled } = useContext(HelpContext);
|
||||
const serverInfo = useServerInfo();
|
||||
|
||||
const configFormats = serverInfo.clientInstallations[protocol];
|
||||
const configFormats = serverInfo.clientInstallations![protocol];
|
||||
const [selected, setSelected] = useState(
|
||||
configFormats[configFormats.length - 1].id
|
||||
);
|
||||
|
@ -69,11 +67,16 @@ export const DownloadDialog = ({
|
|||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
(async () => {
|
||||
const response = await httpClient.doGet<string>(
|
||||
`/admin/realms/${realm}/clients/${id}/installation/providers/${selected}`
|
||||
);
|
||||
const snippet = await adminClient.clients.getInstallationProviders({
|
||||
id,
|
||||
providerId: selected,
|
||||
});
|
||||
if (isMounted) {
|
||||
setSnippet(await response.text());
|
||||
if (typeof snippet === "string") {
|
||||
setSnippet(snippet);
|
||||
} else {
|
||||
setSnippet(JSON.stringify(snippet, undefined, 3));
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
|
|
|
@ -14,9 +14,9 @@ import {
|
|||
GridItem,
|
||||
TextArea,
|
||||
} from "@patternfly/react-core";
|
||||
import { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
|
||||
|
||||
import { useAccess } from "../../context/access/Access";
|
||||
import { AccessType } from "../../context/whoami/who-am-i-model";
|
||||
|
||||
export type FormAccessProps = FormProps & {
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { CheckIcon } from "@patternfly/react-icons";
|
||||
|
||||
import { RealmRepresentation } from "../../realm/models/Realm";
|
||||
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
||||
|
||||
|
@ -60,7 +60,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
|||
search === ""
|
||||
? realmList
|
||||
: realmList.filter(
|
||||
(r) => r.realm.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||
(r) => r.realm!.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||
);
|
||||
setFilteredItems(filtered || []);
|
||||
};
|
||||
|
@ -73,11 +73,11 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
|||
<DropdownItem
|
||||
key={`realm-dropdown-item-${r.realm}`}
|
||||
onClick={() => {
|
||||
setRealm(r.realm);
|
||||
setRealm(r.realm!);
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
<RealmText value={r.realm} />
|
||||
<RealmText value={r.realm!} />
|
||||
</DropdownItem>
|
||||
));
|
||||
|
||||
|
@ -114,7 +114,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
|||
>
|
||||
{filteredItems.map((item) => (
|
||||
<ContextSelectorItem key={item.id}>
|
||||
<RealmText value={item.realm} />
|
||||
<RealmText value={item.realm!} />
|
||||
</ContextSelectorItem>
|
||||
))}
|
||||
<ContextSelectorItem key="add">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { createContext, useContext } from "react";
|
||||
import { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
|
||||
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
||||
import { AccessType } from "../../context/whoami/who-am-i-model";
|
||||
|
||||
type AccessContextProps = {
|
||||
hasAccess: (...types: AccessType[]) => boolean;
|
||||
|
|
18
src/context/auth/AdminClient.tsx
Normal file
18
src/context/auth/AdminClient.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { createContext, useContext } from "react";
|
||||
import KeycloakAdminClient from "keycloak-admin";
|
||||
import { RealmContext } from "../realm-context/RealmContext";
|
||||
|
||||
export const AdminClient = createContext<KeycloakAdminClient | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export const useAdminClient = () => {
|
||||
const adminClient = useContext(AdminClient)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
|
||||
adminClient.setConfig({
|
||||
realmName: realm,
|
||||
});
|
||||
|
||||
return adminClient;
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { KeycloakService } from "./keycloak.service";
|
||||
|
||||
export const KeycloakContext = React.createContext<KeycloakService | undefined>(
|
||||
undefined
|
||||
);
|
|
@ -1,84 +0,0 @@
|
|||
import { KeycloakLoginOptions } from "keycloak-js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type KeycloakClient = Keycloak.KeycloakInstance;
|
||||
|
||||
type Token = {
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
preferred_username: string;
|
||||
};
|
||||
|
||||
export class KeycloakService {
|
||||
private keycloakAuth: KeycloakClient;
|
||||
|
||||
public constructor(keycloak: KeycloakClient) {
|
||||
this.keycloakAuth = keycloak;
|
||||
}
|
||||
|
||||
public authenticated(): boolean {
|
||||
return this.keycloakAuth.authenticated
|
||||
? this.keycloakAuth.authenticated
|
||||
: false;
|
||||
}
|
||||
|
||||
public login(options?: KeycloakLoginOptions): void {
|
||||
this.keycloakAuth.login(options);
|
||||
}
|
||||
|
||||
public logout(redirectUri: string = ""): void {
|
||||
this.keycloakAuth.logout({ redirectUri: redirectUri });
|
||||
}
|
||||
|
||||
public account(): void {
|
||||
this.keycloakAuth.accountManagement();
|
||||
}
|
||||
|
||||
public authServerUrl(): string | undefined {
|
||||
const authServerUrl = this.keycloakAuth.authServerUrl;
|
||||
return authServerUrl!.charAt(authServerUrl!.length - 1) === "/"
|
||||
? authServerUrl
|
||||
: authServerUrl + "/";
|
||||
}
|
||||
|
||||
public realm(): string | undefined {
|
||||
return this.keycloakAuth.realm;
|
||||
}
|
||||
|
||||
public get loggedInUser(): string {
|
||||
const { t } = useTranslation();
|
||||
return this.loggedInUserName(t, this.keycloakAuth.tokenParsed as Token);
|
||||
}
|
||||
|
||||
private loggedInUserName = (t: Function, tokenParsed: Token) => {
|
||||
let userName = t("unknownUser");
|
||||
if (tokenParsed) {
|
||||
const givenName = tokenParsed.given_name;
|
||||
const familyName = tokenParsed.family_name;
|
||||
const preferredUsername = tokenParsed.preferred_username;
|
||||
if (givenName && familyName) {
|
||||
userName = t("fullName", { givenName, familyName });
|
||||
} else {
|
||||
userName = givenName || familyName || preferredUsername || userName;
|
||||
}
|
||||
}
|
||||
return userName;
|
||||
};
|
||||
|
||||
public getToken(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (this.keycloakAuth.token) {
|
||||
this.keycloakAuth
|
||||
.updateToken(5)
|
||||
.then(() => {
|
||||
resolve(this.keycloakAuth.token as string);
|
||||
})
|
||||
.catch(() => {
|
||||
reject("Failed to refresh token");
|
||||
});
|
||||
} else {
|
||||
reject("Not logged in");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,17 +1,24 @@
|
|||
import Keycloak, { KeycloakInstance } from "keycloak-js";
|
||||
import KcAdminClient from "keycloak-admin";
|
||||
|
||||
const realm =
|
||||
new URLSearchParams(window.location.search).get("realm") || "master";
|
||||
export default async function (): Promise<KcAdminClient> {
|
||||
const realm =
|
||||
new URLSearchParams(window.location.search).get("realm") || "master";
|
||||
|
||||
const keycloak: KeycloakInstance = Keycloak({
|
||||
url: "http://localhost:8180/auth/",
|
||||
realm: realm,
|
||||
clientId: "security-admin-console-v2",
|
||||
});
|
||||
const kcAdminClient = new KcAdminClient();
|
||||
|
||||
export default async function (): Promise<KeycloakInstance> {
|
||||
await keycloak.init({ onLoad: "check-sso", pkceMethod: "S256" }).catch(() => {
|
||||
try {
|
||||
await kcAdminClient.init(
|
||||
{ onLoad: "check-sso", pkceMethod: "S256" },
|
||||
{
|
||||
url: "http://localhost:8180/auth/",
|
||||
realm: realm,
|
||||
clientId: "security-admin-console-v2",
|
||||
}
|
||||
);
|
||||
kcAdminClient.baseUrl = "";
|
||||
} catch (error) {
|
||||
alert("failed to initialize keycloak");
|
||||
});
|
||||
return keycloak;
|
||||
}
|
||||
|
||||
return kcAdminClient;
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
import { createContext } from "react";
|
||||
import { HttpClient } from "./http-client";
|
||||
|
||||
export const HttpClientContext = createContext<HttpClient | undefined>(
|
||||
undefined
|
||||
);
|
|
@ -1,150 +0,0 @@
|
|||
import { KeycloakService } from "../auth/keycloak.service";
|
||||
|
||||
type ConfigResolve = (config: RequestInit) => void;
|
||||
|
||||
export interface HttpResponse<T = {}> extends Response {
|
||||
data?: T;
|
||||
}
|
||||
|
||||
export interface RequestInitWithParams extends RequestInit {
|
||||
params?: { [name: string]: string | number };
|
||||
}
|
||||
|
||||
export class AccountServiceError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
||||
*/
|
||||
export class HttpClient {
|
||||
private kcSvc: KeycloakService;
|
||||
|
||||
public constructor(keycloakService: KeycloakService) {
|
||||
this.kcSvc = keycloakService;
|
||||
}
|
||||
|
||||
public async doGet<T>(
|
||||
endpoint: string,
|
||||
config?: RequestInitWithParams
|
||||
): Promise<HttpResponse<T>> {
|
||||
return this.doRequest(endpoint, { ...config, method: "get" });
|
||||
}
|
||||
|
||||
public async doDelete<T>(
|
||||
endpoint: string,
|
||||
config?: RequestInitWithParams
|
||||
): Promise<HttpResponse<T>> {
|
||||
return this.doRequest(endpoint, { ...config, method: "delete" });
|
||||
}
|
||||
|
||||
public async doPost<T>(
|
||||
endpoint: string,
|
||||
body: string | {},
|
||||
config?: RequestInitWithParams
|
||||
): Promise<HttpResponse<T>> {
|
||||
return this.doRequest(endpoint, {
|
||||
...config,
|
||||
body: JSON.stringify(body),
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
public async doPut<T>(
|
||||
endpoint: string,
|
||||
body: string | {},
|
||||
config?: RequestInitWithParams
|
||||
): Promise<HttpResponse<T>> {
|
||||
return this.doRequest(endpoint, {
|
||||
...config,
|
||||
body: JSON.stringify(body),
|
||||
method: "put",
|
||||
});
|
||||
}
|
||||
|
||||
public async doRequest<T>(
|
||||
endpoint: string,
|
||||
config?: RequestInitWithParams
|
||||
): Promise<HttpResponse<T>> {
|
||||
const response: HttpResponse<T> = await fetch(
|
||||
this.makeUrl(endpoint, config).toString(),
|
||||
await this.makeConfig(config)
|
||||
);
|
||||
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (contentType && contentType.indexOf("application/json") !== -1) {
|
||||
try {
|
||||
response.data = await response.json();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
this.handleError(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private handleError(response: HttpResponse): void {
|
||||
if (response != null && response.status === 401) {
|
||||
// session timed out?
|
||||
this.kcSvc.login();
|
||||
}
|
||||
|
||||
if (response != null && response.data != null) {
|
||||
throw new AccountServiceError((response.data as any).errorMessage);
|
||||
} else {
|
||||
throw new AccountServiceError(response.statusText);
|
||||
}
|
||||
}
|
||||
|
||||
private makeUrl(url: string, config?: RequestInitWithParams): string {
|
||||
const searchParams = new URLSearchParams();
|
||||
// add request params
|
||||
if (config && {}.hasOwnProperty.call(config, "params")) {
|
||||
const params: { [name: string]: string } = (config.params as {}) || {};
|
||||
Object.keys(params).forEach((key) =>
|
||||
searchParams.append(key, params[key])
|
||||
);
|
||||
}
|
||||
|
||||
return url + "?" + searchParams.toString();
|
||||
}
|
||||
|
||||
private makeConfig(config: RequestInit = {}): Promise<RequestInit> {
|
||||
return new Promise((resolve: ConfigResolve) => {
|
||||
this.kcSvc
|
||||
.getToken()
|
||||
.then((token: string) => {
|
||||
resolve({
|
||||
...config,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...config.headers,
|
||||
Authorization: "Bearer " + token,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.kcSvc.login();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"unhandledrejection",
|
||||
(event: PromiseRejectionEvent) => {
|
||||
event.promise.catch((error) => {
|
||||
if (error instanceof AccountServiceError) {
|
||||
// We already handled the error. Ignore unhandled rejection.
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
|
@ -1,8 +1,9 @@
|
|||
import React, { createContext, ReactNode, useContext } from "react";
|
||||
import { ServerInfoRepresentation } from "./server-info";
|
||||
import { HttpClientContext } from "../http-service/HttpClientContext";
|
||||
import { ServerInfoRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
|
||||
|
||||
import { sortProviders } from "../../util";
|
||||
import { DataLoader } from "../../components/data-loader/DataLoader";
|
||||
import { useAdminClient } from "../auth/AdminClient";
|
||||
|
||||
export const ServerInfoContext = createContext<ServerInfoRepresentation>(
|
||||
{} as ServerInfoRepresentation
|
||||
|
@ -11,16 +12,13 @@ export const ServerInfoContext = createContext<ServerInfoRepresentation>(
|
|||
export const useServerInfo = () => useContext(ServerInfoContext);
|
||||
|
||||
export const useLoginProviders = () => {
|
||||
return sortProviders(useServerInfo().providers["login-protocol"].providers);
|
||||
return sortProviders(useServerInfo().providers!["login-protocol"].providers);
|
||||
};
|
||||
|
||||
export const ServerInfoProvider = ({ children }: { children: ReactNode }) => {
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const adminClient = useAdminClient();
|
||||
const loader = async () => {
|
||||
const response = await httpClient.doGet<ServerInfoRepresentation>(
|
||||
"/admin/serverinfo"
|
||||
);
|
||||
return response.data!;
|
||||
return await adminClient.serverInfo.find();
|
||||
};
|
||||
return (
|
||||
<DataLoader loader={loader}>
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
export interface ServerInfoRepresentation {
|
||||
systemInfo: SystemInfoRepresentation;
|
||||
memoryInfo: MemoryInfoRepresentation;
|
||||
profileInfo: ProfileInfoRepresentation;
|
||||
themes: { [index: string]: ThemeInfoRepresentation[] };
|
||||
socialProviders: { [index: string]: string }[];
|
||||
identityProviders: { [index: string]: string }[];
|
||||
clientImporters: { [index: string]: string }[];
|
||||
providers: { [index: string]: SpiInfoRepresentation };
|
||||
protocolMapperTypes: { [index: string]: ProtocolMapperTypeRepresentation[] };
|
||||
builtinProtocolMappers: { [index: string]: ProtocolMapperRepresentation[] };
|
||||
clientInstallations: { [index: string]: ClientInstallationRepresentation[] };
|
||||
componentTypes: { [index: string]: ComponentTypeRepresentation[] };
|
||||
passwordPolicies: PasswordPolicyTypeRepresentation[];
|
||||
enums: { [index: string]: string[] };
|
||||
}
|
||||
|
||||
export interface SystemInfoRepresentation {
|
||||
version: string;
|
||||
serverTime: string;
|
||||
uptime: string;
|
||||
uptimeMillis: number;
|
||||
javaVersion: string;
|
||||
javaVendor: string;
|
||||
javaVm: string;
|
||||
javaVmVersion: string;
|
||||
javaRuntime: string;
|
||||
javaHome: string;
|
||||
osName: string;
|
||||
osArchitecture: string;
|
||||
osVersion: string;
|
||||
fileEncoding: string;
|
||||
userName: string;
|
||||
userDir: string;
|
||||
userTimezone: string;
|
||||
userLocale: string;
|
||||
}
|
||||
|
||||
export interface MemoryInfoRepresentation {
|
||||
total: number;
|
||||
totalFormated: string;
|
||||
used: number;
|
||||
usedFormated: string;
|
||||
free: number;
|
||||
freePercentage: number;
|
||||
freeFormated: string;
|
||||
}
|
||||
|
||||
export interface ProfileInfoRepresentation {
|
||||
name: string;
|
||||
disabledFeatures: string[];
|
||||
previewFeatures: string[];
|
||||
experimentalFeatures: string[];
|
||||
}
|
||||
|
||||
export interface ThemeInfoRepresentation {
|
||||
name: string;
|
||||
locales: string[];
|
||||
}
|
||||
|
||||
export interface SpiInfoRepresentation {
|
||||
internal: boolean;
|
||||
providers: { [index: string]: ProviderRepresentation };
|
||||
}
|
||||
|
||||
export interface ProtocolMapperTypeRepresentation {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
helpText: string;
|
||||
priority: number;
|
||||
properties: ConfigPropertyRepresentation[];
|
||||
}
|
||||
|
||||
export interface ProtocolMapperRepresentation {
|
||||
id: string;
|
||||
name: string;
|
||||
protocol: string;
|
||||
protocolMapper: string;
|
||||
consentRequired: boolean;
|
||||
consentText: string;
|
||||
config: { [index: string]: string };
|
||||
}
|
||||
|
||||
export interface ClientInstallationRepresentation {
|
||||
id: string;
|
||||
protocol: string;
|
||||
downloadOnly: boolean;
|
||||
displayType: string;
|
||||
helpText: string;
|
||||
filename: string;
|
||||
mediaType: string;
|
||||
}
|
||||
|
||||
export interface ComponentTypeRepresentation {
|
||||
id: string;
|
||||
helpText: string;
|
||||
properties: ConfigPropertyRepresentation[];
|
||||
metadata: { [index: string]: any };
|
||||
}
|
||||
|
||||
export interface PasswordPolicyTypeRepresentation {
|
||||
id: string;
|
||||
displayName: string;
|
||||
configType: string;
|
||||
defaultValue: string;
|
||||
multipleSupported: boolean;
|
||||
}
|
||||
|
||||
export interface ProviderRepresentation {
|
||||
order: number;
|
||||
operationalInfo: { [index: string]: string };
|
||||
}
|
||||
|
||||
export interface ConfigPropertyRepresentation {
|
||||
name: string;
|
||||
label: string;
|
||||
helpText: string;
|
||||
type: string;
|
||||
defaultValue: any;
|
||||
options: string[];
|
||||
secret: boolean;
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useContext } from "react";
|
||||
import React from "react";
|
||||
import i18n from "../../i18n";
|
||||
|
||||
import WhoAmIRepresentation, { AccessType } from "./who-am-i-model";
|
||||
|
||||
import { HttpClientContext } from "../http-service/HttpClientContext";
|
||||
import { KeycloakContext } from "../auth/KeycloakContext";
|
||||
import { DataLoader } from "../../components/data-loader/DataLoader";
|
||||
import { useAdminClient } from "../auth/AdminClient";
|
||||
import WhoAmIRepresentation, {
|
||||
AccessType,
|
||||
} from "keycloak-admin/lib/defs/whoAmIRepresentation";
|
||||
|
||||
export class WhoAmI {
|
||||
constructor(
|
||||
|
@ -53,24 +53,19 @@ export const WhoAmIContext = React.createContext(new WhoAmI());
|
|||
|
||||
type WhoAmIProviderProps = { children: React.ReactNode };
|
||||
export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => {
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const keycloak = useContext(KeycloakContext);
|
||||
const adminClient = useAdminClient();
|
||||
|
||||
const whoAmILoader = async () => {
|
||||
if (keycloak === undefined) return undefined;
|
||||
if (adminClient.keycloak === undefined) return undefined;
|
||||
|
||||
const realm = keycloak.realm();
|
||||
|
||||
return await httpClient
|
||||
.doGet(`/admin/${realm}/console/whoami/`)
|
||||
.then((r) => r.data as WhoAmIRepresentation);
|
||||
return await adminClient.whoAmI.find();
|
||||
};
|
||||
|
||||
return (
|
||||
<DataLoader loader={whoAmILoader}>
|
||||
{(whoamirep) => (
|
||||
<WhoAmIContext.Provider
|
||||
value={new WhoAmI(keycloak?.realm(), whoamirep.data)}
|
||||
value={new WhoAmI(adminClient.keycloak?.realm, whoamirep.data)}
|
||||
>
|
||||
{children}
|
||||
</WhoAmIContext.Provider>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
export type AccessType =
|
||||
| "view-realm"
|
||||
| "view-identity-providers"
|
||||
| "manage-identity-providers"
|
||||
| "impersonation"
|
||||
| "create-client"
|
||||
| "manage-users"
|
||||
| "query-realms"
|
||||
| "view-authorization"
|
||||
| "query-clients"
|
||||
| "query-users"
|
||||
| "manage-events"
|
||||
| "manage-realm"
|
||||
| "view-events"
|
||||
| "view-users"
|
||||
| "view-clients"
|
||||
| "manage-authorization"
|
||||
| "manage-clients"
|
||||
| "query-groups"
|
||||
| "anyone";
|
||||
|
||||
export default interface WhoAmIRepresentation {
|
||||
userId: string;
|
||||
realm: string;
|
||||
displayName: string;
|
||||
locale: string;
|
||||
createRealm: boolean;
|
||||
realm_access: { [key: string]: AccessType[] };
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -10,8 +10,7 @@ import {
|
|||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
|
@ -33,8 +32,7 @@ export const GroupsCreateModal = ({
|
|||
refresh,
|
||||
}: GroupsCreateModalProps) => {
|
||||
const { t } = useTranslation("groups");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
const form = useForm();
|
||||
const { register, errors } = form;
|
||||
|
@ -46,9 +44,7 @@ export const GroupsCreateModal = ({
|
|||
const submitForm = async () => {
|
||||
if (await form.trigger()) {
|
||||
try {
|
||||
await httpClient.doPost(`/admin/realms/${realm}/groups`, {
|
||||
name: createGroupName,
|
||||
});
|
||||
await adminClient.groups.create({ name: createGroupName });
|
||||
refresh();
|
||||
setIsCreateModalOpen(false);
|
||||
setCreateGroupName("");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useContext } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
|
@ -9,8 +9,7 @@ import { Button, AlertVariant } from "@patternfly/react-core";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { GroupRepresentation } from "./models/groups";
|
||||
import { UsersIcon } from "@patternfly/react-icons";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
|
||||
export type GroupsListProps = {
|
||||
|
@ -32,10 +31,9 @@ export const GroupsList = ({
|
|||
setTableRowSelectedArray,
|
||||
}: GroupsListProps) => {
|
||||
const { t } = useTranslation("groups");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const adminClient = useAdminClient();
|
||||
const columnGroupName: keyof GroupRepresentation = "name";
|
||||
const columnGroupNumber: keyof GroupRepresentation = "membersLength";
|
||||
const { realm } = useContext(RealmContext);
|
||||
const { addAlert } = useAlerts();
|
||||
const [formattedData, setFormattedData] = useState<FormattedData[]>([]);
|
||||
|
||||
|
@ -107,9 +105,7 @@ export const GroupsList = ({
|
|||
rowId: number
|
||||
) => {
|
||||
try {
|
||||
await httpClient.doDelete(
|
||||
`/admin/realms/${realm}/groups/${list![rowId].id}`
|
||||
);
|
||||
await adminClient.groups.del({ id: list![rowId].id! });
|
||||
refresh();
|
||||
setTableRowSelectedArray([]);
|
||||
addAlert(t("Group deleted"), AlertVariant.success);
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { GroupsList } from "./GroupsList";
|
||||
import { GroupsCreateModal } from "./GroupsCreateModal";
|
||||
import { GroupRepresentation } from "./models/groups";
|
||||
import {
|
||||
ServerGroupsArrayRepresentation,
|
||||
ServerGroupMembersRepresentation,
|
||||
} from "./models/server-info";
|
||||
import { TableToolbar } from "../components/table-toolbar/TableToolbar";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import {
|
||||
Button,
|
||||
|
@ -25,10 +19,11 @@ import {
|
|||
AlertVariant,
|
||||
} from "@patternfly/react-core";
|
||||
import "./GroupsSection.css";
|
||||
import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
|
||||
|
||||
export const GroupsSection = () => {
|
||||
const { t } = useTranslation("groups");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const adminClient = useAdminClient();
|
||||
const [rawData, setRawData] = useState<{ [key: string]: any }[]>();
|
||||
const [filteredData, setFilteredData] = useState<{ [key: string]: any }[]>();
|
||||
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
||||
|
@ -38,22 +33,16 @@ export const GroupsSection = () => {
|
|||
Array<number>
|
||||
>([]);
|
||||
const columnID: keyof GroupRepresentation = "id";
|
||||
const membersLength: keyof GroupRepresentation = "membersLength";
|
||||
const columnGroupName: keyof GroupRepresentation = "name";
|
||||
const { addAlert } = useAlerts();
|
||||
const { realm } = useContext(RealmContext);
|
||||
const membersLength = "membersLength";
|
||||
|
||||
const loader = async () => {
|
||||
const groups = await httpClient.doGet<ServerGroupsArrayRepresentation[]>(
|
||||
`/admin/realms/${realm}/groups`
|
||||
);
|
||||
const groupsData = groups.data!;
|
||||
const getMembers = async (id: number) => {
|
||||
const response = await httpClient.doGet<
|
||||
ServerGroupMembersRepresentation[]
|
||||
>(`/admin/realms/${realm}/groups/${id}/members`);
|
||||
const responseData = response.data!;
|
||||
return responseData.length;
|
||||
const groupsData = await adminClient.groups.find();
|
||||
|
||||
const getMembers = async (id: string) => {
|
||||
const response = await adminClient.groups.listMembers({ id });
|
||||
return response.length;
|
||||
};
|
||||
|
||||
const memberPromises = groupsData.map((group: { [key: string]: any }) =>
|
||||
|
@ -101,11 +90,9 @@ export const GroupsSection = () => {
|
|||
if (tableRowSelectedArray.length !== 0) {
|
||||
const deleteGroup = async (rowId: number) => {
|
||||
try {
|
||||
await httpClient.doDelete(
|
||||
`/admin/realms/${realm}/groups/${
|
||||
filteredData ? filteredData![rowId].id : rawData![rowId].id
|
||||
}`
|
||||
);
|
||||
await adminClient.groups.del({
|
||||
id: filteredData ? filteredData![rowId].id : rawData![rowId].id,
|
||||
});
|
||||
loader();
|
||||
} catch (error) {
|
||||
addAlert(`${t("groupDeleteError")} ${error}`, AlertVariant.danger);
|
||||
|
|
|
@ -2,22 +2,20 @@ import React from "react";
|
|||
import ReactDom from "react-dom";
|
||||
import i18n from "./i18n";
|
||||
|
||||
import { App } from "./App";
|
||||
import { AdminClient } from "./context/auth/AdminClient";
|
||||
import init from "./context/auth/keycloak";
|
||||
import { KeycloakContext } from "./context/auth/KeycloakContext";
|
||||
import { KeycloakService } from "./context/auth/keycloak.service";
|
||||
import { HttpClientContext } from "./context/http-service/HttpClientContext";
|
||||
import { HttpClient } from "./context/http-service/http-client";
|
||||
import { App } from "./App";
|
||||
import { RealmContextProvider } from "./context/realm-context/RealmContext";
|
||||
|
||||
console.info("supported languages", ...i18n.languages);
|
||||
init().then((keycloak) => {
|
||||
const keycloakService = new KeycloakService(keycloak);
|
||||
|
||||
init().then((adminClient) => {
|
||||
ReactDom.render(
|
||||
<KeycloakContext.Provider value={keycloakService}>
|
||||
<HttpClientContext.Provider value={new HttpClient(keycloakService)}>
|
||||
<RealmContextProvider>
|
||||
<AdminClient.Provider value={adminClient}>
|
||||
<App />
|
||||
</HttpClientContext.Provider>
|
||||
</KeycloakContext.Provider>,
|
||||
</AdminClient.Provider>
|
||||
</RealmContextProvider>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, PageSection } from "@patternfly/react-core";
|
||||
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { RoleRepresentation } from "../model/role-model";
|
||||
import { RolesList } from "./RoleList";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
|
||||
export const RealmRolesSection = () => {
|
||||
|
@ -16,19 +15,11 @@ export const RealmRolesSection = () => {
|
|||
const [first, setFirst] = useState(0);
|
||||
const { t } = useTranslation("roles");
|
||||
const history = useHistory();
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const adminClient = useAdminClient();
|
||||
const [roles, setRoles] = useState<RoleRepresentation[]>();
|
||||
const { realm } = useContext(RealmContext);
|
||||
|
||||
const loader = async () => {
|
||||
const params: { [name: string]: string | number } = { first, max };
|
||||
|
||||
const result = await httpClient.doGet<RoleRepresentation[]>(
|
||||
`/admin/realms/${realm}/roles`,
|
||||
{ params: params }
|
||||
);
|
||||
setRoles(result.data);
|
||||
};
|
||||
const params: { [name: string]: string | number } = { first, max };
|
||||
const loader = async () => setRoles(await adminClient.roles.find(params));
|
||||
|
||||
useEffect(() => {
|
||||
loader();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
|
@ -11,11 +11,10 @@ import {
|
|||
} from "@patternfly/react-table";
|
||||
|
||||
import { ExternalLink } from "../components/external-link/ExternalLink";
|
||||
import { RoleRepresentation } from "../model/role-model";
|
||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
||||
import { AlertVariant, ButtonVariant } from "@patternfly/react-core";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
|
||||
type RolesListProps = {
|
||||
|
@ -31,8 +30,7 @@ const columns: (keyof RoleRepresentation)[] = [
|
|||
|
||||
export const RolesList = ({ roles, refresh }: RolesListProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
const [selectedRowId, setSelectedRowId] = useState(-1);
|
||||
|
||||
|
@ -71,9 +69,9 @@ export const RolesList = ({ roles, refresh }: RolesListProps) => {
|
|||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await httpClient.doDelete(
|
||||
`/admin/realms/${realm}/roles/${data[selectedRowId].role.name}`
|
||||
);
|
||||
await adminClient.roles.delByName({
|
||||
name: data[selectedRowId].role.name!,
|
||||
});
|
||||
refresh();
|
||||
addAlert(t("roleDeletedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext } from "react";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Text,
|
||||
|
@ -15,17 +15,15 @@ import {
|
|||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import { RoleRepresentation } from "../../model/role-model";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
|
||||
export const NewRoleForm = () => {
|
||||
const { t } = useTranslation("roles");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { addAlert } = useAlerts();
|
||||
const { realm } = useContext(RealmContext);
|
||||
const adminClient = useAdminClient();
|
||||
|
||||
const { register, control, errors, handleSubmit } = useForm<
|
||||
RoleRepresentation
|
||||
|
@ -33,7 +31,7 @@ export const NewRoleForm = () => {
|
|||
|
||||
const save = async (role: RoleRepresentation) => {
|
||||
try {
|
||||
await httpClient.doPost(`admin/realms/${realm}/roles`, role);
|
||||
await adminClient.roles.create(role);
|
||||
addAlert(t("roleCreated"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(`${t("roleCreateError")} '${error}'`, AlertVariant.danger);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext } from "react";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
PageSection,
|
||||
|
@ -12,15 +12,15 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
|
||||
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
|
||||
import { RealmRepresentation } from "../models/Realm";
|
||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
|
||||
export const NewRealmForm = () => {
|
||||
const { t } = useTranslation("realm");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const { register, handleSubmit, setValue, control } = useForm<
|
||||
|
@ -38,7 +38,7 @@ export const NewRealmForm = () => {
|
|||
|
||||
const save = async (realm: RealmRepresentation) => {
|
||||
try {
|
||||
await httpClient.doPost("/admin/realms", realm);
|
||||
await adminClient.realms.create(realm);
|
||||
addAlert(t("Realm created"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { TFunction } from "i18next";
|
||||
import { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
|
||||
|
||||
import { AuthenticationSection } from "./authentication/AuthenticationSection";
|
||||
import { ClientScopeForm } from "./client-scopes/form/ClientScopeForm";
|
||||
import { ClientScopesSection } from "./client-scopes/ClientScopesSection";
|
||||
|
@ -17,8 +19,6 @@ import { SessionsSection } from "./sessions/SessionsSection";
|
|||
import { UserFederationSection } from "./user-federation/UserFederationSection";
|
||||
import { UsersSection } from "./user/UsersSection";
|
||||
import { MappingDetails } from "./client-scopes/details/MappingDetails";
|
||||
|
||||
import { AccessType } from "./context/whoami/who-am-i-model";
|
||||
import { ClientDetails } from "./clients/ClientDetails";
|
||||
|
||||
export type RouteDef = {
|
||||
|
|
|
@ -7,8 +7,8 @@ import roles from "../realm-roles/__tests__/mock-roles.json";
|
|||
import { ServerInfoContext } from "../context/server-info/ServerInfoProvider";
|
||||
|
||||
import { RoleMappingForm } from "../client-scopes/add/RoleMappingForm";
|
||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { HttpClient } from "../context/http-service/http-client";
|
||||
import { AdminClient } from "../context/auth/AdminClient";
|
||||
import KeycloakAdminClient from "keycloak-admin";
|
||||
|
||||
export default {
|
||||
title: "Role Mapping Form",
|
||||
|
@ -17,18 +17,24 @@ export default {
|
|||
|
||||
export const RoleMappingFormExample = () => (
|
||||
<ServerInfoContext.Provider value={serverInfo}>
|
||||
<HttpClientContext.Provider
|
||||
<AdminClient.Provider
|
||||
value={
|
||||
({
|
||||
doGet: () => {
|
||||
return { data: roles };
|
||||
setConfig: () => {},
|
||||
roles: {
|
||||
find: () => {
|
||||
return roles;
|
||||
},
|
||||
},
|
||||
} as unknown) as HttpClient
|
||||
clients: {
|
||||
find: () => roles,
|
||||
},
|
||||
} as unknown) as KeycloakAdminClient
|
||||
}
|
||||
>
|
||||
<Page>
|
||||
<RoleMappingForm clientScopeId="dummy" />
|
||||
</Page>
|
||||
</HttpClientContext.Provider>
|
||||
</AdminClient.Provider>
|
||||
</ServerInfoContext.Provider>
|
||||
);
|
||||
|
|
|
@ -15,45 +15,43 @@ import {
|
|||
TextVariants,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
import { KeycloakCard } from "../components/keycloak-card/KeycloakCard";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
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 { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||
import { UserFederationRepresentation } from "./model/userFederation";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import "./user-federation.css";
|
||||
|
||||
type Config = {
|
||||
enabled: string[];
|
||||
};
|
||||
|
||||
export const UserFederationSection = () => {
|
||||
const [userFederations, setUserFederations] = useState<
|
||||
UserFederationRepresentation[]
|
||||
ComponentRepresentation[]
|
||||
>();
|
||||
const { addAlert } = useAlerts();
|
||||
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 result = await httpClient.doGet<UserFederationRepresentation[]>(
|
||||
`/admin/realms/${realm}/components`,
|
||||
{
|
||||
params: testParams,
|
||||
}
|
||||
);
|
||||
setUserFederations(result.data);
|
||||
const userFederations = await adminClient.components.find(testParams);
|
||||
setUserFederations(userFederations);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loader();
|
||||
}, []);
|
||||
|
||||
const { t } = useTranslation("user-federation");
|
||||
const httpClient = useContext(HttpClientContext)!;
|
||||
const { realm } = useContext(RealmContext);
|
||||
|
||||
const ufAddProviderDropdownItems = [
|
||||
<DropdownItem key="itemLDAP">LDAP</DropdownItem>,
|
||||
<DropdownItem key="itemKerberos">Kerberos</DropdownItem>,
|
||||
|
@ -75,9 +73,8 @@ export const UserFederationSection = () => {
|
|||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
httpClient
|
||||
.doDelete(`/admin/realms/${realm}/components/${currentCard}`)
|
||||
.then(() => loader());
|
||||
await adminClient.components.del({ id: currentCard });
|
||||
await loader();
|
||||
addAlert(t("userFedDeletedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(t("userFedDeleteError", { error }), AlertVariant.danger);
|
||||
|
@ -96,7 +93,7 @@ export const UserFederationSection = () => {
|
|||
<DropdownItem
|
||||
key={`${index}-cardDelete`}
|
||||
onClick={() => {
|
||||
toggleDeleteForCard(userFederation.id);
|
||||
toggleDeleteForCard(userFederation.id!);
|
||||
}}
|
||||
>
|
||||
{t("common:delete")}
|
||||
|
@ -105,14 +102,14 @@ export const UserFederationSection = () => {
|
|||
return (
|
||||
<GalleryItem key={index}>
|
||||
<KeycloakCard
|
||||
id={userFederation.id}
|
||||
id={userFederation.id!}
|
||||
dropdownItems={ufCardDropdownItems}
|
||||
title={userFederation.name}
|
||||
title={userFederation.name!}
|
||||
footerText={
|
||||
userFederation.providerId === "ldap" ? "LDAP" : "Kerberos"
|
||||
}
|
||||
labelText={
|
||||
userFederation.config.enabled
|
||||
(userFederation.config as Config)!.enabled[0] !== "false"
|
||||
? `${t("common:enabled")}`
|
||||
: `${t("common:disabled")}`
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
export interface UserFederationRepresentation {
|
||||
id: string;
|
||||
name: string;
|
||||
providerId: string;
|
||||
providerType: string;
|
||||
parentId: string;
|
||||
config: { [index: string]: any };
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import FileSaver from "file-saver";
|
||||
|
||||
import { ClientRepresentation } from "./clients/models/client-model";
|
||||
import { ProviderRepresentation } from "./context/server-info/server-info";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
import { ProviderRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
|
||||
|
||||
export const sortProviders = (providers: {
|
||||
[index: string]: ProviderRepresentation;
|
||||
|
|
69
yarn.lock
69
yarn.lock
|
@ -5482,6 +5482,13 @@ aws4@^1.8.0:
|
|||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
|
||||
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
|
||||
|
||||
axios@^0.21.0:
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.0.tgz#26df088803a2350dff2c27f96fef99fe49442aca"
|
||||
integrity sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==
|
||||
dependencies:
|
||||
follow-redirects "^1.10.0"
|
||||
|
||||
axobject-query@^2.0.2:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
||||
|
@ -6567,6 +6574,11 @@ camelcase@^6.0.0:
|
|||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
|
||||
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
|
||||
|
||||
camelize@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
|
||||
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
|
||||
|
||||
can-use-dom@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a"
|
||||
|
@ -9415,6 +9427,11 @@ follow-redirects@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6"
|
||||
integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==
|
||||
|
||||
follow-redirects@^1.10.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
|
||||
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
|
||||
|
||||
for-in@^0.1.3:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
|
||||
|
@ -12472,10 +12489,23 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3, jsx-ast-utils@^2.4.1:
|
|||
array-includes "^3.1.1"
|
||||
object.assign "^4.1.0"
|
||||
|
||||
keycloak-js@^11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-11.0.0.tgz#2bc3126e192e3c5279ca157c33f4cc657467540a"
|
||||
integrity sha512-hjpIrO+ujaRsSJC76xEpVlZVAPpXm3OZYruxGa/cZ/PF7lwp9kRmIinqCxdg0jttr4dogCbOZ2YrFpqPx4a8mw==
|
||||
keycloak-admin@^1.14.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/keycloak-admin/-/keycloak-admin-1.14.1.tgz#d30507b1b89270e42dc2e1ab73842afe3df5c0af"
|
||||
integrity sha512-duQYY+I8iSeSOYtsJnyfQ9LUuKeY5oZCp2rkfH4tJ71VYuk0bon4liN2OBGmLO8PBsZ2TXW95rSydZumysuP/g==
|
||||
dependencies:
|
||||
axios "^0.21.0"
|
||||
camelize "^1.0.0"
|
||||
keycloak-js "^11.0.3"
|
||||
lodash "^4.17.20"
|
||||
query-string "^6.13.7"
|
||||
url-join "^4.0.0"
|
||||
url-template "^2.0.8"
|
||||
|
||||
keycloak-js@^11.0.3:
|
||||
version "11.0.3"
|
||||
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-11.0.3.tgz#5f22f22662211e2bfa5327d3d2eb83020a5baa23"
|
||||
integrity sha512-e2OVyCiru25UhJz3aPj5irf//+vJzvAhHdcsCIWAcvF8Te22iUoZqEdNFji8D3zNzDehX4VpuIJwQOYCj6rqTA==
|
||||
dependencies:
|
||||
base64-js "1.3.1"
|
||||
js-sha256 "0.9.0"
|
||||
|
@ -12771,7 +12801,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.5, lodash@~4.17.10, lodash@~4.17.5:
|
||||
"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.10, lodash@~4.17.5:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
@ -15492,6 +15522,15 @@ query-string@^4.1.0:
|
|||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
query-string@^6.13.7:
|
||||
version "6.13.7"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.7.tgz#af53802ff6ed56f3345f92d40a056f93681026ee"
|
||||
integrity sha512-CsGs8ZYb39zu0WLkeOhe0NMePqgYdAuCqxOYKDR5LVCytDZYMGx3Bb+xypvQvPHVPijRXB0HZNFllCzHRe4gEA==
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
split-on-first "^1.0.0"
|
||||
strict-uri-encode "^2.0.0"
|
||||
|
||||
querystring-es3@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||
|
@ -17270,6 +17309,11 @@ spdy@^4.0.1:
|
|||
select-hose "^2.0.0"
|
||||
spdy-transport "^3.0.0"
|
||||
|
||||
split-on-first@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
|
||||
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
|
||||
|
||||
split-string@^3.0.1, split-string@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
|
||||
|
@ -17399,6 +17443,11 @@ strict-uri-encode@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
|
||||
|
||||
strict-uri-encode@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
|
||||
|
||||
string-length@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
||||
|
@ -18421,6 +18470,11 @@ urix@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
||||
|
||||
url-join@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
|
||||
integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
|
||||
|
||||
url-loader@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.3.0.tgz#e0e2ef658f003efb8ca41b0f3ffbf76bab88658b"
|
||||
|
@ -18447,6 +18501,11 @@ url-parse@^1.4.3:
|
|||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
url-template@^2.0.8:
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21"
|
||||
integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE=
|
||||
|
||||
url@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||
|
|
Loading…
Reference in a new issue