diff --git a/.env b/.env index 848b1ae..a824370 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -#VITE_API_BASE_URL=http://127.0.0.1:3000/v1 +# VITE_API_BASE_URL=http://127.0.0.1:3000/v1 #VITE_DEV_BASE_URL=http://127.0.0.1:3000/v1 ## enables choosing units and lisenced account call limits diff --git a/src/api/constants.ts b/src/api/constants.ts index cf47af4..3daf2c9 100644 --- a/src/api/constants.ts +++ b/src/api/constants.ts @@ -131,7 +131,7 @@ export const DEFAULT_WEBHOOK: WebHook = { }; /** Default SIP/SMPP Gateways */ -export const DEFAULT_SIP_GATEWAY: SipGateway = { +export const DEFAULT_SIP_INBOUND_GATEWAY: SipGateway = { voip_carrier_sid: "", ipv4: "", port: 5060, @@ -348,6 +348,12 @@ export const DTMF_TYPE_SELECTION: SelectorOptions[] = [ { name: "Tones", value: "tones" }, ]; +export const TRUNK_TYPE_SELECTION: SelectorOptions[] = [ + { name: "IP Trunk", value: "static_ip" }, + { name: "Auth Trunk", value: "auth" }, + { name: "Registration Trunk", value: "reg" }, +]; + /** Available webhook methods */ export const WEBHOOK_METHODS: WebhookOption[] = [ { diff --git a/src/api/types.ts b/src/api/types.ts index ee50ad0..fc1afbc 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -469,6 +469,8 @@ export interface CarrierRegisterStatus { export type DtmfType = "rfc2833" | "tones" | "info"; +export type TrunkType = "static_ip" | "auth" | "reg"; + export interface Carrier { voip_carrier_sid: string; name: string; @@ -497,6 +499,7 @@ export interface Carrier { register_status: CarrierRegisterStatus; dtmf_type: DtmfType; outbound_sip_proxy: string | null; + trunk_type: TrunkType; } export interface PredefinedCarrier extends Carrier { diff --git a/src/containers/internal/views/carriers/form.tsx b/src/containers/internal/views/carriers/form.tsx index 71d5d39..d5480e0 100644 --- a/src/containers/internal/views/carriers/form.tsx +++ b/src/containers/internal/views/carriers/form.tsx @@ -4,7 +4,6 @@ import { Button, ButtonGroup, Icon, MS, MXS, Tab, Tabs } from "@jambonz/ui-kit"; import { deleteSipGateway, - deleteSmppGateway, postCarrier, postSipGateway, postSmppGateway, @@ -17,7 +16,7 @@ import { postPredefinedCarrierTemplateAccount, } from "src/api"; import { - DEFAULT_SIP_GATEWAY, + DEFAULT_SIP_INBOUND_GATEWAY, DEFAULT_SMPP_GATEWAY, DTMF_TYPE_SELECTION, FQDN, @@ -28,6 +27,7 @@ import { SIP_GATEWAY_PROTOCOL_OPTIONS, TCP_MAX_PORT, TECH_PREFIX_MINLENGTH, + TRUNK_TYPE_SELECTION, USER_ACCOUNT, } from "src/api/constants"; import { Icons, Section, Tooltip } from "src/components"; @@ -61,9 +61,9 @@ import { type SmppGateway, type PredefinedCarrier, type Sbc, - type Smpp, type Application, DtmfType, + TrunkType, } from "src/api/types"; import { setAccountFilter, setLocation } from "src/store/localStore"; import { RegisterStatus } from "./register-status"; @@ -85,13 +85,12 @@ export const CarrierForm = ({ const user = useSelectState("user"); const currentServiceProvider = useSelectState("currentServiceProvider"); - const refSipIp = useRef([]); + const refSipInboundIp = useRef([]); + const refSipOutboundIp = useRef([]); const refSipPort = useRef([]); const refSmppIp = useRef([]); - const refSmppPort = useRef([]); - + const refInboundAuthUsername = useRef(null); const [sbcs] = useApiData("Sbcs"); - const [smpps] = useApiData("Smpps"); const [applications] = useServiceProviderData("Applications"); const [accounts] = useServiceProviderData("Accounts"); const [predefinedCarriers] = @@ -106,12 +105,15 @@ export const CarrierForm = ({ const [applicationSid, setApplicationSid] = useState(""); const [accountSid, setAccountSid] = useState(""); const [dtmfType, setDtmfType] = useState("rfc2833"); + const [trunkType, setTrunkType] = useState("static_ip"); + + const [inboundAuthUsername, setInboundAuthUsername] = useState(""); + const [inboundAuthPassword, setInboundAuthPassword] = useState(""); const [sipRegister, setSipRegister] = useState(false); const [sipUser, setSipUser] = useState(""); const [sipPass, setSipPass] = useState(""); const [sipRealm, setSipRealm] = useState(""); - const [initialRegister, setInitialRegister] = useState(false); const [fromUser, setFromUser] = useState(""); const [fromDomain, setFromDomain] = useState(""); const [regPublicIpInContact, setRegPublicIpInContact] = useState(false); @@ -123,15 +125,23 @@ export const CarrierForm = ({ const [initialSipProxy, setInitialSipProxy] = useState(false); const [outboundSipProxy, setOutboundSipProxy] = useState(""); + const [initialRegister, setInitialRegister] = useState(false); + const [initialSipRegister, setInitialSipRegister] = useState(false); const [smppSystemId, setSmppSystemId] = useState(""); const [smppPass, setSmppPass] = useState(""); const [smppInboundSystemId, setSmppInboundSystemId] = useState(""); const [smppInboundPass, setSmppInboundPass] = useState(""); - const [sipGateways, setSipGateways] = useState([ - DEFAULT_SIP_GATEWAY, - ]); + const [sipInboundGateways, setSipInboundGateways] = useState( + [], + ); + const [sipOutboundGateways, setSipOutboundGateways] = useState( + [], + ); + const [tmpInboundGateways, setTmpInboundGateways] = useState( + [], + ); const [smppGateways, setSmppGateways] = useState([ { ...DEFAULT_SMPP_GATEWAY, @@ -143,9 +153,8 @@ export const CarrierForm = ({ }, ]); - const [sipMessage, setSipMessage] = useState(""); - const [smppInboundMessage, setSmppInboundMessage] = useState(""); - const [smppOutboundMessage, setSmppOutboundMessage] = useState(""); + const [sipInboundMessage, setSipInboundMessage] = useState(""); + const [sipOutboundMessage, setSipOutboundMessage] = useState(""); const validateOutboundSipGateway = ( gateway: string, @@ -257,20 +266,6 @@ export const CarrierForm = ({ setRegPublicIpInContact(obj.register_public_ip_in_contact); } - if ( - obj.requires_register || - obj.register_username || - obj.register_password || - obj.register_sip_realm || - obj.register_from_user || - obj.register_from_domain || - obj.register_public_ip_in_contact - ) { - setInitialRegister(true); - } else { - setInitialRegister(false); - } - if (obj.tech_prefix) { setPrefix(obj.tech_prefix); setInitialPrefix(true); @@ -291,6 +286,22 @@ export const CarrierForm = ({ setInitialSipProxy(false); } + if ( + obj.requires_register || + obj.register_username || + obj.register_password + ) { + setInitialRegister(true); + } else { + setInitialRegister(false); + } + + if (obj.requires_register) { + setInitialSipRegister(true); + } else { + setInitialSipRegister(false); + } + if (obj.smpp_system_id) { setSmppSystemId(obj.smpp_system_id); } @@ -306,11 +317,30 @@ export const CarrierForm = ({ if (obj.dtmf_type) { setDtmfType(obj.dtmf_type); } + if (obj.trunk_type) { + setTrunkType(obj.trunk_type); + } + if (obj.inbound_auth_username) { + setInboundAuthUsername(obj.inbound_auth_username); + } + if (obj.inbound_auth_password) { + setInboundAuthPassword(obj.inbound_auth_password); + } } }; - const addSipGateway = () => { - setSipGateways((curr) => [...curr, DEFAULT_SIP_GATEWAY]); + const addSipInboundGateway = () => { + setSipInboundGateways((curr) => [ + ...curr, + { ...DEFAULT_SIP_INBOUND_GATEWAY, inbound: 1, outbound: 0 }, + ]); + }; + + const addSipOutboundGateway = () => { + setSipOutboundGateways((curr) => [ + ...curr, + { ...DEFAULT_SIP_INBOUND_GATEWAY, inbound: 0, outbound: 1 }, + ]); }; const addSmppGateway = (obj: Partial) => { @@ -323,13 +353,13 @@ export const CarrierForm = ({ ]); }; - const updateSipGateways = ( + const updateSipInboundGateways = ( index: number, key: string, - value: (typeof sipGateways)[number][keyof SipGateway], + value: (typeof sipInboundGateways)[number][keyof SipGateway], ) => { - setSipGateways( - sipGateways.map((g, i) => + setSipInboundGateways( + sipInboundGateways.map((g, i) => i === index ? { ...g, @@ -346,19 +376,38 @@ export const CarrierForm = ({ ); }; - const updateSmppGateways = ( + const updateSipOutboundGateways = ( index: number, key: string, - value: (typeof smppGateways)[number][keyof SmppGateway], + value: (typeof sipOutboundGateways)[number][keyof SipGateway], ) => { - setSmppGateways( - smppGateways.map((g, i) => (i === index ? { ...g, [key]: value } : g)), + setSipOutboundGateways( + sipOutboundGateways.map((g, i) => + i === index + ? { + ...g, + [key]: value, + // If Change to ipv4 and port is null, change port to 5060 + ...(key === "ipv4" && + value && + typeof value === "string" && + getIpValidationType(value) === IP && + g.port === null && { port: 5060 }), + } + : g, + ), ); }; const handleSipGatewayPutPost = (voip_carrier_sid: string) => { + // For auth and reg trunk types, only save outbound gateways + const gatewaysToSave = + trunkType === "auth" || trunkType === "reg" + ? sipOutboundGateways + : [...sipInboundGateways, ...sipOutboundGateways]; + Promise.all( - sipGateways.map(({ sip_gateway_sid, ...g }: SipGateway) => + gatewaysToSave.map(({ sip_gateway_sid, ...g }: SipGateway) => sip_gateway_sid ? putSipGateway(sip_gateway_sid, g) : postSipGateway({ ...g, voip_carrier_sid }), @@ -400,18 +449,6 @@ export const CarrierForm = ({ } }; - const handleSmppGatewayDelete = (g?: SmppGateway) => { - if (g && g.smpp_gateway_sid) { - deleteSmppGateway(g.smpp_gateway_sid).then(() => - toastSuccess( - `SMPP ${ - g.outbound ? "outbound" : "inbound" - } gateway successfully deleted`, - ), - ); - } - }; - const hasEmptySmppGateways = (type: keyof SmppGateway) => { const filtered = smppGateways.filter((g) => g[type]); return ( @@ -423,40 +460,146 @@ export const CarrierForm = ({ }; const getSipValidation = () => { - if (!hasLength(sipGateways)) { - return "You must provide at least one SIP Gateway."; + // Check if there are any gateways at all + if (sipInboundGateways.length === 0 && sipOutboundGateways.length === 0) { + // For Static IP Whitelist, prioritize inbound tab since it requires inbound gateways + if (trunkType === "static_ip") { + setActiveTab("inbound"); + return "Static IP Whitelist trunk type requires at least one inbound gateway."; + } else if (trunkType === "reg" || trunkType === "auth") { + // Auth and Registration trunk needs at least one outbound gateway for routing + setActiveTab("outbound"); + return "You must provide at least one outbound SIP Gateway."; + } + // Auth trunk doesn't require any gateways - skip validation } - for (let i = 0; i < sipGateways.length; i++) { - const gateway = sipGateways[i]; + // Validate Static IP Whitelist trunk type requires at least 1 inbound gateway + if (trunkType === "static_ip" && sipInboundGateways.length < 1) { + setActiveTab("inbound"); + return "Static IP Whitelist trunk type requires at least one inbound gateway."; + } + + // Validate Auth and Registration Trunk require at least 1 outbound gateway + if ( + (trunkType === "auth" || trunkType === "reg") && + sipOutboundGateways.length < 1 + ) { + setActiveTab("outbound"); + return "Auth and Registration trunk types require at least one outbound gateway."; + } + + // Validate Auth Trunk credentials + if (trunkType === "auth") { + if ( + !inboundAuthUsername || + !inboundAuthPassword || + inboundAuthUsername.trim() === "" || + inboundAuthPassword.trim() === "" + ) { + setActiveTab("inbound"); + // Delay focus to allow tab switch to complete + setTimeout(() => { + if (refInboundAuthUsername.current) { + refInboundAuthUsername.current.focus(); + } + }, 100); + return "Auth Trunk requires both username and password credentials."; + } + } + + // Validate inbound gateways + for (let i = 0; i < sipInboundGateways.length; i++) { + const gateway = sipInboundGateways[i]; const type = getIpValidationType(gateway.ipv4); - /** DH: unclear why we had this restriction, removing for now - if (type === FQDN_TOP_LEVEL) { - refSipIp.current[i].focus(); - return "When using an FQDN, you must use a subdomain (e.g. sip.example.com)."; - } else if (type === FQDN && (!gateway.outbound || gateway.inbound)) { - */ if (type === FQDN && (!gateway.outbound || gateway.inbound)) { - refSipIp.current[i].focus(); + if (refSipInboundIp.current[i]) { + refSipInboundIp.current[i].focus(); + } + setActiveTab("inbound"); return "A fully qualified domain name may only be used for outbound calls."; } else if (type === INVALID) { - refSipIp.current[i].focus(); + if (refSipInboundIp.current[i]) { + refSipInboundIp.current[i].focus(); + } + setActiveTab("inbound"); return "Please provide a valid IP address or fully qualified domain name."; } - /** Duplicates validation */ - const dupeSipGateway = sipGateways.find((g) => { + // Check for duplicates within inbound gateways + const dupeInboundGateway = sipInboundGateways.find((g, idx) => { return ( - g !== gateway && + idx !== i && gateway.ipv4 && g.ipv4 === gateway.ipv4 && g.port === gateway.port ); }); - if (dupeSipGateway) { - refSipIp.current[i].focus(); + if (dupeInboundGateway) { + if (refSipInboundIp.current[i]) { + refSipInboundIp.current[i].focus(); + } + setActiveTab("inbound"); + return "Each SIP gateway must have a unique IP address."; + } + } + + // Validate outbound gateways + for (let i = 0; i < sipOutboundGateways.length; i++) { + const gateway = sipOutboundGateways[i]; + const type = getIpValidationType(gateway.ipv4); + + if (type === FQDN && (!gateway.outbound || gateway.inbound)) { + if (refSipOutboundIp.current[i]) { + refSipOutboundIp.current[i].focus(); + } + setActiveTab("outbound"); + return "A fully qualified domain name may only be used for outbound calls."; + } else if (type === INVALID) { + if (refSipOutboundIp.current[i]) { + refSipOutboundIp.current[i].focus(); + } + setActiveTab("outbound"); + return "Please provide a valid IP address or fully qualified domain name."; + } + + // Check for duplicates within outbound gateways + const dupeOutboundGateway = sipOutboundGateways.find((g, idx) => { + return ( + idx !== i && + gateway.ipv4 && + g.ipv4 === gateway.ipv4 && + g.port === gateway.port + ); + }); + + if (dupeOutboundGateway) { + if (refSipOutboundIp.current[i]) { + refSipOutboundIp.current[i].focus(); + } + setActiveTab("outbound"); + return "Each SIP gateway must have a unique IP address."; + } + } + + // Check for duplicates between inbound and outbound gateways + for (let i = 0; i < sipInboundGateways.length; i++) { + const inboundGateway = sipInboundGateways[i]; + const dupeInOutbound = sipOutboundGateways.find((g) => { + return ( + inboundGateway.ipv4 && + g.ipv4 === inboundGateway.ipv4 && + g.port === inboundGateway.port + ); + }); + + if (dupeInOutbound) { + if (refSipInboundIp.current[i]) { + refSipInboundIp.current[i].focus(); + } + setActiveTab("inbound"); return "Each SIP gateway must have a unique IP address."; } } @@ -522,8 +665,13 @@ export const CarrierForm = ({ const handleActiveTab = () => { /** When to switch to `sip` tab */ - const emptySipIp = sipGateways.find((g) => g.ipv4.trim() === ""); - const invalidSipPort = sipGateways.find( + // For auth and reg trunk types, only check outbound gateways for validation + const gatewaysToCheck = + trunkType === "auth" || trunkType === "reg" + ? sipOutboundGateways + : [...sipInboundGateways, ...sipOutboundGateways]; + const emptySipIp = gatewaysToCheck.find((g) => g.ipv4.trim() === ""); + const invalidSipPort = gatewaysToCheck.find( (g) => hasValue(g.port) && !isValidPort(g.port), ); const sipGatewayValidation = getSipValidation(); @@ -572,29 +720,39 @@ export const CarrierForm = ({ return; } - setSipMessage(""); - setSmppInboundMessage(""); - setSmppOutboundMessage(""); + setSipInboundMessage(""); + setSipOutboundMessage(""); const sipGatewayValidation = getSipValidation(); if (sipGatewayValidation) { - setSipMessage(sipGatewayValidation); - return; - } - - /** Conditions to validate SMPP gateway fields... */ - if (shouldValidateSmpp()) { - const smppGatewayValidation = getSmppValidation(); - - if (smppGatewayValidation) { - if (smppGatewayValidation.type === "outbound") { - setSmppOutboundMessage(smppGatewayValidation.msg); - } else { - setSmppInboundMessage(smppGatewayValidation.msg); - } - return; + // For static_ip validation errors, show only in inbound tab + if ( + sipGatewayValidation === + "Static IP Whitelist trunk type requires at least one inbound gateway." + ) { + setSipInboundMessage(sipGatewayValidation); + } else if ( + sipGatewayValidation === + "Auth Trunk requires both username and password credentials." + ) { + // Show auth credentials validation error only in inbound tab + setSipInboundMessage(sipGatewayValidation); + } else if ( + sipGatewayValidation === + "You must provide at least one outbound SIP Gateway." || + sipGatewayValidation === + "Auth and Registration trunk types require at least one outbound gateway." + ) { + // Show in outbound tab when outbound gateways are required + setSipOutboundMessage(sipGatewayValidation); + } else { + // For other validation errors, the validation function already set the correct tab + // Show in both tabs to ensure user sees the error + setSipInboundMessage(sipGatewayValidation); + setSipOutboundMessage(sipGatewayValidation); } + return; } if ( @@ -628,6 +786,9 @@ export const CarrierForm = ({ smpp_inbound_system_id: smppInboundSystemId.trim() || null, smpp_inbound_password: smppInboundPass.trim() || null, dtmf_type: dtmfType, + trunk_type: trunkType, + inbound_auth_username: inboundAuthUsername.trim() || undefined, + inbound_auth_password: inboundAuthPassword.trim() || undefined, outbound_sip_proxy: outboundSipProxy.trim().replaceAll(" ", "") || null, }; @@ -719,7 +880,20 @@ export const CarrierForm = ({ carrierSipGateways.data !== prevSipGateways ) { setPrevSipGateways(carrierSipGateways.data); /** Deadly important */ - setSipGateways(carrierSipGateways.data); + const inboundGateways = carrierSipGateways.data.filter((g) => g.inbound); + const outboundGateways = carrierSipGateways.data.filter((g) => g.outbound); + + // For auth and reg trunk types, don't load inbound gateways but store them in temp state + if (trunkType === "auth" || trunkType === "reg") { + if (inboundGateways.length > 0) { + setTmpInboundGateways(inboundGateways); + } + setSipInboundGateways([]); + setSipOutboundGateways(outboundGateways); + } else { + setSipInboundGateways(inboundGateways); + setSipOutboundGateways(outboundGateways); + } } if ( @@ -742,6 +916,32 @@ export const CarrierForm = ({ } } + const handleInvalidField = (e: React.InvalidEvent) => { + const invalidField = e.target as unknown as HTMLInputElement; + const fieldName = invalidField.name || invalidField.id; + + // Map field names to tabs + if (fieldName === "carrier_name") { + setActiveTab("general"); + } else if (fieldName?.includes("inbound_auth_")) { + setActiveTab("inbound"); + } else if ( + fieldName?.includes("sip_username") || + fieldName?.includes("sip_password") || + fieldName?.includes("sip_realm") || + fieldName?.includes("from_user") || + fieldName?.includes("from_domain") + ) { + setActiveTab("outbound"); // Changed from "registration" to "outbound" + } else if (fieldName?.includes("sip_")) { + setActiveTab("sip"); + } else if (fieldName?.includes("smpp_")) { + setActiveTab("smpp"); + } + + // Allow the default browser validation message to show + }; + return (
{MSG_REQUIRED_FIELDS} @@ -818,37 +1019,25 @@ export const CarrierForm = ({
Active
+
+
+ + Have your carriers whitelist our SIP signaling IPs + + {hasLength(sbcs) && + sbcs.map((sbc) => { + return ( + + {sbc.ipv4}:{sbc.port} + + ); + })} +
+
- + {/** General */} +
-
- - Have your carriers whitelist our SIP signaling IPs - - {hasLength(sbcs) && - sbcs.map((sbc) => { - return ( - - {sbc.ipv4}:{sbc.port} - - ); - })} -
-
-
- - - Prepend a leading + on origination attempts. - + + { + const newTrunkType = e.target.value as TrunkType; + const prevTrunkType = trunkType; + setTrunkType(newTrunkType); + + // Clear auth credentials when switching away from auth trunk + if (newTrunkType !== "auth") { + setInboundAuthUsername(""); + setInboundAuthPassword(""); + } + + // Auto-check authentication and register for Registration Trunk + if (newTrunkType === "reg") { + setInitialRegister(true); + setInitialSipRegister(true); + setSipRegister(true); + } else { + setInitialRegister(false); + setInitialSipRegister(false); + setSipRegister(false); + } + + // Handle inbound gateway management for auth and reg trunk types + if ( + (newTrunkType === "auth" || newTrunkType === "reg") && + prevTrunkType === "static_ip" + ) { + // Store current inbound gateways and clear them + setTmpInboundGateways(sipInboundGateways); + setSipInboundGateways([]); + } else if ( + newTrunkType === "static_ip" && + (prevTrunkType === "auth" || prevTrunkType === "reg") + ) { + // Restore inbound gateways from temp storage + setSipInboundGateways(tmpInboundGateways); + setTmpInboundGateways([]); + } + + // Ensure minimum 1 outbound gateway for auth and reg types + if ( + (newTrunkType === "auth" || newTrunkType === "reg") && + sipOutboundGateways.length === 0 + ) { + setSipOutboundGateways([ + { + ...DEFAULT_SIP_INBOUND_GATEWAY, + inbound: 0, + outbound: 1, + }, + ]); + } + }} + /> +
+
+ {/** Inbound */} + + {trunkType === "static_ip" && ( +
+ + + + Static IP Whitelist requires at least one inbound gateway. + {sipInboundGateways.length === 0 && + " Click the plus icon to add one."} + {sipInboundGateways.length === 1 && + " You can add more gateways or keep this one."} + {sipInboundGateways.length > 1 && + " You can delete gateways but must keep at least one."} + + + {hasLength(sipInboundGateways) ? ( + + ) : ( + + Click plus icon to add SIP Gateway. + + )} + {sipInboundMessage && } + {hasLength(sipInboundGateways) && + sipInboundGateways.map((g, i) => ( +
+
+
+ { + updateSipInboundGateways( + i, + "ipv4", + e.target.value, + ); + }} + ref={(ref: HTMLInputElement) => + (refSipInboundIp.current[i] = ref) + } + /> +
+
+ { + updateSipInboundGateways( + i, + "netmask", + e.target.value, + ); + }} + /> +
+
+
+
+ +
+ +
+ +
+
+ + +
+ ))} + + + +
+ )} + {trunkType === "auth" && ( +
+ + + Enter authentication credentials for inbound calls. + + + + Note: Auth Trunk does not use inbound SIP gateways for + routing. + + + + + setInboundAuthUsername(e.target.value)} + ref={refInboundAuthUsername} + /> + + + setInboundAuthPassword(e.target.value)} + /> +
+ )} + {trunkType === "reg" && ( +
+ + Registration Trunk does not require Inbound settings. + +
+ )} +
+ {/** Outbound */} + +
+ + + Prepend a leading + on origination attempts. + +
+
+
+
@@ -1101,14 +1601,20 @@ export const CarrierForm = ({ SIP gateways* - At least one SIP gateway is required. + + {trunkType === "auth" + ? "Auth Trunk requires at least one outbound SIP gateway for routing calls." + : trunkType === "reg" + ? "Registration Trunk requires at least one outbound SIP gateway for routing calls." + : "At least one SIP gateway is required."} + - {sipMessage && } - {hasLength(sipGateways) && - sipGateways.map((g, i) => ( + {sipOutboundMessage && } + {hasLength(sipOutboundGateways) && + sipOutboundGateways.map((g, i) => (
{ - updateSipGateways(i, "ipv4", e.target.value); + updateSipOutboundGateways( + i, + "ipv4", + e.target.value, + ); }} ref={(ref: HTMLInputElement) => - (refSipIp.current[i] = ref) + (refSipOutboundIp.current[i] = ref) } />
@@ -1140,11 +1650,11 @@ export const CarrierForm = ({ placeholder={ g.protocol === "tls" || g.protocol === "tls/srtp" ? "" - : DEFAULT_SIP_GATEWAY.port?.toString() + : DEFAULT_SIP_INBOUND_GATEWAY.port?.toString() } value={g.port === null ? "" : g.port} onChange={(e) => { - updateSipGateways( + updateSipOutboundGateways( i, "port", g.outbound > 0 && @@ -1167,7 +1677,11 @@ export const CarrierForm = ({ value={g.protocol} options={SIP_GATEWAY_PROTOCOL_OPTIONS} onChange={(e) => { - updateSipGateways(i, "protocol", e.target.value); + updateSipOutboundGateways( + i, + "protocol", + e.target.value, + ); }} /> @@ -1179,7 +1693,11 @@ export const CarrierForm = ({ value={g.netmask} options={NETMASK_OPTIONS} onChange={(e) => { - updateSipGateways(i, "netmask", e.target.value); + updateSipOutboundGateways( + i, + "netmask", + e.target.value, + ); }} /> @@ -1197,7 +1715,7 @@ export const CarrierForm = ({ type="checkbox" checked={g.is_active ? true : false} onChange={(e) => { - updateSipGateways( + updateSipOutboundGateways( i, "is_active", e.target.checked ? 1 : 0, @@ -1207,44 +1725,7 @@ export const CarrierForm = ({
Active
-
- -
-
- -
+
)} - {Boolean(g.outbound) && - (g.protocol === "tls" || g.protocol === "tls/srtp") && ( -
- -
- )} + {(g.protocol === "tls" || g.protocol === "tls/srtp") && ( +
+ +
+ )}
- -
-
- - Have your carriers whitelist our SMPP signaling IPs - - {hasLength(smpps) && - smpps.map((smpp) => { - return ( - - {smpp.ipv4}:{smpp.port} - {smpp.use_tls && " (TLS)"} - - ); - })} -
-
-
- - - { - setSmppSystemId(e.target.value); - }} - /> - - { - setSmppPass(e.target.value); - }} - /> - - - - At least one outbound gateway is required when using system ID - or password above. - - - - {smppOutboundMessage && } - {hasLength(smppGateways.filter((g) => g.outbound)) && - smppGateways.map((g, i) => { - return g.outbound ? ( -
-
-
- - updateSmppGateways(i, "ipv4", e.target.value) - } - ref={(ref: HTMLInputElement) => - (refSmppIp.current[i] = ref) - } - /> -
-
- - updateSmppGateways( - i, - "port", - Number(e.target.value), - ) - } - ref={(ref: HTMLInputElement) => - (refSmppPort.current[i] = ref) - } - /> -
-
- -
-
- -
- ) : null; - })} - - - -
-
- - - { - setSmppInboundSystemId(e.target.value); - }} - /> - - - - Password is required if whitelisting carrier IP address(es) - below. - - - { - setSmppInboundPass(e.target.value); - }} - /> - - - - Fully qualified domain names (e.g. sip.example.com) may only - be used for outbound calls above. - - - - {smppInboundMessage && } - {hasLength(smppGateways.filter((g) => g.inbound)) && - smppGateways.map((g, i) => { - return g.inbound ? ( -
-
-
- - updateSmppGateways(i, "ipv4", e.target.value) - } - ref={(ref: HTMLInputElement) => - (refSmppIp.current[i] = ref) - } - /> -
-
- - updateSmppGateways(i, "netmask", e.target.value) - } - /> -
-
- -
- ) : null; - })} - - - -
-
+ {/** Registration tab removed - content merged into Outbound tab */}
diff --git a/src/styles/_forms.scss b/src/styles/_forms.scss index a2cdb5f..6af01ac 100644 --- a/src/styles/_forms.scss +++ b/src/styles/_forms.scss @@ -218,7 +218,8 @@ fieldset { } } -.gateway { +.gateway, +.gateway-inbound { padding: ui-vars.$px02; border-radius: ui-vars.$px01; border: 2px solid ui-vars.$grey; @@ -284,6 +285,18 @@ fieldset { } } +.gateway-inbound { + > div { + &:nth-child(1) { + grid-template-columns: [col] calc(70% - #{ui-vars.$px02 * 2}) [col] 30%; + + @include mixins.small() { + grid-template-columns: [col] 100%; + } + } + } +} + .lcr { @extend .gateway;