Change the input fields based on access rights (#184)
* poc of possible way to change form dynamically * fixed detail routs * Update src/components/form-access/FormAccess.tsx Co-authored-by: Stan Silvert <ssilvert@redhat.com> * Update src/components/form-access/FormAccess.tsx Co-authored-by: Stan Silvert <ssilvert@redhat.com> * Update src/components/form-access/FormAccess.tsx Co-authored-by: Stan Silvert <ssilvert@redhat.com> * added more form access and logic for Controller * render switches for boolean types * better render of `<Controller` wrapped components * added isDisabled property * added test * small refactor * fixed types * TextArea doesn't support isDisabled * when field is disabled, disable button as well * added jsdoc Co-authored-by: Stan Silvert <ssilvert@redhat.com>
This commit is contained in:
parent
889f8078d2
commit
2543893373
14 changed files with 472 additions and 25 deletions
|
@ -7,6 +7,8 @@ import "@testing-library/jest-dom/extend-expect";
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
|
import 'mutationobserver-shim';
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
lng: 'en',
|
lng: 'en',
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
|
@ -22,3 +24,6 @@ import { configure } from 'enzyme';
|
||||||
import Adapter from 'enzyme-adapter-react-16';
|
import Adapter from 'enzyme-adapter-react-16';
|
||||||
|
|
||||||
configure({ adapter: new Adapter() });
|
configure({ adapter: new Adapter() });
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
global.MutationObserver = window.MutationObserver;
|
|
@ -68,6 +68,7 @@
|
||||||
"grunt": "^1.2.1",
|
"grunt": "^1.2.1",
|
||||||
"grunt-contrib-copy": "^1.0.0",
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
"jest": "^25.4.0",
|
"jest": "^25.4.0",
|
||||||
|
"mutationobserver-shim": "^0.3.7",
|
||||||
"postcss": "^7.0.32",
|
"postcss": "^7.0.32",
|
||||||
"postcss-cli": "^7.1.1",
|
"postcss-cli": "^7.1.1",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
|
|
|
@ -67,6 +67,7 @@ export const PageNav: React.FunctionComponent = () => {
|
||||||
"view-realm",
|
"view-realm",
|
||||||
"query-groups",
|
"query-groups",
|
||||||
"query-users",
|
"query-users",
|
||||||
|
"query-clients",
|
||||||
"view-events"
|
"view-events"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
||||||
import { FormGroup, TextInput } from "@patternfly/react-core";
|
import { FormGroup, TextInput } from "@patternfly/react-core";
|
||||||
import { UseFormMethods } from "react-hook-form";
|
import { UseFormMethods } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
|
|
||||||
type ClientDescriptionProps = {
|
type ClientDescriptionProps = {
|
||||||
form: UseFormMethods;
|
form: UseFormMethods;
|
||||||
|
@ -11,7 +12,7 @@ export const ClientDescription = ({ form }: ClientDescriptionProps) => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
const { register, errors } = form;
|
const { register, errors } = form;
|
||||||
return (
|
return (
|
||||||
<>
|
<FormAccess role="manage-clients" unWrap>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("clientID")}
|
label={t("clientID")}
|
||||||
fieldId="kc-client-id"
|
fieldId="kc-client-id"
|
||||||
|
@ -37,6 +38,6 @@ export const ClientDescription = ({ form }: ClientDescriptionProps) => {
|
||||||
name="description"
|
name="description"
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</>
|
</FormAccess>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { exportClient } from "../util";
|
import { exportClient } from "../util";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { useDownloadDialog } from "../components/download-dialog/DownloadDialog";
|
import { useDownloadDialog } from "../components/download-dialog/DownloadDialog";
|
||||||
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
|
|
||||||
export const ClientSettings = () => {
|
export const ClientSettings = () => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
|
@ -163,7 +164,7 @@ export const ClientSettings = () => {
|
||||||
<Form isHorizontal>
|
<Form isHorizontal>
|
||||||
<ClientDescription form={form} />
|
<ClientDescription form={form} />
|
||||||
</Form>
|
</Form>
|
||||||
<Form isHorizontal>
|
<FormAccess isHorizontal role="manage-clients">
|
||||||
<FormGroup label={t("rootUrl")} fieldId="kc-root-url">
|
<FormGroup label={t("rootUrl")} fieldId="kc-root-url">
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -183,8 +184,8 @@ export const ClientSettings = () => {
|
||||||
ref={form.register}
|
ref={form.register}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Form>
|
</FormAccess>
|
||||||
<Form isHorizontal>
|
<FormAccess isHorizontal role="manage-clients">
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("consentRequired")}
|
label={t("consentRequired")}
|
||||||
fieldId="kc-consent"
|
fieldId="kc-consent"
|
||||||
|
@ -241,7 +242,7 @@ export const ClientSettings = () => {
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link">{t("common:cancel")}</Button>
|
<Button variant="link">{t("common:cancel")}</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</Form>
|
</FormAccess>
|
||||||
</ScrollForm>
|
</ScrollForm>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -6,10 +6,11 @@ import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Grid,
|
Grid,
|
||||||
GridItem,
|
GridItem,
|
||||||
Form,
|
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { UseFormMethods, Controller } from "react-hook-form";
|
import { UseFormMethods, Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||||
|
|
||||||
type CapabilityConfigProps = {
|
type CapabilityConfigProps = {
|
||||||
form: UseFormMethods;
|
form: UseFormMethods;
|
||||||
};
|
};
|
||||||
|
@ -17,7 +18,7 @@ type CapabilityConfigProps = {
|
||||||
export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
|
export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
return (
|
return (
|
||||||
<Form isHorizontal>
|
<FormAccess isHorizontal role="manage-clients">
|
||||||
<FormGroup
|
<FormGroup
|
||||||
hasNoPaddingTop
|
hasNoPaddingTop
|
||||||
label={t("clientAuthentication")}
|
label={t("clientAuthentication")}
|
||||||
|
@ -132,6 +133,6 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Form>
|
</FormAccess>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
117
src/components/form-access/FormAccess.tsx
Normal file
117
src/components/form-access/FormAccess.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import React, {
|
||||||
|
Children,
|
||||||
|
cloneElement,
|
||||||
|
isValidElement,
|
||||||
|
ReactElement,
|
||||||
|
} from "react";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
ActionGroup,
|
||||||
|
Form,
|
||||||
|
FormGroup,
|
||||||
|
FormProps,
|
||||||
|
Grid,
|
||||||
|
GridItem,
|
||||||
|
TextArea,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { useAccess } from "../../context/access/Access";
|
||||||
|
import { AccessType } from "../../context/whoami/who-am-i-model";
|
||||||
|
|
||||||
|
export type FormAccessProps = FormProps & {
|
||||||
|
/**
|
||||||
|
* One of the AccessType's that the user needs to have to view this form. Also see {@link useAccess}.
|
||||||
|
* @type {AccessType}
|
||||||
|
*/
|
||||||
|
role: AccessType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An override property if fine grained access has been setup for this form.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
fineGrainedAccess?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set unWrap when you don't want this component to wrap your "children" in a {@link Form} component.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
unWrap?: boolean;
|
||||||
|
children: ReactElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this in place of a patternfly Form component and add the `role` and `fineGrainedAccess` properties.
|
||||||
|
* @param {FormAccessProps} param0 - all properties of Form + role and fineGrainedAccess
|
||||||
|
*/
|
||||||
|
export const FormAccess = ({
|
||||||
|
children,
|
||||||
|
role,
|
||||||
|
fineGrainedAccess = false,
|
||||||
|
unWrap = false,
|
||||||
|
...rest
|
||||||
|
}: FormAccessProps) => {
|
||||||
|
const { hasAccess } = useAccess();
|
||||||
|
|
||||||
|
const recursiveCloneChildren = (
|
||||||
|
children: ReactElement[],
|
||||||
|
newProps: any
|
||||||
|
): ReactElement[] => {
|
||||||
|
return Children.map(children, (child) => {
|
||||||
|
if (!isValidElement(child)) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.props) {
|
||||||
|
const element = child as ReactElement;
|
||||||
|
if (child.type === Controller) {
|
||||||
|
return cloneElement(child, {
|
||||||
|
...element.props,
|
||||||
|
render: (props: any) => {
|
||||||
|
const renderElement = element.props.render(props);
|
||||||
|
return cloneElement(renderElement, {
|
||||||
|
value: props.value,
|
||||||
|
onChange: props.onChange,
|
||||||
|
...newProps,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const children = recursiveCloneChildren(
|
||||||
|
element.props.children,
|
||||||
|
newProps
|
||||||
|
);
|
||||||
|
if (child.type === TextArea) {
|
||||||
|
return cloneElement(child, {
|
||||||
|
readOnly: newProps.isDisabled,
|
||||||
|
children,
|
||||||
|
} as any);
|
||||||
|
}
|
||||||
|
return cloneElement(
|
||||||
|
child,
|
||||||
|
child.type === FormGroup ||
|
||||||
|
child.type === GridItem ||
|
||||||
|
child.type === Grid ||
|
||||||
|
child.type === ActionGroup
|
||||||
|
? { children }
|
||||||
|
: { ...newProps, children }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!unWrap && (
|
||||||
|
<Form {...rest}>
|
||||||
|
{recursiveCloneChildren(children, {
|
||||||
|
isDisabled: !hasAccess(role) && !fineGrainedAccess,
|
||||||
|
})}
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
{unWrap &&
|
||||||
|
recursiveCloneChildren(children, {
|
||||||
|
isDisabled: !hasAccess(role) && !fineGrainedAccess,
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
62
src/components/form-access/__tests__/FormAccess.test.tsx
Normal file
62
src/components/form-access/__tests__/FormAccess.test.tsx
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import React from "react";
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { FormGroup, Switch, TextInput } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { WhoAmI, WhoAmIContext } from "../../../context/whoami/WhoAmI";
|
||||||
|
import whoami from "../../../context/whoami/__tests__/mock-whoami.json";
|
||||||
|
import { RealmContext } from "../../../context/realm-context/RealmContext";
|
||||||
|
import { AccessContextProvider } from "../../../context/access/Access";
|
||||||
|
|
||||||
|
import { FormAccess } from "../FormAccess";
|
||||||
|
|
||||||
|
describe("<FormAccess />", () => {
|
||||||
|
const Form = ({ realm }: { realm: string }) => {
|
||||||
|
const { register, control } = useForm();
|
||||||
|
return (
|
||||||
|
<WhoAmIContext.Provider value={new WhoAmI("master", whoami)}>
|
||||||
|
<RealmContext.Provider value={{ realm, setRealm: () => {} }}>
|
||||||
|
<AccessContextProvider>
|
||||||
|
<FormAccess role="manage-clients">
|
||||||
|
<FormGroup label="test" fieldId="field">
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id="field"
|
||||||
|
name="fieldName"
|
||||||
|
ref={register()}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<Controller
|
||||||
|
name="consentRequired"
|
||||||
|
defaultValue={false}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
id="kc-consent"
|
||||||
|
label={"on"}
|
||||||
|
labelOff="off"
|
||||||
|
isChecked={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormAccess>
|
||||||
|
</AccessContextProvider>
|
||||||
|
</RealmContext.Provider>
|
||||||
|
</WhoAmIContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
it("render normal form", () => {
|
||||||
|
const comp = mount(<Form realm="master" />);
|
||||||
|
expect(comp).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("render form disabled for test realm", () => {
|
||||||
|
const container = mount(<Form realm="test" />);
|
||||||
|
|
||||||
|
const disabled = container.find("input#field").props().disabled;
|
||||||
|
expect(disabled).toBe(true);
|
||||||
|
|
||||||
|
expect(container.find("input#kc-consent").props().disabled).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,242 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<FormAccess /> render normal form 1`] = `
|
||||||
|
<Form
|
||||||
|
realm="master"
|
||||||
|
>
|
||||||
|
<AccessContextProvider>
|
||||||
|
<FormAccess
|
||||||
|
role="manage-clients"
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<form
|
||||||
|
className="pf-c-form"
|
||||||
|
noValidate={true}
|
||||||
|
>
|
||||||
|
<FormGroup
|
||||||
|
fieldId="field"
|
||||||
|
key=".0"
|
||||||
|
label="test"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pf-c-form__group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pf-c-form__group-label"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className="pf-c-form__label"
|
||||||
|
htmlFor="field"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pf-c-form__label-text"
|
||||||
|
>
|
||||||
|
test
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pf-c-form__group-control"
|
||||||
|
>
|
||||||
|
<ForwardRef
|
||||||
|
id="field"
|
||||||
|
isDisabled={false}
|
||||||
|
key=".0"
|
||||||
|
name="fieldName"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<TextInputBase
|
||||||
|
aria-label={null}
|
||||||
|
className=""
|
||||||
|
id="field"
|
||||||
|
innerRef={[Function]}
|
||||||
|
isDisabled={false}
|
||||||
|
isLeftTruncated={false}
|
||||||
|
isReadOnly={false}
|
||||||
|
isRequired={false}
|
||||||
|
name="fieldName"
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
validated="default"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid={false}
|
||||||
|
aria-label={null}
|
||||||
|
className="pf-c-form-control"
|
||||||
|
disabled={false}
|
||||||
|
id="field"
|
||||||
|
name="fieldName"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
readOnly={false}
|
||||||
|
required={false}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</TextInputBase>
|
||||||
|
</ForwardRef>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormGroup>
|
||||||
|
<Controller
|
||||||
|
control={
|
||||||
|
Object {
|
||||||
|
"defaultValuesRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"fieldArrayDefaultValuesRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"fieldArrayNamesRef": Object {
|
||||||
|
"current": Set {},
|
||||||
|
},
|
||||||
|
"fieldsRef": Object {
|
||||||
|
"current": Object {
|
||||||
|
"consentRequired": Object {
|
||||||
|
"ref": Object {
|
||||||
|
"focus": undefined,
|
||||||
|
"name": "consentRequired",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fieldName": Object {
|
||||||
|
"ref": <input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
id="field"
|
||||||
|
name="fieldName"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fieldsWithValidationRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"formStateRef": Object {
|
||||||
|
"current": Object {
|
||||||
|
"dirtyFields": Object {},
|
||||||
|
"errors": Object {},
|
||||||
|
"isDirty": false,
|
||||||
|
"isSubmitSuccessful": false,
|
||||||
|
"isSubmitted": false,
|
||||||
|
"isSubmitting": false,
|
||||||
|
"isValid": false,
|
||||||
|
"submitCount": 0,
|
||||||
|
"touched": Object {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"getValues": [Function],
|
||||||
|
"isWatchAllRef": Object {
|
||||||
|
"current": false,
|
||||||
|
},
|
||||||
|
"mode": Object {
|
||||||
|
"isOnAll": false,
|
||||||
|
"isOnBlur": false,
|
||||||
|
"isOnChange": false,
|
||||||
|
"isOnSubmit": true,
|
||||||
|
"isOnTouch": false,
|
||||||
|
},
|
||||||
|
"reValidateMode": Object {
|
||||||
|
"isReValidateOnBlur": false,
|
||||||
|
"isReValidateOnChange": true,
|
||||||
|
},
|
||||||
|
"readFormStateRef": Object {
|
||||||
|
"current": Object {
|
||||||
|
"dirtyFields": false,
|
||||||
|
"isDirty": false,
|
||||||
|
"isSubmitting": false,
|
||||||
|
"isValid": false,
|
||||||
|
"touched": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"register": [Function],
|
||||||
|
"removeFieldEventListener": [Function],
|
||||||
|
"renderWatchedInputs": [Function],
|
||||||
|
"resetFieldArrayFunctionRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"setValue": [Function],
|
||||||
|
"shallowFieldsStateRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"shouldUnregister": true,
|
||||||
|
"trigger": [Function],
|
||||||
|
"unregister": [Function],
|
||||||
|
"updateFormState": [Function],
|
||||||
|
"useWatchFieldsRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"useWatchRenderFunctionsRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"validFieldsRef": Object {
|
||||||
|
"current": Object {},
|
||||||
|
},
|
||||||
|
"validateResolver": undefined,
|
||||||
|
"watchFieldsRef": Object {
|
||||||
|
"current": Set {},
|
||||||
|
},
|
||||||
|
"watchInternal": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defaultValue={false}
|
||||||
|
key=".1"
|
||||||
|
name="consentRequired"
|
||||||
|
render={[Function]}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
aria-label=""
|
||||||
|
id="kc-consent"
|
||||||
|
isChecked={false}
|
||||||
|
isDisabled={false}
|
||||||
|
label="on"
|
||||||
|
labelOff="off"
|
||||||
|
onChange={[Function]}
|
||||||
|
value={false}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className="pf-c-switch"
|
||||||
|
data-ouia-component-id="OUIA-Generated-Switch-1"
|
||||||
|
data-ouia-component-type="PF4/Switch"
|
||||||
|
data-ouia-safe={true}
|
||||||
|
htmlFor="kc-consent"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label=""
|
||||||
|
aria-labelledby="kc-consent-on"
|
||||||
|
checked={false}
|
||||||
|
className="pf-c-switch__input"
|
||||||
|
disabled={false}
|
||||||
|
id="kc-consent"
|
||||||
|
onChange={[Function]}
|
||||||
|
type="checkbox"
|
||||||
|
value={false}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="pf-c-switch__toggle"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pf-c-switch__label pf-m-on"
|
||||||
|
id="kc-consent-on"
|
||||||
|
>
|
||||||
|
on
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pf-c-switch__label pf-m-off"
|
||||||
|
id="kc-consent-off"
|
||||||
|
>
|
||||||
|
off
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</Switch>
|
||||||
|
</Controller>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</FormAccess>
|
||||||
|
</AccessContextProvider>
|
||||||
|
</Form>
|
||||||
|
`;
|
|
@ -6,6 +6,7 @@ import {
|
||||||
SplitItem,
|
SplitItem,
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
|
TextInputProps,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { MinusIcon, PlusIcon } from "@patternfly/react-icons";
|
import { MinusIcon, PlusIcon } from "@patternfly/react-icons";
|
||||||
|
|
||||||
|
@ -23,12 +24,16 @@ export function toValue(formValue: MultiLine[]): string[] {
|
||||||
return formValue.map((field) => field.value);
|
return formValue.map((field) => field.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MultiLineInputProps = {
|
export type MultiLineInputProps = Omit<TextInputProps, "form"> & {
|
||||||
form: UseFormMethods;
|
form: UseFormMethods;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MultiLineInput = ({ name, form }: MultiLineInputProps) => {
|
export const MultiLineInput = ({
|
||||||
|
name,
|
||||||
|
form,
|
||||||
|
...rest
|
||||||
|
}: MultiLineInputProps) => {
|
||||||
const { register, control } = form;
|
const { register, control } = form;
|
||||||
const { fields, append, remove } = useFieldArray({
|
const { fields, append, remove } = useFieldArray({
|
||||||
name,
|
name,
|
||||||
|
@ -49,6 +54,7 @@ export const MultiLineInput = ({ name, form }: MultiLineInputProps) => {
|
||||||
ref={register()}
|
ref={register()}
|
||||||
name={`${name}[${index}].value`}
|
name={`${name}[${index}].value`}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
|
@ -57,6 +63,7 @@ export const MultiLineInput = ({ name, form }: MultiLineInputProps) => {
|
||||||
variant={ButtonVariant.link}
|
variant={ButtonVariant.link}
|
||||||
onClick={() => append({})}
|
onClick={() => append({})}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
isDisabled={rest.isDisabled}
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -20,7 +20,7 @@ test("can not create realm", () => {
|
||||||
|
|
||||||
test("getRealmAccess", () => {
|
test("getRealmAccess", () => {
|
||||||
const whoami = new WhoAmI("master", whoamiMock);
|
const whoami = new WhoAmI("master", whoamiMock);
|
||||||
expect(Object.keys(whoami.getRealmAccess()).length).toEqual(2);
|
expect(Object.keys(whoami.getRealmAccess()).length).toEqual(3);
|
||||||
expect(whoami.getRealmAccess()["master"].length).toEqual(18);
|
expect(whoami.getRealmAccess()["master"].length).toEqual(18);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
"locale": "en",
|
"locale": "en",
|
||||||
"createRealm": false,
|
"createRealm": false,
|
||||||
"realm_access": {
|
"realm_access": {
|
||||||
|
"test": [
|
||||||
|
"query-clients",
|
||||||
|
"view-clients"
|
||||||
|
],
|
||||||
"aaa": [
|
"aaa": [
|
||||||
"view-identity-providers",
|
"view-identity-providers",
|
||||||
"view-realm",
|
"view-realm",
|
||||||
|
|
|
@ -37,18 +37,18 @@ export const routes: RoutesFn = (t: TFunction) => [
|
||||||
breadcrumb: t("realm:createRealm"),
|
breadcrumb: t("realm:createRealm"),
|
||||||
access: "manage-realm",
|
access: "manage-realm",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/clients",
|
|
||||||
component: ClientsSection,
|
|
||||||
breadcrumb: t("clients:clientList"),
|
|
||||||
access: "query-clients",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/clients/:id",
|
path: "/clients/:id",
|
||||||
component: ClientSettings,
|
component: ClientSettings,
|
||||||
breadcrumb: t("clients:clientSettings"),
|
breadcrumb: t("clients:clientSettings"),
|
||||||
access: "view-clients",
|
access: "view-clients",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/clients",
|
||||||
|
component: ClientsSection,
|
||||||
|
breadcrumb: t("clients:clientList"),
|
||||||
|
access: "query-clients",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/add-client",
|
path: "/add-client",
|
||||||
component: NewClientForm,
|
component: NewClientForm,
|
||||||
|
@ -61,12 +61,6 @@ export const routes: RoutesFn = (t: TFunction) => [
|
||||||
breadcrumb: t("clients:importClient"),
|
breadcrumb: t("clients:importClient"),
|
||||||
access: "manage-clients",
|
access: "manage-clients",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/client-scopes",
|
|
||||||
component: ClientScopesSection,
|
|
||||||
breadcrumb: t("client-scopes:clientScopeList"),
|
|
||||||
access: "view-clients",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/client-scopes/new",
|
path: "/client-scopes/new",
|
||||||
component: ClientScopeForm,
|
component: ClientScopeForm,
|
||||||
|
@ -91,6 +85,12 @@ export const routes: RoutesFn = (t: TFunction) => [
|
||||||
breadcrumb: t("client-scopes:clientScopeDetails"),
|
breadcrumb: t("client-scopes:clientScopeDetails"),
|
||||||
access: "view-clients",
|
access: "view-clients",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/client-scopes",
|
||||||
|
component: ClientScopesSection,
|
||||||
|
breadcrumb: t("client-scopes:clientScopeList"),
|
||||||
|
access: "view-clients",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/roles",
|
path: "/roles",
|
||||||
component: RealmRolesSection,
|
component: RealmRolesSection,
|
||||||
|
|
|
@ -13365,6 +13365,11 @@ multicast-dns@^6.0.1:
|
||||||
dns-packet "^1.3.1"
|
dns-packet "^1.3.1"
|
||||||
thunky "^1.0.2"
|
thunky "^1.0.2"
|
||||||
|
|
||||||
|
mutationobserver-shim@^0.3.7:
|
||||||
|
version "0.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.7.tgz#8bf633b0c0b0291a1107255ed32c13088a8c5bf3"
|
||||||
|
integrity sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ==
|
||||||
|
|
||||||
mute-stream@0.0.8:
|
mute-stream@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||||
|
@ -15237,7 +15242,7 @@ prepend-http@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||||
|
|
||||||
prettier@2.1.2:
|
prettier@^2.0.5:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5"
|
||||||
integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==
|
integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==
|
||||||
|
|
Loading…
Reference in a new issue