initial version realm settings section (#281)
* initial version realm settings section * simpified save * fixed reload * fixed merge error
This commit is contained in:
parent
daec4957f2
commit
45cda0a969
8 changed files with 392 additions and 10 deletions
|
@ -11,7 +11,7 @@ import {
|
|||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { emptyFormatter, exportClient } from "../util";
|
||||
import { emptyFormatter, exportClient, getBaseUrl } from "../util";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
import { formattedLinkTableCell } from "../components/external-link/FormattedLink";
|
||||
|
@ -23,9 +23,7 @@ export const ClientsSection = () => {
|
|||
const { url } = useRouteMatch();
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const baseUrl = adminClient.keycloak
|
||||
? adminClient.keycloak.authServerUrl!
|
||||
: adminClient.baseUrl + "/";
|
||||
const baseUrl = getBaseUrl(adminClient);
|
||||
|
||||
const loader = async (first?: number, max?: number, search?: string) => {
|
||||
const params: { [name: string]: string | number } = {
|
||||
|
|
|
@ -7,11 +7,14 @@ import React, {
|
|||
import { Controller } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
ClipboardCopy,
|
||||
Form,
|
||||
FormGroup,
|
||||
FormProps,
|
||||
Grid,
|
||||
GridItem,
|
||||
Stack,
|
||||
StackItem,
|
||||
TextArea,
|
||||
} from "@patternfly/react-core";
|
||||
import { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
|
||||
|
@ -91,7 +94,10 @@ export const FormAccess = ({
|
|||
child.type === FormGroup ||
|
||||
child.type === GridItem ||
|
||||
child.type === Grid ||
|
||||
child.type === ActionGroup
|
||||
child.type === ActionGroup ||
|
||||
child.type === ClipboardCopy ||
|
||||
child.type === Stack ||
|
||||
child.type === StackItem
|
||||
? { children }
|
||||
: { ...newProps, children }
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ import { useRealm } from "../../context/realm-context/RealmContext";
|
|||
import { WhoAmIContext } from "../../context/whoami/WhoAmI";
|
||||
|
||||
import "./realm-selector.css";
|
||||
import { toUpperCase } from "../../util";
|
||||
|
||||
type RealmSelectorProps = {
|
||||
realmList: RealmRepresentation[];
|
||||
|
@ -34,9 +35,6 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
|
|||
const history = useHistory();
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
const toUpperCase = (realmName: string) =>
|
||||
realmName.charAt(0).toUpperCase() + realmName.slice(1);
|
||||
|
||||
const RealmText = ({ value }: { value: string }) => (
|
||||
<Split className="keycloak__realm_selector__list-item-split">
|
||||
<SplitItem isFilled>{toUpperCase(value)}</SplitItem>
|
||||
|
|
|
@ -14,6 +14,8 @@ import roles from "./realm-roles/messages.json";
|
|||
import users from "./user/messages.json";
|
||||
import sessions from "./sessions/messages.json";
|
||||
import events from "./events/messages.json";
|
||||
import realmSettings from "./realm-settings/messages.json";
|
||||
import realmSettingsHelp from "./realm-settings/help.json";
|
||||
import storybook from "./stories/messages.json";
|
||||
import userFederation from "./user-federation/messages.json";
|
||||
import userFederationHelp from "./user-federation/help.json";
|
||||
|
@ -36,6 +38,8 @@ const initOptions = {
|
|||
...sessions,
|
||||
...userFederation,
|
||||
...events,
|
||||
...realmSettings,
|
||||
...realmSettingsHelp,
|
||||
...storybook,
|
||||
...userFederation,
|
||||
...userFederationHelp,
|
||||
|
|
|
@ -1,5 +1,333 @@
|
|||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
ButtonVariant,
|
||||
ClipboardCopy,
|
||||
DropdownItem,
|
||||
DropdownSeparator,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
Stack,
|
||||
StackItem,
|
||||
Switch,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabTitleText,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||
import { getBaseUrl, toUpperCase } from "../util";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { FormattedLink } from "../components/external-link/FormattedLink";
|
||||
|
||||
type RealmSettingsHeaderProps = {
|
||||
onChange: (...event: any[]) => void;
|
||||
value: boolean;
|
||||
save: () => void;
|
||||
realmName: string;
|
||||
};
|
||||
|
||||
const RealmSettingsHeader = ({
|
||||
save,
|
||||
onChange,
|
||||
value,
|
||||
realmName,
|
||||
}: RealmSettingsHeaderProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
const history = useHistory();
|
||||
const { setRealm } = useRealm();
|
||||
|
||||
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
||||
titleKey: "realm-settings:disableConfirmTitle",
|
||||
messageKey: "realm-settings:disableConfirm",
|
||||
continueButtonLabel: "common:disable",
|
||||
onConfirm: () => {
|
||||
onChange(!value);
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "realm-settings:deleteConfirmTitle",
|
||||
messageKey: "realm-settings:deleteConfirm",
|
||||
continueButtonLabel: "common:delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await adminClient.realms.del({ realm: realmName });
|
||||
addAlert(t("deletedSuccess"), AlertVariant.success);
|
||||
setRealm("master");
|
||||
history.push("/master");
|
||||
} catch (error) {
|
||||
addAlert(t("deleteError", { error }), AlertVariant.danger);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DisableConfirm />
|
||||
<DeleteConfirm />
|
||||
<ViewHeader
|
||||
titleKey={toUpperCase(realmName)}
|
||||
subKey=""
|
||||
dropdownItems={[
|
||||
<DropdownItem key="import" onClick={() => {}}>
|
||||
{t("partialImport")}
|
||||
</DropdownItem>,
|
||||
<DropdownItem key="export" onClick={() => {}}>
|
||||
{t("partialExport")}
|
||||
</DropdownItem>,
|
||||
<DropdownSeparator key="separator" />,
|
||||
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
|
||||
{t("common:delete")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
isEnabled={value}
|
||||
onToggle={(value) => {
|
||||
if (!value) {
|
||||
toggleDisableDialog();
|
||||
} else {
|
||||
onChange(value);
|
||||
save();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const requireSslTypes = ["all", "external", "none"];
|
||||
|
||||
export const RealmSettingsSection = () => {
|
||||
return <>The Realm Settings Page</>;
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const adminClient = useAdminClient();
|
||||
const { realm: realmName } = useRealm();
|
||||
const { addAlert } = useAlerts();
|
||||
const { register, control, getValues, setValue, handleSubmit } = useForm();
|
||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const baseUrl = getBaseUrl(adminClient);
|
||||
|
||||
useEffect(() => {
|
||||
return asyncStateFetch(
|
||||
() => adminClient.realms.findOne({ realm: realmName }),
|
||||
(realm) => {
|
||||
setRealm(realm);
|
||||
setupForm(realm);
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setupForm = (realm: RealmRepresentation) => {
|
||||
Object.entries(realm).map((entry) => setValue(entry[0], entry[1]));
|
||||
};
|
||||
|
||||
const save = async (realm: RealmRepresentation) => {
|
||||
try {
|
||||
await adminClient.realms.update({ realm: realmName }, realm);
|
||||
setRealm(realm);
|
||||
addAlert(t("saveSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(t("saveError", { error }), AlertVariant.danger);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
defaultValue={true}
|
||||
render={({ onChange, value }) => (
|
||||
<RealmSettingsHeader
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
realmName={realmName}
|
||||
save={() => save(getValues())}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<PageSection variant="light">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onSelect={(_, key) => setActiveTab(key as number)}
|
||||
isBox
|
||||
>
|
||||
<Tab eventKey={0} title={<TabTitleText>{t("general")}</TabTitleText>}>
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
role="manage-realm"
|
||||
className="pf-u-mt-lg"
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<FormGroup label={t("realmId")} fieldId="kc-realm-id" isRequired>
|
||||
<ClipboardCopy isReadOnly>{realmName}</ClipboardCopy>
|
||||
</FormGroup>
|
||||
<FormGroup label={t("displayName")} fieldId="kc-display-name">
|
||||
<TextInput
|
||||
type="text"
|
||||
id="kc-display-name"
|
||||
name="displayName"
|
||||
ref={register}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("htmlDisplayName")}
|
||||
fieldId="kc-html-display-name"
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="kc-html-display-name"
|
||||
name="displayNameHtml"
|
||||
ref={register}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("frontendUrl")}
|
||||
fieldId="kc-frontend-url"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:frontendUrl"
|
||||
forLabel={t("frontendUrl")}
|
||||
forID="kc-frontend-url"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="kc-frontend-url"
|
||||
name="attributes.frontendUrl"
|
||||
ref={register}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("requireSsl")}
|
||||
fieldId="kc-require-ssl"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:requireSsl"
|
||||
forLabel={t("requireSsl")}
|
||||
forID="kc-require-ssl"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="sslRequired"
|
||||
defaultValue="none"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-require-ssl"
|
||||
onToggle={() => setOpen(!open)}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value as string);
|
||||
setOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("requireSsl")}
|
||||
isOpen={open}
|
||||
>
|
||||
{requireSslTypes.map((sslType) => (
|
||||
<SelectOption
|
||||
selected={sslType === value}
|
||||
key={sslType}
|
||||
value={sslType}
|
||||
>
|
||||
{t(`sslType.${sslType}`)}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
hasNoPaddingTop
|
||||
label={t("userManagedAccess")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:userManagedAccess"
|
||||
forLabel={t("userManagedAccess")}
|
||||
forID="kc-user-manged-access"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-user-manged-access"
|
||||
>
|
||||
<Controller
|
||||
name="userManagedAccessAllowed"
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-user-manged-access"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("endpoints")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:endpoints"
|
||||
forLabel={t("endpoints")}
|
||||
forID="kc-endpoints"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-endpoints"
|
||||
>
|
||||
<Stack>
|
||||
<StackItem>
|
||||
<FormattedLink
|
||||
href={`${baseUrl}realms/${realmName}/.well-known/openid-configuration`}
|
||||
title={t("openEndpointConfiguration")}
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<FormattedLink
|
||||
href={`${baseUrl}realms/${realmName}/protocol/saml/descriptor`}
|
||||
title={t("samlIdentityProviderMetadata")}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</FormGroup>
|
||||
|
||||
<ActionGroup>
|
||||
<Button variant="primary" type="submit">
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={() => setupForm(realm!)}>
|
||||
{t("common:reload")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
8
src/realm-settings/help.json
Normal file
8
src/realm-settings/help.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"realm-settings-help": {
|
||||
"frontendUrl": "Set the frontend URL for the realm. Use in combination with the default hostname provider to override the base URL for frontend requests for a specific realm.",
|
||||
"requireSsl": "Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.",
|
||||
"userManagedAccess": "If enabled, users are allowed to manage their resources and permissions using the Account Management Console.",
|
||||
"endpoints": "Shows the configuration of the protocol endpoints"
|
||||
}
|
||||
}
|
30
src/realm-settings/messages.json
Normal file
30
src/realm-settings/messages.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"realm-settings": {
|
||||
"partialImport": "Partial import",
|
||||
"partialExport": "Partial export",
|
||||
"deleteRealm": "Delete realm",
|
||||
"deleteConfirmTitle": "Delete realm?",
|
||||
"deleteConfirm": "If you delete this realm, all associated data will be removed.",
|
||||
"deletedSuccess": "The realm has been deleted",
|
||||
"deleteError": "Could not delete realm: {{error}}",
|
||||
"disableConfirmTitle": "Disable realm?",
|
||||
"disableConfirm": "User and clients can't access the realm if it's disabled. Are you sure you want to continue?",
|
||||
"saveSuccess": "Realm successfully updated",
|
||||
"saveError": "Realm could not be updated: {error}",
|
||||
"general": "General",
|
||||
"realmId": "Realm ID",
|
||||
"displayName": "Display name",
|
||||
"htmlDisplayName": "HTML Display name",
|
||||
"frontendUrl": "Frontend URL",
|
||||
"requireSsl": "Require SSL",
|
||||
"sslType": {
|
||||
"all": "All requests",
|
||||
"external": "External requests",
|
||||
"none": "None"
|
||||
},
|
||||
"userManagedAccess": "User-managed access",
|
||||
"endpoints": "Endpoints",
|
||||
"openEndpointConfiguration": "Open Endpoint Configuration",
|
||||
"samlIdentityProviderMetadata": "SAML 2.0 Identity Provider Metadata"
|
||||
}
|
||||
}
|
10
src/util.ts
10
src/util.ts
|
@ -3,6 +3,7 @@ import FileSaver from "file-saver";
|
|||
import _ from "lodash";
|
||||
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||
import { ProviderRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
|
||||
import KeycloakAdminClient from "keycloak-admin";
|
||||
|
||||
export const sortProviders = (providers: {
|
||||
[index: string]: ProviderRepresentation;
|
||||
|
@ -49,6 +50,9 @@ export const exportClient = (client: ClientRepresentation): void => {
|
|||
);
|
||||
};
|
||||
|
||||
export const toUpperCase = (realmName: string) =>
|
||||
realmName.charAt(0).toUpperCase() + realmName.slice(1);
|
||||
|
||||
export const convertToFormValues = (
|
||||
obj: any,
|
||||
prefix: string,
|
||||
|
@ -73,3 +77,9 @@ export const emptyFormatter = (): IFormatter => (
|
|||
) => {
|
||||
return data ? data : "—";
|
||||
};
|
||||
|
||||
export const getBaseUrl = (adminClient: KeycloakAdminClient) => {
|
||||
return adminClient.keycloak
|
||||
? adminClient.keycloak.authServerUrl!
|
||||
: adminClient.baseUrl + "/";
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue