Add user profile tab to realm settings (#1509)
This commit is contained in:
parent
f57f8c5560
commit
c61ba73781
4 changed files with 164 additions and 1 deletions
|
@ -51,6 +51,7 @@ import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
import { DEFAULT_LOCALE } from "../i18n";
|
import { DEFAULT_LOCALE } from "../i18n";
|
||||||
import { toDashboard } from "../dashboard/routes/Dashboard";
|
import { toDashboard } from "../dashboard/routes/Dashboard";
|
||||||
import environment from "../environment";
|
import environment from "../environment";
|
||||||
|
import { UserProfileTab } from "./UserProfileTab";
|
||||||
|
|
||||||
type RealmSettingsHeaderProps = {
|
type RealmSettingsHeaderProps = {
|
||||||
onChange: (value: boolean) => void;
|
onChange: (value: boolean) => void;
|
||||||
|
@ -438,6 +439,15 @@ export const RealmSettingsTabs = ({
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="userProfile"
|
||||||
|
data-testid="rs-user-profile-tab"
|
||||||
|
title={
|
||||||
|
<TabTitleText>{t("realm-settings:userProfile")}</TabTitleText>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<UserProfileTab />
|
||||||
|
</Tab>
|
||||||
</KeycloakTabs>
|
</KeycloakTabs>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|
144
src/realm-settings/UserProfileTab.tsx
Normal file
144
src/realm-settings/UserProfileTab.tsx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import type ClientProfilesRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfilesRepresentation";
|
||||||
|
import { CodeEditor, Language } from "@patternfly/react-code-editor";
|
||||||
|
import {
|
||||||
|
ActionGroup,
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
PageSection,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
TabTitleText,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import type { editor } from "monaco-editor";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
import { prettyPrintJSON } from "../util";
|
||||||
|
|
||||||
|
export const UserProfileTab = () => {
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
const [activeTab, setActiveTab] = useState("attributes");
|
||||||
|
const [profiles, setProfiles] = useState<ClientProfilesRepresentation>();
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [refreshCount, setRefreshCount] = useState(0);
|
||||||
|
|
||||||
|
useFetch(
|
||||||
|
() =>
|
||||||
|
adminClient.clientPolicies.listProfiles({
|
||||||
|
includeGlobalProfiles: true,
|
||||||
|
realm,
|
||||||
|
}),
|
||||||
|
(profiles) => setProfiles(profiles),
|
||||||
|
[refreshCount]
|
||||||
|
);
|
||||||
|
|
||||||
|
async function onSave(updatedProfiles: ClientProfilesRepresentation) {
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await adminClient.clientPolicies.createProfiles({
|
||||||
|
...updatedProfiles,
|
||||||
|
realm,
|
||||||
|
});
|
||||||
|
|
||||||
|
setRefreshCount(refreshCount + 1);
|
||||||
|
addAlert(t("userProfileSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addError("realm-settings:userProfileError", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onSelect={(_, key) => setActiveTab(key.toString())}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
eventKey="attributes"
|
||||||
|
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
||||||
|
></Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="attributesGroup"
|
||||||
|
title={<TabTitleText>{t("attributesGroup")}</TabTitleText>}
|
||||||
|
></Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="jsonEditor"
|
||||||
|
title={<TabTitleText>{t("jsonEditor")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
{/** The code editor needs to be rendered conditionally to prevent it from being initialized
|
||||||
|
* while the tab contents are hidden. If the contents of the tab are hidden then it
|
||||||
|
* might not initialize correctly.
|
||||||
|
*/}
|
||||||
|
{activeTab === "jsonEditor" && (
|
||||||
|
<JsonEditorTab
|
||||||
|
profiles={profiles}
|
||||||
|
onSave={onSave}
|
||||||
|
isSaving={isSaving}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type JsonEditorTabProps = {
|
||||||
|
profiles?: ClientProfilesRepresentation;
|
||||||
|
onSave: (profiles: ClientProfilesRepresentation) => void;
|
||||||
|
isSaving: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const JsonEditorTab = ({ profiles, onSave, isSaving }: JsonEditorTabProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { addError } = useAlerts();
|
||||||
|
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
||||||
|
|
||||||
|
useEffect(() => resetCode(), [profiles, editor]);
|
||||||
|
|
||||||
|
function resetCode() {
|
||||||
|
editor?.setValue(profiles ? prettyPrintJSON(profiles) : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
const value = editor?.getValue();
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
onSave(JSON.parse(value));
|
||||||
|
} catch (error) {
|
||||||
|
addError("realm-settings:invalidJsonError", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection variant="light">
|
||||||
|
<CodeEditor
|
||||||
|
language={Language.json}
|
||||||
|
height="30rem"
|
||||||
|
onEditorDidMount={(editor) => setEditor(editor)}
|
||||||
|
isLanguageLabelVisible
|
||||||
|
/>
|
||||||
|
<Form>
|
||||||
|
<ActionGroup>
|
||||||
|
<Button variant="primary" onClick={save} isDisabled={isSaving}>
|
||||||
|
{t("common:save")}
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={resetCode} isDisabled={isSaving}>
|
||||||
|
{t("common:revert")}
|
||||||
|
</Button>
|
||||||
|
</ActionGroup>
|
||||||
|
</Form>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
};
|
|
@ -338,6 +338,14 @@ export default {
|
||||||
addClientProfile: "Add client profile",
|
addClientProfile: "Add client profile",
|
||||||
emptyProfiles: "No client profiles configured",
|
emptyProfiles: "No client profiles configured",
|
||||||
tokens: "Tokens",
|
tokens: "Tokens",
|
||||||
|
userProfile: "User profile",
|
||||||
|
jsonEditor: "JSON editor",
|
||||||
|
attributes: "Attributes",
|
||||||
|
attributesGroup: "Attributes group",
|
||||||
|
invalidJsonError:
|
||||||
|
"Unable to save user profile, the provided information is not valid JSON.",
|
||||||
|
userProfileSuccess: "User profile settings successfully updated.",
|
||||||
|
userProfileError: "Could not update user profile settings: {{error}}",
|
||||||
key: "Key",
|
key: "Key",
|
||||||
value: "Value",
|
value: "Value",
|
||||||
status: "Status",
|
status: "Status",
|
||||||
|
|
|
@ -13,7 +13,8 @@ export type RealmSettingsTab =
|
||||||
| "securityDefences"
|
| "securityDefences"
|
||||||
| "sessions"
|
| "sessions"
|
||||||
| "tokens"
|
| "tokens"
|
||||||
| "clientPolicies";
|
| "clientPolicies"
|
||||||
|
| "userProfile";
|
||||||
|
|
||||||
export type RealmSettingsParams = {
|
export type RealmSettingsParams = {
|
||||||
realm: string;
|
realm: string;
|
||||||
|
|
Loading…
Reference in a new issue