11import "./PolicyForm.scss" ;
2- import React , { useState } from "react" ;
3- import { Button , ButtonType , Chip , ChipType , SegmentedControl , Tooltip } from "@surfnet/sds" ;
2+ import React , { Fragment , useState } from "react" ;
3+ import { Button , ButtonType , Chip , ChipType } from "@surfnet/sds" ;
44import I18n from "../locale/I18n.js" ;
55import InputField from "../components/InputField.jsx" ;
66import { useAppStore } from "../stores/AppStore.js" ;
@@ -29,6 +29,11 @@ export const PolicyForm = ({
2929 const [ confirmation , setConfirmation ] = useState ( { } ) ;
3030 const [ attributeValueErrors , setAttributeValueErrors ] = useState ( { } ) ;
3131
32+ const accessOptions = [ "allow" , "deny" ] . map ( name => ( { value : name , label : I18n . t ( `policies.form.${ name } ` ) } ) ) ;
33+ const conditionalOptions = [ "any" , "all" ] . map ( name => ( { value : name , label : I18n . t ( `policies.form.${ name } ` ) } ) ) ;
34+ const negatedOptions = [ "any" , "none" ] . map ( name => ( { value : name , label : I18n . t ( `policies.form.${ name } ` ) } ) ) ;
35+ const orOptions = [ "and" , "or" ] . map ( name => ( { value : name , label : I18n . t ( `policies.form.${ name } ` ) } ) ) ;
36+
3237 const required = [ "name" , "denyAdvice" , "denyAdviceNl" ] ;
3338
3439 const { setFlash, allowedAttributes} = useAppStore ( useShallow ( state => ( {
@@ -42,7 +47,8 @@ export const PolicyForm = ({
4247
4348 const isValid = ( ) => {
4449 const allAttributesValuesValid = Object . values ( attributeValueErrors ) . every ( values => isEmpty ( values ) ) ;
45- return required . every ( attr => ! isEmpty ( policy . data [ attr ] ) ) && ! duplicatePolicyName && allAttributesValuesValid ;
50+ const hasAttributes = policy . data . attributes . filter ( attr => ! isEmpty ( attr . name ) && ! isEmpty ( attr . value ) ) . length > 0 ;
51+ return required . every ( attr => ! isEmpty ( policy . data [ attr ] ) ) && ! duplicatePolicyName && allAttributesValuesValid && hasAttributes ;
4652 }
4753
4854 const doDeletePolicy = ( confirmationRequired , policy ) => {
@@ -68,6 +74,7 @@ export const PolicyForm = ({
6874
6975 const submit = ( ) => {
7076 setInitial ( false ) ;
77+
7178 if ( isValid ( ) ) {
7279 const promise = isExistingPolicy ? updatePolicy : newPolicy ;
7380 //We need to destructure the attributes with multiple values, to single attribute / value pairs
@@ -103,11 +110,13 @@ export const PolicyForm = ({
103110 }
104111
105112 const attributeDeleted = index => {
106- const newAttributes = policy . data . attributes . filter ( ( item , i ) => i !== index ) ;
107- internalUpdatePolicy ( { attributes : defaultAttributes ( newAttributes ) } ) ;
108113 const deletedAttribute = policy . data . attributes [ index ] ;
109114 delete attributeValueErrors [ deletedAttribute . name ] ;
110115 setAttributeValueErrors ( { ...attributeValueErrors } ) ;
116+
117+ const filteredAttributes = policy . data . attributes . filter ( ( item , i ) => i !== index ) ;
118+ const newAttributes = defaultAttributes ( filteredAttributes ) ;
119+ internalUpdatePolicy ( { attributes : newAttributes } ) ;
111120 }
112121
113122 const attributeValueChanged = ( values , index ) => {
@@ -159,7 +168,7 @@ export const PolicyForm = ({
159168 /> }
160169 < div className = "policy-form-header" >
161170 < div className = "header-top" >
162- < h2 > { I18n . t ( `appAccess.${ isExistingPolicy ? " editPolicy" : "newPolicy" } ` ) } </ h2 >
171+ < h2 > { I18n . t ( `appAccess.${ isExistingPolicy ? ( policy . data . type === "reg" ? " editPolicy" : "editStepUpPolicy" ) : ( policy . data . type === "reg" ? " newPolicy" : "newStepUpPolicy" ) } ` ) } </ h2 >
163172 { isExistingPolicy &&
164173 < div className = "policy-header-actions" >
165174 < Chip type = { ChipType . Status_info }
@@ -184,64 +193,72 @@ export const PolicyForm = ({
184193 < ErrorIndicator adjustMargin = { true }
185194 msg = { I18n . t ( "appAccess.duplicateName" , { name : policy . data . name } ) } /> }
186195
187- < SelectField
188- value = { serviceProviderOptions . filter ( option => policy . data . serviceProviderIds . some ( sp => sp . name === option . value ) ) }
189- searchable = { true }
190- name = { I18n . t ( "policies.serviceProviders" ) }
191- options = { serviceProviderOptions }
192- placeholder = { I18n . t ( "policies.serviceProvidersPlaceholderPolicy" ) }
193- onChange = { val => internalUpdatePolicy ( {
194- serviceProviderIds : isEmpty ( val ) ? [ ] :
195- val . map ( sp => ( { name : sp . value } ) )
196- } ) }
197- isMulti = { true } />
198-
199- < div className = "row" >
200- < div className = "row-item" >
201- < span className = "label standalone" > { I18n . t ( "appAccess.allowDeny" ) }
202- < Tooltip tip = { I18n . t ( "appAccess.denyRuleTooltip" ) } />
203- </ span >
204- < SegmentedControl onClick = { val => denyRuleToggle ( val ) }
205- option = { policy . data . denyRule ? "deny" : "allow" }
206- options = { [ "deny" , "allow" ] }
207- optionLabelResolver = { option => I18n . t ( `appAccess.${ option } ` ) } />
208- </ div >
209- < div className = "row-item" >
210- < span className = "label standalone" > { I18n . t ( "appAccess.allAttributesMatch" ) }
211- < Tooltip tip = { I18n . t ( "appAccess.allAttributesMatchTooltip" ) } />
212- </ span >
213- < SegmentedControl onClick = { val => internalUpdatePolicy ( { allAttributesMustMatch : val === "all" } ) }
214- option = { policy . data . allAttributesMustMatch ? "all" : "any" }
215- options = { [ "all" , "any" ] }
216- optionLabelResolver = { option => I18n . t ( `appAccess.${ option } ` ) } />
217- </ div >
196+ < label className = "stand-alone" > { I18n . t ( "policies.serviceProviders" ) } </ label >
197+ < div className = "row-service-providers" >
198+ < SelectField value = { policy . data . serviceProvidersNegated ? negatedOptions [ 1 ] : negatedOptions [ 0 ] }
199+ required = { true }
200+ onChange = { ( ) => internalUpdatePolicy ( { serviceProvidersNegated : ! policy . data . serviceProvidersNegated } ) }
201+ className = "any-of-service-providers"
202+ options = { negatedOptions } />
203+ < SelectField
204+ value = { serviceProviderOptions . filter ( option => policy . data . serviceProviderIds . some ( sp => sp . name === option . value ) ) }
205+ searchable = { true }
206+ options = { serviceProviderOptions }
207+ className = "service-providers"
208+ placeholder = { I18n . t ( "policies.serviceProvidersPlaceholderPolicy" ) }
209+ onChange = { val => internalUpdatePolicy ( {
210+ serviceProviderIds : isEmpty ( val ) ? [ ] :
211+ val . map ( sp => ( { name : sp . value } ) )
212+ } ) }
213+ isMulti = { true } />
218214 </ div >
215+
219216 < span className = "label standalone" > { I18n . t ( "appAccess.filters" ) } </ span >
220217 < div className = "filters" >
221218 { policy . data . attributes . map ( ( attribute , index ) =>
222- < div key = { index } className = "attribute" >
223- < div className = "attribute-name-wrapper" >
224- < SelectField name = { I18n . t ( "appAccess.attribute" ) }
225- placeholder = { I18n . t ( "appAccess.attributePlaceholder" ) }
226- value = { allowedAttributes . find ( attr => attr . value === attribute . name ) }
219+ < Fragment key = { index } >
220+ < div className = "deletable-attribute" >
221+ { index === 0 &&
222+ < SelectField value = { policy . data . denyRule ? accessOptions [ 1 ] : accessOptions [ 0 ] }
223+ required = { true }
224+ className = "select-access-rule"
225+ onChange = { option => denyRuleToggle ( option . value ) }
226+ options = { accessOptions } />
227+ }
228+ { index !== 0 &&
229+ < SelectField
230+ value = { policy . data . allAttributesMustMatch ? orOptions [ 0 ] : orOptions [ 1 ] }
231+ required = { true }
232+ className = "select-access-rule"
233+ onChange = { ( ) => internalUpdatePolicy ( { allAttributesMustMatch : ! policy . data . allAttributesMustMatch } ) }
234+ options = { orOptions } />
235+ }
236+ < SelectField placeholder = { I18n . t ( "appAccess.attributePlaceholder" ) }
237+ value = { isEmpty ( attribute . name ) ? null : allowedAttributes . find ( attr => attr . value === attribute . name ) }
227238 required = { true }
239+ className = "attribute-name"
228240 onChange = { option => attributeSelected ( option , index ) }
229241 options = { policy . data . denyRule ? allowedAttributes
230242 . filter ( option => option . allowedInDenyRule ) : allowedAttributes } />
231- { ( ! isEmpty ( attribute . name ) && ! isEmpty ( attribute . value ) ) &&
232- < Button type = { ButtonType . Delete }
233- onClick = { ( ) => attributeDeleted ( index ) }
234- />
235- }
243+
244+ < SelectField value = { conditionalOptions [ 0 ] }
245+ required = { true }
246+ disabled = { true }
247+ className = "conditional-options"
248+ options = { conditionalOptions } />
249+
250+ < SelectField value = { attribute . value }
251+ creatable = { true }
252+ required = { true }
253+ className = "attribute-value"
254+ error = { ! initial && isEmpty ( attribute . value ) }
255+ placeholder = { I18n . t ( "appAccess.permittedValuesPlaceholder" ) }
256+ onChange = { values => attributeValueChanged ( values , index ) }
257+ />
258+ < Button type = { ButtonType . Delete }
259+ onClick = { ( ) => attributeDeleted ( index ) }
260+ />
236261 </ div >
237- < SelectField name = { I18n . t ( "appAccess.permittedValues" ) }
238- value = { attribute . value }
239- creatable = { true }
240- required = { true }
241- error = { ! initial && isEmpty ( attribute . value ) }
242- placeholder = { I18n . t ( "appAccess.permittedValuesPlaceholder" ) }
243- onChange = { values => attributeValueChanged ( values , index ) }
244- />
245262 { ! isEmpty ( attributeValueErrors [ attribute . name ] ) &&
246263 < ErrorIndicator adjustMargin = { false }
247264 msg = { I18n . t ( "appAccess.attributeValueErrors" ,
@@ -251,18 +268,19 @@ export const PolicyForm = ({
251268 attributeValueErrors [ attribute . name ] . map ( val => `'${ val } '` ) ,
252269 I18n . t ( "forms.and" ) )
253270 } ) } /> }
254- </ div > ) }
255- { policy . data . attributes . every ( attribute => attribute . name && ! isEmpty ( attribute . value ) ) &&
256- < div className = "add-attribute-container" >
257- < SelectField placeholder = { I18n . t ( "appAccess.addAttributePlaceholder" ) }
258- value = { null }
259- onChange = { option => attributeAdded ( option ) }
260- options = { policy . data . denyRule ? allowedAttributes
261- . filter ( option => option . allowedInDenyRule ) : allowedAttributes } />
262- </ div >
263- }
271+ </ Fragment > ) }
272+ { ( ! initial && policy . data . attributes . filter ( attr => ! isEmpty ( attr . name ) && ! isEmpty ( attr . value ) ) . length === 0 ) &&
273+ < ErrorIndicator msg = { I18n . t ( "policies.attributesRequired" ) } /> }
274+ < div className = "add-attribute-container" >
275+ < SelectField placeholder = { I18n . t ( "appAccess.addAttributePlaceholder" ) }
276+ value = { null }
277+ onChange = { option => attributeAdded ( option ) }
278+ options = { policy . data . denyRule ? allowedAttributes
279+ . filter ( option => option . allowedInDenyRule ) : allowedAttributes } / >
280+ </ div >
264281
265282 </ div >
283+
266284 < InputField name = { I18n . t ( "appAccess.denyEn" ) }
267285 value = { policy . data . denyAdvice }
268286 required = { true }
0 commit comments