diff --git a/portals/publisher/src/main/webapp/WEB-INF/web.xml b/portals/publisher/src/main/webapp/WEB-INF/web.xml index 14195538220..514f259158d 100644 --- a/portals/publisher/src/main/webapp/WEB-INF/web.xml +++ b/portals/publisher/src/main/webapp/WEB-INF/web.xml @@ -126,6 +126,7 @@ /scopes/* /settings/* /policies/* + /global-policies/* /api-products/* /service-catalog/* /login/* diff --git a/portals/publisher/src/main/webapp/site/public/images/landing-icons/globalpolicies.svg b/portals/publisher/src/main/webapp/site/public/images/landing-icons/globalpolicies.svg new file mode 100644 index 00000000000..323b0a81cc4 --- /dev/null +++ b/portals/publisher/src/main/webapp/site/public/images/landing-icons/globalpolicies.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/portals/publisher/src/main/webapp/site/public/locales/en.json b/portals/publisher/src/main/webapp/site/public/locales/en.json index ff89ac03024..d9e75cd5582 100644 --- a/portals/publisher/src/main/webapp/site/public/locales/en.json +++ b/portals/publisher/src/main/webapp/site/public/locales/en.json @@ -17,6 +17,30 @@ "value": "Cancel" } ], + "Actions": [ + { + "type": 0, + "value": "Actions" + } + ], + "Active.Deployment.Available": [ + { + "type": 0, + "value": "An active deployment is available" + } + ], + "Active.Deployments.Available": [ + { + "type": 0, + "value": "Active deployments are available" + } + ], + "Adding.Policy.Mapping.Error": [ + { + "type": 0, + "value": "Error occurred while adding the policy mapping" + } + ], "Api.category.dropdown.tooltip": [ { "type": 0, @@ -1611,6 +1635,12 @@ "value": "Edit Complexity Values" } ], + "Apis.Details.Configuration.ApiKeyHeader.helper.text": [ + { + "type": 0, + "value": "ApiKey header name cannot contain spaces or special characters" + } + ], "Apis.Details.Configuration.AuthHeader.helper.text": [ { "type": 0, @@ -1635,6 +1665,12 @@ "value": "Backend Throughput" } ], + "Apis.Details.Configuration.Configuration.ApiKeyHeader.tooltip": [ + { + "type": 0, + "value": "The header name that is used to send the api key information. \"ApiKey\" is the default header." + } + ], "Apis.Details.Configuration.Configuration.AuthHeader.tooltip": [ { "type": 0, @@ -1665,6 +1701,12 @@ "value": "Edit API Endpoints" } ], + "Apis.Details.Configuration.Configuration.apiKey.header.label": [ + { + "type": 0, + "value": "ApiKey Header" + } + ], "Apis.Details.Configuration.Configuration.auth.header.label": [ { "type": 0, @@ -4627,6 +4669,12 @@ "value": "Endpoint provided" } ], + "Apis.Details.LifeCycle.CheckboxLabels.mandatory.properties.provided": [ + { + "type": 0, + "value": "Mandatory Properties provided" + } + ], "Apis.Details.LifeCycle.LifeCycle.change.not.allowed": [ { "type": 0, @@ -6113,13 +6161,25 @@ "value": "Show in devportal" } ], + "Apis.Details.Properties.\n Properties.editable.show.in.devporal": [ + { + "type": 0, + "value": "Show in devportal" + } + ], + "Apis.Details.Properties.Properties.\n show.add.property.custom.property.name": [ + { + "type": 1, + "value": "message" + } + ], "Apis.Details.Properties.Properties.\n show.add.property.invalid.error": [ { "type": 0, "value": "Invalid property name" } ], - "Apis.Details.Properties.Properties.\n show.add.property.property.name": [ + "Apis.Details.Properties.Properties.\n show.add.property.property.name": [ { "type": 0, "value": "Name" @@ -7919,6 +7979,24 @@ "value": "Context" } ], + "Apis.Listing.ApiThumb.owners": [ + { + "type": 0, + "value": "Owners" + } + ], + "Apis.Listing.ApiThumb.owners.business": [ + { + "type": 0, + "value": "Business" + } + ], + "Apis.Listing.ApiThumb.owners.technical": [ + { + "type": 0, + "value": "Technical" + } + ], "Apis.Listing.ApiThumb.version": [ { "type": 0, @@ -8565,12 +8643,30 @@ "value": "Policies" } ], + "Base.Header.navbar.GlobalNavBar.global.policies": [ + { + "type": 0, + "value": "Global Policies" + } + ], "Base.Header.navbar.GlobalNavBar.scopes": [ { "type": 0, "value": "Scopes" } ], + "Cancel": [ + { + "type": 0, + "value": "Cancel" + } + ], + "Cannot.Find.PolicyObj.For.PolicyId": [ + { + "type": 0, + "value": "Cannot find policy for Id:" + } + ], "CommonPolicies.CreatePolicy.breadcrumb.create.new.policy": [ { "type": 0, @@ -8699,24 +8795,302 @@ "value": "Policies" } ], + "Confirm.Delete": [ + { + "type": 0, + "value": "Confirm Deletion" + } + ], + "Confirm.Delete.Verify": [ + { + "type": 0, + "value": "Are you sure you want to delete the policy?" + } + ], + "Confirm.Deploy": [ + { + "type": 0, + "value": "Confirm Deployment" + } + ], + "Confirm.Deploy.Verify": [ + { + "type": 0, + "value": "Are you sure you want to depoly the policy in the selected gateways?" + } + ], + "Confirm.UnDeploy": [ + { + "type": 0, + "value": "Confirm Undeployment" + } + ], + "Confirm.Undeploy.Verify": [ + { + "type": 0, + "value": "Are you sure you want to undepoly the policy?" + } + ], "Connection.Timeout": [ { "type": 0, "value": "Connection Timeout" } ], + "Delete": [ + { + "type": 0, + "value": "Delete" + } + ], + "Deploy": [ + { + "type": 0, + "value": "Deploy" + } + ], + "Deploy.Helper": [ + { + "type": 0, + "value": "If another global policy is already deployed to a gateway, that gateway will not be available for deployment of this policy. Please undeploy the previously deployed global policy first." + } + ], + "Deployed.Gateway.Listing.Table.Header.Description": [ + { + "type": 0, + "value": "Description" + } + ], + "Deployed.Gateway.Listing.Table.Header.Name": [ + { + "type": 0, + "value": "Deployed Gateways" + } + ], + "Deployed.Gateway.Listing.Table.Not.Available": [ + { + "type": 0, + "value": "No deployed gateways" + } + ], "Endpoint.Suspension.State": [ { "type": 0, "value": "Endpoint Suspension State" } ], + "Error.Deploy.Policy": [ + { + "type": 0, + "value": "Error occurred while deploying the policy" + } + ], + "Error.Retrieve.Policy": [ + { + "type": 0, + "value": "Error occurred while retrieving the policy" + } + ], + "Error.Retrieve.Policy.List": [ + { + "type": 0, + "value": "Error occurred while retrieving the policy list" + } + ], + "Error.Undeploy.Policy": [ + { + "type": 0, + "value": "Error occurred while undeploying the policy" + } + ], + "Error.Validating.Regex": [ + { + "type": 0, + "value": "Error while validating the regex" + } + ], "Error.while.validating.the.imported.graphQLSchema": [ { "type": 0, "value": "Error while validating the imported schema" } ], + "Fault.Details.Policies.PolicyList.Title": [ + { + "type": 0, + "value": "Fault" + } + ], + "Fetching.Policies.Error": [ + { + "type": 0, + "value": "Error while fetching policies" + } + ], + "Fetching.Policies.Settings": [ + { + "type": 0, + "value": "Error while fetching settings" + } + ], + "Global.Details.Policies.AttachedPolicyCard.apiSpecificPolicy.download.error": [ + { + "type": 0, + "value": "Something went wrong while downloading the policy" + } + ], + "Global.Details.Policies.AttachedPolicyCard.commonPolicy.download.error": [ + { + "type": 0, + "value": "Something went wrong while downloading the policy" + } + ], + "Global.Details.Policies.AttachedPolicyForm.General.cancel": [ + { + "type": 0, + "value": "Cancel" + } + ], + "Global.Details.Policies.AttachedPolicyForm.General.description.title": [ + { + "type": 0, + "value": "Description" + } + ], + "Global.Details.Policies.AttachedPolicyForm.General.description.value.not.provided": [ + { + "type": 0, + "value": "Oops! Looks like this policy does not have a description" + } + ], + "Global.Details.Policies.AttachedPolicyForm.General.description.value.provided": [ + { + "type": 1, + "value": "description" + } + ], + "Global.Details.Policies.AttachedPolicyForm.General.reset": [ + { + "type": 0, + "value": "Reset" + } + ], + "Global.Details.Policies.AttachedPolicyForm.General.save": [ + { + "type": 0, + "value": "Save" + } + ], + "Global.Details.Policies.AttachedPolicyForm.General.saving": [ + { + "type": 0, + "value": "Saving" + } + ], + "Global.Details.Policies.DraggablePolicyCard.policy.view": [ + { + "type": 0, + "value": "View" + } + ], + "Global.Details.Policies.PolicyConfigurationEditDrawer.title": [ + { + "type": 0, + "value": "Configure " + }, + { + "type": 1, + "value": "policy" + } + ], + "Global.Details.Policies.PolicyConfiguringDrawer.title": [ + { + "type": 0, + "value": "Configure " + }, + { + "type": 1, + "value": "policy" + } + ], + "Global.Details.Policies.PolicyList.title": [ + { + "type": 0, + "value": "Policy List" + } + ], + "Global.Details.Policies.SaveOperationPolicies.save": [ + { + "type": 0, + "value": "Save" + } + ], + "Global.Details.Policies.SaveOperationPolicies.update": [ + { + "type": 0, + "value": "Update" + } + ], + "Global.Policies": [ + { + "type": 0, + "value": "Global Policies" + } + ], + "Global.Policy.Listing.Table.Header.Name": [ + { + "type": 0, + "value": "Global Policy" + } + ], + "GlobalPolicies.Listing.Table.Header.Actions.Edit": [ + { + "type": 0, + "value": "Edit" + } + ], + "GlobalPolicies.Listing.Table.Header.Actions.View": [ + { + "type": 0, + "value": "View" + } + ], + "GlobalPolicies.Listing.onboarding.create.new": [ + { + "type": 0, + "value": "Let’s get started!" + } + ], + "GlobalPolicies.Listing.onboarding.policies.tooltip": [ + { + "type": 0, + "value": "Global policies provide you the ability to deploy policy mappings to all the APIs deployed in a specific gateway and not just one single API. Click below to create your first global policy" + } + ], + "GlobalPolicies.Listing.policies.title.add.new.policy": [ + { + "type": 0, + "value": "Add new global policy" + } + ], + "GlobalPolicies.Listing.policies.title.name": [ + { + "type": 0, + "value": "Global Policies" + } + ], + "GlobalPolicies.Listing.policies.title.tooltip": [ + { + "type": 0, + "value": "This will add policies globally to the gateways. Please navigate to the Policies tab under any desired API if you want to add API / operation level policies" + } + ], + "GlobalPolicies.Listing.table.header.actions.delete": [ + { + "type": 0, + "value": "Delete" + } + ], "LoginDenied.logout": [ { "type": 0, @@ -8735,6 +9109,90 @@ "value": "Error 403 : Forbidden" } ], + "Polcies.TextField.Description": [ + { + "type": 0, + "value": "Description" + } + ], + "Polcies.TextField.Name": [ + { + "type": 0, + "value": "Name" + } + ], + "Policy.Delete.Error": [ + { + "type": 0, + "value": "Error while deleting the policy" + } + ], + "Policy.Delete.Successful": [ + { + "type": 0, + "value": "Policy deleted successfully" + } + ], + "Policy.Deploy.Successful": [ + { + "type": 0, + "value": "Policy deployed successfully" + } + ], + "Policy.Description.Cannot.Be.Empty": [ + { + "type": 0, + "value": "Policy description cannot be empty" + } + ], + "Policy.Mapping.Added.Successfully": [ + { + "type": 0, + "value": "Policy mapping added successfully" + } + ], + "Policy.Mapping.Cannot.Be.Empty": [ + { + "type": 0, + "value": "Policy mapping cannot be empty" + } + ], + "Policy.Mapping.Update.Error": [ + { + "type": 0, + "value": "Error occurred while updating the policy mapping" + } + ], + "Policy.Mapping.Update.Success": [ + { + "type": 0, + "value": "Policy mapping updated successfully" + } + ], + "Policy.Name.Cannot.Be.Empty": [ + { + "type": 0, + "value": "Policy name cannot be empty" + } + ], + "Policy.Undeploy.Successful": [ + { + "type": 0, + "value": "Policy undeployed successfully" + } + ], + "Request.Details.Policies.PolicyList.Title": [ + { + "type": 0, + "value": "Request" + } + ], + "Response.Details.Policies.PolicyList.Title": [ + { + "type": 0, + "value": "Response" + } + ], "Scopes.Create.CreateScope.cancel": [ { "type": 0, @@ -9055,6 +9513,18 @@ "value": "List of APIs" } ], + "Select.Gateways.Label": [ + { + "type": 0, + "value": "Select gateways to deploy" + } + ], + "Select.Gateways.Placeholder": [ + { + "type": 0, + "value": "Select gateways to deploy" + } + ], "ServiceCatalog.CreateApi.api.context.label": [ { "type": 0, @@ -9527,6 +9997,12 @@ "value": "Version" } ], + "Undeploy": [ + { + "type": 0, + "value": "Undeploy" + } + ], "UnexpectedError.logout": [ { "type": 0, @@ -9641,6 +10117,24 @@ "value": "The server encountered an internal error or misconfiguration and was unable to complete your request." } ], + "globalPolicies.create.create.heading": [ + { + "type": 0, + "value": "Create a new global policy" + } + ], + "globalPolicies.create.edit.heading": [ + { + "type": 0, + "value": "Edit global policy" + } + ], + "globalPolicies.heading": [ + { + "type": 0, + "value": "Global Policies" + } + ], "save": [ { "type": 0, diff --git a/portals/publisher/src/main/webapp/site/public/locales/raw.en.json b/portals/publisher/src/main/webapp/site/public/locales/raw.en.json index 84090bf8c12..bb371fcee1b 100644 --- a/portals/publisher/src/main/webapp/site/public/locales/raw.en.json +++ b/portals/publisher/src/main/webapp/site/public/locales/raw.en.json @@ -8,6 +8,18 @@ "APIs.details.endpoints.certificate.usage.cancel": { "defaultMessage": "Cancel" }, + "Actions": { + "defaultMessage": "Actions" + }, + "Active.Deployment.Available": { + "defaultMessage": "An active deployment is available" + }, + "Active.Deployments.Available": { + "defaultMessage": "Active deployments are available" + }, + "Adding.Policy.Mapping.Error": { + "defaultMessage": "Error occurred while adding the policy mapping" + }, "Api.category.dropdown.tooltip": { "defaultMessage": "Allow to group APIs that have similar attributes. There has to be pre-defined API categories in the environment in order to be attached to an API." }, @@ -702,6 +714,9 @@ "Apis.Details.Configurartion.components.QueryAnalysis.edit": { "defaultMessage": "Edit Complexity Values" }, + "Apis.Details.Configuration.ApiKeyHeader.helper.text": { + "defaultMessage": "ApiKey header name cannot contain spaces or special characters" + }, "Apis.Details.Configuration.AuthHeader.helper.text": { "defaultMessage": "Authorization header name cannot contain spaces or special characters" }, @@ -714,6 +729,9 @@ "Apis.Details.Configuration.Components.MaxBackendTps.maximum.backend.throughput": { "defaultMessage": "Backend Throughput" }, + "Apis.Details.Configuration.Configuration.ApiKeyHeader.tooltip": { + "defaultMessage": "The header name that is used to send the api key information. \"ApiKey\" is the default header." + }, "Apis.Details.Configuration.Configuration.AuthHeader.tooltip": { "defaultMessage": "The header name that is used to send the authorization information. \"Authorization\" is the default header." }, @@ -729,6 +747,9 @@ "Apis.Details.Configuration.Configuration.Endpoints.edit.api.endpoints": { "defaultMessage": "Edit API Endpoints" }, + "Apis.Details.Configuration.Configuration.apiKey.header.label": { + "defaultMessage": "ApiKey Header" + }, "Apis.Details.Configuration.Configuration.auth.header.label": { "defaultMessage": "Authorization Header" }, @@ -2184,6 +2205,9 @@ "Apis.Details.LifeCycle.CheckboxLabels.endpoints.provided": { "defaultMessage": "Endpoint provided" }, + "Apis.Details.LifeCycle.CheckboxLabels.mandatory.properties.provided": { + "defaultMessage": "Mandatory Properties provided" + }, "Apis.Details.LifeCycle.LifeCycle.change.not.allowed": { "defaultMessage": "* You are not authorized to change the life cycle state of the API due to insufficient permissions" }, @@ -2907,10 +2931,16 @@ "Apis.Details.Properties.\n Properties.editable.show.in.devporal": { "defaultMessage": "Show in devportal" }, + "Apis.Details.Properties.\n Properties.editable.show.in.devporal": { + "defaultMessage": "Show in devportal" + }, + "Apis.Details.Properties.Properties.\n show.add.property.custom.property.name": { + "defaultMessage": "{message}" + }, "Apis.Details.Properties.Properties.\n show.add.property.invalid.error": { "defaultMessage": "Invalid property name" }, - "Apis.Details.Properties.Properties.\n show.add.property.property.name": { + "Apis.Details.Properties.Properties.\n show.add.property.property.name": { "defaultMessage": "Name" }, "Apis.Details.Properties.Properties.\n property.name.exists": { @@ -3771,6 +3801,15 @@ "Apis.Listing.ApiThumb.context": { "defaultMessage": "Context" }, + "Apis.Listing.ApiThumb.owners": { + "defaultMessage": "Owners" + }, + "Apis.Listing.ApiThumb.owners.business": { + "defaultMessage": "Business" + }, + "Apis.Listing.ApiThumb.owners.technical": { + "defaultMessage": "Technical" + }, "Apis.Listing.ApiThumb.version": { "defaultMessage": "Version" }, @@ -4092,9 +4131,18 @@ "Base.Header.navbar.GlobalNavBar.common.policies": { "defaultMessage": "Policies" }, + "Base.Header.navbar.GlobalNavBar.global.policies": { + "defaultMessage": "Global Policies" + }, "Base.Header.navbar.GlobalNavBar.scopes": { "defaultMessage": "Scopes" }, + "Cancel": { + "defaultMessage": "Cancel" + }, + "Cannot.Find.PolicyObj.For.PolicyId": { + "defaultMessage": "Cannot find policy for Id:" + }, "CommonPolicies.CreatePolicy.breadcrumb.create.new.policy": { "defaultMessage": "Create New Policy" }, @@ -4155,15 +4203,150 @@ "CommonPolicies.ViewPolicy.policies.title": { "defaultMessage": "Policies" }, + "Confirm.Delete": { + "defaultMessage": "Confirm Deletion" + }, + "Confirm.Delete.Verify": { + "defaultMessage": "Are you sure you want to delete the policy?" + }, + "Confirm.Deploy": { + "defaultMessage": "Confirm Deployment" + }, + "Confirm.Deploy.Verify": { + "defaultMessage": "Are you sure you want to depoly the policy in the selected gateways?" + }, + "Confirm.UnDeploy": { + "defaultMessage": "Confirm Undeployment" + }, + "Confirm.Undeploy.Verify": { + "defaultMessage": "Are you sure you want to undepoly the policy?" + }, "Connection.Timeout": { "defaultMessage": "Connection Timeout" }, + "Delete": { + "defaultMessage": "Delete" + }, + "Deploy": { + "defaultMessage": "Deploy" + }, + "Deploy.Helper": { + "defaultMessage": "If another global policy is already deployed to a gateway, that gateway will not be available for deployment of this policy. Please undeploy the previously deployed global policy first." + }, + "Deployed.Gateway.Listing.Table.Header.Description": { + "defaultMessage": "Description" + }, + "Deployed.Gateway.Listing.Table.Header.Name": { + "defaultMessage": "Deployed Gateways" + }, + "Deployed.Gateway.Listing.Table.Not.Available": { + "defaultMessage": "No deployed gateways" + }, "Endpoint.Suspension.State": { "defaultMessage": "Endpoint Suspension State" }, + "Error.Deploy.Policy": { + "defaultMessage": "Error occurred while deploying the policy" + }, + "Error.Retrieve.Policy": { + "defaultMessage": "Error occurred while retrieving the policy" + }, + "Error.Retrieve.Policy.List": { + "defaultMessage": "Error occurred while retrieving the policy list" + }, + "Error.Undeploy.Policy": { + "defaultMessage": "Error occurred while undeploying the policy" + }, + "Error.Validating.Regex": { + "defaultMessage": "Error while validating the regex" + }, "Error.while.validating.the.imported.graphQLSchema": { "defaultMessage": "Error while validating the imported schema" }, + "Fault.Details.Policies.PolicyList.Title": { + "defaultMessage": "Fault" + }, + "Fetching.Policies.Error": { + "defaultMessage": "Error while fetching policies" + }, + "Fetching.Policies.Settings": { + "defaultMessage": "Error while fetching settings" + }, + "Global.Details.Policies.AttachedPolicyCard.apiSpecificPolicy.download.error": { + "defaultMessage": "Something went wrong while downloading the policy" + }, + "Global.Details.Policies.AttachedPolicyCard.commonPolicy.download.error": { + "defaultMessage": "Something went wrong while downloading the policy" + }, + "Global.Details.Policies.AttachedPolicyForm.General.cancel": { + "defaultMessage": "Cancel" + }, + "Global.Details.Policies.AttachedPolicyForm.General.description.title": { + "defaultMessage": "Description" + }, + "Global.Details.Policies.AttachedPolicyForm.General.description.value.not.provided": { + "defaultMessage": "Oops! Looks like this policy does not have a description" + }, + "Global.Details.Policies.AttachedPolicyForm.General.description.value.provided": { + "defaultMessage": "{description}" + }, + "Global.Details.Policies.AttachedPolicyForm.General.reset": { + "defaultMessage": "Reset" + }, + "Global.Details.Policies.AttachedPolicyForm.General.save": { + "defaultMessage": "Save" + }, + "Global.Details.Policies.AttachedPolicyForm.General.saving": { + "defaultMessage": "Saving" + }, + "Global.Details.Policies.DraggablePolicyCard.policy.view": { + "defaultMessage": "View" + }, + "Global.Details.Policies.PolicyConfigurationEditDrawer.title": { + "defaultMessage": "Configure {policy}" + }, + "Global.Details.Policies.PolicyConfiguringDrawer.title": { + "defaultMessage": "Configure {policy}" + }, + "Global.Details.Policies.PolicyList.title": { + "defaultMessage": "Policy List" + }, + "Global.Details.Policies.SaveOperationPolicies.save": { + "defaultMessage": "Save" + }, + "Global.Details.Policies.SaveOperationPolicies.update": { + "defaultMessage": "Update" + }, + "Global.Policies": { + "defaultMessage": "Global Policies" + }, + "Global.Policy.Listing.Table.Header.Name": { + "defaultMessage": "Global Policy" + }, + "GlobalPolicies.Listing.Table.Header.Actions.Edit": { + "defaultMessage": "Edit" + }, + "GlobalPolicies.Listing.Table.Header.Actions.View": { + "defaultMessage": "View" + }, + "GlobalPolicies.Listing.onboarding.create.new": { + "defaultMessage": "Let’s get started!" + }, + "GlobalPolicies.Listing.onboarding.policies.tooltip": { + "defaultMessage": "Global policies provide you the ability to deploy policy mappings to all the APIs deployed in a specific gateway and not just one single API. Click below to create your first global policy" + }, + "GlobalPolicies.Listing.policies.title.add.new.policy": { + "defaultMessage": "Add new global policy" + }, + "GlobalPolicies.Listing.policies.title.name": { + "defaultMessage": "Global Policies" + }, + "GlobalPolicies.Listing.policies.title.tooltip": { + "defaultMessage": "This will add policies globally to the gateways. Please navigate to the Policies tab under any desired API if you want to add API / operation level policies" + }, + "GlobalPolicies.Listing.table.header.actions.delete": { + "defaultMessage": "Delete" + }, "LoginDenied.logout": { "defaultMessage": "Logout" }, @@ -4173,6 +4356,48 @@ "LoginDenied.title": { "defaultMessage": "Error 403 : Forbidden" }, + "Polcies.TextField.Description": { + "defaultMessage": "Description" + }, + "Polcies.TextField.Name": { + "defaultMessage": "Name" + }, + "Policy.Delete.Error": { + "defaultMessage": "Error while deleting the policy" + }, + "Policy.Delete.Successful": { + "defaultMessage": "Policy deleted successfully" + }, + "Policy.Deploy.Successful": { + "defaultMessage": "Policy deployed successfully" + }, + "Policy.Description.Cannot.Be.Empty": { + "defaultMessage": "Policy description cannot be empty" + }, + "Policy.Mapping.Added.Successfully": { + "defaultMessage": "Policy mapping added successfully" + }, + "Policy.Mapping.Cannot.Be.Empty": { + "defaultMessage": "Policy mapping cannot be empty" + }, + "Policy.Mapping.Update.Error": { + "defaultMessage": "Error occurred while updating the policy mapping" + }, + "Policy.Mapping.Update.Success": { + "defaultMessage": "Policy mapping updated successfully" + }, + "Policy.Name.Cannot.Be.Empty": { + "defaultMessage": "Policy name cannot be empty" + }, + "Policy.Undeploy.Successful": { + "defaultMessage": "Policy undeployed successfully" + }, + "Request.Details.Policies.PolicyList.Title": { + "defaultMessage": "Request" + }, + "Response.Details.Policies.PolicyList.Title": { + "defaultMessage": "Response" + }, "Scopes.Create.CreateScope.cancel": { "defaultMessage": "Cancel" }, @@ -4329,6 +4554,12 @@ "Scopes.Usage.UsageViewAPI.api.usage": { "defaultMessage": "List of APIs" }, + "Select.Gateways.Label": { + "defaultMessage": "Select gateways to deploy" + }, + "Select.Gateways.Placeholder": { + "defaultMessage": "Select gateways to deploy" + }, "ServiceCatalog.CreateApi.api.context.label": { "defaultMessage": "Context" }, @@ -4551,6 +4782,9 @@ "ServiceCatalog.ServicesTableView.ServicesTableView.version": { "defaultMessage": "Version" }, + "Undeploy": { + "defaultMessage": "Undeploy" + }, "UnexpectedError.logout": { "defaultMessage": "Logout" }, @@ -4608,6 +4842,15 @@ "error.list.500.description": { "defaultMessage": "The server encountered an internal error or misconfiguration and was unable to complete your request." }, + "globalPolicies.create.create.heading": { + "defaultMessage": "Create a new global policy" + }, + "globalPolicies.create.edit.heading": { + "defaultMessage": "Edit global policy" + }, + "globalPolicies.heading": { + "defaultMessage": "Global Policies" + }, "save": { "defaultMessage": "Save" }, diff --git a/portals/publisher/src/main/webapp/source/src/app/ProtectedApp.jsx b/portals/publisher/src/main/webapp/source/src/app/ProtectedApp.jsx index 93bbe307cb5..95fa4e583cc 100644 --- a/portals/publisher/src/main/webapp/source/src/app/ProtectedApp.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/ProtectedApp.jsx @@ -37,6 +37,7 @@ import Configurations from 'Config'; import { QueryClientProviderX } from 'AppData/hooks/ReactQueryX'; import Scopes from 'AppComponents/Scopes/Scopes'; import CommonPolicies from 'AppComponents/CommonPolicies/CommonPolicies'; +import GlobalPolicies from 'AppComponents/GlobalPolicies/GlobalPolicies'; import merge from 'lodash/merge'; import User from './data/User'; import Utils from './data/Utils'; @@ -215,6 +216,7 @@ export default class Protected extends Component { + diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavBar.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavBar.jsx index 6c2d583c8d1..1c8e795d19c 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavBar.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavBar.jsx @@ -78,7 +78,7 @@ const GlobalNavBar = (props) => { let isRootPage = false; const { pathname } = location; - if (/^\/(apis|api-products|scopes|policies|service-catalog)($|\/$)/g.test(pathname)) { + if (/^\/(apis|api-products|scopes|policies|global-policies|service-catalog)($|\/$)/g.test(pathname)) { isRootPage = true; } useEffect(() => { diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavLinks.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavLinks.jsx index 28c963cd5bc..e99c82abeeb 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavLinks.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Base/Header/navbar/GlobalNavLinks.jsx @@ -22,7 +22,7 @@ import List from '@material-ui/core/List'; import LaunchIcon from '@material-ui/icons/Launch'; import { useTheme } from '@material-ui/styles'; import { FormattedMessage } from 'react-intl'; -import AuthManager from 'AppData/AuthManager'; +import AuthManager, { isRestricted } from 'AppData/AuthManager'; import { makeStyles } from '@material-ui/core/styles'; import Divider from '@material-ui/core/Divider'; @@ -118,6 +118,19 @@ function GlobalNavLinks(props) { defaultMessage='Policies' /> + {(!isRestricted(['apim:gateway_policy_manage', 'apim:gateway_policy_view'])) + && ( + + + + )} {analyticsMenuEnabled && ( <> diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Create/CreateGlobalPolicy.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Create/CreateGlobalPolicy.tsx new file mode 100644 index 00000000000..752be1c09cc --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Create/CreateGlobalPolicy.tsx @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React from 'react'; +import Policies from 'AppComponents/GlobalPolicies/Policies/GlobalSpecificComponents/Policies'; + +/** + * Global Policies Creating Page. + * @returns {JSX} - Creating Page. + */ +const CreateGlobalPolicy: React.FC = () => { + return ( + + ); +}; + +export default CreateGlobalPolicy; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Edit/EditGlobalPolicy.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Edit/EditGlobalPolicy.tsx new file mode 100644 index 00000000000..9842d51c97e --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Edit/EditGlobalPolicy.tsx @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React from 'react'; +import Policies from 'AppComponents/GlobalPolicies/Policies/GlobalSpecificComponents/Policies'; +import { useParams } from 'react-router-dom'; + +interface RouteParams { + policyId: string; +} + +/** + * Global Policies Editing Page. + * @returns {JSX} - Editing Page. + */ +const EditGlobalPolicy: React.FC = () => { + const { policyId } = useParams(); + return ( + + ); +}; + +export default EditGlobalPolicy; \ No newline at end of file diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/GlobalPolicies.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/GlobalPolicies.tsx new file mode 100644 index 00000000000..8001a3a2fb8 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/GlobalPolicies.tsx @@ -0,0 +1,68 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; +import ResourceNotFound from 'AppComponents/Base/Errors/ResourceNotFound'; +import { isRestricted } from 'AppData/AuthManager'; +import Listing from './Listing/Listing'; +import GlobalPoliciesCreate from './Create/CreateGlobalPolicy'; +import GlobalPoliciesEdit from './Edit/EditGlobalPolicy'; +import GlobalPoliciesView from './View/ViewGlobalPolicy'; + +/** + * `Route` elements for shared Global Policies UI. + * @returns {TSX} - Shared Global Policies routes. + */ +const GlobalPolicies = () => { + return ( + + {!isRestricted(['apim:gateway_policy_manage', 'apim:gateway_policy_view']) && + + } + {!isRestricted(['apim:gateway_policy_manage']) && + <> + + + + } + {!isRestricted(['apim:gateway_policy_view']) && + + } + + + ); +}; + +export default GlobalPolicies; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Listing/Listing.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Listing/Listing.tsx new file mode 100644 index 00000000000..308e067eb58 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Listing/Listing.tsx @@ -0,0 +1,1110 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/* eslint-disable react/jsx-props-no-spreading */ + +import React, { useState, useEffect } from 'react'; +import { + Button, + Grid, + IconButton, + Tooltip, + Typography, + makeStyles, + Chip, + TableCell, + TableRow, + Dialog, + TextField, + DialogTitle, + DialogContent, + DialogActions, + DialogContentText, + useTheme +} from '@material-ui/core'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import APIMAlert from 'AppComponents/Shared/Alert'; +import Icon from '@material-ui/core/Icon'; +import CloudOffRoundedIcon from '@material-ui/icons/CloudOffRounded'; +import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; +import { isRestricted } from 'AppData/AuthManager'; +import API from 'AppData/api'; +import { Progress } from 'AppComponents/Shared'; +import { useIntl, FormattedMessage } from 'react-intl'; +import AddCircle from '@material-ui/icons/AddCircle'; +import MUIDataTable, { MUIDataTableOptions, MUIDataTableColumnDef } from 'mui-datatables'; +import Box from '@material-ui/core/Box'; +import OnboardingMenuCard from 'AppComponents/Shared/Onboarding/OnboardingMenuCard'; +import Onboarding from 'AppComponents/Shared/Onboarding/Onboarding'; +import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; +import ResourceNotFoundError from 'AppComponents/Base/Errors/ResourceNotFoundError'; +import { Link } from 'react-router-dom'; + +const useStyles = makeStyles((theme) => ({ + table: { + marginLeft: 'auto', + marginRight: 'auto', + '& > td[class^=MUIDataTableBodyCell-cellHide-]': { + display: 'none', + }, + '& .MUIDataTableBodyCell-cellHide-793': { + display: 'none', + }, + '& td': { + wordBreak: 'break-word', + }, + '& th': { + minWidth: '150px', + }, + }, + heading: { + flexGrow: 1, + marginTop: 10, + }, + titleWrapper: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + marginBottom: theme.spacing(2), + marginLeft: 'auto', + marginRight: 'auto', + }, + mainTitle: { + paddingLeft: 0, + }, + buttonIcon: { + marginRight: theme.spacing(1), + }, + button: { + width: '112px', + height: '37px', + }, + icon: { + marginRight: theme.spacing(0.5), + }, + chip: { + marginRight: '8px', + marginBottom: '4px', + marginTop: '4px', + }, + dialogBackdrop: { + backgroundColor: 'rgba(0, 0, 0, 0.08)' + }, + dialogPaper: { + boxShadow: 'none' + }, + noDeployedGateways: { + color: 'grey', + fontStyle: 'italic' + }, + iconSmall: { + fontSize: '16px' + }, +})); + +interface Policy { + id: string; + description: string; + displayName: string; + appliedGatewayLabels: string[]; +} + +interface Deployment { + gatewayLabel: string; + gatewayDeployment: boolean; +} + +interface Environment { + id: string; + name: string; + displayName: string; + type: string; + serverUrl: string; + provider: string; + showInApiConsole: boolean; + vhosts: any; + endpointURIs: any; + additionalProperties: any; +} + +interface SelectedGateway { + id: string; + gatewayLabels: string[]; +} + +/** + * Global policies Lisitng Page. + * @returns {TSX} - Listing Page. + */ +const Listing: React.FC = () => { + const classes = useStyles(); + const [policies, setPolicies] = useState([]); + const [selectedGateways, setSelectedGateways] = useState([]); + const [environments, setEnvironments] = useState([]); + const [loading, setLoading] = useState(false); + const [notFound, setnotFound] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [isDeployDialogOpen, setIsDeployDialogOpen] = useState(false); + const [isUndeployDialogOpen, setIsUndeployDialogOpen] = useState(false); + const [selectedPolicyName, setSelectedPolicyName] = useState(''); + const [selectedPolicyId, setSelectedPolicyId] = useState(''); + const [deployingGateway, setDeployingGateway] = useState(''); + const theme : any = useTheme(); + const { globalPolicyAddIcon } = theme.custom.landingPage.icons; + const intl = useIntl(); + + /** + * Empty array to store the policies + * @param {Policy[]} inputPolicies - Policies. + * @returns {Policy[]} - Initial object array to hold selected gateways from the dropdown. + */ + const getInitialSelectedGateways = (inputPolicies: Policy[]): SelectedGateway[] => { + const selectedGatewaysList: SelectedGateway[] = []; + + inputPolicies.forEach((policy) => { + const { id } = policy; + const selectedGatewayValue: SelectedGateway = { + id, + gatewayLabels: [], + }; + selectedGatewaysList.push(selectedGatewayValue); + }); + + return selectedGatewaysList; + }; + + /** + * After deploying or undeploying, we need to clean the selected gateways list. + * @param {string} policyId - Policy ID. + * @returns {SelectedGateway[]} - Deployed policies' selected gateways should be empty. + */ + const cleanSeletectedGateways = (policyId: string): SelectedGateway[] => { + const updatedSelectedGateways: SelectedGateway[] = selectedGateways.map((data) => { + if (data.id === policyId) { + return { + ...data, + gatewayLabels: [], + }; + } + return data; + }); + + return updatedSelectedGateways; + }; + + /** + * Fetch the data from the backend to the compoenent. + */ + const fetchGlobalPolicies = () => { + setLoading(true); + const promisedPolicies = API.getAllGatewayPolicies(); + promisedPolicies + .then((response: any) => { + setPolicies(response.body.list); + setSelectedGateways(getInitialSelectedGateways(response.body.list)); + setLoading(false); + }) + .catch(() => { + APIMAlert.error(intl.formatMessage({ + id: 'Fetching.Policies.Error', + defaultMessage: 'Error while fetching policies', + })); + setnotFound(true); + setLoading(false); + }) + }; + + /** + * Fetch environements publisher settings from the backend to the compoenent. + * This data has been used to get the full gateway environment list. + */ + const fetchSettings = () => { + setLoading(true); + const promisedSettings = API.getSettings(); + promisedSettings + .then((response: any) => { + setEnvironments(response.environment); + }) + .catch(() => { + APIMAlert.error(intl.formatMessage({ + id: 'Fetching.Policies.Settings', + defaultMessage: 'Error while fetching settings', + })); + setnotFound(true); + }) + .finally(() => { + setLoading(false); + }); + }; + + /** + * Get the applied gateway labels as an array list for a specific policy by ID. + * @param {string} id - Policy Identifier. + * @returns {string[]} - Applied Gateway labels list. + */ + const getAppliedGatewayLabelsById = (id: string) => { + const policy = policies.find(item => item.id === id); + return policy ? policy.appliedGatewayLabels : []; + } + + /** + * Get the applied gateway labels as an array list for a specific policy by ID. + * @param {string} id - Policy Identifier. + * @returns {string[]} - Applied Gateway labels list. + */ + const getSelectedGatewayLabelsById = (id: string) => { + const policy = selectedGateways.find(item => item.id === id); + return policy ? policy.gatewayLabels : []; + } + + /** + * Get the deployment array for a specific policy by ID. Below is an example. + * [{"gatewayLabel": "A","gatewayDeployment": false},{"B": "GateWay1","gatewayDeployment": false}]. + * @param {string} policyId - Policy Identifier. + * @returns {Deployment[]} - Deployment array. + */ + const getDeploymentArray = (policyId: string) => { + const appliedGatewayList = getAppliedGatewayLabelsById(policyId); + const allGatewayList = environments.map((env: any) => { + return env.name; + }); + return allGatewayList.map(gatewayLabel => ({ + gatewayLabel, + gatewayDeployment: appliedGatewayList.includes(gatewayLabel), + })); + } + + /** + * Toggle the deployment status of a specific gateway for a specific policy. + * If false, it will become true (depolying) and vice versa (undeploying). + * @param {Deployment[]} deploymentArray - Array which contains Labels and it's boolean value. + * @param {string} environment - Gateway environment which is required to be toggled. + * @returns {Deployment[]} - Output array. + */ + const toggleGatewayDeployment = (deploymentArray: Deployment[], environment: string) => { + const updatedArray = deploymentArray.map(item => { + if (item.gatewayLabel === environment) { + return { ...item, gatewayDeployment: !item.gatewayDeployment }; + } + return item; + }); + return updatedArray; + }; + + /** + * Function to add a label to a specific policy in UI. + * @param {string} policyId - Policy Identifier. + * @param {string[]} newLabels - Newly added enviroment/gateway to applied Gateway Labels. + */ + const addLabelsToPolicy = (policyId: string, newLabels: string[]) => { + const updatedPolicies: Policy[] = policies.map((policy) => { + if (policy.id === policyId) { + return { + ...policy, + appliedGatewayLabels: [...policy.appliedGatewayLabels, ...newLabels], + }; + } + return policy; + }); + setPolicies(updatedPolicies); + setSelectedGateways(cleanSeletectedGateways(policyId)); + }; + + /** + * Function to remove a label from a specific policy in UI. + * @param {string} policyId - Policy Identifier. + * @param {string} labelToRemove - Removing enviroment/gateway from applied Gateway Labels. + */ + const removeLabelFromPolicy = (policyId: string, labelToRemove: string) => { + const updatedPolicies = policies.map((policy) => { + if (policy.id === policyId) { + return { + ...policy, + appliedGatewayLabels: policy.appliedGatewayLabels.filter((label) => label !== labelToRemove), + }; + } + return policy; + }); + setPolicies(updatedPolicies); + setSelectedGateways(cleanSeletectedGateways(policyId)); + }; + + /** + * Function to undeploy a policy mapping to another enviroment/gateway. + * @param {string} gatewayPolicyMappingId - Policy Identifier. + * @param {string} environement - Deploying enviroment/gateway. + * @param {boolean} deploying - Deploying or undeploying. + */ + const undeploy = (gatewayPolicyMappingId: string, environement: string) => { + setIsUndeployDialogOpen(false); + setLoading(true); + const deploymentArray = getDeploymentArray(gatewayPolicyMappingId); + const updatedDeploymentArray = toggleGatewayDeployment(deploymentArray, environement); + /** + * call the backend API and handle the response + */ + const promise = API.engageGlobalPolicy(gatewayPolicyMappingId, updatedDeploymentArray); + promise + .then((response) => { + if (response.status === 200 || response.status === 201) { + setLoading(false); + APIMAlert.success(intl.formatMessage({ + id: 'Policy.Undeploy.Successful', + defaultMessage: 'Policy undeployed successfully', + })); + /** + * If successful, remove from the state rather than getting from backend. + */ + removeLabelFromPolicy(gatewayPolicyMappingId, environement); + } + else { + APIMAlert.error(intl.formatMessage({ + id: 'Error.Deploy.Policy', + defaultMessage: 'Error occurred while deploying the policy', + })); + } + }) + .catch(() => { + APIMAlert.error(intl.formatMessage({ + id: 'Error.Undeploy.Policy', + defaultMessage: 'Error occurred while undeploying the policy', + })); + }) + setLoading(false); + } + + /** + * Function to undeploy a policy mapping to another enviroment/gateway. + * @param {string} gatewayPolicyMappingId - Policy Identifier. + * @param {string[]} deployingGateways - Deploying enviroment/gateway. + * @param {boolean} deploying - Deploying or undeploying. + */ + const deploy = (gatewayPolicyMappingId: string, deployingGateways: string[]) => { + setIsDeployDialogOpen(false); + setLoading(true); + const deploymentArray = getDeploymentArray(gatewayPolicyMappingId); + + /** + * Iterate through the selected gateways and update the deployment array. + */ + const updatedDeploymentArray = deploymentArray.map(({ gatewayLabel, gatewayDeployment }) => { + if (deployingGateways.includes(gatewayLabel) && gatewayDeployment === false) { + return { gatewayLabel, gatewayDeployment: true }; + } + return { gatewayLabel, gatewayDeployment }; + }); + + /** + * call the backend API and handle the response + */ + const promise = API.engageGlobalPolicy(gatewayPolicyMappingId, updatedDeploymentArray); + promise + .then((response) => { + if (response.status === 200 || response.status === 201) { + setLoading(false); + APIMAlert.success(intl.formatMessage({ + id: 'Policy.Deploy.Successful', + defaultMessage: 'Policy deployed successfully', + })); + /** + * If successful, add to the state rather than getting from backend. + */ + addLabelsToPolicy( + gatewayPolicyMappingId, + deployingGateways + ); + } + else { + APIMAlert.error(intl.formatMessage({ + id: 'Error.Deploy.Policy', + defaultMessage: 'Error occurred while deploying the policy', + })); + } + }) + .catch(() => { + APIMAlert.error(intl.formatMessage({ + id: 'Error.Deploy.Policy', + defaultMessage: 'Error occurred while deploying the policy', + })); + }) + setLoading(false); + } + + const getAllDepoloyedGateways = () => { + const allGateways = policies.map((policy) => + getAppliedGatewayLabelsById(policy.id)).reduce((acc, val) => acc.concat(val), []); + return allGateways; + } + + /** + * Function to delete a policy mapping + * @param {string} gatewayPolicyMappingId - Policy Identifier. + */ + const deletePolicy = (gatewayPolicyMappingId: string) => { + setIsDeleteDialogOpen(false); + setLoading(true); + /** + * call the backend API and handle the response + */ + const promise = API.deleteGatewayPolicyByPolicyId(gatewayPolicyMappingId); + promise + .then(() => { + /** + * If successful, remove from the state rather than getting from backend. + */ + const updatedPolicies = policies.filter((policy) => policy.id !== gatewayPolicyMappingId); + setPolicies(updatedPolicies); + // Remove from the selected gateways list as well + setSelectedGateways(selectedGateways.filter((gateway) => gateway.id !== gatewayPolicyMappingId)); + APIMAlert.success(intl.formatMessage({ + id: 'Policy.Delete.Successful', + defaultMessage: 'Policy deleted successfully', + })); + setLoading(false); + }) + .catch(() => { + APIMAlert.error(intl.formatMessage({ + id: 'Policy.Delete.Error', + defaultMessage: 'Error while deleting the policy', + })); + setLoading(false); + }); + } + + useEffect(() => { + fetchGlobalPolicies(); + fetchSettings(); + }, []); + + /** + * Get the Global Policy edit link. + * @param {string} policyId - Policy Identifier. + * @returns {string} - Url for the Global Policy edit. + */ + const getEditUrl = (policyId: string) => { + return `/global-policies/${policyId}/edit`; + }; + + /** + * Get the Global Policy view link. + * @param {string} policyId - Policy Identifier. + * @returns {string} - Url for the Global Policy view. + */ + const getViewUrl = (policyId: string) => { + return `/global-policies/${policyId}/view`; + }; + + /** + * Sorts an array of Policy objects by their display names in ascending order. + * @param {Policy[]} policies - An array of Policy objects to be sorted. + * @returns {Policy[]} - The sorted array of Policy objects. + */ + policies?.sort((a: Policy, b: Policy) => a.displayName.localeCompare(b.displayName)); + + + const policiesList = policies; + + /** + * Dialog box (Modal or Pop up) which as for the confirmation to delete. + * @returns {JSX.Element} - Delete Dialog. + */ + const deleteDialog = () => { + return ( + + + + + + + + {selectedPolicyName}? + + + + + + + + ); + } + + /** + * Dialog box (Modal or Pop up) which as for the confirmation to deploy. + * @param {string} policyID - Policy ID. + * @param {string[]} deployingGatewayList - Deploying Gateway List. + * @returns {JSX.Element} - Delete Dialog. + */ + const deployDialog = (policyID: string, deployingGatewayList: string[]) => { + return ( + + + + + + + + + + + + + + + ); + } + + /** + * Dialog box (Modal or Pop up) which as for the confirmation to deploy. + * @param {string} policyID - Policy ID. + * @param {string} gateway - Undeploying Gateway. + * @returns {JSX.Element} - Delete Dialog. + */ + const undeployDialog = (policyID: string, gateway: string) => { + return ( + + + + + + + + + + + + + + + ); + } + + /** + * Columns for the MUI table. + */ + const columns: MUIDataTableColumnDef[] = [ + { + name: 'id', + options: { + filter: false, + display: false, + viewColumns: false, + } + }, + { + name: 'displayName', + label: intl.formatMessage({ + id: 'Global.Policy.Listing.Table.Header.Name', + defaultMessage: 'Global Policy', + }), + options: { + customBodyRender: (value: string, tableMeta: any) => { + const policyDescription = tableMeta.rowData[3]; + return ( +
+ + + {value} + + + + + + + + + +
+ ); + } + } + }, + /** + * Deployed Gateway Column. + */ + { + name: 'appliedGatewayLabels', + label: intl.formatMessage({ + id: 'Deployed.Gateway.Listing.Table.Header.Name', + defaultMessage: 'Deployed Gateways', + }), + options: { + customBodyRender: (value: string[] | undefined, tableMeta: any) => { + const policyId = tableMeta.rowData[0]; + + const handleUndeployClick = (gateway: string) => { + setDeployingGateway(gateway); + setIsUndeployDialogOpen(true); + } + + if (value && value.length > 0) { + return ( +
+ {value.slice().sort((a, b) => + a.localeCompare(b, undefined, { sensitivity: 'base' })).map((gateway: string) => + ( + <> + handleUndeployClick(gateway)} + deleteIcon={ + !isRestricted(['apim:gateway_policy_manage']) + ? : <>} + /> + ))} + {undeployDialog(policyId, deployingGateway)} +
+ ); + } else { + return ( +

+ +

+ ); + } + } + } + }, + /** + * Description Column. + */ + { + name: 'description', + label: intl.formatMessage({ + id: 'Deployed.Gateway.Listing.Table.Header.Description', + defaultMessage: 'Description', + }), + options: { + display: false, + } + }, + /** + * Action Column. + */ + { + name: intl.formatMessage({ + id: 'Actions', + defaultMessage: 'Actions', + }), + options: { + customBodyRender: (value: any, tableMeta: any) => { + const policyId = tableMeta.rowData[0]; + const policyName = tableMeta.rowData[1]; + + /** + * Handle deletion. If verifications are passed, it will open the dialog box for the confirmation. + */ + const handleDeleteClick = () => { + /** + * If there is active depoloyments, we need to block the deletion. + */ + const appliedGatewayList = getAppliedGatewayLabelsById(policyId); + if (appliedGatewayList.length > 0){ + APIMAlert.error((appliedGatewayList.length === 1) ? + intl.formatMessage({ + id: 'Active.Deployment.Available', + defaultMessage: 'An active deployment is available', + }) + : intl.formatMessage({ + id: 'Active.Deployments.Available', + defaultMessage: 'Active deployments are available', + })); + } + else { + setSelectedPolicyId(policyId); + setSelectedPolicyName(policyName); + setIsDeleteDialogOpen(true); + } + }; + if (!isRestricted(['apim:gateway_policy_manage'])){ + return ( + + + <> + + {/** + * Dialog box (Modal or Pop up) which as for the confirmation to delete. + */} + {deleteDialog()} + + + ); + } + else { + return ( + + + + ); + } + }, + filter: false, + sort: false, + }, + }, + ]; + + /** + * Options for the MUI table. + */ + const options: MUIDataTableOptions = { + filterType: 'multiselect', + selectableRows: 'none', + filter: false, + sort: false, + print: false, + download: false, + viewColumns: false, + rowsPerPageOptions: [5, 10, 25, 50, 100], + expandableRows: !isRestricted(['apim:gateway_policy_manage']), + expandableRowsHeader: false, + expandableRowsOnClick: false, + renderExpandableRow: (rowData, rowMeta) => { + /** + * Expandable area where you can deploy and undeploy. + */ + const gatewayList = environments.map((env: any) => { + return env.name; + }); + const rowIndex = rowMeta.dataIndex; + + /** + * Expanded row's policy information. + */ + const policy = policiesList[rowIndex]; + /** + * Gateways which are deployed by any policy. These cannot be deployed again. + */ + const allDepoloyedGateways = getAllDepoloyedGateways(); + /** + * Gateways except the ones which are already deployed. + */ + const gatewaysWithoutAppliedLabels = + gatewayList.filter((item) => !policy.appliedGatewayLabels.includes(item)); + const deployableGateways = + gatewaysWithoutAppliedLabels.filter((item) => !allDepoloyedGateways.includes(item)); + + const handleSelectChange = (id: string) => ( + event: React.ChangeEvent<{}>, + value: string[], + ) => { + const newSelectedGateways = selectedGateways.map((data) => { + if (data.id === id) { + return { + ...data, + gatewayLabels: value, + }; + } + return data; + }); + setSelectedGateways(newSelectedGateways); + }; + + return ( + + + + + + + + + a.localeCompare(b, undefined, { sensitivity: 'base' }))} + value={getSelectedGatewayLabelsById(policy.id)} + onChange={handleSelectChange(policy.id)} + renderInput={(params) => ( + + )} + /> + + + + + + + + + + + + + {deployDialog(policy.id, getSelectedGatewayLabelsById(policy.id))} + + + + + ); + }, + }; + + /** + * If there are no policies, then show the onboarding page. + */ + if (policies && policies.length === 0 && !loading) { + return ( + + } + subTitle={ + + } + > + + )} + iconName={globalPolicyAddIcon} + /> + + ); + } + + if (loading) { + return ; + } + + if (notFound || !policies) { + return ; + } + + /** + * MUI Table for the policies. + */ + return ( +
+ + + + + + } + placement='bottom-start' + > + + + + + {!isRestricted(['apim:gateway_policy_manage']) + ? + + + :null} + + + + +
+ ); +}; + +export default Listing; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalPolicyContext.js b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalPolicyContext.js new file mode 100644 index 00000000000..fe48f86574d --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalPolicyContext.js @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { useContext } from 'react'; + +const GlobalPolicyContext = React.createContext({}); +export const useGlobalPolicyContext = () => useContext(GlobalPolicyContext); +export const GlobalPolicyContextProvider = GlobalPolicyContext.Provider; +export default GlobalPolicyContext; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/DraggablePolicyCard.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/DraggablePolicyCard.tsx new file mode 100644 index 00000000000..5c280580f59 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/DraggablePolicyCard.tsx @@ -0,0 +1,189 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { useState, CSSProperties, useMemo } from 'react'; +import { makeStyles } from '@material-ui/core'; +import Avatar from '@material-ui/core/Avatar'; +import Box from '@material-ui/core/Box'; +import Tooltip from '@material-ui/core/Tooltip'; +import { Link } from 'react-router-dom'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; +import Utils from 'AppData/Utils'; +import VisibilityIcon from '@material-ui/icons/Visibility'; +import IconButton from '@material-ui/core/IconButton'; +import { FormattedMessage } from 'react-intl'; +import { useDrag } from 'react-dnd'; +import type { Policy } from '../Types'; + +const useStyles = makeStyles(() => ({ + policyCardText: { + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + }, + listItem: { + maxHeight: '100%', + overflow: 'auto', + }, + policyActions: { + visibility: 'hidden', + '&:hover': { + visibility: 'inherit', + }, + }, +})); + +const style: CSSProperties = { + border: '2px solid', + marginTop: '0.4rem', + cursor: 'move', + borderRadius: '0.3em', +}; + +interface DraggablePolicyCardProps { + policyObj: Policy; + showCopyIcon?: boolean; + isLocalToAPI: boolean; + fetchPolicies: () => void; +} + +/** + * Renders a single draggable policy block. + * @param {any} DraggablePolicyCardProps - Input props from parent components. + * @returns {TSX} - Draggable Policy card UI. + */ +const DraggablePolicyCard: React.FC = ({ + policyObj, + showCopyIcon, + isLocalToAPI, +}) => { + const [hovered, setHovered] = useState(false); + const classes = useStyles(); + /** + * React DnD Library has been used here. + * React DnD hook to make the policy card draggable. + */ + const [{ isDragging }, drag] = useDrag( + () => ({ + type: `policyCard-${policyObj.id}`, + item: { droppedPolicy: policyObj }, + options: { + dropEffect: showCopyIcon ? 'copy' : 'move', + }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }), + [showCopyIcon], + ); + const containerStyle = useMemo( + () => ({ + ...style, + opacity: isDragging ? 0.4 : 1, + borderColor: Utils.stringToColor(policyObj.displayName), + width: '100%', + }), + [isDragging], + ); + + /** + * Policy List of the Global Policies component only contains global policies (No policies local to API). + * Policy Lists each single Policy's isAPISpecific will be false in that case. + * This additional layer is added to avoid rendering if something went wrong. + */ + if (isLocalToAPI) { + return null; + } + + return ( + <> + +
+ setHovered(true)} + onMouseOut={() => setHovered(false)} + > + + + {Utils.stringAvatar( + policyObj.displayName.toUpperCase(), + )} + + + + + + + + + + } + > + + + + + + + +
+
+ + ); +}; + +export default DraggablePolicyCard; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/General.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/General.tsx new file mode 100644 index 00000000000..9ae3d8d8ed7 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/General.tsx @@ -0,0 +1,457 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { useState, FC, useContext } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { + Grid, + Typography, + Button, + TextField, + CircularProgress, + Box, + FormControlLabel, + Checkbox, + Select, + InputLabel, + FormControl, + FormHelperText, +} from '@material-ui/core'; +import Alert from 'AppComponents/Shared/Alert'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { Progress } from 'AppComponents/Shared'; +import { PolicySpec, GlobalPolicy, AttachedPolicy, Policy, PolicySpecAttribute } from '../Types'; +import GlobalPolicyContext from "../GlobalPolicyContext"; + +const useStyles = makeStyles(theme => ({ + resetBtn: { + display: 'flex', + justifyContent: 'right', + alignItems: 'center', + }, + btn: { + marginRight: '1em', + }, + drawerInfo: { + marginBottom: '1em', + }, + mandatoryStar: { + color: theme.palette.error.main, + marginLeft: theme.spacing(0.1), + }, + formControl: { + width: '80%', + }, +})); + +interface GeneralProps { + policyObj: AttachedPolicy | null; + setDroppedPolicy?: React.Dispatch>; + currentFlow: string; + target: string; + verb: string; + globalPolicy: GlobalPolicy; + policySpec: PolicySpec; + handleDrawerClose: () => void; + isEditMode: boolean; +} + +/** + * This component renders the form of adding a global policy as a global level policy. + * @param {GeneralProps} props - The props passed to the component. + * @returns {TSX} - Returns the JSX element. + */ +const General: FC = ({ + policyObj, + setDroppedPolicy, + currentFlow, + globalPolicy, + policySpec, + handleDrawerClose, + isEditMode, +}) => { + const intl = useIntl(); + const classes = useStyles(); + const [saving, setSaving] = useState(false); + const initState: any = {}; + const { updateGlobalOperations } = useContext(GlobalPolicyContext); + policySpec.policyAttributes.forEach(attr => { initState[attr.name] = null }); + const [state, setState] = useState(initState); + + if (!policyObj) { + return + } + + const onInputChange = (event: any, specType: string) => { + if (specType.toLowerCase() === 'boolean') { + setState({ ...state, [event.target.name]: event.target.checked }); + } else if ( + specType.toLowerCase() === 'string' + || specType.toLowerCase() === 'integer' + || specType.toLowerCase() === 'enum' + ) { + setState({ ...state, [event.target.name]: event.target.value }); + } + } + + const getValueOfPolicyParam = (policyParamName: string) => { + return globalPolicy.parameters[policyParamName]; + } + + /** + * This function is triggered when the form is submitted for save. + * This handle the dynamic nature of the form, where the form fields are generated based on the policy spec. + * @param {React.FormEvent} event - Form submit event. + */ + const submitForm = async (event: React.FormEvent) => { + event.preventDefault(); + setSaving(true); + const updateCandidates: any = {}; + Object.keys(state).forEach((key) => { + const value = state[key]; + const attributeSpec = policySpec.policyAttributes.find( + (attribute: PolicySpecAttribute) => attribute.name === key, + ); + if (value === null && getValueOfPolicyParam(key) && getValueOfPolicyParam(key) !== '') { + updateCandidates[key] = getValueOfPolicyParam(key); + } else if (value === null && attributeSpec?.defaultValue && attributeSpec?.defaultValue !== null) { + updateCandidates[key] = attributeSpec.defaultValue; + } else { + updateCandidates[key] = value; + } + }); + + const globalPolicyToSave = {...globalPolicy}; + globalPolicyToSave.parameters = updateCandidates; + + updateGlobalOperations(globalPolicyToSave, currentFlow); + + if (setDroppedPolicy) setDroppedPolicy(null); + setSaving(false); + handleDrawerClose(); + }; + + /** + * Function to get the error string, if there are any errors. Empty string to indicate the absence of errors. + * @param {PolicySpecAttribute} specInCheck - The policy attribute that needs to be checked for any errors. + * @returns {string} - String with the error message, where empty string indicates that there are no errors. + */ + const getError = (specInCheck: PolicySpecAttribute) => { + let error = ''; + const value = state[specInCheck.name]; + if (value !== null) { + if (specInCheck.required && value === '') { + error = intl.formatMessage({ + id: 'Apis.Details.Policies.AttachedPolicyForm.General.required.error', + defaultMessage: 'Required field is empty', + }); + } else if ( + value !== '' && + specInCheck.validationRegex && + !(!specInCheck.validationRegex || specInCheck.validationRegex === '') + ) { + // To check if the regex is a valid regex + try { + if (!new RegExp(specInCheck.validationRegex).test(value)) { + error = intl.formatMessage({ + id: 'Apis.Details.Policies.AttachedPolicyForm.General.regex.error', + defaultMessage: 'Please enter a valid input', + }); + } + } catch { + Alert.error(intl.formatMessage({ + id: 'Error.Validating.Regex', + defaultMessage: 'Error while validating the regex', + })); + } + } + } + return error; + } + + const getValue = (spec: PolicySpecAttribute) => { + const specName = spec.name; + const previousVal = getValueOfPolicyParam(specName); + if (state[specName] !== null) { + return state[specName]; + } else if (previousVal !== null && previousVal !== undefined) { + if (spec.type.toLowerCase() === 'integer') return parseInt(previousVal, 10); + else if (spec.type.toLowerCase() === 'boolean') return (previousVal.toString() === 'true'); + else return previousVal; + } else if (spec.defaultValue !== null && spec.defaultValue !== undefined) { + if (spec.type.toLowerCase() === 'integer') return parseInt(spec.defaultValue, 10); + else if (spec.type.toLowerCase() === 'boolean') return (spec.defaultValue.toString() === 'true'); + else return spec.defaultValue; + } else { + return ''; + } + } + + /** + * Reset the input fields + */ + const resetAll = () => { + setState(initState); + } + + /** + * Function to check whether there are any errors in the form. + * If there are errors, we disable the save button. + * @returns {boolean} - Boolean value indicating whether or not the form has any errors. + */ + const formHasErrors = () => { + let formHasAnError = false; + policySpec.policyAttributes.forEach((spec) => { + if(getError(spec) !== '') { + formHasAnError = true + } + }) + return formHasAnError; + } + + /** + * Function to check if the form content is in state that needs to be saved. + * @returns {boolean} - Whether or not the save button should be disabled. + */ + const isSaveDisabled = () => { + if (!isEditMode) { + let isDisabled = false; + policySpec.policyAttributes.forEach((spec) => { + if (spec.type !== 'Boolean') { + const currentState = state[spec.name]; + const currentVal = getValue(spec); + if (spec.required && !(currentState || currentVal)) { + isDisabled = true; + } + } + }); + return isDisabled; + } else { + let isDisabled = true; + policySpec.policyAttributes.forEach((spec) => { + if (spec.type !== 'Boolean') { + const currentState = state[spec.name]; + if (currentState !== null) { + isDisabled = false; + } + } else { + const currentState = state[spec.name]; + if ( + currentState !== null && + (currentState.toString() === 'true' || + currentState.toString() === 'false') + ) { + isDisabled = false; + } + } + }); + return isDisabled; + } + }; + + const hasAttributes = policySpec.policyAttributes.length !== 0; + const resetDisabled = Object.values(state).filter((value: any) => + (value !== null && (value.toString() !== 'true' || value.toString() !== 'false')) || !!value + ).length === 0; + + if (!policySpec) { + return + } + + return ( + +
+ + + {hasAttributes && ( +
+ +
+ )} +
+ + + + + {policySpec.description ? ( + + ) : ( + + )} + +
+
+ {/** + * This will render the dynamical form fields based on the policy spec. + */} + {policySpec.policyAttributes && policySpec.policyAttributes.map((spec: PolicySpecAttribute) => ( + + + {/* When the attribute type is string or integer */} + {(spec.type.toLowerCase() === 'string' + || spec.type.toLowerCase() === 'integer') && ( + + {spec.displayName} + {spec.required && ( + * + )} + + )} + helperText={getError(spec) === '' ? spec.description : getError(spec)} + error={getError(spec) !== ''} + variant='outlined' + name={spec.name} + type={spec.type.toLowerCase() === 'integer' ? 'number' : 'text'} + value={getValue(spec)} + onChange={(e: any) => onInputChange(e, spec.type)} + fullWidth + /> + )} + + {/* When the attribute type is enum */} + {spec.type.toLowerCase() === 'enum' && ( + <> + + + <> + {spec.displayName} + {spec.required && ( + * + )} + + + + + {getError(spec) === '' ? spec.description : getError(spec)} + + + + )} + + {/* When attribute type is boolean */} + {spec.type.toLowerCase() === 'boolean' && ( + onInputChange(e, spec.type)} + name={spec.name} + color='primary' + /> + } + label={( + <> + {spec.displayName} + {spec.required && ( + * + )} + + )} + /> + )} + + ))} + + + + +
+
+
+ ); +}; + + +export default General; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/Policies.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/Policies.tsx new file mode 100644 index 00000000000..a951c735de6 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/Policies.tsx @@ -0,0 +1,611 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { Card, CardContent, makeStyles, Typography } from '@material-ui/core'; +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; +import Alert from 'AppComponents/Shared/Alert'; +import TextField from '@material-ui/core/TextField'; +import React, { useState, useEffect, useMemo, FC } from 'react'; +import Paper from '@material-ui/core/Paper'; +import Box from '@material-ui/core/Box'; +import Icon from '@material-ui/core/Icon'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import { DndProvider } from 'react-dnd'; +import { useIntl, FormattedMessage } from 'react-intl'; +import { arrayMove } from '@dnd-kit/sortable'; +import API from 'AppData/api'; +import { Progress } from 'AppComponents/Shared'; +import cloneDeep from 'lodash.clonedeep'; +import { useHistory, Link } from 'react-router-dom'; +import PolicyList from './PolicyList'; +import type { Policy, PolicySpec, GlobalLevelPolicy } from '../Types'; +import { GlobalPolicyContextProvider } from '../GlobalPolicyContext'; +import PolicyPanel from './PolicyPanel'; +import { uuidv4 } from '../Utils'; + +const useStyles = makeStyles((theme) => ({ + textField: { + backgroundColor: 'white', + }, + titleLink: { + color: theme.palette.primary.dark, + marginRight: theme.spacing(1), + }, + titleWrapper: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + marginBottom: theme.spacing(3), + }, + operationListingBox: { + display: 'flex', + overflowY: 'scroll', + }, + paper: { + display: 'flex', + flexDirection: 'column', + flex: 1 + }, + button: { + width: '200px' + } +})); + +interface PolicyProps { + isCreateNew: boolean; + policyID: string | null; + disabled: boolean; +} + +/** + * Renders the Global Policy management page. + * @param {boolean} isCreateNew - This value is true if form is for create new and false for edit. + * @param {string} policyID - This value is to indentify the policy (Null if creating a new one). + * @param {boolean} disabled - This value is to disable the form (True if viewing the policy). + * @returns {TSX} - Policy management page to render. + */ +const Policies: FC = ({ + isCreateNew, + policyID, + disabled, +}) => { + const classes = useStyles(); + const history = useHistory(); + const [loading, setLoading] = useState(false); + const [policies, setPolicies] = useState(null); + const [allPolicies, setAllPolicies] = useState(null); + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [appliedGatewayLabels, setAppliedGatewayLabels] = useState([]); + const intl = useIntl(); + + /** + * Global level policy mapping. It will be initially empty. + */ + const initGlobalLevelPolicy: GlobalLevelPolicy = { + request: [], + response: [], + fault: [], + } + const getInitGlobalLevelPoliciesState = () => { + return initGlobalLevelPolicy; + }; + const [globalLevelPolicies, + setGlobalLevelPolicies] = useState(getInitGlobalLevelPoliciesState()); + + /** + * Fetches all common policies to front-end to show in the Policy List. + */ + const fetchPolicies = () => { + const commonPoliciesPromise = API.getCommonOperationPolicies(); + Promise.all([commonPoliciesPromise]).then((response) => { + const [commonPoliciesResponse] = response; + const commonPolicies = commonPoliciesResponse.body.list; + /** + * Similar to policies in Global Policies scenario. + * But as we are reusing PoliciesExpansion, both PolicySpec[] and Policy[] types are required. + */ + setAllPolicies(commonPolicies); + + commonPolicies.sort( + (a: Policy, b: Policy) => a.name.localeCompare(b.name)) + + setPolicies(commonPolicies); + }).catch(() => { + Alert.error(intl.formatMessage({ + id: 'Error.Retrieve.Policy.List', + defaultMessage: 'Error occurred while retrieving the policy list', + })); + }); + } + + /** + * Assign UUIDs to the input. + * Each Policy operation requires a uuid to identify (Since two operations can have same policy ID). + * This requires for the UI so assigning this is required after getting data from the backend. + * @param {any} input - Policy List. + * @returns {any} - Policy list which has UUIDs for each operations. + */ + const assignUUIDs = (input: any) => { + const inputResponse: any = cloneDeep(input); + if (inputResponse && inputResponse.policyMapping) { + const { request, response, fault } = inputResponse.policyMapping; + if (request) { + inputResponse.policyMapping.request = request.map((item: any) => ({ + ...item, + uuid: uuidv4(), + })); + } + if (response) { + inputResponse.policyMapping.response = response.map((item: any) => ({ + ...item, + uuid: uuidv4(), + })); + } + if (fault) { + inputResponse.policyMapping.fault = fault.map((item: any) => ({ + ...item, + uuid: uuidv4(), + })); + } + } + return inputResponse; + }; + + /** + * Remove UUIDs from the input. + * Each Policy operation has a uuid to identify (Since two operations can have same policy ID). + * This requires for the UI so removing this is required before sending to backend to overcome backend validation. + * @param {any} input - Global Level Policies which has UUIDs for each operations. + * @returns {any} - Global Level Policies which does not have UUIDs for each operations. + */ + const removeUUIDs = (input: any) => { + const inputWithoutUUIDs: any = cloneDeep(input); + if (inputWithoutUUIDs) { + const { request, response, fault } = inputWithoutUUIDs; + if (request) { + inputWithoutUUIDs.request = request.map((item: any) => { + const { uuid, ...rest } = item; + return rest; + }); + } + if (response) { + inputWithoutUUIDs.response = response.map((item: any) => { + const { uuid, ...rest } = item; + return rest; + }); + } + if (fault) { + inputWithoutUUIDs.fault = fault.map((item: any) => { + const { uuid, ...rest } = item; + return rest; + }); + } + } + return inputWithoutUUIDs; + }; + + /** + * If this is the editng page (isCreateNew:False), fetching the data from backend. + */ + const fetchGlobalPolicyByID = () => { + setLoading(true); + const gatewayPolicyMappingId = String(policyID); + + /** + * Backend Call and handle the response. + */ + const promisedPolicy = API.getGatewayPolicyMappingContentByPolicyMappingId(gatewayPolicyMappingId); + promisedPolicy + .then((response) => { + const responseUpdated = assignUUIDs(response.body); + setGlobalLevelPolicies(responseUpdated.policyMapping); + setDescription(responseUpdated.description); + setName(responseUpdated.displayName); + setAppliedGatewayLabels(responseUpdated.appliedGatewayLabels); + }) + .catch(() => { + Alert.error(intl.formatMessage({ + id: 'Error.Retrieve.Policy', + defaultMessage: 'Error occurred while retrieving the policy', + })); + }) + .finally(() => { + setLoading(false); + }); + } + + useEffect(() => { + fetchPolicies(); + if (!isCreateNew && policyID){ + fetchGlobalPolicyByID(); + } + }, []); + + /** + * A Context Operation for Policy Panel UI. + * Triggers as we click delete icon in a drag`n`droped the policy. + * @param {string} uuid - Operation uuid. + * @param {string} currentFlow - Which flow needs to be udpated: request, response or fault. + */ + const deleteGlobalOperation = (uuid: string, currentFlow: string) => { + const newGlobalLevelPolicies: any = cloneDeep(globalLevelPolicies); + const index = newGlobalLevelPolicies[currentFlow].map((p: any) => p.uuid).indexOf(uuid); + newGlobalLevelPolicies[currentFlow].splice(index, 1); + setGlobalLevelPolicies(newGlobalLevelPolicies); + } + + /** + * A Context Operation for Policy Panel UI. + * Function to rearrange the API Operation ordering. + * @param {string} oldIndex - Original index of the policy. + * @param {string} newIndex - New index of the policy. + * @param {string} currentFlow - Which flow needs to be udpated: request, response or fault. + */ + const rearrangeGlobalOperations = ( + oldIndex: number, newIndex: number, currentFlow: string, + ) => { + const newAPIPolicies: any = cloneDeep(globalLevelPolicies); + const policyArray = newAPIPolicies[currentFlow]; + newAPIPolicies[currentFlow] = arrayMove(policyArray, oldIndex, newIndex); + setGlobalLevelPolicies(newAPIPolicies); + }; + + /** + * A Context Operation for Policy Panel UI. + * Triggers as we saved a drag`n`droped policy or edit a already dragged one. + * @param {any} updatedOperation - Saved info as parameters: {headerName: <>, headerValue: <>}, policyId: <>, etc. + * @param {string} currentFlow - Folow request/response/fault. + */ + const updateGlobalOperations = ( + updatedOperation: any, currentFlow: string, + ) => { + const newGlobalLevelPolicies: any = cloneDeep(globalLevelPolicies); + /** + * Check whether the policy operation already exists. + */ + const flowPolicy = (newGlobalLevelPolicies)[currentFlow].find( + (p: any) => + p.policyId === updatedOperation.policyId && + p.uuid === updatedOperation.uuid, + ); + + if (flowPolicy) { + /** + * Edit the already dragged and dropped policy. + */ + flowPolicy.parameters = { ...updatedOperation.parameters }; + } else { + /** + * Save the newly dragged and dropped policy. + */ + const uuid = uuidv4(); + (newGlobalLevelPolicies)[currentFlow].push({ ...updatedOperation, uuid }); + } + setGlobalLevelPolicies(newGlobalLevelPolicies); + } + + /** + * Function to validate before saving or updating. + * @returns {boolean} - True if all the required fields are filled. + */ + const validate = () => { + let isValidate = true; + if (name === '') { + Alert.error(intl.formatMessage({ + id: 'Policy.Name.Cannot.Be.Empty', + defaultMessage: 'Policy name cannot be empty', + })); + isValidate = false; + } + if (description === '') { + Alert.error(intl.formatMessage({ + id: 'Policy.Description.Cannot.Be.Empty', + defaultMessage: 'Policy description cannot be empty', + })); + isValidate = false; + } + if ((!globalLevelPolicies.request || globalLevelPolicies.request.length === 0) && + (!globalLevelPolicies.response || globalLevelPolicies.response.length === 0) && + (!globalLevelPolicies.fault || globalLevelPolicies.fault.length === 0)) { + Alert.error(intl.formatMessage({ + id: 'Policy.Mapping.Cannot.Be.Empty', + defaultMessage: 'Policy mapping cannot be empty', + })); + isValidate = false; + } + return isValidate; + } + + /** + * Function to save a policy mapping. + * Triggers if we click save button. + */ + const save = () => { + setLoading(true); + + if (validate()){ + /** + * Remove UUIDs before sending to backend. + * If not, as backend is not expecting UUIDs, backend validation will fail. + */ + const policyMapping = removeUUIDs(globalLevelPolicies); + + /** + * Backend Call and handle the response. + */ + const requestBody = { + "id": uuidv4(), + "policyMapping": policyMapping, + "description": description, + "displayName": name, + "appliedGatewayLabels": [] + }; + const promise = API.addGatewayPoliciesToFlows(requestBody); + promise + .then((response) => { + setLoading(false); + if (response.status === 200 || response.status === 201) { + Alert.success(intl.formatMessage({ + id: 'Policy.Mapping.Added.Successfully', + defaultMessage: 'Policy mapping added successfully', + })); + history.goBack(); + } + else { + Alert.error(intl.formatMessage({ + id: 'Adding.Policy.Mapping.Error', + defaultMessage: 'Error occurred while adding the policy mapping', + })); + } + }) + .catch(() => { + Alert.error(intl.formatMessage({ + id: 'Adding.Policy.Mapping.Error', + defaultMessage: 'Error occurred while adding the policy mapping', + })); + }) + } + setLoading(false); + } + + /** + * Function to update a policy mapping. + * Triggers if we click update button. + */ + const update = () => { + setLoading(true); + + if (validate()){ + /** + * Remove UUIDs before sending to backend. + * If not, as backend is not expecting UUIDs, backend validation will fail. + */ + const policyMapping = removeUUIDs(globalLevelPolicies); + + /** + * Backend Call and handle the response. + */ + const requestBody = { + "id": policyID, + "policyMapping": policyMapping, + "description": description, + "displayName": name, + "appliedGatewayLabels": appliedGatewayLabels + }; + const gatewayPolicyMappingId = String(policyID); + const promise = API.updateGatewayPoliciesToFlows(gatewayPolicyMappingId, requestBody); + promise + .then((response) => { + if (response.status === 200 || response.status === 201) { + setLoading(false); + Alert.success(intl.formatMessage({ + id: 'Policy.Mapping.Update.Success', + defaultMessage: 'Policy mapping updated successfully', + })); + history.goBack(); + } + else { + Alert.error(intl.formatMessage({ + id: 'Policy.Mapping.Update.Error', + defaultMessage: 'Error occurred while updating the policy mapping', + })); + } + }) + .catch(() => { + Alert.error(intl.formatMessage({ + id: 'Policy.Mapping.Update.Error', + defaultMessage: 'Error occurred while updating the policy mapping', + })); + }) + } + setLoading(false); + } + + /** + * To memoize the value passed into GlobalPolicyContextProvider. + */ + const providerValue = useMemo( + () => ({ + globalLevelPolicies, + updateGlobalOperations, + deleteGlobalOperation, + rearrangeGlobalOperations + }), + [ + globalLevelPolicies, + updateGlobalOperations, + deleteGlobalOperation, + rearrangeGlobalOperations + ], + ); + + /** + * Handle Name field changes. + * @param {any} event changing event. + */ + const handleNameChange = (event: any) => { + setName(event.target.value); + }; + + /** + * Handle Description field changes. + * @param {any} event changing event. + */ + const handleDescriptionChange = (event: any) => { + setDescription(event.target.value); + }; + + /** + * Loading screen if loading is true or there is no policies yet. + */ + if (!policies || loading) { + return + } + + return ( + + + + {/** + * Breadcrumb Navigation. + */} + +
+ + + + + + keyboard_arrow_right + + {isCreateNew ? + + : + + } + +
+
+ + {/** + * Name & Description Fields. + */} + + + + + + + + + {/** + * Left side panel where we can drop policies. + */} + + + + + + + + + + + + + {/** + * Right side policy list. + */} + + +
+ + {/** + * Edit & Save buttons. + */} + + + +
+
+ ); +}; + +export default Policies; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PoliciesSection.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PoliciesSection.tsx new file mode 100644 index 00000000000..e1fd751b063 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PoliciesSection.tsx @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { Grid } from '@material-ui/core'; +import React, { FC } from 'react'; +import Box from '@material-ui/core/Box'; +import type { Policy, PolicySpec } from '../Types'; +import PoliciesExpansion from '../SharedComponents/PoliciesExpansion'; + +interface PolicySectionProps { + allPolicies: PolicySpec[] | null; + policyList: Policy[]; +} + +/** + * Renders the policy management page (Basically the PoliciesExpansion component). + * @returns {TSX} - Policy management page to render. + */ +const PoliciesSection: FC = ({ + allPolicies, + policyList, +}) => { + const borderColor = ''; + + return ( + + + + + + + + + + ); +}; + +export default PoliciesSection; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyConfigurationEditDrawer.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyConfigurationEditDrawer.tsx new file mode 100644 index 00000000000..6cf06e52487 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyConfigurationEditDrawer.tsx @@ -0,0 +1,164 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC, useEffect, useContext, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import Box from '@material-ui/core/Box'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import { + Drawer, + makeStyles, + ListItemIcon, + Theme, + Typography, +} from '@material-ui/core'; +import IconButton from '@material-ui/core/IconButton'; +import { Settings, Close } from '@material-ui/icons'; +import Divider from '@material-ui/core/Divider'; +import General from './General'; +import type { PolicySpec, GlobalPolicy, AttachedPolicy } from '../Types'; +import GlobalPolicyContext from '../GlobalPolicyContext'; + +const useStyles = makeStyles((theme: Theme) => ({ + drawerPaper: { + backgroundColor: 'white', + width: '30%', + }, + iconSize: { + height: '1.2em', + width: '1.2em', + color: theme.palette.grey[700], + }, +})); + +interface PolicyConfigurationEditDrawerProps { + policyObj: AttachedPolicy | null; + currentFlow: string; + target: string; + verb: string; + drawerOpen: boolean; + setDrawerOpen: React.Dispatch>; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; +} + +/** + * Renders the policy configuration edit drawer. (Right drawer for editing an added policy). + * @param {JSON} props - Input props from parent components. + * @returns {TSX} - Right drawer for policy configuration. + */ +const PolicyConfigurationEditDrawer: FC = ({ + policyObj, + currentFlow, + target, + verb, + allPolicies, + drawerOpen, + setDrawerOpen, +}) => { + const classes = useStyles(); + const { globalLevelPolicies } = useContext(GlobalPolicyContext); + const [policySpec, setPolicySpec] = useState(); + + useEffect(() => { + /** + * Find the right policy spec (attributes + etc) using the policy Object ID. + * If the policy is already deleted from common policies, it will not be found in Policy Section. + */ + if (policyObj) { + setPolicySpec( + allPolicies?.find( + (policy: PolicySpec) => policy.id === policyObj.id, + ), + ); + setDrawerOpen(true); + } + }, [policyObj]); + + /** + * Find the editing operation flow policy + */ + const operationFlowPolicy = (globalLevelPolicies)[ + currentFlow + ].find((policy: any) => policy.uuid === policyObj?.uniqueKey); + + const globalPolicy: GlobalPolicy = operationFlowPolicy || { + policyName: policyObj?.name, + policyId: policyObj?.id, + policyVersion: policyObj?.version, + parameters: {}, + }; + + const handleDrawerClose = () => { + setDrawerOpen(false); + }; + + return ( + + + + + + + + + + + } + /> + + + + + + + + + {policySpec && ( + + )} + + + ); +}; + +export default PolicyConfigurationEditDrawer; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyConfiguringDrawer.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyConfiguringDrawer.tsx new file mode 100644 index 00000000000..594da2a2c73 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyConfiguringDrawer.tsx @@ -0,0 +1,177 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC, useEffect, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import Box from '@material-ui/core/Box'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import { + Drawer, + makeStyles, + ListItemIcon, + Theme, + Typography, +} from '@material-ui/core'; +import IconButton from '@material-ui/core/IconButton'; +import { Settings, Close } from '@material-ui/icons'; +import Divider from '@material-ui/core/Divider'; +import { Progress } from 'AppComponents/Shared'; +import General from './General'; +import { PolicySpec, GlobalPolicy, Policy } from '../Types'; + +const useStyles = makeStyles((theme: Theme) => ({ + drawerPaper: { + backgroundColor: 'white', + width: '30%', + }, + iconSize: { + height: '1.2em', + width: '1.2em', + color: theme.palette.grey[700], + }, +})); + +interface PolicyConfiguringDrawerProps { + policyObj: Policy | null; + setDroppedPolicy: React.Dispatch>; + currentFlow: string; + target: string; + verb: string; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; +} + +/** + * Renders the policy configuring drawer. (Right drawer for adding a policy) + * @param {JSON} props - Input props from parent components. + * @returns {TSX} - Right drawer for policy configuration. + */ +const PolicyConfiguringDrawer: FC = ({ + policyObj, + setDroppedPolicy, + currentFlow, + target, + verb, + allPolicies, +}) => { + const classes = useStyles(); + const [drawerOpen, setDrawerOpen] = useState(!!policyObj); + const [policySpec, setPolicySpec] = useState(); + + useEffect(() => { + /** + * Find the right policy spec (attributes + etc) using the policy Object ID. + */ + if (policyObj) { + setPolicySpec( + allPolicies?.find( + (policy: PolicySpec) => policy.id === policyObj.id, + ), + ); + setDrawerOpen(true); + } + }, [policyObj]); + + if (!policySpec) { + return ; + } + + const globalPolicy: GlobalPolicy = { + policyName: policyObj?.name, + policyId: policyObj?.id, + policyVersion: policyObj?.version, + parameters: {}, + }; + + const handleDrawerClose = () => { + setDrawerOpen(false); + setDroppedPolicy(null); + }; + + /** + * Converts the PolicyObj prop of type Policy to AttachedPolicy. + * @returns {AttachedPolicy} - Returns a policy object of type AttachedPolicy. + */ + const getPolicyOfTypeAttachedPolicy = () => { + if (policyObj) { + return { + id: policyObj?.id, + name: policyObj?.name, + displayName: policyObj?.displayName, + version: policyObj?.version, + applicableFlows: policyObj?.applicableFlows, + uniqueKey: '', + }; + } else { + return null; + } + }; + + return ( + + + + + + + + + + + } + /> + + + + + + + + + + + + ); +}; + +export default PolicyConfiguringDrawer; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyList.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyList.tsx new file mode 100644 index 00000000000..62badc2907c --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyList.tsx @@ -0,0 +1,172 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { useState, FC } from 'react'; +import Paper from '@material-ui/core/Paper'; +import Box from '@material-ui/core/Box'; +import Card from '@material-ui/core/Card'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; +import CardContent from '@material-ui/core/CardContent'; +import { FormattedMessage } from 'react-intl'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core'; +import CONSTS from 'AppData/Constants'; +import type { Policy } from '../Types'; +import TabPanel from '../SharedComponents/TabPanel'; + +const useStyles = makeStyles(() => ({ + flowTabs: { + '& button': { + minWidth: 50, + }, + }, + flowTab: { + fontSize: 'smaller', + }, + paper: { + display: 'flex', + flexDirection: 'column', + flex: 1, + width: '35%', + }, + policyList: { + overflowY: 'auto', + maxHeight: '100%', + paddingRight: '20px' + } +})); + +interface PolicyListPorps { + policyList: Policy[]; + fetchPolicies: () => void; +} + +/** + * Renders the local policy list. + * @param {JSON} props - Input props from parent components. + * @returns {TSX} - List of policies local to the API segment. + */ +const PolicyList: FC = ({policyList, fetchPolicies}) => { + const classes = useStyles(); + const [selectedTab, setSelectedTab] = useState(0); // Request flow related tab is active by default + const gatewayType = CONSTS.GATEWAY_TYPE.synapse; + + return ( + + + + +
+ + + + + + + setSelectedTab(tab)} + indicatorColor='primary' + textColor='primary' + variant='standard' + aria-label='Policies local to API' + className={classes.flowTabs} + > + + + } + id='request-tab' + aria-controls='request-tabpanel' + /> + + + } + id='response-tab' + aria-controls='response-tabpanel' + /> + + + } + id='fault-tab' + aria-controls='fault-tabpanel' + /> + + + + policy.applicableFlows.includes( + 'request', + ) && + policy.supportedGateways.includes( + gatewayType, + ), + )} + index={0} + selectedTab={selectedTab} + fetchPolicies={fetchPolicies} + /> + + policy.applicableFlows.includes( + 'response', + ) && + policy.supportedGateways.includes( + gatewayType, + ), + )} + index={1} + selectedTab={selectedTab} + fetchPolicies={fetchPolicies} + /> + + policy.applicableFlows.includes('fault'), + )} + index={2} + selectedTab={selectedTab} + fetchPolicies={fetchPolicies} + /> + + +
+
+
+
+
+ ); +}; + +export default PolicyList; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyPanel.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyPanel.tsx new file mode 100644 index 00000000000..ee9409bb0e1 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/GlobalSpecificComponents/PolicyPanel.tsx @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { Box } from '@material-ui/core'; +import React, { FC } from 'react'; +import PoliciesSection from './PoliciesSection'; +import type { Policy, PolicySpec } from '../Types'; + +interface PolicyPanelProps { + children?: React.ReactNode; + allPolicies: PolicySpec[] | null; + policyList: Policy[]; +} + +/** + * Renders the policy section of the policy management page. + * This inculdes the dropping zone. The policies are rendered in the PoliciesSection component. + * @param {JSON} props - Input props from parent components. + * @returns {TSX} - Policy Panel. + */ +const PolicyPanel: FC = ({ + allPolicies, + policyList, +}) => { + + return ( + + + + ); +}; + +export default PolicyPanel; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/AttachedPolicyCard.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/AttachedPolicyCard.tsx new file mode 100644 index 00000000000..c0d8e5ec611 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/AttachedPolicyCard.tsx @@ -0,0 +1,153 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC, useContext, useState } from 'react'; +import { Alert } from 'AppComponents/Shared'; +import API from 'AppData/api.js'; +import Utils from 'AppData/Utils'; +import { FormattedMessage } from 'react-intl'; +import ApiContext from 'AppComponents/Apis/Details/components/ApiContext'; +import AttachedPolicyCardShared from 'AppComponents/Shared/PoliciesUI/AttachedPolicyCard'; +import type { AttachedPolicy, PolicySpec } from '../Types'; +import PolicyConfigurationEditDrawer from '../GlobalSpecificComponents/PolicyConfigurationEditDrawer'; +import GlobalPolicyContext from '../GlobalPolicyContext'; + +interface AttachedPolicyCardProps { + policyObj: AttachedPolicy; + currentPolicyList: AttachedPolicy[]; + setCurrentPolicyList: React.Dispatch>; + currentFlow: string; + verb: string; + target: string; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; +} + +/** + * Renders a single sortable policy card. + * @param {any} AttachedPolicyCardProps Input props from parent components. + * @returns {TSX} Sortable attached policy card UI. + */ +const AttachedPolicyCard: FC = ({ + policyObj, + currentPolicyList, + setCurrentPolicyList, + currentFlow, + verb, + target, + allPolicies, + isAPILevelPolicy, +}) => { + + const { api } = useContext(ApiContext); + const { deleteGlobalOperation } = useContext(GlobalPolicyContext); + const [drawerOpen, setDrawerOpen] = useState(false); + + + /** + * Handle policy delete + * @param {React.MouseEvent} event event + */ + const handleDelete = (event: React.MouseEvent) => { + const filteredList = currentPolicyList.filter( + (policy) => policy.uniqueKey !== policyObj.uniqueKey, + ); + const policyToDelete = currentPolicyList.find( + (policy) => policy.uniqueKey === policyObj.uniqueKey, + ); + setCurrentPolicyList(filteredList); + deleteGlobalOperation( + policyToDelete?.uniqueKey, + currentFlow, + ); + event.stopPropagation(); + event.preventDefault(); + }; + + /** + * Handle policy download + * @param {React.MouseEvent} event event + */ + const handlePolicyDownload = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + if (policyObj.isAPISpecific) { + const globalPolicyContentPromise = API.getOperationPolicyContent( + policyObj.id, + api.id, + ); + globalPolicyContentPromise + .then((globalPolicyResponse) => { + Utils.forceDownload(globalPolicyResponse); + }) + .catch(() => { + Alert.error( + , + ); + }); + } else { + const commonPolicyContentPromise = API.getCommonOperationPolicyContent( + policyObj.id, + ); + commonPolicyContentPromise + .then((commonPolicyResponse) => { + Utils.forceDownload(commonPolicyResponse); + }) + .catch(() => { + Alert.error( + , + ); + }); + } + }; + + const handleDrawerOpen = () => { + if (policyObj.id !== '') { + /** + * Drawer will only appear for policies that have an ID. + * Note that a migrated policy will have an empty string as the ID at the initial stage. + */ + setDrawerOpen(true); + } + }; + + return ( + + ); +}; + +export default AttachedPolicyCard; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/AttachedPolicyList.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/AttachedPolicyList.tsx new file mode 100644 index 00000000000..b08ffc18005 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/AttachedPolicyList.tsx @@ -0,0 +1,108 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC, useContext } from 'react'; +import { + PointerSensor, + useSensor, + useSensors, + DragEndEvent, +} from '@dnd-kit/core'; +import AttachedPolicyListShared from 'AppComponents/Shared/PoliciesUI/AttachedPolicyList'; +import AttachedPolicyCard from './AttachedPolicyCard'; + +import type { AttachedPolicy, PolicySpec } from '../Types'; +import GlobalPolicyContext from '../GlobalPolicyContext'; + + +interface AttachedPolicyListProps { + currentPolicyList: AttachedPolicy[]; + setCurrentPolicyList: React.Dispatch>; + policyDisplayStartDirection: string; + currentFlow: string; + target: string; + verb: string; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; +} + +/** + * Renders the Gateway selection section. + * @param {JSON} props Input props from parent components. + * @returns {TSX} Radio group for the API Gateway. + */ +const AttachedPolicyList: FC = ({ + currentPolicyList, + setCurrentPolicyList, + policyDisplayStartDirection, + currentFlow, + target, + verb, + allPolicies, + isAPILevelPolicy, +}) => { + const reversedPolicyList = [...currentPolicyList].reverse(); + const policyListToDisplay = (policyDisplayStartDirection === 'left') ? currentPolicyList : reversedPolicyList; + const { rearrangeGlobalOperations } = useContext(GlobalPolicyContext); + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 5, + }, + }), + ); + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (active.id !== over?.id) { + const policyListCopy = [...currentPolicyList]; + const oldIndex = policyListCopy.findIndex( + (item) => item.uniqueKey === active.id, + ); + const newIndex = policyListCopy.findIndex( + (item) => item.uniqueKey === over?.id, + ); + + rearrangeGlobalOperations( + oldIndex, + newIndex, + currentFlow, + ); + } + }; + + return ( + + ); +}; + +export default AttachedPolicyList; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/FlowArrow.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/FlowArrow.tsx new file mode 100644 index 00000000000..69d242d31ef --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/FlowArrow.tsx @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC } from 'react'; +import FlowArrowShared from 'AppComponents/Shared/PoliciesUI/FlowArrow'; + +interface FlowArrowProps { + arrowDirection: string; +} + +/** + * Flow Arrow component. + * @param {JSON} props Input props from parent components. + * @returns {TSX} Flow Arrow component. + */ +const FlowArrow: FC = ({ arrowDirection }) => { + return ( + + ); +} + +export default FlowArrow; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/PoliciesExpansion.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/PoliciesExpansion.tsx new file mode 100644 index 00000000000..f184143ad33 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/PoliciesExpansion.tsx @@ -0,0 +1,246 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { Alert } from 'AppComponents/Shared'; +import PoliciesExpansionShared from 'AppComponents/Shared/PoliciesUI/PoliciesExpansion'; +import { useIntl } from 'react-intl'; +import PolicyDropzone from './PolicyDropzone'; +import type { AttachedPolicy, Policy, PolicySpec } from '../Types'; +import FlowArrow from './FlowArrow'; +import GlobalPolicyContext from '../GlobalPolicyContext'; + +const defaultPolicyForMigration = { + id: '', + category: 'Mediation', + name: '', + displayName: '', + version: '', + description: '', + applicableFlows: [], + supportedGateways: ['Synapse'], + supportedApiTypes: [], + policyAttributes: [], + isAPISpecific: true, +}; + +interface PoliciesExpansionProps { + target: any; + verb: string; + allPolicies: PolicySpec[] | null; + isChoreoConnectEnabled: boolean; + policyList: Policy[]; + isAPILevelPolicy: boolean; +} + +const PoliciesExpansion: FC = ({ + target, + verb, + allPolicies, + isChoreoConnectEnabled, + policyList, + isAPILevelPolicy, +}) => { + /** + * Policies attached for each request, response and fault flow. + */ + const [requestFlowPolicyList, setRequestFlowPolicyList] = useState([]); + const [responseFlowPolicyList, setResponseFlowPolicyList] = useState([]); + const [faultFlowPolicyList, setFaultFlowPolicyList] = useState([]); + + /** + * Droppable policy identifier list for each request, response and fault flow. + */ + const [requestFlowDroppablePolicyList, setRequestFlowDroppablePolicyList] = useState([]); + const [responseFlowDroppablePolicyList, setResponseFlowDroppablePolicyList] = useState([]); + const [faultFlowDroppablePolicyList, setFaultFlowDroppablePolicyList] = useState([]); + + const { globalLevelPolicies } = useContext(GlobalPolicyContext); + const intl = useIntl(); + + /** + * This is where the applicable (droppable) flows are set for each policy. + */ + useEffect(() => { + const requestList = []; + const responseList = []; + const faultList = []; + for (const policy of policyList) { + if (policy.applicableFlows.includes('request')) { + requestList.push(`policyCard-${policy.id}`); + } + if (policy.applicableFlows.includes('response')) { + responseList.push(`policyCard-${policy.id}`); + } + if (policy.applicableFlows.includes('fault')) { + faultList.push(`policyCard-${policy.id}`); + } + } + setRequestFlowDroppablePolicyList(requestList); + setResponseFlowDroppablePolicyList(responseList); + setFaultFlowDroppablePolicyList(faultList); + }, [policyList]); + + /** + * In here, we are populating the attached policy list for each flow. + * This will be triggered once we saved a drag`n`droped policy. + * This comes after Policies.tsx updateGlobalOperations() method. + * This will hold data in the UI until we save the policy. + */ + useEffect(() => { + (async () => { + const apiPolicies = globalLevelPolicies; + + /** + * Populate request flow attached policy list. + */ + const requestFlowList: AttachedPolicy[] = []; + const requestFlow = apiPolicies.request; + for (const requestFlowAttachedPolicy of requestFlow) { + const { policyId, policyName, policyVersion, uuid } = + requestFlowAttachedPolicy; + if (policyId === null) { + /** + * Handling migration flow. + */ + requestFlowList.push({ + ...defaultPolicyForMigration, + name: policyName, + displayName: policyName, + applicableFlows: ['request'], + uniqueKey: uuid, + }); + } else { + const policyObj = allPolicies?.find( + (policy: PolicySpec) => + policy.name === policyName && + policy.version === policyVersion, + ); + if (policyObj) { + requestFlowList.push({ ...policyObj, uniqueKey: uuid }); + } else { + Alert.error(intl.formatMessage({ + id: 'Cannot.Find.PolicyObj.For.PolicyId', + defaultMessage: 'Cannot find policy for Id: ', + }) + policyId); + } + } + } + setRequestFlowPolicyList(requestFlowList); + + /** + * Populate response flow attached policy list. + */ + const responseFlowList: AttachedPolicy[] = []; + const responseFlow = apiPolicies.response; + for (const responseFlowAttachedPolicy of responseFlow) { + const { policyId, policyName, policyVersion, uuid } = + responseFlowAttachedPolicy; + if (policyId === null) { + /** + * Handling migration flow. + */ + responseFlowList.push({ + ...defaultPolicyForMigration, + name: policyName, + displayName: policyName, + applicableFlows: ['response'], + uniqueKey: uuid, + }); + } else { + const policyObj = allPolicies?.find( + (policy: PolicySpec) => + policy.name === policyName && + policy.version === policyVersion, + ); + if (policyObj) { + responseFlowList.push({ ...policyObj, uniqueKey: uuid }); + } else { + Alert.error(intl.formatMessage({ + id: 'Cannot.Find.PolicyObj.For.PolicyId', + defaultMessage: 'Cannot find policy for Id: ', + }) + policyId); + } + } + } + setResponseFlowPolicyList(responseFlowList); + + if (!isChoreoConnectEnabled) { + /** + * Populate fault flow attached policy list. + */ + const faultFlowList: AttachedPolicy[] = []; + const faultFlow = apiPolicies.fault; + for (const faultFlowAttachedPolicy of faultFlow) { + const { policyId, policyName, policyVersion, uuid } = + faultFlowAttachedPolicy; + if (policyId === null) { + /** + * Handling migration flow. + */ + faultFlowList.push({ + ...defaultPolicyForMigration, + name: policyName, + displayName: policyName, + applicableFlows: ['fault'], + uniqueKey: uuid, + }); + } else { + const policyObj = allPolicies?.find( + (policy: PolicySpec) => + policy.name === policyName && + policy.version === policyVersion, + ); + if (policyObj) { + faultFlowList.push({ ...policyObj, uniqueKey: uuid }); + } else { + Alert.error(intl.formatMessage({ + id: 'Cannot.Find.PolicyObj.For.PolicyId', + defaultMessage: 'Cannot find policy for Id: ', + }) + policyId); + } + } + } + setFaultFlowPolicyList(faultFlowList); + } + })(); + }, [globalLevelPolicies]); + + return ( + + ); +}; + +export default PoliciesExpansion; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/PolicyDropzone.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/PolicyDropzone.tsx new file mode 100644 index 00000000000..c0cccdba739 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/PolicyDropzone.tsx @@ -0,0 +1,90 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC, useState } from 'react'; +import { useDrop } from 'react-dnd'; +import PolicyDropzoneShared from 'AppComponents/Shared/PoliciesUI/PolicyDropzone'; +import type { AttachedPolicy, Policy, PolicySpec } from '../Types'; +import AttachedPolicyList from './AttachedPolicyList'; +import PolicyConfiguringDrawer from '../GlobalSpecificComponents/PolicyConfiguringDrawer'; + +interface PolicyDropzoneProps { + policyDisplayStartDirection: string; + currentPolicyList: AttachedPolicy[]; + setCurrentPolicyList: React.Dispatch>; + droppablePolicyList: string[]; + currentFlow: string; + target: string; + verb: string; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; +} + +/** + * Renders the dropzone which accepts policy cards that are dragged and dropped. + * @param {JSON} props Input props from parent components. + * @returns {TSX} List of policies local to the API segment. + */ +const PolicyDropzone: FC = ({ + policyDisplayStartDirection, + currentPolicyList, + setCurrentPolicyList, + droppablePolicyList, + currentFlow, + target, + verb, + allPolicies, + isAPILevelPolicy, +}) => { + const [droppedPolicy, setDroppedPolicy] = useState(null); + + /** + * React DnD Library has been used here. + * Drop handler for the dropzone. + * This will set the dropped policy to the state. + * This data will be sent to the lower level components. + */ + const [{ canDrop }, drop] = useDrop({ + accept: droppablePolicyList, + drop: (item: any) => setDroppedPolicy(item.droppedPolicy), + collect: (monitor) => ({ + canDrop: monitor.canDrop(), + }), + }); + + return ( + + ); +}; + +export default PolicyDropzone; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/TabPanel.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/TabPanel.tsx new file mode 100644 index 00000000000..540f1d5c902 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/SharedComponents/TabPanel.tsx @@ -0,0 +1,59 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC } from 'react'; +import TabPanelShared from 'AppComponents/Shared/PoliciesUI/TabPanel'; +import type { Policy } from '../Types'; +import DraggablePolicyCard from '../GlobalSpecificComponents/DraggablePolicyCard'; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + policyList: Policy[]; + selectedTab: number; + fetchPolicies: () => void; +} + +/** + * Tab panel component to render content of a particular tab. + * Renders the available policy list under the relevant flow related tab (i.e. request, response or fault). + * @param {JSON} props Input props from parent components. + * @returns {TSX} Tab panel. + */ +const TabPanel: FC = ({ + index, + policyList, + selectedTab, + fetchPolicies, +}) => { + const flowNames = ['request', 'response', 'fault']; + const currentFlow = flowNames[index]; + + return ( + + ); +}; + +export default TabPanel; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/Types.d.ts b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/Types.d.ts new file mode 100644 index 00000000000..9df14f1095e --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/Types.d.ts @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export type Policy = { + id: string; + name: string; + version: string; + displayName: string; + applicableFlows: string[]; + supportedGateways: string[]; + supportedApiTypes: string[]; + isAPISpecific: boolean; + supportedGateways: string[]; +}; + +export type AttachedPolicy = { + id: string; + name: string; + displayName: string; + version: string; + applicableFlows: string[]; + uniqueKey: string; + attributes?: any; + isAPISpecific?: boolean; +}; + +export type PolicySpecAttribute = { + name: string; + displayName: string; + version: string; + description: string; + required: boolean; + type: string; + validationRegex: string; + defaultValue: any; + allowedValues: string[]; +}; + +export type PolicySpec = { + id: string; + category: string; + name: string; + displayName: string; + version: string; + description: string; + applicableFlows: string[]; + supportedGateways: string[]; + supportedApiTypes: string[]; + policyAttributes: PolicySpecAttribute[]; + isAPISpecific?: boolean; + md5?: string; +}; + +export type GlobalPolicy = { + policyName?: string; + policyId?: string; + policyVersion?: string; + parameters: any; + uuid?: string; +}; + +export type GlobalLevelPolicy = { + request?: any[]; + response?: any[]; + fault?: any[]; +}; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/Utils.ts b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/Utils.ts new file mode 100644 index 00000000000..a826ad869bf --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/Policies/Utils.ts @@ -0,0 +1,30 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/* eslint-disable no-mixed-operators */ +/* eslint-disable eqeqeq */ +/* eslint-disable no-bitwise */ +export const uuidv4 = () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c == 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +}; + +export { uuidv4 as default }; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/View/ViewGlobalPolicy.tsx b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/View/ViewGlobalPolicy.tsx new file mode 100644 index 00000000000..14848f41ad2 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/GlobalPolicies/View/ViewGlobalPolicy.tsx @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React from 'react'; +import Policies from 'AppComponents/GlobalPolicies/Policies/GlobalSpecificComponents/Policies'; +import { useParams } from 'react-router-dom'; + +interface RouteParams { + policyId: string; +} + +/** + * Global Policies Editing Page. + * @returns {JSX} - Editing Page. + */ +const ViewGlobalPolicy: React.FC = () => { + const { policyId } = useParams(); + return ( + + ); +}; + +export default ViewGlobalPolicy; \ No newline at end of file diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/CustomIcon.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/CustomIcon.jsx index ab12c48f318..a27c126264c 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Shared/CustomIcon.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/CustomIcon.jsx @@ -417,6 +417,32 @@ export default function CustomIcon(props) { ); + } else if (icon === 'global-policies') { + return ( + + ); } return null; } diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/AttachedPolicyCard.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/AttachedPolicyCard.tsx new file mode 100644 index 00000000000..778689b6760 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/AttachedPolicyCard.tsx @@ -0,0 +1,168 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { CSSProperties, FC } from 'react'; +import Avatar from '@material-ui/core/Avatar'; +import Box from '@material-ui/core/Box'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { makeStyles } from '@material-ui/core'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; +import DeleteIcon from '@material-ui/icons/Delete'; +import CloudDownloadIcon from '@material-ui/icons/CloudDownload'; +import Utils from 'AppData/Utils'; +import type { AttachedPolicy, PolicySpec } from './Types'; + +const useStyles = makeStyles(() => ({ + actionsBox: { + display: 'flex', + flexDirection: 'column', + marginTop: '1em', + }, +})); + +interface AttachedPolicyCardSharedProps { + policyObj: AttachedPolicy; + currentFlow: string; + verb: string; + target: string; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; + drawerOpen: any; + handleDrawerOpen: () => void; + handlePolicyDownload: (event: React.MouseEvent) => void; + handleDelete: (event: React.MouseEvent) => void; + setDrawerOpen: React.Dispatch>; + PolicyConfigurationEditDrawer: any; +} + +/** + * Renders a single sortable policy card. + * @param {any} AttachedPolicyCardProps Input props from parent components. + * @returns {TSX} Sortable attached policy card UI. + */ +const AttachedPolicyCardShared: FC = ({ + policyObj, + currentFlow, + verb, + target, + allPolicies, + isAPILevelPolicy, + drawerOpen, + handleDrawerOpen, + handlePolicyDownload, + handleDelete, + setDrawerOpen, + PolicyConfigurationEditDrawer +}) => { + const classes = useStyles(); + const policyColor = Utils.stringToColor(policyObj.displayName); + const policyBackgroundColor = drawerOpen + ? `rgba(${Utils.hexToRGB(policyColor)}, 0.2)` + : 'rgba(0, 0, 0, 0)'; + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: policyObj.uniqueKey.toString() }); + + const style: CSSProperties = { + transform: CSS.Transform.toString(transform), + transition, + border: '2px solid', + height: '90%', + cursor: 'move', + borderRadius: '0.3em', + padding: '0.2em', + borderColor: policyColor, + marginLeft: '0.2em', + marginRight: '0.2em', + backgroundColor: policyBackgroundColor, + opacity: isDragging ? 0.5 : 1, + }; + return ( + <> +
+ + + {Utils.stringAvatar( + policyObj.displayName.toUpperCase(), + )} + + + + + + + + + + +
+ {drawerOpen && ( + + )} + + ); +} + +export default AttachedPolicyCardShared; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/AttachedPolicyList.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/AttachedPolicyList.tsx new file mode 100644 index 00000000000..2f651fcb5c6 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/AttachedPolicyList.tsx @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC } from 'react'; +import { + DndContext, + closestCenter, + DragEndEvent, +} from '@dnd-kit/core'; +import { + horizontalListSortingStrategy, + SortableContext, +} from '@dnd-kit/sortable'; +import type { AttachedPolicy, PolicySpec } from './Types'; + +interface AttachedPolicyListSharedProps { + currentPolicyList: AttachedPolicy[]; + setCurrentPolicyList: React.Dispatch>; + currentFlow: string; + target: string; + verb: string; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; + sensors: any; + handleDragEnd: (event: DragEndEvent) => void; + policyListToDisplay: AttachedPolicy[]; + AttachedPolicyCard: any; +} + +const AttachedPolicyListShared: FC = ({ + currentPolicyList, + setCurrentPolicyList, + currentFlow, + target, + verb, + allPolicies, + isAPILevelPolicy, + sensors, + handleDragEnd, + policyListToDisplay, + AttachedPolicyCard, +}) => { + return ( + <> + + item.uniqueKey)} + strategy={horizontalListSortingStrategy} + > + {policyListToDisplay.map((policy: AttachedPolicy) => ( + + ))} + + + + ); +}; + +export default AttachedPolicyListShared; \ No newline at end of file diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/FlowArrow.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/FlowArrow.tsx new file mode 100644 index 00000000000..5f10bb627ec --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/FlowArrow.tsx @@ -0,0 +1,67 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC } from 'react'; +import { makeStyles } from '@material-ui/core'; +import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'; +import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos'; +import Box from '@material-ui/core/Box'; + +const useStyles = makeStyles(() => ({ + arrowColor: { + backgroundColor: 'black', + opacity: 0.4, + }, + iconSize: { + fontSize: '2em', + color: 'black', + opacity: 0.4, + } +})); + +interface FlowArrowSharedProps { + arrowDirection: string; +} + +const FlowArrowShared: FC = ({ arrowDirection }) => { + const classes = useStyles(); + + return ( + <> + {arrowDirection === 'left' + ? ( + + + + + + + ) : ( + + + + + + + ) + } + + ); +} + +export default FlowArrowShared; \ No newline at end of file diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/PoliciesExpansion.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/PoliciesExpansion.tsx new file mode 100644 index 00000000000..58194af3a1e --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/PoliciesExpansion.tsx @@ -0,0 +1,156 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC } from 'react'; +import Grid from '@material-ui/core/Grid'; +import Box from '@material-ui/core/Box'; +import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import { FormattedMessage } from 'react-intl'; +import type { AttachedPolicy, PolicySpec } from './Types'; + +const useStyles = makeStyles((theme: Theme) => ({ + flowSpecificPolicyAttachGrid: { + marginTop: theme.spacing(1), + overflowX: 'scroll', + }, +})); + +interface PoliciesExpansionSharedProps { + target: any; + verb: string; + allPolicies: PolicySpec[] | null; + isChoreoConnectEnabled: boolean; + isAPILevelPolicy: boolean; + requestFlowPolicyList: AttachedPolicy[]; + setRequestFlowPolicyList: React.Dispatch>; + requestFlowDroppablePolicyList: string[]; + responseFlowPolicyList: AttachedPolicy[]; + setResponseFlowPolicyList: React.Dispatch>; + responseFlowDroppablePolicyList: string[]; + faultFlowPolicyList: AttachedPolicy[]; + setFaultFlowPolicyList: React.Dispatch>; + faultFlowDroppablePolicyList: string[]; + FlowArrow: any; + PolicyDropzone: any; +} + +const PoliciesExpansionShared: FC = ({ + target, + verb, + allPolicies, + isChoreoConnectEnabled, + isAPILevelPolicy, + requestFlowPolicyList, + setRequestFlowPolicyList, + requestFlowDroppablePolicyList, + responseFlowPolicyList, + setResponseFlowPolicyList, + responseFlowDroppablePolicyList, + faultFlowPolicyList, + setFaultFlowPolicyList, + faultFlowDroppablePolicyList, + FlowArrow, + PolicyDropzone +}) => { + const classes = useStyles(); + + return ( + + + + + + + + + + + + + + + + + + {!isChoreoConnectEnabled && ( + + + + + + + + )} + + + + ); +}; + +export default PoliciesExpansionShared; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/PolicyDropzone.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/PolicyDropzone.tsx new file mode 100644 index 00000000000..bfd80483b4c --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/PolicyDropzone.tsx @@ -0,0 +1,144 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import React, { FC } from 'react'; +import { Grid, makeStyles, Theme, Typography } from '@material-ui/core'; +import green from '@material-ui/core/colors/green'; +import red from '@material-ui/core/colors/red'; +import clsx from 'clsx'; +import type { AttachedPolicy, Policy, PolicySpec } from './Types'; + +const useStyles = makeStyles((theme: Theme) => ({ + dropzoneDiv: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + height: '8rem', + padding: '0.8rem', + width: '100%', + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + textAlign: 'center', + borderRadius: '0.3em', + display: 'flex', + alignItems: 'center', + overflowX: 'scroll', + }, + acceptDrop: { + backgroundColor: green[50], + borderColor: 'green', + }, + rejectDrop: { + backgroundColor: red[50], + borderColor: 'red', + }, + alignLeft: { + justifyContent: 'left', + }, + alignRight: { + justifyContent: 'right', + }, + alignCenter: { + justifyContent: 'center', + }, +})); + +interface PolicyDropzoneSharedProps { + policyDisplayStartDirection: string; + currentPolicyList: AttachedPolicy[]; + setCurrentPolicyList: React.Dispatch>; + currentFlow: string; + target: string; + verb: string; + allPolicies: PolicySpec[] | null; + isAPILevelPolicy: boolean; + drop: any; + canDrop: any; + droppedPolicy: Policy | null; + setDroppedPolicy: any; + AttachedPolicyList: any; + PolicyConfiguringDrawer: any; +} + +const PolicyDropzoneShared: FC = ({ + policyDisplayStartDirection, + currentPolicyList, + setCurrentPolicyList, + currentFlow, + target, + verb, + allPolicies, + isAPILevelPolicy, + drop, + canDrop, + droppedPolicy, + setDroppedPolicy, + AttachedPolicyList, + PolicyConfiguringDrawer +}) => { + const classes = useStyles(); + return ( + <> + +
+ {currentPolicyList.length === 0 ? ( + Drag and drop policies here + ) : ( + + )} +
+
+ {droppedPolicy && ( + + )} + + ); +} + +export default PolicyDropzoneShared; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/TabPanel.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/TabPanel.tsx new file mode 100644 index 00000000000..5d22152f223 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/TabPanel.tsx @@ -0,0 +1,66 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { Box } from '@material-ui/core'; +import React, { FC } from 'react'; +import type { Policy } from './Types'; + +interface TabPanelSharedProps { + children?: React.ReactNode; + currentFlow: string; + index: number; + policyList: Policy[]; + selectedTab: number; + fetchPolicies: () => void; + DraggablePolicyCard: any; +} + +const TabPanelShared: FC = ({ + selectedTab, + index, + currentFlow, + policyList, + fetchPolicies, + DraggablePolicyCard +}) => { + return ( + + ); +} + +export default TabPanelShared; \ No newline at end of file diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/Types.d.ts b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/Types.d.ts new file mode 100644 index 00000000000..91bfd0ee553 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/Types.d.ts @@ -0,0 +1,67 @@ +/* +* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +* +* WSO2 LLC. licenses this file to you under the Apache License, +* Version 2.0 (the "License"); you may not use this file except +* in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export type Policy = { + id: string; + name: string; + version: string; + displayName: string; + applicableFlows: string[]; + supportedGateways: string[]; + supportedApiTypes: string[]; + isAPISpecific: boolean; + supportedGateways: string[]; +}; + +export type AttachedPolicy = { + id: string; + name: string; + displayName: string; + version: string; + applicableFlows: string[]; + uniqueKey: string; + attributes?: any; + isAPISpecific?: boolean; +}; + +export type PolicySpecAttribute = { + name: string; + displayName: string; + version: string; + description: string; + required: boolean; + type: string; + validationRegex: string; + defaultValue: any; + allowedValues: string[]; +}; + +export type PolicySpec = { + id: string; + category: string; + name: string; + displayName: string; + version: string; + description: string; + applicableFlows: string[]; + supportedGateways: string[]; + supportedApiTypes: string[]; + policyAttributes: PolicySpecAttribute[]; + isAPISpecific?: boolean; + md5?: string; +}; diff --git a/portals/publisher/src/main/webapp/source/src/app/data/api.js b/portals/publisher/src/main/webapp/source/src/app/data/api.js index 9dc6a9eb9ff..79aefa5a871 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/api.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/api.js @@ -3045,6 +3045,111 @@ class API extends Resource { }); } + /** + * Get all global policies + * @param {number} limit Limit of the global policy list which needs to be retrieved + * @param {number} offset Offset of the global policy list which needs to be retrieved + * @param {String} query Search attribute by using an "gatewayLabel:" modifier + * @returns {Promise} Promise containing global policies list + */ + static getAllGatewayPolicies(limit = null, offset = 0, query = null) { + const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; + return restApiClient.then(client => { + return client.apis['Gateway Policies'].getAllGatewayPolicies( + { limit, offset, query }, + this._requestMetaData(), + ); + }); + } + + /** + * Add a new global policy + * @param {Object} body policy schema which holds the newly added data + * @returns {Promise} Promise containing the added global policy + */ + static addGatewayPoliciesToFlows(body) { + const apiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; + const requestBody = { + requestBody: body, + }; + return apiClient.then(client => { + return client.apis['Gateway Policies'].addGatewayPoliciesToFlows( + {}, + requestBody, + this._requestMetaData(), + ); + }); + } + + /** + * Delete a global policy + * @param {String} gatewayPolicyMappingId UUID of the global policy to delete + * @returns {Promise} Response + */ + static deleteGatewayPolicyByPolicyId(gatewayPolicyMappingId) { + const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; + return restApiClient.then(client => { + return client.apis['Gateway Policies'].deleteGatewayPolicyByPolicyId( + {gatewayPolicyMappingId}, + this._requestMetaData(), + ); + }); + } + + /** + * Get the global policy by ID + * @param {String} gatewayPolicyMappingId UUID of the global policy + * @returns {Promise} Response containing the information of the requested global policy + */ + static getGatewayPolicyMappingContentByPolicyMappingId(gatewayPolicyMappingId) { + const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; + return restApiClient.then(client => { + return client.apis['Gateway Policies'].getGatewayPolicyMappingContentByPolicyMappingId( + {gatewayPolicyMappingId}, + this._requestMetaData(), + ); + }); + } + + /** + * Update the global policy by ID + * @param {String} gatewayPolicyMappingId UUID of the global policy + * @param {Object} body policy schema which holds the updated data + * @returns {Promise} Response containing the information of the requested global policy + */ + static updateGatewayPoliciesToFlows(gatewayPolicyMappingId, body) { + const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; + const requestBody = { + requestBody: body, + }; + return restApiClient.then(client => { + return client.apis['Gateway Policies'].updateGatewayPoliciesToFlows( + {gatewayPolicyMappingId}, + requestBody, + this._requestMetaData(), + ); + }); + } + + /** + * Deploy the global policy for an gateway environment + * @param {String} gatewayPolicyMappingId UUID of the global policy + * @param {Object} body policy schema which holds the deployed and undeployed gateyway environments + * @returns {Promise} Response + */ + static engageGlobalPolicy(gatewayPolicyMappingId, body) { + const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; + const requestBody = { + requestBody: body, + }; + return restApiClient.then(client => { + return client.apis['Gateway Policies'].engageGlobalPolicy( + {gatewayPolicyMappingId}, + requestBody, + this._requestMetaData(), + ); + }); + } } API.CONSTS = { diff --git a/portals/publisher/src/main/webapp/source/src/app/data/defaultTheme.js b/portals/publisher/src/main/webapp/source/src/app/data/defaultTheme.js index 07004e1cbfc..b4432f16c57 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/defaultTheme.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/defaultTheme.js @@ -251,6 +251,7 @@ export default { scopesAddIcon: '/site/public/images/landing-icons/scopes.svg', commonPolicyAddIcon: '/site/public/images/landing-icons/scopes.svg', apiproductAddIcon: '/site/public/images/landing-icons/apiproduct.svg', + globalPolicyAddIcon: '/site/public/images/landing-icons/globalpolicies.svg' }, menu: { primary: '#34679D',