diff --git a/ui/package.json b/ui/package.json
index 4a45f715a..3badfdea5 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -21,6 +21,7 @@
"query-string": "^7.0.0",
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
+ "react-bootstrap-typeahead": "^5.1.4",
"react-dom": "^17.0.2",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^5.2.0",
diff --git a/ui/public/assets/schema/source/metadata-source.json b/ui/public/assets/schema/source/metadata-source.json
index c06e299a6..d31a32d44 100644
--- a/ui/public/assets/schema/source/metadata-source.json
+++ b/ui/public/assets/schema/source/metadata-source.json
@@ -26,38 +26,7 @@
"default": false
},
"organization": {
- "type": "object",
- "properties": {
- "name": {
- "title": "label.organization-name",
- "description": "tooltip.organization-name",
- "type": "string"
- },
- "displayName": {
- "title": "label.organization-display-name",
- "description": "tooltip.organization-display-name",
- "type": "string"
- },
- "url": {
- "title": "label.organization-url",
- "description": "tooltip.organization-url",
- "type": "string"
- }
- },
- "dependencies": {
- "name": [
- "displayName",
- "url"
- ],
- "displayName": [
- "name",
- "url"
- ],
- "url": [
- "name",
- "displayName"
- ]
- }
+ "$ref": "#/definitions/Organization"
},
"contacts": {
"title": "label.contact-information",
@@ -68,73 +37,7 @@
}
},
"mdui": {
- "type": "object",
- "widget": {
- "id": "fieldset"
- },
- "fieldsets": [
- {
- "type": "group",
- "fields": [
- "displayName",
- "informationUrl",
- "description"
- ]
- },
- {
- "type": "group",
- "fields": [
- "privacyStatementUrl",
- "logoUrl",
- "logoWidth",
- "logoHeight"
- ]
- }
- ],
- "properties": {
- "displayName": {
- "title": "label.display-name",
- "description": "tooltip.mdui-display-name",
- "type": "string"
- },
- "informationUrl": {
- "title": "label.information-url",
- "description": "tooltip.mdui-information-url",
- "type": "string"
- },
- "privacyStatementUrl": {
- "title": "label.privacy-statement-url",
- "description": "tooltip.mdui-privacy-statement-url",
- "type": "string"
- },
- "description": {
- "title": "label.description",
- "description": "tooltip.mdui-description",
- "type": "string",
- "widget": {
- "id": "textarea"
- }
- },
- "logoUrl": {
- "title": "label.logo-url",
- "description": "tooltip.mdui-logo-url",
- "type": "string"
- },
- "logoHeight": {
- "title": "label.logo-height",
- "description": "tooltip.mdui-logo-height",
- "min": 0,
- "type": "integer",
- "default": 0
- },
- "logoWidth": {
- "title": "label.logo-width",
- "description": "tooltip.mdui-logo-width",
- "min": 0,
- "type": "integer",
- "default": 0
- }
- }
+ "$ref": "#/definitions/MDUI"
},
"securityInfo": {
"type": "object",
@@ -162,70 +65,28 @@
"title": "label.is-there-a-x509-certificate",
"description": "tooltip.is-there-a-x509-certificate",
"type": "boolean",
- "widget": {
- "id": "boolean-radio"
- },
- "oneOf": [
- {
- "enum": [
- true
- ],
- "description": "value.true"
- },
- {
- "enum": [
- false
- ],
- "description": "value.false"
- }
- ],
- "default": false
+ "enumNames": [
+ "value.true",
+ "value.false"
+ ]
},
"authenticationRequestsSigned": {
"title": "label.authentication-requests-signed",
"description": "tooltip.authentication-requests-signed",
"type": "boolean",
- "widget": {
- "id": "boolean-radio"
- },
- "oneOf": [
- {
- "enum": [
- true
- ],
- "description": "value.true"
- },
- {
- "enum": [
- false
- ],
- "description": "value.false"
- }
- ],
- "default": false
+ "enumNames": [
+ "value.true",
+ "value.false"
+ ]
},
"wantAssertionsSigned": {
"title": "label.want-assertions-signed",
"description": "tooltip.want-assertions-signed",
"type": "boolean",
- "widget": {
- "id": "boolean-radio"
- },
- "oneOf": [
- {
- "enum": [
- true
- ],
- "description": "value.true"
- },
- {
- "enum": [
- false
- ],
- "description": "value.false"
- }
- ],
- "default": false
+ "enumNames": [
+ "value.true",
+ "value.false"
+ ]
},
"x509Certificates": {
"title": "label.x509-certificates",
@@ -246,9 +107,6 @@
},
"serviceProviderSsoDescriptor": {
"type": "object",
- "widget": {
- "id": "fieldset"
- },
"fieldsets": [
{
"type": "group",
@@ -286,7 +144,7 @@
]
},
"nameIdFormats": {
- "$ref": "#/definitions/NameIdFormatList"
+ "$ref": "#/definitions/nameIdFormats"
}
}
},
@@ -331,45 +189,59 @@
"type": "boolean",
"default": false
},
- "forceAuthn": {
- "title": "label.force-authn",
- "description": "tooltip.force-authn",
- "type": "boolean",
- "default": false
- },
"omitNotBefore": {
"title": "label.omit-not-before-condition",
- "type": "boolean",
"description": "tooltip.omit-not-before-condition",
+ "type": "boolean",
"default": false
},
+ "responderId": {
+ "title": "label.responder-id",
+ "description": "tooltip.responder-id",
+ "type": "string",
+ "default": ""
+ },
"nameIdFormats": {
- "$ref": "#/definitions/NameIdFormatList"
+ "$ref": "#/definitions/nameIdFormats"
},
"authenticationMethods": {
- "$ref": "#/definitions/AuthenticationMethodList"
+ "$ref": "#/definitions/authenticationMethods"
},
- "responderId": {
- "title": "label.responder-id",
- "description": "tooltip.responder-id",
- "type": "string"
+ "forceAuthn": {
+ "title": "label.force-authn",
+ "description": "tooltip.force-authn",
+ "type": "boolean",
+ "default": false
}
}
},
"attributeRelease": {
"type": "array",
+ "title": "label.attribute-release",
"description": "Attribute release table - select the attributes you want to release (default unchecked)",
- "widget": {
- "id": "checklist",
- "dataUrl": "/customAttributes"
- },
"items": {
- "type": "string"
- }
+ "type": "string",
+ "enum": [
+ "eduPersonPrincipalName",
+ "uid",
+ "mail",
+ "surname",
+ "givenName",
+ "eduPersonAffiliation",
+ "eduPersonScopedAffiliation",
+ "eduPersonPrimaryAffiliation",
+ "eduPersonEntitlement",
+ "eduPersonAssurance",
+ "eduPersonUniqueId",
+ "employeeNumber"
+ ]
+ },
+ "uniqueItems": true
}
},
"definitions": {
"Contact": {
+ "title": "label.contact",
"type": "object",
"required": [
"name",
@@ -421,7 +293,7 @@
"title": "label.contact-email-address",
"description": "tooltip.contact-email",
"type": "string",
- "pattern": "^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$",
+ "pattern": "^(mailto:)?(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$",
"minLength": 1,
"maxLength": 255
}
@@ -431,7 +303,6 @@
"type": "object",
"title": "label.certificate",
"required": [
- "name",
"type",
"value"
],
@@ -440,7 +311,6 @@
"title": "label.certificate-name-display-only",
"description": "tooltip.certificate-name",
"type": "string",
- "minLength": 1,
"maxLength": 255
},
"type": {
@@ -470,8 +340,7 @@
],
"description": "value.both"
}
- ],
- "default": "both"
+ ]
},
"value": {
"title": "label.certificate",
@@ -485,6 +354,10 @@
"AssertionConsumerService": {
"type": "object",
"title": "label.assertion-consumer-service-endpoint",
+ "required": [
+ "locationUrl",
+ "binding"
+ ],
"properties": {
"locationUrl": {
"title": "label.assertion-consumer-service-location",
@@ -509,66 +382,45 @@
],
"description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
+ {
+ "enum": [
+ "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ ],
+ "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ },
+ {
+ "enum": [
+ "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+ ],
+ "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+ },
+ {
+ "enum": [
+ "urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
+ ],
+ "description": "urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
+ },
{
"enum": [
"urn:oasis:names:tc:SAML:1.0:profiles:browser-post"
],
"description": "urn:oasis:names:tc:SAML:1.0:profiles:browser-post"
+ },
+ {
+ "enum": [
+ "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01"
+ ],
+ "description": "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01"
}
]
},
"makeDefault": {
"title": "label.mark-as-default",
"description": "tooltip.mark-as-default",
- "type": "boolean",
- "default": false
+ "type": "boolean"
}
}
},
- "NameIdFormatList": {
- "title": "label.nameid-format-to-send",
- "placeholder": "label.nameid-format",
- "description": "tooltip.nameid-format",
- "type": "array",
- "uniqueItems": true,
- "items": {
- "type": "string",
- "minLength": 1,
- "maxLength": 255,
- "widget": {
- "id": "datalist",
- "data": [
- "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
- "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
- "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
- "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
- ]
- }
- },
- "default": null
- },
- "AuthenticationMethodList": {
- "title": "label.authentication-methods-to-use",
- "description": "tooltip.authentication-methods-to-use",
- "type": "array",
- "placeholder": "label.authentication-method",
- "uniqueItems": true,
- "items": {
- "type": "string",
- "title": "label.authentication-method",
- "minLength": 1,
- "maxLength": 255,
- "widget": {
- "id": "datalist",
- "data": [
- "https://refeds.org/profile/mfa",
- "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken",
- "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
- ]
- }
- },
- "default": null
- },
"LogoutEndpoint": {
"title": "label.new-endpoint",
"description": "tooltip.new-endpoint",
@@ -610,10 +462,181 @@
"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
],
"description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ {
+ "enum": [
+ "urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ ],
+ "description": "urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ },
+ {
+ "enum": [
+ "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+ ],
+ "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
}
]
}
}
+ },
+ "MDUI": {
+ "type": "object",
+ "widget": {
+ "id": "fieldset"
+ },
+ "fieldsets": [
+ {
+ "type": "group",
+ "fields": [
+ "displayName",
+ "informationUrl",
+ "description"
+ ]
+ },
+ {
+ "type": "group",
+ "fields": [
+ "privacyStatementUrl",
+ "logoUrl",
+ "logoWidth",
+ "logoHeight"
+ ]
+ }
+ ],
+ "properties": {
+ "displayName": {
+ "title": "label.display-name",
+ "description": "tooltip.mdui-display-name",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
+ },
+ "informationUrl": {
+ "title": "label.information-url",
+ "description": "tooltip.mdui-information-url",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
+ },
+ "privacyStatementUrl": {
+ "title": "label.privacy-statement-url",
+ "description": "tooltip.mdui-privacy-statement-url",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
+ },
+ "description": {
+ "title": "label.description",
+ "description": "tooltip.mdui-description",
+ "type": "string",
+ "widget": {
+ "id": "textarea"
+ },
+ "minLength": 1,
+ "maxLength": 255
+ },
+ "logoUrl": {
+ "title": "label.logo-url",
+ "description": "tooltip.mdui-logo-url",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
+ },
+ "logoHeight": {
+ "title": "label.logo-height",
+ "description": "tooltip.mdui-logo-height",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "logoWidth": {
+ "title": "label.logo-width",
+ "description": "tooltip.mdui-logo-width",
+ "minimum": 0,
+ "type": "integer"
+ }
+ }
+ },
+ "Organization": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "title": "label.organization-name",
+ "description": "tooltip.organization-name",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
+ },
+ "displayName": {
+ "title": "label.organization-display-name",
+ "description": "tooltip.organization-display-name",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
+ },
+ "url": {
+ "title": "label.organization-url",
+ "description": "tooltip.organization-url",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
+ }
+ },
+ "dependencies": {
+ "name": {
+ "required": [
+ "displayName",
+ "url"
+ ]
+ },
+ "displayName": {
+ "required": [
+ "name",
+ "url"
+ ]
+ },
+ "url": {
+ "required": [
+ "name",
+ "displayName"
+ ]
+ }
+ }
+ },
+ "nameIdFormats": {
+ "title": "label.nameid-format-to-send",
+ "description": "tooltip.nameid-format",
+ "type": "array",
+ "uniqueItems": true,
+ "additionalProperties": {
+ "type": "string"
+ },
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255,
+ "examples": [
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
+ "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
+ "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
+ ]
+ }
+ },
+ "authenticationMethods": {
+ "title": "label.authentication-methods-to-use",
+ "description": "tooltip.authentication-methods-to-use",
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255,
+ "examples": [
+ "https://refeds.org/profile/mfa",
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken",
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
+ ]
+ }
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/form/component/AddButton.js b/ui/src/app/form/component/AddButton.js
new file mode 100644
index 000000000..c5bb7714e
--- /dev/null
+++ b/ui/src/app/form/component/AddButton.js
@@ -0,0 +1,14 @@
+import React from "react";
+import { faPlus } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import Button from "react-bootstrap/Button";
+import Translate from "../../i18n/components/translate";
+
+const AddButton = ({className, ...props}) => (
+
+);
+export default AddButton;
\ No newline at end of file
diff --git a/ui/src/app/form/component/CustomFieldTemplate.js b/ui/src/app/form/component/CustomFieldTemplate.js
deleted file mode 100644
index 39a1a0202..000000000
--- a/ui/src/app/form/component/CustomFieldTemplate.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from "react";
-
-import Form from "react-bootstrap/Form";
-import ListGroup from "react-bootstrap/ListGroup";
-
-import {Translate} from '../../i18n/components/translate';
-
-export function CustomFieldTemplate ({
- id,
- children,
- displayLabel,
- rawErrors = [],
- rawHelp,
- rawDescription,
- ...props
-}) {
-
- console.log(props);
- return (
- <>{!props.hidden ?
- {children}
- {displayLabel && rawDescription ? (
- 0 ? "text-danger" : "text-muted"}>
-
-
- ) : null}
- {rawErrors.length > 0 && (
-
- {rawErrors.map((error) => {
- return (
-
-
- {error}
-
-
- );
- })}
-
- )}
- {rawHelp && (
- 0 ? "text-danger" : "text-muted"}
- id={id}>
-
-
- )}
-
- : <>>}>
- );
-};
-
-export default CustomFieldTemplate;
\ No newline at end of file
diff --git a/ui/src/app/form/component/IconButton.js b/ui/src/app/form/component/IconButton.js
new file mode 100644
index 000000000..4246d5df0
--- /dev/null
+++ b/ui/src/app/form/component/IconButton.js
@@ -0,0 +1,23 @@
+import React from "react";
+import Button from "react-bootstrap/Button";
+
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faArrowDown, faArrowUp, faTrash, faPlus } from "@fortawesome/free-solid-svg-icons";
+
+const mappings = {
+ remove: ,
+ plus: ,
+ "arrow-up": ,
+ "arrow-down": ,
+};
+
+const IconButton = (props) => {
+ const { icon, className, ...otherProps } = props;
+ return (
+
+ );
+};
+
+export default IconButton;
\ No newline at end of file
diff --git a/ui/src/app/form/component/InfoIcon.js b/ui/src/app/form/component/InfoIcon.js
new file mode 100644
index 000000000..da27ca22f
--- /dev/null
+++ b/ui/src/app/form/component/InfoIcon.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
+import Popover from 'react-bootstrap/Popover';
+import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
+import Translate from '../../i18n/components/translate';
+
+export function InfoIcon ({ value, ...props }) {
+ return(
+
+
+
+ )}>
+
+
+ );
+}
\ No newline at end of file
diff --git a/ui/src/app/form/component/fields/DescriptionField.js b/ui/src/app/form/component/fields/DescriptionField.js
new file mode 100644
index 000000000..aeea23ace
--- /dev/null
+++ b/ui/src/app/form/component/fields/DescriptionField.js
@@ -0,0 +1,12 @@
+import React from "react";
+import { InfoIcon } from "../InfoIcon";
+
+const DescriptionField = ({ description }) => {
+ if (description) {
+ return ;
+ }
+
+ return null;
+};
+
+export default DescriptionField;
\ No newline at end of file
diff --git a/ui/src/app/form/component/fields/TitleField.js b/ui/src/app/form/component/fields/TitleField.js
new file mode 100644
index 000000000..eecebb032
--- /dev/null
+++ b/ui/src/app/form/component/fields/TitleField.js
@@ -0,0 +1,8 @@
+import React from "react";
+import Translate from "../../../i18n/components/translate";
+
+const TitleField = ({ title }) => (
+
+);
+
+export default TitleField;
\ No newline at end of file
diff --git a/ui/src/app/form/component/index.js b/ui/src/app/form/component/index.js
index a8888fab1..a13d935a4 100644
--- a/ui/src/app/form/component/index.js
+++ b/ui/src/app/form/component/index.js
@@ -1,3 +1,38 @@
+import TextWidget from './widgets/TextWidget';
+import TextareaWidget from './widgets/TextareaWidget';
+import SelectWidget from './widgets/SelectWidget';
+import CheckboxWidget from './widgets/CheckboxWidget';
+import AttributeReleaseWidget from './widgets/AttributeReleaseWidget';
+import RadioWidget from './widgets/RadioWidget';
+import OptionWidget from './widgets/OptionWidget';
+import UpDownWidget from './widgets/UpDownWidget';
+
+import FieldTemplate from './templates/FieldTemplate';
+import ArrayFieldTemplate from './templates/ArrayFieldTemplate';
+import ObjectFieldTemplate from './templates/ObjectFieldTemplate';
+
+import TitleField from './fields/TitleField';
+import DescriptionField from './fields/DescriptionField';
+
export const fields = {
// SchemaField: CustomSchemaField
+ TitleField,
+ DescriptionField
+};
+
+export const templates = {
+ FieldTemplate,
+ ArrayFieldTemplate,
+ ObjectFieldTemplate
+}
+
+export const widgets = {
+ OptionWidget,
+ TextWidget,
+ TextareaWidget,
+ SelectWidget,
+ CheckboxWidget,
+ RadioWidget,
+ UpDownWidget,
+ AttributeReleaseWidget
};
\ No newline at end of file
diff --git a/ui/src/app/form/component/templates/ArrayFieldTemplate.js b/ui/src/app/form/component/templates/ArrayFieldTemplate.js
new file mode 100644
index 000000000..9029888a1
--- /dev/null
+++ b/ui/src/app/form/component/templates/ArrayFieldTemplate.js
@@ -0,0 +1,199 @@
+import React from "react";
+import { utils } from "@rjsf/core";
+import Row from "react-bootstrap/Row";
+import Col from "react-bootstrap/Col";
+import Container from "react-bootstrap/Container";
+
+import AddButton from "../AddButton";
+import IconButton from "../IconButton";
+
+const { isMultiSelect, getDefaultRegistry } = utils;
+
+const ArrayFieldTemplate = (props) => {
+ const { schema, registry = getDefaultRegistry() } = props;
+
+ // TODO: update types so we don't have to cast registry as any
+ if (isMultiSelect(schema, (registry).rootSchema)) {
+
+ return ;
+ } else {
+ return ;
+ }
+};
+
+
+const ArrayFieldTitle = ({
+ TitleField,
+ idSchema,
+ title,
+ required,
+}) => {
+ if (!title) {
+ return null;
+ }
+
+ const id = `${idSchema.$id}__title`;
+ return ;
+};
+
+
+const ArrayFieldDescription = ({
+ DescriptionField,
+ idSchema,
+ description,
+}) => {
+ if (!description) {
+ return null;
+ }
+
+ const id = `${idSchema.$id}__description`;
+ return ;
+};
+
+// Used in the two templates
+const DefaultArrayItem = (props) => {
+ const btnStyle = {
+ flex: 1,
+ paddingLeft: 6,
+ paddingRight: 6,
+ fontWeight: "bold",
+ };
+ return (
+
+
+ {props.children}
+
+
+ {props.hasToolbar && (
+
+ {(props.hasMoveUp || props.hasMoveDown) && (
+
+
+
+ )}
+
+ {(props.hasMoveUp || props.hasMoveDown) && (
+
+
+
+ )}
+
+ {props.hasRemove && (
+
+
+
+ )}
+
+ )}
+
+
+
+ );
+};
+
+const DefaultFixedArrayFieldTemplate = (props) => {
+ console.log(props)
+ return (
+
+ );
+};
+
+const DefaultNormalArrayFieldTemplate = (props) => {
+ return (
+
+
+
+
+
+ {props.canAdd && (
+
+ )}
+ {(props.uiSchema["ui:description"] || props.schema.description) && (
+
+ )}
+
+
+ {props.items && props.items.map(p => DefaultArrayItem(p))}
+
+
+
+
+
+ );
+};
+
+export default ArrayFieldTemplate;
\ No newline at end of file
diff --git a/ui/src/app/form/component/templates/FieldTemplate.js b/ui/src/app/form/component/templates/FieldTemplate.js
new file mode 100644
index 000000000..656bba72f
--- /dev/null
+++ b/ui/src/app/form/component/templates/FieldTemplate.js
@@ -0,0 +1,48 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+import ListGroup from "react-bootstrap/ListGroup";
+
+import {Translate} from '../../../i18n/components/translate';
+
+export function FieldTemplate ({
+ id,
+ label,
+ children,
+ displayLabel,
+ rawErrors = [],
+ rawHelp,
+ rawDescription,
+ ...props
+}) {
+ return (
+ <>{!props.hidden ?
+
+ {children}
+ {rawErrors.length > 0 && (
+
+ {rawErrors.map((error) => {
+ return (
+
+
+ {error}
+
+
+ );
+ })}
+
+ )}
+ {rawHelp && (
+ 0 ? "text-danger" : "text-muted"}
+ id={id}>
+
+
+ )}
+
+ : <>>
+ }>
+ );
+};
+
+export default FieldTemplate;
\ No newline at end of file
diff --git a/ui/src/app/form/component/templates/ObjectFieldTemplate.js b/ui/src/app/form/component/templates/ObjectFieldTemplate.js
new file mode 100644
index 000000000..87bb170de
--- /dev/null
+++ b/ui/src/app/form/component/templates/ObjectFieldTemplate.js
@@ -0,0 +1,49 @@
+import React from "react";
+
+import Container from "react-bootstrap/Container";
+import Row from "react-bootstrap/Row";
+import Col from "react-bootstrap/Col";
+
+const ObjectFieldTemplate = ({
+ DescriptionField,
+ description,
+ TitleField,
+ title,
+ properties,
+ required,
+ uiSchema,
+ idSchema,
+ schema,
+ hidden
+}) => {
+ return (
+ <>
+ {!hidden &&
+ <>
+ {(uiSchema["ui:title"] || (title && schema.title)) && (
+
+ )}
+ {description && (
+
+ )}
+
+ {properties.map((element, index) => (
+
+ {element.content}
+
+ ))}
+
+ >
+ }
+ >
+ );
+};
+
+export default ObjectFieldTemplate;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/AttributeReleaseWidget.js b/ui/src/app/form/component/widgets/AttributeReleaseWidget.js
new file mode 100644
index 000000000..c547891d8
--- /dev/null
+++ b/ui/src/app/form/component/widgets/AttributeReleaseWidget.js
@@ -0,0 +1,133 @@
+import React from "react";
+import Form from "react-bootstrap/Form";
+import Translate from "../../../i18n/components/translate";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
+
+const selectValue = (value, selected, all) => {
+ const at = all.indexOf(value);
+ const updated = selected.slice(0, at).concat(value, selected.slice(at));
+
+ // As inserting values at predefined index positions doesn't work with empty
+ // arrays, we need to reorder the updated selection to match the initial order
+ return updated.sort((a, b) => all.indexOf(a) > all.indexOf(b));
+};
+
+const deselectValue = (value, selected) => {
+ return selected.filter((v) => v !== value);
+};
+
+const AttributeReleaseWidget = ({
+ schema,
+ label,
+ id,
+ disabled,
+ options,
+ value,
+ autofocus,
+ readonly,
+ required,
+ onChange,
+ onBlur,
+ onFocus,
+}) => {
+ const { enumOptions, enumDisabled } = options;
+
+ const _onChange = (option) => ({
+ target: { checked },
+ }) => {
+ const all = (enumOptions).map(({ value }) => value);
+
+ if (checked) {
+ console.log(selectValue(option.value, value, all))
+ onChange(selectValue(option.value, value, all));
+ } else {
+ onChange(deselectValue(option.value, value));
+ }
+ };
+
+ const _onBlur = ({ target: { value } }) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }) => onFocus(id, value);
+
+ const onCheckAll = () => {
+ const all = (enumOptions).map(({ value }) => value);
+ let update = [];
+ enumOptions.forEach(v => update = selectValue(v.value, update, all));
+
+ onChange(update);
+ }
+ const onClearAll = () => {
+ onChange([]);
+ }
+
+ return (
+
+ );
+};
+
+/*
+*/
+
+export default AttributeReleaseWidget;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/CheckboxWidget.js b/ui/src/app/form/component/widgets/CheckboxWidget.js
new file mode 100644
index 000000000..4e02c38c1
--- /dev/null
+++ b/ui/src/app/form/component/widgets/CheckboxWidget.js
@@ -0,0 +1,57 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+import Translate from "../../../i18n/components/translate";
+import { InfoIcon } from "../InfoIcon";
+
+const CheckboxWidget = (props) => {
+ const {
+ id,
+ value,
+ required,
+ disabled,
+ readonly,
+ label,
+ schema,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus,
+ } = props;
+
+ const _onChange = ({
+ target: { checked },
+ }) => onChange(checked);
+ const _onBlur = ({
+ target: { checked },
+ }) => onBlur(id, checked);
+ const _onFocus = ({
+ target: { checked },
+ }) => onFocus(id, checked);
+
+ const desc = label || schema.description;
+ return (
+
+
+
+
+ {(label || schema.title) && required ? * : null}
+
+ {schema.description && }
+ }
+ checked={typeof value === "undefined" ? false : value}
+ required={required}
+ disabled={disabled || readonly}
+ autoFocus={autofocus}
+ onChange={_onChange}
+ type="checkbox"
+ onBlur={_onBlur}
+ onFocus={_onFocus}
+ />
+
+ );
+};
+
+export default CheckboxWidget;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/OptionWidget.js b/ui/src/app/form/component/widgets/OptionWidget.js
new file mode 100644
index 000000000..a6a935043
--- /dev/null
+++ b/ui/src/app/form/component/widgets/OptionWidget.js
@@ -0,0 +1,106 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+import Translate from "../../../i18n/components/translate";
+import { InfoIcon } from "../InfoIcon";
+
+import { Typeahead } from 'react-bootstrap-typeahead';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faArrowDown, faArrowUp } from "@fortawesome/free-solid-svg-icons";
+
+const ToggleButton = ({ isOpen, onClick }) => (
+
+);
+
+const OptionWidget = ({
+ id,
+ placeholder,
+ required,
+ readonly,
+ disabled,
+ type,
+ label,
+ value,
+ onChange,
+ onBlur,
+ onFocus,
+ autofocus,
+ options,
+ schema,
+ rawErrors = [],
+ uiSchema,
+ ...props
+}) => {
+ const _onChange = (selected) => onChange(selected[0] === '' ? options.emptyValue : selected[0]);
+ const _onBlur = ({ target: { value } }) => onBlur(id, value);
+ const _onFocus = ({ target: { value } }) => onFocus(id, value);
+ const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`;
+
+ const opts = Array.isArray(options) || options.enumOptions ? options : schema.examples ? schema.examples : [];
+
+ console.log(opts);
+
+ return (
+
+ 0 ? "text-danger" : ""}`}>
+
+
+ {(label || schema.title) && required ? * : null}
+
+ {schema.description && }
+
+ 0 ? "is-invalid" : ""}`}
+ options={opts}
+ placeholder={uiSchema.placeholder ? uiSchema.placeholder : ''}
+ disabled={disabled || readonly}
+ onChange={_onChange}
+ onBlur={_onBlur}
+ onFocus={_onFocus}
+ filterBy={(option, props) => true}
+ renderMenuItemChildren={(option, {options, text}, index) => {
+ return {option};
+ }}>
+ {({ isMenuShown, toggleMenu }) => (
+ toggleMenu()} />
+ )}
+
+
+ {/**/}
+ {schema.examples ? (
+
+ ) : null}
+
+ );
+};
+
+export default OptionWidget;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/RadioWidget.js b/ui/src/app/form/component/widgets/RadioWidget.js
new file mode 100644
index 000000000..f533b704e
--- /dev/null
+++ b/ui/src/app/form/component/widgets/RadioWidget.js
@@ -0,0 +1,73 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+import Translate from "../../../i18n/components/translate";
+import { InfoIcon } from "../InfoIcon";
+
+
+const RadioWidget = ({
+ id,
+ schema,
+ options,
+ value,
+ required,
+ disabled,
+ readonly,
+ label,
+ onChange,
+ onBlur,
+ onFocus,
+}) => {
+ const { enumOptions, enumDisabled } = options;
+
+ const _onChange = ({
+ target: { value },
+ }) =>
+ onChange(schema.type === "boolean" ? value !== "false" : value);
+ const _onBlur = ({ target: { value } }) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }) => onFocus(id, value);
+
+ const inline = Boolean(options && options.inline);
+
+ return (
+
+
+
+
+ {(label || schema.title) && required ? * : null}
+
+ {schema.description && }
+
+ {(enumOptions).map((option, i) => {
+ const itemDisabled =
+ Array.isArray(enumDisabled) &&
+ enumDisabled.indexOf(option.value) !== -1;
+ const checked = option.value === value;
+
+ const radio = (
+ }
+ id={option.label}
+ key={i}
+ name={id}
+ type="radio"
+ disabled={disabled || itemDisabled || readonly}
+ checked={checked}
+ required={required}
+ value={option.value}
+ onChange={_onChange}
+ onBlur={_onBlur}
+ onFocus={_onFocus}
+ />
+ );
+ return radio;
+ })}
+
+ );
+};
+
+export default RadioWidget;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/SelectWidget.js b/ui/src/app/form/component/widgets/SelectWidget.js
new file mode 100644
index 000000000..bf8a525f8
--- /dev/null
+++ b/ui/src/app/form/component/widgets/SelectWidget.js
@@ -0,0 +1,135 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import { utils } from "@rjsf/core";
+
+import Translate from "../../../i18n/components/translate";
+import { InfoIcon } from "../InfoIcon";
+
+const { asNumber, guessType } = utils;
+
+const nums = new Set(["number", "integer"]);
+
+/**
+ * This is a silly limitation in the DOM where option change event values are
+ * always retrieved as strings.
+ */
+const processValue = (schema, value) => {
+ // "enum" is a reserved word, so only "type" and "items" can be destructured
+ const { type, items } = schema;
+ if (value === "") {
+ return undefined;
+ } else if (type === "array" && items && nums.has(items.type)) {
+ return value.map(asNumber);
+ } else if (type === "boolean") {
+ return value === "true";
+ } else if (type === "number") {
+ return asNumber(value);
+ }
+
+ // If type is undefined, but an enum is present, try and infer the type from
+ // the enum values
+ if (schema.enum) {
+ if (schema.enum.every((x) => guessType(x) === "number")) {
+ return asNumber(value);
+ } else if (schema.enum.every((x) => guessType(x) === "boolean")) {
+ return value === "true";
+ }
+ }
+
+ return value;
+};
+
+const SelectWidget = ({
+ schema,
+ id,
+ options,
+ label,
+ required,
+ disabled,
+ readonly,
+ value,
+ multiple,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus,
+ placeholder,
+ rawErrors = [],
+}) => {
+ const { enumOptions, enumDisabled } = options;
+
+ const emptyValue = multiple ? [] : "";
+
+ function getValue(
+ event,
+ multiple
+ ) {
+ if (multiple) {
+ return [].slice
+ .call(event.target.options)
+ .filter((o) => o.selected)
+ .map((o) => o.value);
+ } else {
+ return event.target.value;
+ }
+ }
+
+ return (
+
+ 0 ? "text-danger" : ""}`}>
+
+
+ {(label || schema.title) && required ? * : null}
+
+ {schema.description && }
+
+ 0 ? "is-invalid" : ""}
+ onBlur={
+ onBlur &&
+ ((event) => {
+ const newValue = getValue(event, multiple);
+ onBlur(id, processValue(schema, newValue));
+ })
+ }
+ onFocus={
+ onFocus &&
+ ((event) => {
+ const newValue = getValue(event, multiple);
+ onFocus(id, processValue(schema, newValue));
+ })
+ }
+ onChange={(event) => {
+ const newValue = getValue(event, multiple);
+ onChange(processValue(schema, newValue));
+ }}>
+ {!multiple && schema.default === undefined && (
+
+ )}
+ {(enumOptions).map(({ value, label }, i) => {
+ const disabled =
+ Array.isArray(enumDisabled) &&
+ (enumDisabled).indexOf(value) != -1;
+ return (
+
+ );
+ })}
+
+
+ );
+};
+
+export default SelectWidget;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/TextWidget.js b/ui/src/app/form/component/widgets/TextWidget.js
new file mode 100644
index 000000000..5160a00b8
--- /dev/null
+++ b/ui/src/app/form/component/widgets/TextWidget.js
@@ -0,0 +1,69 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+import Translate from "../../../i18n/components/translate";
+import { InfoIcon } from "../InfoIcon";
+
+const TextWidget = ({
+ id,
+ placeholder,
+ required,
+ readonly,
+ disabled,
+ type,
+ label,
+ value,
+ onChange,
+ onBlur,
+ onFocus,
+ autofocus,
+ options,
+ schema,
+ rawErrors = [],
+ ...props
+}) => {
+ const _onChange = ({target: { value }}) => onChange(value === "" ? options.emptyValue : value);
+ const _onBlur = ({ target: { value } }) => onBlur(id, value);
+ const _onFocus = ({target: { value }} ) => onFocus(id, value);
+ const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`;
+
+
+ // const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""]
+ return (
+
+ 0 ? "text-danger" : ""}`}>
+
+
+ {(label || schema.title) && required ? * : null}
+
+ {schema.description && }
+
+ 0 ? "is-invalid" : ""}
+ list={schema.examples ? `examples_${id}` : undefined}
+ type={inputType}
+ value={value || value === 0 ? value : ""}
+ onChange={_onChange}
+ onBlur={_onBlur}
+ onFocus={_onFocus}
+ />
+ {schema.examples ? (
+
+ ) : null}
+
+ );
+};
+
+export default TextWidget;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/TextareaWidget.js b/ui/src/app/form/component/widgets/TextareaWidget.js
new file mode 100644
index 000000000..e4829b4a5
--- /dev/null
+++ b/ui/src/app/form/component/widgets/TextareaWidget.js
@@ -0,0 +1,66 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+import FormControl from "react-bootstrap/FormControl";
+import InputGroup from "react-bootstrap/InputGroup";
+
+import Translate from "../../../i18n/components/translate";
+import { InfoIcon } from "../InfoIcon";
+
+const TextareaWidget = ({
+ id,
+ placeholder,
+ value,
+ required,
+ disabled,
+ autofocus,
+ label,
+ readonly,
+ onBlur,
+ onFocus,
+ onChange,
+ options,
+ schema,
+ rawErrors = [],
+}) => {
+ const _onChange = ({
+ target: { value },
+ }) =>
+ onChange(value === "" ? options.emptyValue : value);
+ const _onBlur = ({
+ target: { value },
+ }) => onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }) => onFocus(id, value);
+
+ return (
+ <>
+ 0 ? "text-danger" : ""}`}>
+
+
+ {(label || schema.title) && required ? * : null}
+
+ {schema.description && }
+
+
+
+
+ >
+ );
+};
+
+export default TextareaWidget;
\ No newline at end of file
diff --git a/ui/src/app/form/component/widgets/UpDownWidget.js b/ui/src/app/form/component/widgets/UpDownWidget.js
new file mode 100644
index 000000000..ca50b659e
--- /dev/null
+++ b/ui/src/app/form/component/widgets/UpDownWidget.js
@@ -0,0 +1,55 @@
+import React from "react";
+
+import Form from "react-bootstrap/Form";
+
+import Translate from "../../../i18n/components/translate";
+import { InfoIcon } from "../InfoIcon";
+
+const UpDownWidget = ({
+ id,
+ required,
+ readonly,
+ disabled,
+ label,
+ value,
+ onChange,
+ onBlur,
+ onFocus,
+ autofocus,
+ schema
+}) => {
+ const _onChange = ({
+ target: { value },
+ }) => onChange(value);
+ const _onBlur = ({ target: { value } }) =>
+ onBlur(id, value);
+ const _onFocus = ({
+ target: { value },
+ }) => onFocus(id, value);
+
+ return (
+
+
+
+
+ {(label || schema.title) && required ? * : null}
+
+ {schema.description && }
+
+
+
+ );
+};
+
+export default UpDownWidget;
\ No newline at end of file
diff --git a/ui/src/app/metadata/domain/data.js b/ui/src/app/metadata/domain/data.js
new file mode 100644
index 000000000..45412d300
--- /dev/null
+++ b/ui/src/app/metadata/domain/data.js
@@ -0,0 +1,12 @@
+export const DurationOptions = [
+ "PT0S",
+ "PT30S",
+ "PT1M",
+ "PT10M",
+ "PT30M",
+ "PT1H",
+ "PT4H",
+ "PT8H",
+ "PT12H",
+ "PT24H"
+];
\ No newline at end of file
diff --git a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js
index 082ab3203..57176a5c6 100644
--- a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js
+++ b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js
@@ -23,4 +23,7 @@ export const BaseProviderDefinition = {
}), {})
}
}) : changes),
+ uiSchema: {
+
+ }
}
\ No newline at end of file
diff --git a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js
index 0595d0bad..e61cfb184 100644
--- a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js
+++ b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js
@@ -1,4 +1,7 @@
+import defaultsDeep from 'lodash/defaultsDeep';
+
import API_BASE_PATH from "../../../App.constant";
+import {DurationOptions} from '../data';
import { BaseProviderDefinition } from "./BaseProviderDefinition";
export const LocalDynamicMetadataProviderWizard = {
@@ -6,6 +9,14 @@ export const LocalDynamicMetadataProviderWizard = {
label: 'LocalDynamicMetadataProvider',
type: 'LocalDynamicMetadataResolver',
schema: `${API_BASE_PATH}/ui/MetadataResolver/LocalDynamicMetadataResolver`,
+ uiSchema: {
+ dynamicMetadataResolverAttributes: {
+ minCacheDuration: {
+ 'ui:widget': 'OptionWidget',
+ options: DurationOptions
+ }
+ }
+ },
steps: [
{
id: 'common',
@@ -69,6 +80,11 @@ export const LocalDynamicMetadataProviderWizard = {
export const LocalDynamicMetadataProviderEditor = {
...LocalDynamicMetadataProviderWizard,
+ uiSchema: defaultsDeep({
+ '@type': {
+ 'ui:disabled': true
+ }
+ }, LocalDynamicMetadataProviderWizard.uiSchema),
steps: [
{
id: 'common',
diff --git a/ui/src/app/metadata/domain/source/SourceDefinition.js b/ui/src/app/metadata/domain/source/SourceDefinition.js
index f838317ac..d8b02f08a 100644
--- a/ui/src/app/metadata/domain/source/SourceDefinition.js
+++ b/ui/src/app/metadata/domain/source/SourceDefinition.js
@@ -1,10 +1,12 @@
-import API_BASE_PATH from "../../../App.constant";
+import defaultsDeep from 'lodash/defaultsDeep';
+import API_BASE_PATH from '../../../App.constant';
export const SourceBase = {
label: 'Metadata Source',
type: '@MetadataProvider',
steps: [],
- schema: `${API_BASE_PATH}/ui/MetadataSources`,
+ schema: `/assets/schema/source/metadata-source.json`,
+ //${API_BASE_PATH}/ui/MetadataSources
validatorParams: [/*getAllOtherIds*/],
bindings: {
@@ -117,8 +119,49 @@ export const SourceBase = {
return validators;
},
uiSchema: {
+ attributeRelease: {
+ 'ui:widget': 'AttributeReleaseWidget'
+ },
+ relyingPartyOverrides: {
+ nameIdFormats: {
+ items: {
+ 'ui:widget': 'OptionWidget'
+ }
+ },
+ authenticationMethods: {
+ items: {
+ 'ui:widget': 'OptionWidget'
+ }
+ }
+ },
+ securityInfo: {
+ x509CertificateAvailable: {
+ 'ui:widget': 'radio'
+ },
+ authenticationRequestsSigned: {
+ 'ui:widget': 'radio'
+ },
+ wantAssertionsSigned: {
+ 'ui:widget': 'radio'
+ },
+ x509Certificates: {
+ items: {
+ type: {
+ 'ui:widget': 'radio'
+ },
+ value: {
+ 'ui:widget': 'textarea'
+ }
+ }
+ }
+ },
mdui: {
- 'ui:widget': 'hidden'
+ logoHeight: {
+ 'ui:widget': 'updown'
+ },
+ logoWidth: {
+ 'ui:widget': 'updown'
+ }
}
}
}
@@ -126,7 +169,7 @@ export const SourceBase = {
export const SourceEditor = {
...SourceBase,
- schema: `${API_BASE_PATH}/ui/MetadataSources`,
+ uiSchema: defaultsDeep({}, SourceBase.uiSchema),
steps: [
{
index: 1,
diff --git a/ui/src/app/metadata/editor/MetadataEditorForm.js b/ui/src/app/metadata/editor/MetadataEditorForm.js
index 12e6e5e64..1d0d9adc5 100644
--- a/ui/src/app/metadata/editor/MetadataEditorForm.js
+++ b/ui/src/app/metadata/editor/MetadataEditorForm.js
@@ -2,8 +2,8 @@ import React from 'react';
import Form from '@rjsf/bootstrap-4';
-import { fields } from '../../form/component';
-import { CustomFieldTemplate } from '../../form/component/CustomFieldTemplate';
+import { fields, widgets } from '../../form/component';
+import { templates } from '../../form/component';
import { useUiSchema } from '../hooks/schema';
export function MetadataEditorForm ({ metadata, definition, schema, current }) {
@@ -14,14 +14,23 @@ export function MetadataEditorForm ({ metadata, definition, schema, current }) {
React.useEffect(() => setData(metadata), [metadata]);
+ const onSubmit = () => {};
+
return (
<>
+ FieldTemplate={templates.FieldTemplate}
+ ObjectFieldTemplate={templates.ObjectFieldTemplate}
+ ArrayFieldTemplate={templates.ArrayFieldTemplate}
+ fields={ fields }
+ widgets={widgets}>
+ <>>
+
{JSON.stringify(data, null, 4)}
>
);
diff --git a/ui/src/app/metadata/hooks/schema.js b/ui/src/app/metadata/hooks/schema.js
index f7b3ee2c3..263bc8bbe 100644
--- a/ui/src/app/metadata/hooks/schema.js
+++ b/ui/src/app/metadata/hooks/schema.js
@@ -1,8 +1,5 @@
import React from 'react';
-import assignInWith from 'lodash/assignInWith';
-import { I18nContext } from '../../i18n/context/I18n.provider';
-
const fillInRootProperties = (keys, ui) => {
return keys.reduce((sch, key, idx) => {
if (!sch.hasOwnProperty(key)) {
diff --git a/ui/src/theme/project/forms.scss b/ui/src/theme/project/forms.scss
index 2b2ec2ab9..4b9c67712 100644
--- a/ui/src/theme/project/forms.scss
+++ b/ui/src/theme/project/forms.scss
@@ -87,6 +87,20 @@ select.form-control:disabled {
}
}
+.toggle-typeahead {
+ padding-right: 36px;
+}
+
+.toggle-button {
+ bottom: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+ background: white;
+}
+
@media only screen and (max-width: 1200px) {
.form-section:not(:first-child) {
border-left: 0px;
diff --git a/ui/yarn.lock b/ui/yarn.lock
index c008997fc..bbc9aa851 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -1113,7 +1113,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.13.8", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.13.8", "@babel/runtime@^7.3.4", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
@@ -1257,6 +1257,14 @@
dependencies:
"@hapi/hoek" "^8.3.0"
+"@hypnosphi/create-react-context@^0.3.1":
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz#f8bfebdc7665f5d426cba3753e0e9c7d3154d7c6"
+ integrity sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==
+ dependencies:
+ gud "^1.0.0"
+ warning "^4.0.3"
+
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1485,7 +1493,7 @@
schema-utils "^2.6.5"
source-map "^0.7.3"
-"@popperjs/core@^2.8.6":
+"@popperjs/core@^2.5.3", "@popperjs/core@^2.8.6":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
@@ -1495,7 +1503,7 @@
resolved "https://registry.yarnpkg.com/@restart/context/-/context-2.1.4.tgz#a99d87c299a34c28bd85bb489cb07bfd23149c02"
integrity sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==
-"@restart/hooks@^0.3.26":
+"@restart/hooks@^0.3.22", "@restart/hooks@^0.3.25", "@restart/hooks@^0.3.26":
version "0.3.26"
resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.3.26.tgz#ade155a7b0b014ef1073391dda46972c3a14a129"
integrity sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g==
@@ -3359,7 +3367,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
-classnames@*, classnames@^2.2.6:
+classnames@*, classnames@^2.2.0, classnames@^2.2.6:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
@@ -3544,6 +3552,11 @@ compute-lcm@^1.1.0:
validate.io-function "^1.0.2"
validate.io-integer-array "^1.0.0"
+compute-scroll-into-view@^1.0.17:
+ version "1.0.17"
+ resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
+ integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==
+
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -4078,7 +4091,7 @@ dedent@^0.7.0:
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
-deep-equal@^1.0.1:
+deep-equal@^1.0.1, deep-equal@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
@@ -5485,6 +5498,11 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
+gud@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
+ integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==
+
gzip-size@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
@@ -5964,7 +5982,7 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
-invariant@^2.2.4:
+invariant@^2.2.1, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
@@ -7154,6 +7172,11 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
+lodash.debounce@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+ integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -8281,6 +8304,11 @@ pnp-webpack-plugin@1.6.4:
dependencies:
ts-pnp "^1.1.6"
+popper.js@^1.14.4:
+ version "1.16.1"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
+ integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
+
portfinder@^1.0.26:
version "1.0.28"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
@@ -9047,7 +9075,7 @@ prop-types-extra@^1.1.0:
react-is "^16.3.2"
warning "^4.0.0"
-prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -9245,6 +9273,23 @@ react-app-polyfill@^2.0.0:
regenerator-runtime "^0.13.7"
whatwg-fetch "^3.4.1"
+react-bootstrap-typeahead@^5.1.4:
+ version "5.1.4"
+ resolved "https://registry.yarnpkg.com/react-bootstrap-typeahead/-/react-bootstrap-typeahead-5.1.4.tgz#856d30438bb7a254cfeafa3ec03ddf90285f7a4d"
+ integrity sha512-CfoaSjGV3MZS9yrkLfYkvimzRYru1Ko7qKKzacbsBeZ/sHCxFvUhxnCXmTy/K2vZCkOmAQpU/sbgQVOkMRNlSQ==
+ dependencies:
+ "@babel/runtime" "^7.3.4"
+ "@restart/hooks" "^0.3.22"
+ classnames "^2.2.0"
+ fast-deep-equal "^3.1.1"
+ invariant "^2.2.1"
+ lodash.debounce "^4.0.8"
+ prop-types "^15.5.8"
+ react-overlays "^4.1.1"
+ react-popper "^1.0.0"
+ scroll-into-view-if-needed "^2.2.20"
+ warning "^4.0.1"
+
react-bootstrap@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-1.5.2.tgz#07dabec53d10491a520c49f102170b440fa89008"
@@ -9342,6 +9387,20 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+react-overlays@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.1.1.tgz#0060107cbe1c5171a744ccda3fbf0556d064bc5f"
+ integrity sha512-WtJifh081e6M24KnvTQoNjQEpz7HoLxqt8TwZM7LOYIkYJ8i/Ly1Xi7RVte87ZVnmqQ4PFaFiNHZhSINPSpdBQ==
+ dependencies:
+ "@babel/runtime" "^7.12.1"
+ "@popperjs/core" "^2.5.3"
+ "@restart/hooks" "^0.3.25"
+ "@types/warning" "^3.0.0"
+ dom-helpers "^5.2.0"
+ prop-types "^15.7.2"
+ uncontrollable "^7.0.0"
+ warning "^4.0.3"
+
react-overlays@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.0.1.tgz#7e2c3cd3c0538048b0b7451d203b1289c561b7f2"
@@ -9356,6 +9415,19 @@ react-overlays@^5.0.0:
uncontrollable "^7.2.1"
warning "^4.0.3"
+react-popper@^1.0.0:
+ version "1.3.11"
+ resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.11.tgz#a2cc3f0a67b75b66cfa62d2c409f9dd1fcc71ffd"
+ integrity sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+ "@hypnosphi/create-react-context" "^0.3.1"
+ deep-equal "^1.1.1"
+ popper.js "^1.14.4"
+ prop-types "^15.6.1"
+ typed-styles "^0.0.7"
+ warning "^4.0.2"
+
react-refresh@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@@ -10035,6 +10107,13 @@ schema-utils@^3.0.0:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
+scroll-into-view-if-needed@^2.2.20:
+ version "2.2.28"
+ resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz#5a15b2f58a52642c88c8eca584644e01703d645a"
+ integrity sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==
+ dependencies:
+ compute-scroll-into-view "^1.0.17"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -11103,6 +11182,11 @@ type@^2.0.0:
resolved "https://registry.yarnpkg.com/type/-/type-2.3.0.tgz#ada7c045f07ead08abf9e2edd29be1a0c0661132"
integrity sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg==
+typed-styles@^0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9"
+ integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==
+
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
@@ -11115,7 +11199,7 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-uncontrollable@^7.2.1:
+uncontrollable@^7.0.0, uncontrollable@^7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738"
integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==
@@ -11454,7 +11538,7 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
-warning@^4.0.0, warning@^4.0.3:
+warning@^4.0.0, warning@^4.0.1, warning@^4.0.2, warning@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==