diff --git a/src/components/blocks/Sbcs.js b/src/components/blocks/Sbcs.js index 9dbadf4..d9c23be 100644 --- a/src/components/blocks/Sbcs.js +++ b/src/components/blocks/Sbcs.js @@ -3,7 +3,7 @@ import axios from 'axios'; import styled from 'styled-components'; import { useHistory } from 'react-router-dom'; import { NotificationDispatchContext } from '../../contexts/NotificationContext'; -import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext'; +// import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext'; const Container = styled.div` margin-top: 0.25rem; @@ -21,7 +21,7 @@ const Container = styled.div` const Sbcs = props => { let history = useHistory(); const dispatch = useContext(NotificationDispatchContext); - const currentServiceProvider = useContext(ServiceProviderValueContext); + // const currentServiceProvider = useContext(ServiceProviderValueContext); const [ sbcs, setSbcs ] = useState(''); useEffect(() => { const getAPIData = async () => { @@ -38,7 +38,8 @@ const Sbcs = props => { const sbcResults = await axios({ method: 'get', baseURL: process.env.REACT_APP_API_BASE_URL, - url: `/Sbcs?service_provider_sid=${currentServiceProvider}`, + // url: `/Sbcs?service_provider_sid=${currentServiceProvider}`, + url: '/Sbcs', headers: { Authorization: `Bearer ${localStorage.getItem('token')}`, }, diff --git a/src/components/elements/Tooltip.js b/src/components/elements/Tooltip.js index 301458a..cb76598 100644 --- a/src/components/elements/Tooltip.js +++ b/src/components/elements/Tooltip.js @@ -1,7 +1,11 @@ import styled from 'styled-components/macro'; +import React, { useState, useRef, useEffect, useCallback } from 'react'; + +import Link from './Link'; const Tooltip = styled.span` display: none; + label > span:hover > & { display: inline; position: absolute; @@ -14,15 +18,105 @@ const Tooltip = styled.span` box-shadow: 0 0.375rem 0.25rem rgba(0, 0, 0, 0.12), 0 0 0.25rem rgba(0, 0, 0, 0.18); z-index: 80; - ${props => !props.large ? ` - white-space: nowrap; - ` : ` - text-align: left; - width: 22rem; - bottom: calc(100% + 0.5rem); - `} + white-space: nowrap; + } +`; + +const StyledLinkWithTooltip = styled.span` + position: relative; + + > span { + font-size: 14px; + position: absolute; + left: 50%; + transform: translate3d(-50%, calc(-100% - 5px), 0); + padding: 0.75rem 1rem; + border-radius: 0.25rem; + border: 1px solid #C6C6C6; + background: #FFF; + z-index: 80; + white-space: pre; + + &:after { + content: ""; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid #FFF; + position: absolute; + left: 50%; + top: 100%; + transform: translateX(-50%); + z-index: 2; + } + + &:before { + content: ""; + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid #C6C6C6; + position: absolute; + left: 50%; + top: 100%; + transform: translateX(-50%); + z-index: 1; } } `; +const LinkWithTooltip = props => { + const [isActive, setIsActive] = useState(false); + const tooltipRef = useRef(); + const triggerRef = useRef(); + + const handleLinkClick = useCallback(() => { + setIsActive((oldActive) => { + const newActive = !oldActive; + return newActive; + }); + }, [setIsActive]); + + const handleOuterClick = useCallback((e) => { + if (!tooltipRef.current) { + return; + } + + if (tooltipRef.current.contains(e.target)) { + return; + } + + if (triggerRef.current.contains(e.target)) { + return; + } + + handleLinkClick(); + }, [tooltipRef, triggerRef, handleLinkClick]); + + useEffect(() => { + document.addEventListener('click', handleOuterClick, false); + + return () => document.removeEventListener('click', handleOuterClick, false); + }, [handleOuterClick]); + + return ( + + + {props.children} + + {isActive ? ( + + {props.tipText} + + ) : null} + + ); +}; + +export { + LinkWithTooltip, +}; + export default Tooltip; diff --git a/src/components/forms/CarrierForm.js b/src/components/forms/CarrierForm.js index 022c59e..f5f1370 100644 --- a/src/components/forms/CarrierForm.js +++ b/src/components/forms/CarrierForm.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, useContext, useRef } from 'react'; import { useParams, useHistory } from 'react-router-dom'; import axios from 'axios'; import styled from "styled-components/macro"; -import { Menu, Dropdown } from "antd"; +import { Menu, Dropdown, Tabs, Switch } from "antd"; import { NotificationDispatchContext } from '../../contexts/NotificationContext'; import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext'; @@ -19,6 +19,48 @@ import Loader from '../blocks/Loader'; import sortSipGateways from '../../helpers/sortSipGateways'; import Select from '../elements/Select'; import handleErrors from "../../helpers/handleErrors"; +import { LinkWithTooltip } from '../elements/Tooltip'; + +const { TabPane } = Tabs; + +const StyledSection = styled.fieldset` + margin: auto; + width: calc(100% - 0.5rem); + margin-bottom: 1.5rem; + padding: 2rem; + border-radius: 0.5rem; + background: #FFF; + border:1px solid rgb(0 0 0 / 10%); + + > *:first-child { + margin-top: 0; + } + + > *:last-child { + margin-bottom: 0; + } +`; + +const SMPPGatewaysInboundInputGroup = styled(InputGroup)` + grid-column: 2 / 3; + display: grid; + grid-gap: 1rem; + grid-template-columns: 1fr 80px 50px 70px; +`; + +const SMPPGatewaysOutboundInputGroup = styled(InputGroup)` + grid-column: 2 / 3; + display: grid; + grid-gap: 1rem; + grid-template-columns: 1fr 80px 50px 20px 50px; +`; + +const StyledLegend = styled.legend` + font-size: 16px; + color: #231f20; + padding: 0 0.5rem; + width: fit-content; +`; const StyledForm = styled(Form)` @media (max-width: 978.98px) { @@ -135,6 +177,34 @@ const CarrierItem = styled.div` ${(props) => props.disabled ? 'opacity: 0.5;' : ''} `; +const Section = styled.section` + padding: 0 2rem 2rem; +`; + +const Subtitle = styled.p` + font-size: 14px; + color: #231f20; + margin: 1.5rem 0; +`; + +const CarriersVoiceTipText = ({sbcs}) => { + const text = <> + SIP signaling IPs: + {sbcs.map((sbc) =>
{sbc.ipv4}
)} + ; + + return Have your carriers whitelist and send calls to our SIP signaling IPs; +}; + +const CarriersSmppTipText = ({smpps}) => { + const text = <> + SMPP signaling IPs: + {smpps.map((smpp) =>
{smpp.ipv4}
)} + ; + + return Have your carriers whitelist and send SMPP to our SMPP signaling IPs; +}; + const CarrierForm = (props) => { const { voip_carrier_sid } = useParams(); const type = voip_carrier_sid ? 'edit' : 'add'; @@ -157,6 +227,14 @@ const CarrierForm = (props) => { const refAdd = useRef(null); const refTechPrefix = useRef(null); + const refSmppSystemId = useRef(null); + const refSmppPassword = useRef(null); + const refSmppInboundSystemId = useRef(null); + const refSmppInboundPassword = useRef(null); + const refSmppIp = useRef([]); + const refSmppPort = useRef([]); + const refSmppTrash = useRef([]); + // Form inputs const [ name, setName ] = useState(''); const [ nameInvalid, setNameInvalid ] = useState(false); @@ -187,30 +265,60 @@ const CarrierForm = (props) => { const [ applicationValues, setApplicationValues ] = useState([]); const [ accounts, setAccounts ] = useState([]); const [ accountSid, setAccountSid ] = useState(''); - const [ carrierSid, setCarrierSid ] = useState(''); const [ showLoader, setShowLoader ] = useState(true); const [ errorMessage, setErrorMessage ] = useState(''); - const [requiredTechPrefix, setRequiredTechPrefix] = useState(false); const [techPrefix, setTechPrefix] = useState(''); const [techPrefixInvalid, setTechPrefixInvalid ] = useState(false); const [suportSIP, setSupportSIP] = useState(false); const [diversion, setDiversion] = useState(""); const [carrierActive, setCarrierActive] = useState(true); - const [predefinedCarriers, setPredefinedCarriers] = useState([]); + const [activeTab, setActiveTab] = useState('1'); + const [sbcs, setSbcs] = useState([]); - // eslint-disable-next-line - const [sipRealm, setSipRealm] = useState(''); - // eslint-disable-next-line - const [staticIPs, setStaticIPs] = useState(null); + // SMPP state + const [ smpps, setSmpps ] = useState([]); + const [ smpp_system_id, setSmppSystemId ] = useState(''); + const [ smpp_system_idInvalid, setSmppSystemIdInvalid ] = useState(false); + const [ smpp_password, setSmppPassword ] = useState(''); + const [ smpp_passwordInvalid, setSmppPasswordInvalid ] = useState(false); + const [ smpp_inbound_system_id, setSmppInboundSystemId ] = useState(''); + const [ smpp_inbound_system_idInvalid, setSmppInboundSystemIdInvalid ] = useState(false); + const [ smpp_inbound_password, setSmppInboundPassword ] = useState(''); + const [ smpp_inbound_passwordInvalid, setSmppInboundPasswordInvalid ] = useState(false); + const [ smppGateways, setSmppGateways ] = useState([ + { + smpp_gateway_sid: '', + ipv4: '', + port: 2775, + voip_carrier_sid: '', + inbound: false, + use_tls: 0, + outbound: true, + is_primary: 1, + invalidIp: false, + invalidPort: false, + }, + { + smpp_gateway_sid: '', + ipv4: '', + port: 2775, + voip_carrier_sid: '', + inbound: true, + use_tls: 0, + outbound: false, + is_primary: 0, + invalidIp: false, + invalidPort: false, + } + ]); useEffect(() => { const getAPIData = async () => { let isMounted = true; try { - const promises = []; // Get Application Data @@ -230,8 +338,26 @@ const CarrierForm = (props) => { Authorization: `Bearer ${jwt}`, }, }); - promises.push(applicationPromise); - promises.push(accountsPromise); + const smppsPromise = axios({ + method: 'get', + baseURL: process.env.REACT_APP_API_BASE_URL, + url: '/Smpps', + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + const sbcsPromise = await axios({ + method: 'get', + baseURL: process.env.REACT_APP_API_BASE_URL, + url: '/Sbcs', + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + promises.push(applicationPromise); // 0 + promises.push(accountsPromise); // 1 + promises.push(smppsPromise); // 2 + promises.push(sbcsPromise) // 3 if (type === 'edit') { const carrierPromise = axios({ @@ -252,19 +378,32 @@ const CarrierForm = (props) => { }, }); - promises.push(carrierPromise); - promises.push(sipGatewaysPromise); + const smppGatewaysPromise = axios({ + method: 'get', + baseURL: process.env.REACT_APP_API_BASE_URL, + url: `/SmppGateways?voip_carrier_sid=${voip_carrier_sid}`, + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + + promises.push(carrierPromise); // 4 + promises.push(sipGatewaysPromise); // 5 + promises.push(smppGatewaysPromise); // 6 } const promiseResponses = await Promise.all(promises); setApplicationValues(promiseResponses[0].data); setAccounts(promiseResponses[1].data); + setSmpps(promiseResponses[2].data); + setSbcs(promiseResponses[3].data); if (type === 'edit') { - const carrier = promiseResponses[2].data; - const allSipGateways = promiseResponses[3].data; + const carrier = promiseResponses[4].data; + const allSipGateways = promiseResponses[5].data; + const allSmppGateways = promiseResponses[6].data; if (!carrier) { isMounted = false; @@ -281,7 +420,12 @@ const CarrierForm = (props) => { return s.voip_carrier_sid === carrier.voip_carrier_sid; }); + const currentSmppGateways = allSmppGateways.filter(s => { + return s.voip_carrier_sid === carrier.voip_carrier_sid; + }); + sortSipGateways(currentSipGateways); + sortSipGateways(currentSmppGateways); setName(carrier.name || ''); setE164(carrier.e164_leading_plus === 1); @@ -304,6 +448,23 @@ const CarrierForm = (props) => { invalidInbound: false, invalidOutbound: false, }))); + setSmppSystemId(carrier.smpp_system_id || ''); + setSmppPassword(carrier.smpp_password || ''); + setSmppInboundPassword(carrier.smpp_inbound_password || ''); + setSmppInboundSystemId(carrier.smpp_inbound_system_id || ''); + setSmppGateways(currentSmppGateways.map(s => ({ + smpp_gateway_sid: s.smpp_gateway_sid, + ipv4: s.ipv4, + port: s.port, + netmask: s.netmask, + voip_carrier_sid: s.voip_carrier_sid, + inbound: s.inbound, + outbound: s.outbound, + use_tls: s.use_tls, + is_primary: s.is_primary, + invalidIp: false, + invalidPort: false, + }))); setCarrierSid(carrier.voip_carrier_sid); setTechPrefix(carrier.tech_prefix || ''); setRequiredTechPrefix(carrier.tech_prefix ? true : false); @@ -380,12 +541,37 @@ const CarrierForm = (props) => { setSipGateways(newSipGateways); }; + const addSmppGateway = (inbound) => { + const newSmppGateways = [ + ...smppGateways, + { + smpp_gateway_sid: '', + ipv4: '', + port: 2775, + inbound: inbound, + outbound: !inbound, + voip_carrier_sid: '', + use_tls: 0, + is_primary: 0, + invalidIp: false, + invalidPort: false, + } + ]; + setSmppGateways(newSmppGateways); + }; + const removeSipGateway = index => { const newSipGateways = sipGateways.filter((s,i) => i !== index); setSipGateways(newSipGateways); setErrorMessage(''); }; + const removeSmppGateway = index => { + const newSmppGateways = smppGateways.filter((s,i) => i !== index); + setSmppGateways(newSmppGateways); + setErrorMessage(''); + }; + const updateSipGateways = (e, i, key) => { const newSipGateways = [...sipGateways]; const newValue = @@ -405,6 +591,24 @@ const CarrierForm = (props) => { setSipGateways(newSipGateways); }; + const updateSmppGateways = (e, i, key) => { + const newSmppGateways = [...smppGateways]; + const newValue = + key === 'invalidIp' || + key === 'invalidPort' + ? true + : (key === 'use_tls') || (key === 'is_primary') + ? e?1:0 + : e.target.value; + newSmppGateways[i][key] = newValue; + + if (key === "outbound" && newValue === true) { + newSmppGateways[i]["netmask"] = 32; + } + + setSmppGateways(newSmppGateways); + }; + const resetInvalidFields = () => { setNameInvalid(false); setUsernameInvalid(false); @@ -419,6 +623,16 @@ const CarrierForm = (props) => { newSipGateways[i].invalidOutbound = false; }); setSipGateways(newSipGateways); + setSmppSystemIdInvalid(false); + setSmppPasswordInvalid(false); + setSmppInboundSystemIdInvalid(false); + setSmppInboundPasswordInvalid(false); + const newSmppGateways = [...smppGateways]; + newSmppGateways.forEach((s, i) => { + s.invalidIp = false; + s.invalidPort = false; + }); + setSmppGateways(newSmppGateways); }; const handleSubmit = async e => { @@ -430,6 +644,10 @@ const CarrierForm = (props) => { resetInvalidFields(); let errorMessages = []; let focusHasBeenSet = false; + const regIp = /^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])$/; + const regFqdn = /^([a-zA-Z][^.]*)(\.[^.]+){2,}$/; + const regFqdnTopLevel = /^([a-zA-Z][^.]*)(\.[^.]+)$/; + const regPort = /^[0-9]+$/; if (!name) { errorMessages.push('Please provide a name.'); @@ -498,11 +716,6 @@ const CarrierForm = (props) => { } } - const regIp = /^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])$/; - const regFqdn = /^([a-zA-Z][^.]*)(\.[^.]+){2,}$/; - const regFqdnTopLevel = /^([a-zA-Z][^.]*)(\.[^.]+)$/; - const regPort = /^[0-9]+$/; - sipGateways.forEach(async (gateway, i) => { //----------------------------------------------------------------------------- // IP validation @@ -609,6 +822,162 @@ const CarrierForm = (props) => { }); }); + const assertEmptySmppIpsForAll = smppGateways.map((g) => g.ipv4.trim()).join(''); + + // These validations need to execute for SMS tab + if (smpp_system_id || smpp_password || smpp_inbound_password || assertEmptySmppIpsForAll !== '') { + // DH: allow creation of smpp gateways without system id/password, as we can get those + // aftewr the fact from TelecomxXchange + /* + if (!smpp_system_id) { + errorMessages.push('You must provide Outbound System ID.'); + setSmppSystemIdInvalid(true); + if (!focusHasBeenSet) { + refSmppSystemId.current.focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + + if (!smpp_password) { + errorMessages.push('You must provide Outbound System Password.'); + setSmppPasswordInvalid(true); + if (!focusHasBeenSet) { + refSmppPassword.current.focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + */ + + if(smpps && smpps.length > 0) { + const smppInboundGateways = smppGateways.filter((g) => g.inbound); + const smppOutboundGateways = smppGateways.filter((g) => g.outbound); + // const assertEmptySmppIpsForInbound = smppInboundGateways.map((g) => g.ipv4.trim()).join(''); + + // Validate Outbound Gateways -- At least one is required + if (!smppOutboundGateways.length) { + errorMessages.push('You must provide at least one Outbound Gateway.'); + addSmppGateway(false); // False means NOT inbound, so YES it is outbound + setActiveTab('2'); // SMPP tab + } + + // Validate Inbound Gateways -- Password required ONLY if adding Gateway(s) + if (smppInboundGateways.length) { + // We only need to validate this if the user entered something into an Inbound IP field + + // DH: see above + /* + if (!smpp_inbound_password && assertEmptySmppIpsForInbound !== '') { + errorMessages.push('You must provide an Inbound System Password when adding IP Address(es) to whitelist.'); + setSmppInboundPasswordInvalid(true); + if (!focusHasBeenSet) { + refSmppInboundPassword.current.focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + */ + } + + smppGateways.forEach(async (gateway, i) => { + //----------------------------------------------------------------------------- + // IP validation + //----------------------------------------------------------------------------- + const type = regIp.test(gateway.ipv4.trim()) + ? 'ip' + : regFqdn.test(gateway.ipv4.trim()) + ? 'fqdn' + : regFqdnTopLevel.test(gateway.ipv4.trim()) + ? 'fqdn-top-level' + : (gateway.ipv4.trim() === '') ? 'empty' : 'invalid'; + const gatewayTypeText = gateway.outbound ? 'Outbound' : 'Inbound'; + if (!gateway.ipv4 && gateway.outbound) { + errorMessages.push(`The ${gatewayTypeText} IP Address cannot be blank. Please provide an IP address or delete the row.`); + updateSmppGateways(null, i, 'invalidIp'); + if (!focusHasBeenSet) { + refSmppIp.current[i].focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + + else if (type === 'fqdn-top-level') { + errorMessages.push('When using an FQDN, you must use a subdomain (e.g. smpp.example.com).'); + updateSipGateways(null, i, 'invalidIp'); + if (!focusHasBeenSet) { + refSmppIp.current[i].focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + + else if (type === 'invalid') { + errorMessages.push(`Please provide a valid ${gatewayTypeText} IP address or fully qualified domain name.`); + updateSmppGateways(null, i, 'invalidIp'); + if (!focusHasBeenSet) { + refSmppIp.current[i].focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + + //----------------------------------------------------------------------------- + // Port validation + //----------------------------------------------------------------------------- + if ( + !gateway.port || (gateway.port && ( + !(regPort.test(gateway.port.toString().trim())) + || (parseInt(gateway.port.toString().trim()) < 0) + || (parseInt(gateway.port.toString().trim()) > 65535)) + ) + ) { + errorMessages.push(`Please provide a valid ${gatewayTypeText} port number between 0 and 65535`); + updateSmppGateways(null, i, 'invalidPort'); + if (!focusHasBeenSet) { + refSmppPort.current[i].focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + + //----------------------------------------------------------------------------- + // inbound/outbound validation + //----------------------------------------------------------------------------- + if (type === 'fqdn' && (!gateway.outbound || gateway.inbound)) { + errorMessages.push('A fully qualified domain name may only be used for outbound calls.'); + updateSmppGateways(null, i, 'invalidIp'); + if (!focusHasBeenSet) { + refSmppIp.current[i].focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + + //----------------------------------------------------------------------------- + // duplicates validation + //----------------------------------------------------------------------------- + smppGateways.forEach((otherGateway, j) => { + if (i >= j) return; + if (!gateway.ip) return; + if (type === 'invalid') return; + if (gateway.ip === otherGateway.ip && gateway.port === otherGateway.port) { + errorMessages.push('Each SMPP gateway must have a unique IP address.'); + updateSmppGateways(null, i, 'invalidIp'); + updateSmppGateways(null, i, 'invalidPort'); + updateSmppGateways(null, j, 'invalidIp'); + updateSmppGateways(null, j, 'invalidPort'); + if (!focusHasBeenSet) { + refSmppTrash.current[j].focus(); + setActiveTab('2'); + focusHasBeenSet = true; + } + } + }); + }); + } + } + // remove duplicate error messages for (let i = 0; i < errorMessages.length; i++) { for (let j = 0; j < errorMessages.length; j++) { @@ -660,14 +1029,19 @@ const CarrierForm = (props) => { tech_prefix: techPrefix ? techPrefix.trim() : null, diversion: diversion ? diversion.trim() : null, is_active: carrierActive ? 1 : 0, + smpp_system_id: smpp_system_id ? smpp_system_id.trim(): null, + smpp_password: smpp_password ? smpp_password.trim(): null, + smpp_inbound_system_id: smpp_inbound_system_id ? smpp_inbound_system_id.trim(): null, + smpp_inbound_password: smpp_inbound_password ? smpp_inbound_password.trim(): null }, }); const voip_carrier_sid = voipCarrier.data.sid; // get updated gateway info from API in order to delete ones that user has removed from UI let sipGatewaysFromAPI; + let smppGatewaysFromAPI; if (!creatingNewCarrier) { - const results = await axios({ + let results = await axios({ method: 'get', baseURL: process.env.REACT_APP_API_BASE_URL, url: `/SipGateways?voip_carrier_sid=${carrierSid}`, @@ -676,6 +1050,17 @@ const CarrierForm = (props) => { }, }); sipGatewaysFromAPI = results.data.filter(s => s.voip_carrier_sid === carrierSid); + if(smpps && smpps.length > 0) { + results = await axios({ + method: 'get', + baseURL: process.env.REACT_APP_API_BASE_URL, + url: `/SmppGateways?voip_carrier_sid=${voip_carrier_sid}`, + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + smppGatewaysFromAPI = results.data.filter(s => s.voip_carrier_sid === carrierSid); + } } //----------------------------------------------------------------------------- @@ -684,6 +1069,7 @@ const CarrierForm = (props) => { // Keeping track of created SIP gateways in case one throws an error, then all // of the ones created before that (as well as the carrier) have to be deleted. let completedSipGateways = []; + let completedSmppGateways = []; try { for (const s of sipGateways) { const creatingNewGateway = creatingNewCarrier || s.sip_gateway_sid === ''; @@ -721,6 +1107,46 @@ const CarrierForm = (props) => { completedSipGateways.push(result.data.sid); } }; + if(smpps && smpps.length > 0) { + for (const s of smppGateways) { + const creatingNewGateway = creatingNewCarrier || s.smpp_gateway_sid === ''; + + const method = creatingNewGateway + ? 'post' + : 'put'; + + const url = creatingNewGateway + ? '/SmppGateways' + : `/SmppGateways/${s.smpp_gateway_sid}`; + + const data = { + ipv4: s.ipv4.trim(), + port: s.port.toString().trim(), + netmask: s.netmask, + inbound: s.inbound, + outbound: s.outbound, + use_tls: s.use_tls, + is_primary: s.is_primary + }; + + if (creatingNewGateway) { + data.voip_carrier_sid = voip_carrier_sid || carrierSid; + } + + const result = await axios({ + method, + baseURL: process.env.REACT_APP_API_BASE_URL, + url, + headers: { + Authorization: `Bearer ${jwt}`, + }, + data, + }); + if (creatingNewGateway) { + completedSmppGateways.push(result.data.sid); + } + } + } } catch (err) { if (completedSipGateways.length) { for (const sid of completedSipGateways) { @@ -734,6 +1160,18 @@ const CarrierForm = (props) => { }); } } + if(smpps && smpps.length > 0) { + for (const sid of completedSmppGateways) { + await axios({ + method: 'delete', + baseURL: process.env.REACT_APP_API_BASE_URL, + url: `/SmppGateways/${sid}`, + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + } + } if (voip_carrier_sid) { await axios({ method: 'delete', @@ -762,6 +1200,21 @@ const CarrierForm = (props) => { }); } } + if(smpps && smpps.length > 0) { + for (const remote of smppGatewaysFromAPI) { + const match = smppGateways.filter(local => local.smpp_gateway_sid === remote.smpp_gateway_sid); + if (!match.length) { + await axios({ + method: 'delete', + baseURL: process.env.REACT_APP_API_BASE_URL, + url: `/SmppGateways/${remote.smpp_gateway_sid}`, + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + } + } + } } isMounted = false; @@ -828,377 +1281,584 @@ const CarrierForm = (props) => { } }; - // eslint-disable-next-line - const getSubTitle = () => { - let title = <> ; - if (sipRealm) { - title = staticIPs - ? `Have your carrier send your calls to your static IP(s): ${staticIPs.join( - ", " - )}` - : `Have your carrier send calls to ${sipRealm}`; - } - return title; + const onChangeTab = activeKey => { + setActiveTab(activeKey); }; return ( showLoader ? ( ) : ( - - - - setName(e.target.value)} - placeholder="Carrier name" - invalid={nameInvalid} - autoFocus - ref={refName} - /> - {type === 'add' && ( - - - {predefinedCarriers.map((item) => { - const disabled = !staticIPs && item.requires_static_ip; - return ( - - !disabled && pickupCarrier(item.value)} - > - {item.text} - - - ); - })} - - } - > - - - - )} - - - - setCarrierActive(e.target.checked)} - /> - - - setE164(e.target.checked)} - /> - - - - - {accountSid && ( - <> - - setName(e.target.value)} + placeholder="Carrier name" + invalid={nameInvalid} + autoFocus + ref={refName} + /> + {type === 'add' && ( + + + {predefinedCarriers.map((item) => { + const disabled = item.requires_static_ip; + return ( + + !disabled && pickupCarrier(item.value)} + > + {item.text} + + + ); + })} + + } > - {a.name} - - ))} - - - )} + + + + )} + -
+ + setCarrierActive(e.target.checked)} + /> +
+
+ +
+ + + + + + + + setE164(e.target.checked)} + /> - { - !authenticate ? ( - <> - - - ) : ( - <> - - setUsername(e.target.value)} - placeholder="SIP username for authenticating outbound calls" - invalid={usernameInvalid} - ref={refUsername} - /> - - -
- setRegister(e.target.checked)} - /> - { - register ? ( + + + + {accountSid && ( <> - - setRealm(e.target.value)} - placeholder="SIP realm for registration" - invalid={realmInvalid} - ref={refRealm} - /> + + - ) : ( - null - ) - } - - ) - } + )} -
+
- { - requiredTechPrefix ? ( - <> - - setTechPrefix(e.target.value)} - placeholder="Tech Prefix" - invalid={techPrefixInvalid} - ref={refTechPrefix} - /> - - ) : ( - <> - - - ) - } + { + !authenticate ? ( + <> + + + ) : ( + <> + + setUsername(e.target.value)} + placeholder="SIP username for authenticating outbound calls" + invalid={usernameInvalid} + ref={refUsername} + /> + + +
+ setRegister(e.target.checked)} + /> + { + register ? ( + <> + + setRealm(e.target.value)} + placeholder="SIP realm for registration" + invalid={realmInvalid} + ref={refRealm} + /> + + ) : ( + null + ) + } + + ) + } -
+
- { - suportSIP ? ( - <> - - setDiversion(e.target.value)} - placeholder="Phone number or SIP URI" - /> - - ) : ( - <> - - - ) - } + { + requiredTechPrefix ? ( + <> + + setTechPrefix(e.target.value)} + placeholder="Tech Prefix" + invalid={techPrefixInvalid} + ref={refTechPrefix} + /> + + ) : ( + <> + + + ) + } -
+
+ + { + suportSIP ? ( + <> + + setDiversion(e.target.value)} + placeholder="Phone number or SIP URI" + /> + + ) : ( + <> + + + ) + } + +
+ + SIP Gateways + + Network Address / Port / Netmask + + {sipGateways.map((g, i) => ( + + updateSipGateways(e, i, 'ip')} + placeholder={'1.2.3.4'} + invalid={sipGateways[i].invalidIp} + ref={ref => refIp.current[i] = ref} + /> + updateSipGateways(e, i, 'port')} + placeholder="5060" + invalid={sipGateways[i].invalidPort} + ref={ref => refPort.current[i] = ref} + /> + + + updateSipGateways(e, i, 'inbound')} + invalid={sipGateways[i].invalidInbound} + ref={ref => refInbound.current[i] = ref} + /> + updateSipGateways(e, i, 'outbound')} + invalid={sipGateways[i].invalidOutbound} + ref={ref => refOutbound.current[i] = ref} + /> + removeSipGateway(i)} + ref={ref => refTrash.current[i] = ref} + /> + + + ))} + + + + +
+
+ + + + + + Outbound SMPP + + + setSmppSystemId(e.target.value)} + placeholder="SMPP system id to authenticate with" + invalid={smpp_system_idInvalid} + ref={refSmppSystemId} + /> + + + +
Carrier SMPP Gateways
+
+
+ + + + + + {smppGateways.map((g, i) => ( + g.outbound? + ( + updateSmppGateways(e, i, 'ipv4')} + placeholder={'1.2.3.4'} + invalid={g.invalidIp} + ref={ref => refSmppIp.current[i] = ref} + /> + updateSmppGateways(e, i, 'port')} + placeholder="2775" + invalid={g.invalidPort} + ref={ref => refSmppPort.current[i] = ref} + /> + updateSmppGateways(e, i, 'use_tls')} + /> + {/* updateSmppGateways(e, i, 'is_primary')} + /> */} + removeSmppGateway(i)} + ref={ref => refSmppTrash.current[i] = ref} + /> + ) + : + null + ))} +
+ + + + + Inbound SMPP + + + setSmppInboundSystemId(e.target.value)} + placeholder="SMPP system id to authenticate with" + invalid={smpp_inbound_system_idInvalid} + ref={refSmppInboundSystemId} + /> + + + +
Carrier IP Address(es) to whitelist
+
+
+ + + + + {smppGateways.map((g, i) => ( + g.inbound? + ( + updateSmppGateways(e, i, 'ipv4')} + placeholder={'1.2.3.4'} + invalid={g.invalidIp} + ref={ref => refSmppIp.current[i] = ref} + /> + {/* updateSmppGateways(e, i, 'port')} + placeholder="2775" + invalid={g.invalidPort} + ref={ref => refSmppPort.current[i] = ref} + /> */} + + removeSmppGateway(i)} + ref={ref => refSmppTrash.current[i] = ref} + /> +
+
) + : + null + ))} +
+ + + + + +
- SIP Gateways - - Network Address / Port / Netmask - - {sipGateways.map((g, i) => ( - - updateSipGateways(e, i, 'ip')} - placeholder={'1.2.3.4'} - invalid={sipGateways[i].invalidIp} - ref={ref => refIp.current[i] = ref} - /> - updateSipGateways(e, i, 'port')} - placeholder="5060" - invalid={sipGateways[i].invalidPort} - ref={ref => refPort.current[i] = ref} - /> - - - updateSipGateways(e, i, 'inbound')} - invalid={sipGateways[i].invalidInbound} - ref={ref => refInbound.current[i] = ref} - /> - updateSipGateways(e, i, 'outbound')} - invalid={sipGateways[i].invalidOutbound} - ref={ref => refOutbound.current[i] = ref} - /> - removeSipGateway(i)} - ref={ref => refTrash.current[i] = ref} - /> - - - ))} - - + - {errorMessage && ( - +
+ +
)} - - +
+ + - - - + + +
+ ) ); }; diff --git a/src/components/pages/internal/CarriersAddEdit.js b/src/components/pages/internal/CarriersAddEdit.js index 4f9b5de..bc24c04 100644 --- a/src/components/pages/internal/CarriersAddEdit.js +++ b/src/components/pages/internal/CarriersAddEdit.js @@ -2,7 +2,6 @@ import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; import InternalTemplate from '../../templates/InternalTemplate'; import CarrierForm from '../../forms/CarrierForm'; -import Sbcs from '../../blocks/Sbcs'; const CarriersAddEdit = () => { let { voip_carrier_sid } = useParams(); @@ -15,7 +14,6 @@ const CarriersAddEdit = () => { } breadcrumbs={[ { name: 'Carriers', url: '/internal/carriers' }, { name: pageTitle }, diff --git a/src/components/pages/internal/CarriersList.js b/src/components/pages/internal/CarriersList.js index b92e8a0..80219b9 100644 --- a/src/components/pages/internal/CarriersList.js +++ b/src/components/pages/internal/CarriersList.js @@ -7,7 +7,6 @@ import styled from 'styled-components/macro'; import { NotificationDispatchContext } from '../../../contexts/NotificationContext'; import InternalTemplate from '../../templates/InternalTemplate'; import TableContent from '../../blocks/TableContent.js'; -import Sbcs from '../../blocks/Sbcs'; import sortSipGateways from '../../../helpers/sortSipGateways'; import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext'; import InputGroup from '../../../components/elements/InputGroup'; @@ -246,7 +245,6 @@ const CarriersList = () => { title="Carriers" addButtonText="Add a Carrier" addButtonLink="/internal/carriers/add" - subtitle={} > Used By: