diff --git a/cypress/e2e/console/admin-panel/packet-broker/networks.spec.js b/cypress/e2e/console/admin-panel/packet-broker/networks.spec.js index db702ac160..e3a83f2526 100644 --- a/cypress/e2e/console/admin-panel/packet-broker/networks.spec.js +++ b/cypress/e2e/console/admin-panel/packet-broker/networks.spec.js @@ -45,7 +45,12 @@ describe('Packet Broker networks', () => { cy.intercept('/api/v3/pba/home-networks/policies*', { fixture: 'console/packet-broker/policies-home-network.json', }) - cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker/networks`) + cy.visit( + `${Cypress.config( + 'consoleRootPath', + )}/admin-panel/packet-broker/routing-configuration/networks`, + ) + cy.findByLabelText('Use custom routing policies').check() const { networks } = this.networks const networksFiltered = networks.filter( @@ -73,7 +78,11 @@ describe('Packet Broker networks', () => { n => n.forwarder_id.net_id === 19 && n.forwarder_id.tenant_id === 'johan', ) - cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker/networks/19/johan`) + cy.visit( + `${Cypress.config( + 'consoleRootPath', + )}/admin-panel/packet-broker/routing-configuration/networks/19/johan`, + ) cy.findAllByText(`${network.id.net_id.toString(16).padStart(6, '0')}/${network.id.tenant_id}`) cy.findByText( diff --git a/cypress/e2e/console/admin-panel/packet-broker/registration.spec.js b/cypress/e2e/console/admin-panel/packet-broker/registration.spec.js index 5f4afc5366..4bfd68d939 100644 --- a/cypress/e2e/console/admin-panel/packet-broker/registration.spec.js +++ b/cypress/e2e/console/admin-panel/packet-broker/registration.spec.js @@ -61,15 +61,15 @@ describe('Packet Broker registration', () => { cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker`) cy.findByText('Packet Broker', { selector: 'h1' }).should('be.visible') - cy.findByText(/Packet Broker can be used to exchange traffic/).should('be.visible') - cy.findByText('Packet Broker documentation', { selector: 'a' }).should('be.visible') + cy.findByText(/Packet Broker is a service by The Things Industries/).should('be.visible') + cy.findByText('Packet Broker', { selector: 'a' }).should('be.visible') cy.findByText('Packet Broker website', { selector: 'a' }).should('be.visible') - cy.findByText('Register network', { selector: 'span' }).should('be.visible') + cy.findByText('Enable Packet Broker', { selector: 'span' }).should('be.visible') cy.findByTestId('switch') .should('be.visible') .and('not.be.checked') .and('not.have.attr', 'disabled') - cy.findByText(/To enable peering/).should('be.visible') + cy.findByText(/Enabling will allow/).should('be.visible') cy.findByText('Default routing policy').should('not.exist') cy.findByText('Networks').should('not.exist') @@ -89,18 +89,20 @@ describe('Packet Broker registration', () => { cy.loginConsole({ user_id: 'admin', password: 'admin' }) cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker`) - cy.findByText('Register network').click() - cy.findByText('Register network').next().findByTestId('switch').should('be.checked') - cy.findByText('List network publicly') + cy.findByText('Enable Packet Broker').click() + cy.findByText('Enable Packet Broker').next().findByTestId('switch').should('be.checked') + cy.findByText('List my network in Packet Broker publicly') .should('be.visible') .next() .findByTestId('switch') .should('be.checked') - cy.findByTestId('feature-info-forwarder-enabled').should('be.visible') - cy.findByTestId('feature-info-home-network-enabled').should('be.visible') - cy.findByTestId('tabs').findByText('Default routing policy').should('be.visible') - cy.findByTestId('tabs').findByText('Networks').should('be.visible') - cy.findByLabelText('Do not use a default routing policy for this network').should('be.checked') + cy.findByLabelText('Forward traffic to all networks registered in Packet Broker').should( + 'exist', + ) + cy.findByLabelText( + 'Forward traffic to The Things Stack Sandbox (community network) only', + ).should('exist') + cy.findByLabelText('Use custom routing policies').should('exist') cy.findByTestId('error-notification').should('not.exist') }) diff --git a/cypress/e2e/console/admin-panel/packet-broker/routing-policies.spec.js b/cypress/e2e/console/admin-panel/packet-broker/routing-policies.spec.js index 9b9f519f12..be0fee18a5 100644 --- a/cypress/e2e/console/admin-panel/packet-broker/routing-policies.spec.js +++ b/cypress/e2e/console/admin-panel/packet-broker/routing-policies.spec.js @@ -32,12 +32,63 @@ describe('Packet Broker routing policies', () => { cy.loginConsole({ user_id: 'admin', password: 'admin' }) }) - it('succeeds setting a default routing policy', () => { + it('succeeds setting a "traffic to all networks" routing configuration', () => { cy.intercept('GET', '/api/v3/pba/home-networks/policies/default', { statusCode: 404 }) cy.intercept('PUT', '/api/v3/pba/home-networks/policies/default', {}) + cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/19', {}) + cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/19/johan', {}) + cy.intercept('/api/v3/pba/networks*', { fixture: 'console/packet-broker/networks.json' }) + cy.intercept('/api/v3/pba/home-networks/policies*', { + fixture: 'console/packet-broker/policies-home-network.json', + }) cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker`) - cy.findByLabelText('Use default routing policy for this network').check() + cy.findByLabelText('Forward traffic to all networks registered in Packet Broker').check() + cy.findByRole('button', { name: 'Save routing configuration' }).click() + + cy.findByTestId('error-notification').should('not.exist') + cy.findByTestId('toast-notification') + .should('be.visible') + .findByText('Default routing configuration set') + .should('be.visible') + }) + + it('succeeds setting a "only ttn" routing configuration', () => { + cy.intercept('GET', '/api/v3/pba/home-networks/policies/default', { statusCode: 404 }) + cy.intercept('/api/v3/pba/networks*', { fixture: 'console/packet-broker/networks.json' }) + cy.intercept('/api/v3/pba/home-networks/policies*', { + fixture: 'console/packet-broker/policies-home-network.json', + }) + cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/default', {}) + cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/19', {}) + cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/19/johan', {}) + cy.intercept('PUT', '/api/v3/pba/home-networks/policies/19/ttn', {}) + cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker`) + + cy.findByLabelText( + 'Forward traffic to The Things Stack Sandbox (community network) only', + ).check() + cy.findByRole('button', { name: 'Save routing configuration' }).click() + + cy.findByTestId('error-notification').should('not.exist') + cy.findByTestId('toast-notification') + .should('be.visible') + .findByText('Default routing configuration set') + .should('be.visible') + }) + + it('succeeds setting a custom routing configuration with a default routing policy', () => { + cy.intercept('GET', '/api/v3/pba/home-networks/policies/default', { + fixture: 'console/packet-broker/default-policy.json', + }) + cy.intercept('PUT', '/api/v3/pba/home-networks/policies/default', {}) + cy.intercept('/api/v3/pba/networks*', { fixture: 'console/packet-broker/networks.json' }) + cy.intercept('/api/v3/pba/home-networks/policies*', { + fixture: 'console/packet-broker/policies-home-network.json', + }) + cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker`) + + cy.findByLabelText('Use custom routing policies').check() // Check routing policy form checkboxes. cy.findByText('Uplink') @@ -56,30 +107,35 @@ describe('Packet Broker routing policies', () => { cy.findByLabelText('MAC data').check() cy.findByLabelText('Application data').check() }) - cy.findByRole('button', { name: 'Save default policy' }).click() + cy.findByRole('button', { name: 'Save routing configuration' }).click() cy.findByTestId('error-notification').should('not.exist') cy.findByTestId('toast-notification') .should('be.visible') - .findByText('Default routing policy set') + .findByText('Default routing configuration set') .should('be.visible') }) it('succeeds unsetting a default routing policy', () => { + cy.intercept('PUT', '/api/v3/pba/home-networks/policies/default', {}) + cy.intercept('/api/v3/pba/networks*', { fixture: 'console/packet-broker/networks.json' }) + cy.intercept('/api/v3/pba/home-networks/policies*', { + fixture: 'console/packet-broker/policies-home-network.json', + }) cy.intercept('GET', '/api/v3/pba/home-networks/policies/default', { - fixture: 'console/packet-broker/default-policy.json', + fixture: 'console/packet-broker/default-custom-policy.json', }) cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/default', {}) cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker`) cy.findByLabelText('Do not use a default routing policy for this network').check() - cy.findByRole('button', { name: 'Save default policy' }).click() + cy.findByRole('button', { name: 'Save routing configuration' }).click() cy.findByTestId('error-notification').should('not.exist') cy.findByTestId('toast-notification') .should('be.visible') - .findByText('Default routing policy set') + .findByText('Default routing configuration set') .should('be.visible') }) @@ -93,7 +149,11 @@ describe('Packet Broker routing policies', () => { }) cy.intercept('PUT', '/api/v3/pba/home-networks/policies/19', {}) - cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker/networks/19`) + cy.visit( + `${Cypress.config( + 'consoleRootPath', + )}/admin-panel/packet-broker/routing-configuration/networks/19`, + ) // Check routing policy form checkboxes. cy.findByLabelText('Use network specific routing policy').check() @@ -138,7 +198,11 @@ describe('Packet Broker routing policies', () => { }) cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/19', {}) - cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker/networks/19`) + cy.visit( + `${Cypress.config( + 'consoleRootPath', + )}/admin-panel/packet-broker/routing-configuration/networks/19`, + ) cy.findByLabelText('Do not use a routing policy for this network').check() cy.findByRole('button', { name: 'Save routing policy' }).click() @@ -161,7 +225,11 @@ describe('Packet Broker routing policies', () => { }) cy.intercept('DELETE', '/api/v3/pba/home-networks/policies/19', {}) - cy.visit(`${Cypress.config('consoleRootPath')}/admin-panel/packet-broker/networks/19`) + cy.visit( + `${Cypress.config( + 'consoleRootPath', + )}/admin-panel/packet-broker/routing-configuration/networks/19`, + ) cy.findByLabelText('Use default routing policy for this network').check() cy.findByRole('button', { name: 'Save routing policy' }).click() diff --git a/cypress/fixtures/console/packet-broker/default-custom-policy.json b/cypress/fixtures/console/packet-broker/default-custom-policy.json new file mode 100644 index 0000000000..edd8728179 --- /dev/null +++ b/cypress/fixtures/console/packet-broker/default-custom-policy.json @@ -0,0 +1,11 @@ +{ + "updated_at": "2021-06-21T12:09:26.810087Z", + "uplink": { + "join_request": true, + "mac_data": false, + "application_data": true, + "signal_quality": false, + "localization": false + }, + "downlink": { "join_accept": true, "mac_data": false, "application_data": true } +} diff --git a/pkg/webui/console/components/default-routing-policy-form/index.js b/pkg/webui/console/components/default-routing-policy-form/index.js new file mode 100644 index 0000000000..ce33d11b2a --- /dev/null +++ b/pkg/webui/console/components/default-routing-policy-form/index.js @@ -0,0 +1,118 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed 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, { useCallback, useState } from 'react' +import { Col, Row } from 'react-grid-system' +import { defineMessages } from 'react-intl' + +import Form, { useFormContext } from '@ttn-lw/components/form' +import Checkbox from '@ttn-lw/components/checkbox' +import Radio from '@ttn-lw/components/radio-button' + +import Message from '@ttn-lw/lib/components/message' + +import sharedMessages from '@ttn-lw/lib/shared-messages' + +import policyMessages from '@console/lib/packet-broker/messages' + +const m = defineMessages({ + doNotUseADefaultPolicy: 'Do not use a default routing policy for this network', +}) + +const useDefaultEncode = val => val === 'default' +const useDefaultDecode = val => (val ? 'default' : 'no-default') + +const DefaultRoutingPolicyForm = () => { + const { values } = useFormContext() + const [useDefault, setUseDefault] = useState(values._use_default_policy || false) + const handlePolicySourceChange = useCallback(setUseDefault, [setUseDefault]) + + return ( + + + + + + + + {useDefault && ( + <> + + + + + + + + + + + + + + + + )} + + ) +} + +export default DefaultRoutingPolicyForm diff --git a/pkg/webui/console/components/gateway-visibility-form/index.js b/pkg/webui/console/components/gateway-visibility-form/index.js index 3a5372f6af..535477a7ac 100644 --- a/pkg/webui/console/components/gateway-visibility-form/index.js +++ b/pkg/webui/console/components/gateway-visibility-form/index.js @@ -74,19 +74,19 @@ const GatewayVisibilityForm = ({ onSubmit, initialValues, error }) => { here to modify the default payload formatter for this application. The payload formatter of this application is currently set to `{defaultFormatter}`', pasteRepositoryFormatter: 'Paste repository formatter', @@ -419,7 +418,7 @@ const PayloadFormattersForm = ({ > {() => ( <> - + val === 'default' const policySourceDecode = val => (val ? 'default' : 'specific') -const useDefaultEncode = val => val === 'default' -const useDefaultDecode = val => (val ? 'default' : 'no-default') -const RoutingPolicyForm = ({ - onSubmit, - initialValues, - error, - defaultPolicy, - networkLevel, - submitMessage, -}) => { +const RoutingPolicyForm = ({ onSubmit, initialValues, error, defaultPolicy, submitMessage }) => { const handleSubmit = useCallback(values => onSubmit(validationSchema.cast(values)), [onSubmit]) const [useDefault, setUseDefault] = useState(initialValues._use_default_policy || false) const handlePolicySourceChange = useCallback(setUseDefault, [setUseDefault]) const hasDefaultPolicy = isValidPolicy(defaultPolicy) - const showDefaultPolicySheet = networkLevel && useDefault && isValidPolicy(defaultPolicy) - const showPolicyCheckboxes = (useDefault && !networkLevel) || (!useDefault && networkLevel) - + const showDefaultPolicySheet = useDefault && isValidPolicy(defaultPolicy) + const showPolicyCheckboxes = !useDefault return (
- {networkLevel ? ( - - - - - ) : ( - - - - - )} + + + + {showDefaultPolicySheet && ( @@ -196,14 +170,12 @@ RoutingPolicyForm.propTypes = { _use_default_policy: PropTypes.bool, policy: PropTypes.shape({}), }), - networkLevel: PropTypes.bool, onSubmit: PropTypes.func.isRequired, submitMessage: PropTypes.message, } RoutingPolicyForm.defaultProps = { error: undefined, - networkLevel: false, defaultPolicy: undefined, submitMessage: m.saveDefaultPolicy, initialValues: { diff --git a/pkg/webui/console/store/actions/packet-broker.js b/pkg/webui/console/store/actions/packet-broker.js index fc90e5b136..640826f679 100644 --- a/pkg/webui/console/store/actions/packet-broker.js +++ b/pkg/webui/console/store/actions/packet-broker.js @@ -210,6 +210,21 @@ export const [ }, ] = createRequestActions(DELETE_HOME_NETWORK_ROUTING_POLICY_BASE, (id, policy) => ({ id, policy })) +export const DELETE_ALL_HOME_NETWORK_ROUTING_POLICIES_BASE = + 'DELETE_ALL_HOME_NETWORK_ROUTING_POLICIES' +export const [ + { + request: DELETE_ALL_HOME_NETWORK_ROUTING_POLICIES, + success: DELETE_ALL_HOME_NETWORK_ROUTING_POLICIES_SUCCESS, + failure: DELETE_ALL_HOME_NETWORK_ROUTING_POLICIES_FAILURE, + }, + { + request: deleteAllHomeNetworkRoutingPolicies, + success: deleteAllHomeNetworkRoutingPoliciesSuccess, + failure: deleteAllHomeNetworkRoutingPoliciesFailure, + }, +] = createRequestActions(DELETE_ALL_HOME_NETWORK_ROUTING_POLICIES_BASE, ids => ({ ids })) + export const GET_HOME_NETWORK_ROUTING_POLICIES_BASE = 'GET_HOME_NETWORK_ROUTING_POLICIES' export const [ { diff --git a/pkg/webui/console/store/middleware/logics/packet-broker.js b/pkg/webui/console/store/middleware/logics/packet-broker.js index 1eeed2ac8f..6017934e48 100644 --- a/pkg/webui/console/store/middleware/logics/packet-broker.js +++ b/pkg/webui/console/store/middleware/logics/packet-broker.js @@ -263,7 +263,12 @@ const setPacketBrokerHomeNetworkPolicyLogic = createRequestLogic({ const ids = extractPacketBrokerIdsFromCombinedId(id) await tts.PacketBrokerAgent.setHomeNetworkRoutingPolicy(ids.net_id, ids.tenant_id, policy) - return policy + const newPolicy = { home_network_id: { net_id: ids.net_id }, ...policy } + if ('tenant_id' in ids) { + newPolicy.home_network_id.tenant_id = ids.tenant_id + } + + return newPolicy }, }) @@ -289,6 +294,34 @@ const deletePacketBrokerHomeNetworkPolicyLogic = createRequestLogic({ }, }) +const deleteAllPacketBrokerHomeNetworkPoliciesLogic = createRequestLogic({ + type: packetBroker.DELETE_ALL_HOME_NETWORK_ROUTING_POLICIES, + process: async ({ action }) => { + const { + payload: { ids }, + } = action + + try { + await Promise.all( + ids.map(async id => { + const ids = extractPacketBrokerIdsFromCombinedId(id) + if (typeof ids === 'number') { + return tts.PacketBrokerAgent.deleteHomeNetworkRoutingPolicy(ids) + } + + return tts.PacketBrokerAgent.deleteHomeNetworkRoutingPolicy(ids.net_id, ids.tenant_id) + }), + ) + + return ids + } catch (error) { + if (!isNotFoundError(error)) { + throw error + } + } + }, +}) + const getDefaultGatewayVisibilityLogic = createRequestLogic({ type: packetBroker.GET_HOME_NETWORK_DEFAULT_GATEWAY_VISIBILITY, process: async () => { @@ -346,6 +379,7 @@ export default [ getPacketBrokerHomeNetworkPoliciesLogic, setPacketBrokerHomeNetworkPolicyLogic, deletePacketBrokerHomeNetworkPolicyLogic, + deleteAllPacketBrokerHomeNetworkPoliciesLogic, getDefaultGatewayVisibilityLogic, setDefaultGatewayVisibilityLogic, deleteDefaultGatewayVisibilityLogic, diff --git a/pkg/webui/console/views/admin-packet-broker/admin-packet-broker.js b/pkg/webui/console/views/admin-packet-broker/admin-packet-broker.js index c5d01fc26b..04d50bcd99 100644 --- a/pkg/webui/console/views/admin-packet-broker/admin-packet-broker.js +++ b/pkg/webui/console/views/admin-packet-broker/admin-packet-broker.js @@ -15,48 +15,37 @@ import React, { useCallback, useState } from 'react' import { Container, Col, Row } from 'react-grid-system' import { useSelector, useDispatch } from 'react-redux' -import { Routes, Route } from 'react-router-dom' +import { Routes, Route, Navigate } from 'react-router-dom' import classnames from 'classnames' import PacketBrokerLogo from '@assets/misc/packet-broker.svg' import Link from '@ttn-lw/components/link' import PageTitle from '@ttn-lw/components/page-title' -import Icon from '@ttn-lw/components/icon' import Switch from '@ttn-lw/components/switch' import Tabs from '@ttn-lw/components/tabs' import PortalledModal from '@ttn-lw/components/modal/portalled' -import Notification from '@ttn-lw/components/notification' import ErrorNotification from '@ttn-lw/components/error-notification' +import Notification from '@ttn-lw/components/notification' import Message from '@ttn-lw/lib/components/message' -import RequireRequest from '@ttn-lw/lib/components/require-request' import GenericNotFound from '@ttn-lw/lib/components/full-view-error/not-found' -import SubViewErrorComponent from '@console/views/sub-view-error' - import sharedMessages from '@ttn-lw/lib/shared-messages' import { isNotEnabledError } from '@console/lib/packet-broker/utils' -import { - registerPacketBroker, - deregisterPacketBroker, - getHomeNetworkDefaultRoutingPolicy, - getHomeNetworkDefaultGatewayVisibility, -} from '@console/store/actions/packet-broker' +import { registerPacketBroker, deregisterPacketBroker } from '@console/store/actions/packet-broker' import { selectRegistered, selectRegisterEnabled, selectEnabled, selectListed, - selectInfo, selectInfoError, } from '@console/store/selectors/packet-broker' -import DefaultRoutingPolicyView from './default-routing-policy' -import NetworkRoutingPoliciesView from './network-routing-policies' +import RoutingConfigurationView from './routing-configuration' import DefaultGatewayVisibilityView from './default-gateway-visibility' import m from './messages' @@ -70,7 +59,6 @@ const PacketBroker = () => { const enabled = useSelector(selectEnabled) const [unlistModalVisible, setUnlistModalVisible] = useState(false) const listed = useSelector(selectListed) - const info = useSelector(selectInfo) const infoError = useSelector(selectInfoError) const dispatch = useDispatch() const showError = Boolean(infoError) && !isNotEnabledError(infoError) @@ -81,7 +69,7 @@ const PacketBroker = () => { } else { setDeregisterModalVisible(true) } - }, [dispatch, registered, setDeregisterModalVisible]) + }, [dispatch, registered]) const handleDeregisterModalComplete = useCallback( approved => { @@ -112,22 +100,19 @@ const PacketBroker = () => { ) const tabs = [ - { title: m.defaultRoutingPolicy, link: '/admin-panel/packet-broker', name: 'default' }, + { + title: m.routingConfig, + link: '/admin-panel/packet-broker/routing-configuration', + name: 'default', + exact: false, + }, { title: m.defaultGatewayVisibility, link: '/admin-panel/packet-broker/default-gateway-visibility', name: 'default-gateway-visibility', }, - { - title: sharedMessages.networks, - link: '/admin-panel/packet-broker/networks', - name: 'networks', - exact: false, - }, ] - const boldMessage = { b: msg => {msg} } - return ( @@ -138,29 +123,24 @@ const PacketBroker = () => { Packet Broker
- + - Packet Broker documentation + Packet Broker {' | '} - {' | '} - - -

- + + + + {!enabled && } {showError && } {enabled && ( - + {registerEnabled && ( )} - {registered && ( -
- {info.forwarder_enabled ? ( - - - - - ) : ( - - - - - )} - {info.home_network_enabled ? ( - - - - - ) : ( - - - - - )} -
- )} { {registered && ( <> -