mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-01-25 02:08:19 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8538d40696 | ||
|
|
eda1fa0dc4 | ||
|
|
0174315a68 | ||
|
|
b86bf0c403 | ||
|
|
3fc1c800ac | ||
|
|
ee4483288d | ||
|
|
d3f1dbf332 |
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Drachtio Communications Services, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -46,6 +46,38 @@ const StyledInput = styled.input`
|
||||
&:active:not([disabled]):after {
|
||||
background: #A40D40;
|
||||
}
|
||||
|
||||
&::file-selector-button {
|
||||
content: '${props => props.validFile ? 'Choose a Different File' : 'Choose File'}';
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 2.25rem;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0.25rem;
|
||||
background: #D91C5C;
|
||||
color: #FFF;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&:focus::file-selector-button {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.12),
|
||||
inset 0 0 0 0.25rem #890934;
|
||||
}
|
||||
|
||||
&:hover:not([disabled])::file-selector-button {
|
||||
background: #BD164E;
|
||||
}
|
||||
|
||||
&:active:not([disabled])::file-selector-button {
|
||||
background: #A40D40;
|
||||
}
|
||||
`;
|
||||
|
||||
const FileUpload = (props, ref) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
|
||||
import Form from '../elements/Form';
|
||||
import Input from '../elements/Input';
|
||||
import Label from '../elements/Label';
|
||||
@@ -49,40 +50,52 @@ const AccountForm = props => {
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
const jwt = localStorage.getItem("token");
|
||||
const currentServiceProvider = useContext(ServiceProviderValueContext);
|
||||
|
||||
// Refs
|
||||
const refName = useRef(null);
|
||||
const refSipRealm = useRef(null);
|
||||
const refRegWebhook = useRef(null);
|
||||
const refUser = useRef(null);
|
||||
const refPassword = useRef(null);
|
||||
const refRegUser = useRef(null);
|
||||
const refRegPassword = useRef(null);
|
||||
const refQueueWebhook = useRef(null);
|
||||
const refQueueUser = useRef(null);
|
||||
const refQueuePassword = useRef(null);
|
||||
|
||||
// Form inputs
|
||||
const [ name, setName ] = useState('');
|
||||
const [ sipRealm, setSipRealm ] = useState('');
|
||||
const [ name, setName ] = useState('');
|
||||
const [ sipRealm, setSipRealm ] = useState('');
|
||||
const [ deviceCallingApplication, setDeviceCallingApplication ] = useState('');
|
||||
const [ regWebhook, setRegWebhook ] = useState('');
|
||||
const [ method, setMethod ] = useState('POST');
|
||||
const [ user, setUser ] = useState('' || '');
|
||||
const [ password, setPassword ] = useState('' || '');
|
||||
const [ regWebhook, setRegWebhook ] = useState('');
|
||||
const [ regMethod, setRegMethod ] = useState('POST');
|
||||
const [ regUser, setRegUser ] = useState('' || '');
|
||||
const [ regPassword, setRegPassword ] = useState('' || '');
|
||||
const [ webhookSecret, setWebhookSecret ] = useState('');
|
||||
const [ queueWebhook, setQueueWebhook ] = useState('');
|
||||
const [ queueMethod, setQueueMethod ] = useState('POST');
|
||||
const [ queueUser, setQueueUser ] = useState('' || '');
|
||||
const [ queuePassword, setQueuePassword ] = useState('' || '');
|
||||
|
||||
// Invalid form inputs
|
||||
const [ invalidName, setInvalidName ] = useState(false);
|
||||
const [ invalidSipRealm, setInvalidSipRealm ] = useState(false);
|
||||
const [ invalidRegWebhook, setInvalidRegWebhook ] = useState(false);
|
||||
const [ invalidUser, setInvalidUser ] = useState(false);
|
||||
const [ invalidPassword, setInvalidPassword ] = useState(false);
|
||||
const [ invalidName, setInvalidName ] = useState(false);
|
||||
const [ invalidSipRealm, setInvalidSipRealm ] = useState(false);
|
||||
const [ invalidRegWebhook, setInvalidRegWebhook ] = useState(false);
|
||||
const [ invalidRegUser, setInvalidRegUser ] = useState(false);
|
||||
const [ invalidRegPassword, setInvalidRegPassword ] = useState(false);
|
||||
const [ invalidQueueWebhook, setInvalidQueueWebhook ] = useState(false);
|
||||
const [ invalidQueueUser, setInvalidQueueUser ] = useState(false);
|
||||
const [ invalidQueuePassword, setInvalidQueuePassword ] = useState(false);
|
||||
|
||||
const [ showLoader, setShowLoader ] = useState(true);
|
||||
const [ errorMessage, setErrorMessage ] = useState('');
|
||||
|
||||
const [ showAuth, setShowAuth ] = useState(false);
|
||||
const toggleAuth = () => setShowAuth(!showAuth);
|
||||
const [ showRegAuth, setShowRegAuth ] = useState(false);
|
||||
const [ showQueueAuth, setShowQueueAuth ] = useState(false);
|
||||
const toggleRegAuth = () => setShowRegAuth(!showRegAuth);
|
||||
const toggleQueueAuth = () => setShowQueueAuth(!showQueueAuth);
|
||||
|
||||
const [ accounts, setAccounts ] = useState([]);
|
||||
const [ accountSid, setAccountSid ] = useState('');
|
||||
const [ serviceProviderSid, setServiceProviderSid ] = useState('');
|
||||
const [ accountApplications, setAccountApplications ] = useState([]);
|
||||
|
||||
const [ menuOpen, setMenuOpen ] = useState(null);
|
||||
@@ -187,18 +200,6 @@ const AccountForm = props => {
|
||||
promiseList.push(applicationsPromise);
|
||||
}
|
||||
|
||||
if (props.type === 'add') {
|
||||
const serviceProvidersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/ServiceProviders',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
promiseList.push(serviceProvidersPromise);
|
||||
}
|
||||
|
||||
const promiseAllValues = await Promise.all(promiseList);
|
||||
|
||||
const accountsData = (promiseAllValues[0] && promiseAllValues[0].data) || [];
|
||||
@@ -212,11 +213,6 @@ const AccountForm = props => {
|
||||
setAccountApplications(accountApplicationsData);
|
||||
}
|
||||
|
||||
if (props.type === 'add') {
|
||||
const serviceProviders = (promiseAllValues[1] && promiseAllValues[1].data) || '';
|
||||
setServiceProviderSid(serviceProviders[0].service_provider_sid);
|
||||
}
|
||||
|
||||
if (props.type === 'setup' && accountsData.length > 1) {
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
@@ -250,16 +246,27 @@ const AccountForm = props => {
|
||||
setSipRealm(acc.sip_realm || '');
|
||||
setDeviceCallingApplication(acc.device_calling_application_sid || '');
|
||||
setRegWebhook((acc.registration_hook && acc.registration_hook.url ) || '');
|
||||
setMethod((acc.registration_hook && acc.registration_hook.method ) || 'post');
|
||||
setUser((acc.registration_hook && acc.registration_hook.username) || '');
|
||||
setPassword((acc.registration_hook && acc.registration_hook.password) || '');
|
||||
setRegMethod((acc.registration_hook && acc.registration_hook.method ) || 'post');
|
||||
setRegUser((acc.registration_hook && acc.registration_hook.username) || '');
|
||||
setRegPassword((acc.registration_hook && acc.registration_hook.password) || '');
|
||||
setQueueWebhook((acc.queue_event_hook && acc.queue_event_hook.url ) || '');
|
||||
setQueueMethod((acc.queue_event_hook && acc.queue_event_hook.method ) || 'post');
|
||||
setQueueUser((acc.queue_event_hook && acc.queue_event_hook.username) || '');
|
||||
setQueuePassword((acc.queue_event_hook && acc.queue_event_hook.password) || '');
|
||||
setWebhookSecret(acc.webhook_secret || '');
|
||||
|
||||
if (
|
||||
(acc.registration_hook && acc.registration_hook.username) ||
|
||||
(acc.registration_hook && acc.registration_hook.password)
|
||||
) {
|
||||
setShowAuth(true);
|
||||
setShowRegAuth(true);
|
||||
}
|
||||
|
||||
if (
|
||||
(acc.queue_event_hook && acc.queue_event_hook.username) ||
|
||||
(acc.queue_event_hook && acc.queue_event_hook.password)
|
||||
) {
|
||||
setShowQueueAuth(true);
|
||||
}
|
||||
}
|
||||
setShowLoader(false);
|
||||
@@ -298,8 +305,11 @@ const AccountForm = props => {
|
||||
setInvalidName(false);
|
||||
setInvalidSipRealm(false);
|
||||
setInvalidRegWebhook(false);
|
||||
setInvalidUser(false);
|
||||
setInvalidPassword(false);
|
||||
setInvalidRegUser(false);
|
||||
setInvalidRegPassword(false);
|
||||
setInvalidQueueWebhook(false);
|
||||
setInvalidQueueUser(false);
|
||||
setInvalidQueuePassword(false);
|
||||
let errorMessages = [];
|
||||
let focusHasBeenSet = false;
|
||||
|
||||
@@ -342,15 +352,29 @@ const AccountForm = props => {
|
||||
});
|
||||
|
||||
|
||||
if ((user && !password) || (!user && password)) {
|
||||
errorMessages.push('Username and password must be either both filled out or both empty.');
|
||||
setInvalidUser(true);
|
||||
setInvalidPassword(true);
|
||||
if ((regUser && !regPassword) || (!regUser && regPassword)) {
|
||||
errorMessages.push('Registration webhook username and password must be either both filled out or both empty.');
|
||||
setInvalidRegUser(true);
|
||||
setInvalidRegPassword(true);
|
||||
if (!focusHasBeenSet) {
|
||||
if (!user) {
|
||||
refUser.current.focus();
|
||||
if (!regUser) {
|
||||
refRegUser.current.focus();
|
||||
} else {
|
||||
refPassword.current.focus();
|
||||
refRegPassword.current.focus();
|
||||
}
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((queueUser && !queuePassword) || (!queueUser && queuePassword)) {
|
||||
errorMessages.push('Queue event webhook username and password must be either both filled out or both empty.');
|
||||
setInvalidQueueUser(true);
|
||||
setInvalidQueuePassword(true);
|
||||
if (!focusHasBeenSet) {
|
||||
if (!queueUser) {
|
||||
refQueueUser.current.focus();
|
||||
} else {
|
||||
refQueuePassword.current.focus();
|
||||
}
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
@@ -369,15 +393,21 @@ const AccountForm = props => {
|
||||
sip_realm: sipRealm.trim() || null,
|
||||
registration_hook: {
|
||||
url: regWebhook.trim(),
|
||||
method: method,
|
||||
username: user.trim() || null,
|
||||
password: password || null,
|
||||
method: regMethod,
|
||||
username: regUser.trim() || null,
|
||||
password: regPassword || null,
|
||||
},
|
||||
queue_event_hook: {
|
||||
url: queueWebhook.trim(),
|
||||
method: queueMethod,
|
||||
username: queueUser.trim() || null,
|
||||
password: queuePassword || null,
|
||||
},
|
||||
webhook_secret: webhookSecret || null,
|
||||
};
|
||||
|
||||
if (props.type === 'add') {
|
||||
axiosData.service_provider_sid = serviceProviderSid;
|
||||
axiosData.service_provider_sid = currentServiceProvider;
|
||||
}
|
||||
|
||||
if (props.type === 'edit') {
|
||||
@@ -567,26 +597,26 @@ const AccountForm = props => {
|
||||
large={props.type === 'setup'}
|
||||
name="method"
|
||||
id="method"
|
||||
value={method}
|
||||
onChange={e => setMethod(e.target.value)}
|
||||
value={regMethod}
|
||||
onChange={e => setRegMethod(e.target.value)}
|
||||
>
|
||||
<option value="POST">POST</option>
|
||||
<option value="GET">GET</option>
|
||||
</Select>
|
||||
</InputGroup>
|
||||
|
||||
{showAuth ? (
|
||||
{showRegAuth ? (
|
||||
<InputGroup>
|
||||
<Label indented htmlFor="user">User</Label>
|
||||
<Input
|
||||
large={props.type === 'setup'}
|
||||
name="user"
|
||||
id="user"
|
||||
value={user || ''}
|
||||
onChange={e => setUser(e.target.value)}
|
||||
value={regUser || ''}
|
||||
onChange={e => setRegUser(e.target.value)}
|
||||
placeholder="Optional"
|
||||
invalid={invalidUser}
|
||||
ref={refUser}
|
||||
invalid={invalidRegUser}
|
||||
ref={refRegUser}
|
||||
/>
|
||||
<Label htmlFor="password" middle>Password</Label>
|
||||
<PasswordInput
|
||||
@@ -594,12 +624,12 @@ const AccountForm = props => {
|
||||
allowShowPassword
|
||||
name="password"
|
||||
id="password"
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
password={regPassword}
|
||||
setPassword={setRegPassword}
|
||||
setErrorMessage={setErrorMessage}
|
||||
placeholder="Optional"
|
||||
invalid={invalidPassword}
|
||||
ref={refPassword}
|
||||
invalid={invalidRegPassword}
|
||||
ref={refRegPassword}
|
||||
/>
|
||||
</InputGroup>
|
||||
) : (
|
||||
@@ -607,7 +637,75 @@ const AccountForm = props => {
|
||||
text
|
||||
formLink
|
||||
type="button"
|
||||
onClick={toggleAuth}
|
||||
onClick={toggleRegAuth}
|
||||
>
|
||||
Use HTTP Basic Authentication
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Label htmlFor="queueWebhook">Queue Event Webhook</Label>
|
||||
<InputGroup>
|
||||
<Input
|
||||
large={props.type === 'setup'}
|
||||
name="queueWebhook"
|
||||
id="queueWebhook"
|
||||
value={queueWebhook}
|
||||
onChange={e => setQueueWebhook(e.target.value)}
|
||||
placeholder="URL to notify when a member joins or leaves a queue"
|
||||
invalid={invalidQueueWebhook}
|
||||
ref={refQueueWebhook}
|
||||
/>
|
||||
|
||||
<Label
|
||||
middle
|
||||
htmlFor="method"
|
||||
>
|
||||
Method
|
||||
</Label>
|
||||
<Select
|
||||
large={props.type === 'setup'}
|
||||
name="method"
|
||||
id="method"
|
||||
value={queueMethod}
|
||||
onChange={e => setQueueMethod(e.target.value)}
|
||||
>
|
||||
<option value="POST">POST</option>
|
||||
</Select>
|
||||
</InputGroup>
|
||||
|
||||
{showQueueAuth ? (
|
||||
<InputGroup>
|
||||
<Label indented htmlFor="user">User</Label>
|
||||
<Input
|
||||
large={props.type === 'setup'}
|
||||
name="user"
|
||||
id="user"
|
||||
value={queueUser || ''}
|
||||
onChange={e => setQueueUser(e.target.value)}
|
||||
placeholder="Optional"
|
||||
invalid={invalidQueueUser}
|
||||
ref={refQueueUser}
|
||||
/>
|
||||
<Label htmlFor="password" middle>Password</Label>
|
||||
<PasswordInput
|
||||
large={props.type === 'setup'}
|
||||
allowShowPassword
|
||||
name="password"
|
||||
id="password"
|
||||
password={queuePassword}
|
||||
setPassword={setQueuePassword}
|
||||
setErrorMessage={setErrorMessage}
|
||||
placeholder="Optional"
|
||||
invalid={invalidQueuePassword}
|
||||
ref={refQueuePassword}
|
||||
/>
|
||||
</InputGroup>
|
||||
) : (
|
||||
<Button
|
||||
text
|
||||
formLink
|
||||
type="button"
|
||||
onClick={toggleQueueAuth}
|
||||
>
|
||||
Use HTTP Basic Authentication
|
||||
</Button>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
|
||||
import Form from '../elements/Form';
|
||||
import Input from '../elements/Input';
|
||||
import Label from '../elements/Label';
|
||||
@@ -20,6 +21,7 @@ import CopyableText from '../elements/CopyableText';
|
||||
const ApplicationForm = props => {
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
const currentServiceProvider = useContext(ServiceProviderValueContext);
|
||||
|
||||
// Refs
|
||||
const refName = useRef(null);
|
||||
@@ -118,7 +120,7 @@ const ApplicationForm = props => {
|
||||
applicationsPromise,
|
||||
]);
|
||||
|
||||
const accounts = promiseAllValues[0].data;
|
||||
const accounts = promiseAllValues[0].data.filter(a => a.service_provider_sid === currentServiceProvider);
|
||||
const applications = promiseAllValues[1].data;
|
||||
|
||||
setAccounts(accounts);
|
||||
@@ -493,7 +495,7 @@ const ApplicationForm = props => {
|
||||
-- Choose the account this application will be associated with --
|
||||
</option>
|
||||
)}
|
||||
{accounts.map(a => (
|
||||
{accounts.filter(a => a.service_provider_sid === currentServiceProvider).map(a => (
|
||||
<option
|
||||
key={a.account_sid}
|
||||
value={a.account_sid}
|
||||
|
||||
@@ -5,6 +5,7 @@ import styled from "styled-components/macro";
|
||||
import { Menu, Dropdown } from "antd";
|
||||
|
||||
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
|
||||
import Form from '../elements/Form';
|
||||
import Input from '../elements/Input';
|
||||
import PasswordInput from '../elements/PasswordInput';
|
||||
@@ -18,7 +19,6 @@ import Loader from '../blocks/Loader';
|
||||
import sortSipGateways from '../../helpers/sortSipGateways';
|
||||
import Select from '../elements/Select';
|
||||
import handleErrors from "../../helpers/handleErrors";
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext'
|
||||
|
||||
const StyledForm = styled(Form)`
|
||||
@media (max-width: 978.98px) {
|
||||
@@ -185,6 +185,8 @@ const CarrierForm = (props) => {
|
||||
]);
|
||||
|
||||
const [ applicationValues, setApplicationValues ] = useState([]);
|
||||
const [ accounts, setAccounts ] = useState([]);
|
||||
const [ accountSid, setAccountSid ] = useState('');
|
||||
|
||||
const [ carrierSid, setCarrierSid ] = useState('');
|
||||
const [ showLoader, setShowLoader ] = useState(true);
|
||||
@@ -220,7 +222,16 @@ const CarrierForm = (props) => {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
promises.push(applicationPromise);
|
||||
promises.push(accountsPromise);
|
||||
|
||||
if (type === 'edit') {
|
||||
const carrierPromise = axios({
|
||||
@@ -248,11 +259,12 @@ const CarrierForm = (props) => {
|
||||
const promiseResponses = await Promise.all(promises);
|
||||
|
||||
setApplicationValues(promiseResponses[0].data);
|
||||
setAccounts(promiseResponses[1].data);
|
||||
|
||||
if (type === 'edit') {
|
||||
|
||||
const carrier = promiseResponses[1].data;
|
||||
const allSipGateways = promiseResponses[2].data;
|
||||
const carrier = promiseResponses[2].data;
|
||||
const allSipGateways = promiseResponses[3].data;
|
||||
|
||||
if (!carrier) {
|
||||
isMounted = false;
|
||||
@@ -274,6 +286,7 @@ const CarrierForm = (props) => {
|
||||
setName(carrier.name || '');
|
||||
setE164(carrier.e164_leading_plus === 1);
|
||||
setApplication(carrier.application_sid || '');
|
||||
setAccountSid(carrier.account_sid || '');
|
||||
setAuthenticate(carrier.register_username ? true : false);
|
||||
setRegister(carrier.requires_register === 1);
|
||||
setUsername(carrier.register_username || '');
|
||||
@@ -639,6 +652,7 @@ const CarrierForm = (props) => {
|
||||
name: name.trim() || null,
|
||||
e164_leading_plus: e164 ? 1 : 0,
|
||||
application_sid: application || null,
|
||||
account_sid: accountSid || null,
|
||||
requires_register: register ? 1 : 0,
|
||||
register_username: username ? username.trim() : null,
|
||||
register_password: password ? password : null,
|
||||
@@ -751,7 +765,11 @@ const CarrierForm = (props) => {
|
||||
}
|
||||
|
||||
isMounted = false;
|
||||
history.push('/internal/carriers');
|
||||
if (accountSid) {
|
||||
history.push(`/internal/carriers?account_sid=${accountSid}`);
|
||||
} else {
|
||||
history.push('/internal/carriers');
|
||||
}
|
||||
const dispatchMessage = type === 'add'
|
||||
? 'Carrier created successfully'
|
||||
: 'Carrier updated successfully';
|
||||
@@ -894,29 +912,65 @@ const CarrierForm = (props) => {
|
||||
onChange={e => setE164(e.target.checked)}
|
||||
/>
|
||||
|
||||
<Label htmlFor="application">Application</Label>
|
||||
<Label htmlFor="account">Used by</Label>
|
||||
<Select
|
||||
name="application"
|
||||
id="application"
|
||||
value={application}
|
||||
onChange={e => setApplication(e.target.value)}
|
||||
name="account"
|
||||
id="account"
|
||||
value={accountSid}
|
||||
onChange={(e) => {
|
||||
setAccountSid(e.target.value);
|
||||
setApplication('');
|
||||
}}
|
||||
>
|
||||
<option value="">
|
||||
{type === 'add'
|
||||
? '-- OPTIONAL: Application to invoke on calls arriving from this carrier --'
|
||||
: '-- NONE --'
|
||||
}
|
||||
All accounts
|
||||
</option>
|
||||
{applicationValues.map(a => (
|
||||
{accounts.filter(a => a.service_provider_sid === currentServiceProvider).map(a => (
|
||||
<option
|
||||
key={a.application_sid}
|
||||
value={a.application_sid}
|
||||
key={a.account_sid}
|
||||
value={a.account_sid}
|
||||
>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{accountSid && (
|
||||
<>
|
||||
<Label htmlFor="application">Default Application</Label>
|
||||
<Select
|
||||
name="application"
|
||||
id="application"
|
||||
value={application}
|
||||
onChange={e => setApplication(e.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
{type === 'add'
|
||||
? '-- OPTIONAL: Application to invoke on calls arriving from this carrier --'
|
||||
: '-- NONE --'
|
||||
}
|
||||
</option>
|
||||
{applicationValues.filter((a) => {
|
||||
// Map an application to a service provider through it's account_sid
|
||||
const acct = accounts.find(ac => a.account_sid === ac.account_sid);
|
||||
|
||||
if (accountSid) {
|
||||
return a.account_sid === accountSid;
|
||||
}
|
||||
|
||||
return acct.service_provider_sid === currentServiceProvider;
|
||||
}).map(a => (
|
||||
<option
|
||||
key={a.application_sid}
|
||||
value={a.application_sid}
|
||||
>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
|
||||
<hr style={{ margin: '0.5rem -2rem' }} />
|
||||
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
|
||||
import Form from '../elements/Form';
|
||||
import Input from '../elements/Input';
|
||||
import Label from '../elements/Label';
|
||||
@@ -16,6 +17,7 @@ const PhoneNumberForm = props => {
|
||||
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
const currentServiceProvider = useContext(ServiceProviderValueContext);
|
||||
|
||||
// Refs
|
||||
const refPhoneNumber = useRef(null);
|
||||
@@ -358,7 +360,10 @@ const PhoneNumberForm = props => {
|
||||
name="account"
|
||||
id="account"
|
||||
value={account}
|
||||
onChange={e => setAccount(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setAccount(e.target.value);
|
||||
setApplication('');
|
||||
}}
|
||||
invalid={invalidAccount}
|
||||
ref={refAccount}
|
||||
>
|
||||
@@ -368,7 +373,7 @@ const PhoneNumberForm = props => {
|
||||
) && (
|
||||
<option value="">-- Choose the account that this phone number should be associated with --</option>
|
||||
)}
|
||||
{accountValues.map(a => (
|
||||
{accountValues.filter(a => a.service_provider_sid === currentServiceProvider).map(a => (
|
||||
<option
|
||||
key={a.account_sid}
|
||||
value={a.account_sid}
|
||||
@@ -391,7 +396,16 @@ const PhoneNumberForm = props => {
|
||||
: '-- NONE --'
|
||||
}
|
||||
</option>
|
||||
{applicationValues.map(a => (
|
||||
{applicationValues.filter((a) => {
|
||||
// Map an application to a service provider through it's account_sid
|
||||
const acct = accountValues.find(ac => a.account_sid === ac.account_sid);
|
||||
|
||||
if (account) {
|
||||
return a.account_sid === account;
|
||||
}
|
||||
|
||||
return acct.service_provider_sid === currentServiceProvider;
|
||||
}).map(a => (
|
||||
<option
|
||||
key={a.application_sid}
|
||||
value={a.application_sid}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import styled from 'styled-components';
|
||||
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
|
||||
import { ShowMsTeamsDispatchContext } from '../../contexts/ShowMsTeamsContext';
|
||||
import Form from '../elements/Form';
|
||||
@@ -11,9 +12,23 @@ import InputGroup from '../elements/InputGroup';
|
||||
import FormError from '../blocks/FormError';
|
||||
import Button from '../elements/Button';
|
||||
import Loader from '../blocks/Loader';
|
||||
import Modal from '../blocks/Modal';
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
|
||||
import handleErrors from "../../helpers/handleErrors";
|
||||
|
||||
const Td = styled.td`
|
||||
padding: 0.5rem 0;
|
||||
&:first-child {
|
||||
font-weight: 500;
|
||||
padding-right: 1.5rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
& ul {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const SettingsForm = () => {
|
||||
const history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
@@ -40,8 +55,9 @@ const SettingsForm = () => {
|
||||
|
||||
const [ showLoader, setShowLoader ] = useState(true);
|
||||
const [ errorMessage, setErrorMessage ] = useState('');
|
||||
|
||||
const [ serviceProviderSid, setServiceProviderSid ] = useState('');
|
||||
const [ serviceProviders, setServiceProviders ] = useState([]);
|
||||
const [ confirmDelete, setConfirmDelete ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const getSettingsData = async () => {
|
||||
@@ -59,14 +75,16 @@ const SettingsForm = () => {
|
||||
const serviceProvidersResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}`,
|
||||
url: `/ServiceProviders`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
|
||||
const sp = serviceProvidersResponse.data;
|
||||
const sps = serviceProvidersResponse.data;
|
||||
const sp = sps.find(s => s.service_provider_sid === currentServiceProvider);
|
||||
|
||||
setServiceProviders(sps);
|
||||
setServiceProviderName(sp.name || '');
|
||||
setServiceProviderSid(sp.service_provider_sid || '');
|
||||
setEnableMsTeams(sp.ms_teams_fqdn ? true : false);
|
||||
@@ -96,6 +114,32 @@ const SettingsForm = () => {
|
||||
setEnableMsTeams(e.target.checked);
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
setErrorMessage('');
|
||||
|
||||
axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${serviceProviderSid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
setConfirmDelete(false);
|
||||
setErrorMessage('');
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'success',
|
||||
message: 'Service Provider Deleted'
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorMessage(error.response.data.msg);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
let isMounted = true;
|
||||
try {
|
||||
@@ -210,69 +254,114 @@ const SettingsForm = () => {
|
||||
return (
|
||||
showLoader
|
||||
? <Loader height="365px" />
|
||||
: <Form
|
||||
large
|
||||
wideLabel
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Label htmlFor="serviceProviderName">Service Provider Name</Label>
|
||||
<Input
|
||||
name="serviceProviderName"
|
||||
id="serviceProviderName"
|
||||
value={serviceProviderName}
|
||||
onChange={e => setServiceProviderName(e.target.value)}
|
||||
invalid={invalidServiceProviderName}
|
||||
ref={refServiceProviderName}
|
||||
/>
|
||||
<div>{/* needed for CSS grid layout */}</div>
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
id="enableMsTeams"
|
||||
label="Enable Microsoft Teams Direct Routing"
|
||||
checked={enableMsTeams}
|
||||
onChange={toggleMsTeams}
|
||||
invalid={invalidEnableMsTeams}
|
||||
ref={refEnableMsTeams}
|
||||
/>
|
||||
: (
|
||||
<>
|
||||
<Form
|
||||
large
|
||||
wideLabel
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Label htmlFor="serviceProviderName">Service Provider Name</Label>
|
||||
<Input
|
||||
name="serviceProviderName"
|
||||
id="serviceProviderName"
|
||||
value={serviceProviderName}
|
||||
onChange={e => setServiceProviderName(e.target.value)}
|
||||
invalid={invalidServiceProviderName}
|
||||
ref={refServiceProviderName}
|
||||
/>
|
||||
<div>{/* needed for CSS grid layout */}</div>
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
id="enableMsTeams"
|
||||
label="Enable Microsoft Teams Direct Routing"
|
||||
checked={enableMsTeams}
|
||||
onChange={toggleMsTeams}
|
||||
invalid={invalidEnableMsTeams}
|
||||
ref={refEnableMsTeams}
|
||||
/>
|
||||
|
||||
<Label htmlFor="sbcDomainName">SBC Domain Name</Label>
|
||||
<Input
|
||||
name="sbcDomainName"
|
||||
id="sbcDomainName"
|
||||
value={sbcDomainName}
|
||||
onChange={e => setSbcDomainName(e.target.value)}
|
||||
placeholder="Fully qualified domain name used for Microsoft Teams"
|
||||
invalid={invalidSbcDomainName}
|
||||
autoFocus={enableMsTeams}
|
||||
ref={refSbcDomainName}
|
||||
disabled={!enableMsTeams}
|
||||
title={(!enableMsTeams && "You must enable Microsoft Teams Direct Routing in order to provide an SBC Domain Name") || ""}
|
||||
/>
|
||||
<Label htmlFor="sbcDomainName">SBC Domain Name</Label>
|
||||
<Input
|
||||
name="sbcDomainName"
|
||||
id="sbcDomainName"
|
||||
value={sbcDomainName}
|
||||
onChange={e => setSbcDomainName(e.target.value)}
|
||||
placeholder="Fully qualified domain name used for Microsoft Teams"
|
||||
invalid={invalidSbcDomainName}
|
||||
autoFocus={enableMsTeams}
|
||||
ref={refSbcDomainName}
|
||||
disabled={!enableMsTeams}
|
||||
title={(!enableMsTeams && "You must enable Microsoft Teams Direct Routing in order to provide an SBC Domain Name") || ""}
|
||||
/>
|
||||
|
||||
{errorMessage && (
|
||||
<FormError grid message={errorMessage} />
|
||||
)}
|
||||
{errorMessage && !confirmDelete && (
|
||||
<FormError grid message={errorMessage} />
|
||||
)}
|
||||
|
||||
<InputGroup flexEnd spaced>
|
||||
<Button
|
||||
grid
|
||||
gray
|
||||
type="button"
|
||||
onClick={() => {
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'info',
|
||||
message: 'Changes canceled',
|
||||
});
|
||||
<InputGroup flexEnd spaced>
|
||||
<Button
|
||||
grid
|
||||
gray
|
||||
type="button"
|
||||
onClick={() => {
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'info',
|
||||
message: 'Changes canceled',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
{serviceProviders.length > 1 && (
|
||||
<Button
|
||||
grid
|
||||
gray
|
||||
type="button"
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button grid>Save</Button>
|
||||
</InputGroup>
|
||||
</Form>
|
||||
|
||||
{confirmDelete && serviceProviders.length > 1 && (
|
||||
<Modal
|
||||
title="Are you sure you want to delete the Service Provider?"
|
||||
loader={false}
|
||||
content={
|
||||
<div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<Td>Service Provider Name:</Td>
|
||||
<Td>{serviceProviderName}</Td>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>SBC Domain Name:</Td>
|
||||
<Td>{sbcDomainName || '[none]'}</Td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{errorMessage && (
|
||||
<FormError message={errorMessage} />
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
handleCancel={() => {
|
||||
setConfirmDelete(false);
|
||||
setErrorMessage('');
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button grid>Save</Button>
|
||||
</InputGroup>
|
||||
</Form>
|
||||
handleSubmit={handleDelete}
|
||||
actionText="Delete"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import handleErrors from '../../helpers/handleErrors';
|
||||
import Form from '../elements/Form';
|
||||
import Input from '../elements/Input';
|
||||
import Label from '../elements/Label';
|
||||
import Select from '../elements/Select';
|
||||
import InputGroup from '../elements/InputGroup';
|
||||
import PasswordInput from '../elements/PasswordInput';
|
||||
import Radio from '../elements/Radio';
|
||||
@@ -79,6 +80,8 @@ const SpeechServicesAddEdit = (props) => {
|
||||
const [ secretAccessKey, setSecretAccessKey ] = useState('');
|
||||
const [ useForTts, setUseForTts ] = useState(false);
|
||||
const [ useForStt, setUseForStt ] = useState(false);
|
||||
const [ accounts, setAccounts ] = useState([]);
|
||||
const [ accountSid, setAccountSid ] = useState('');
|
||||
|
||||
// Invalid form inputs
|
||||
const [ invalidVendorGoogle, setInvalidVendorGoogle ] = useState(false);
|
||||
@@ -100,6 +103,17 @@ const SpeechServicesAddEdit = (props) => {
|
||||
const getAPIData = async () => {
|
||||
let isMounted = true;
|
||||
try {
|
||||
const accountsResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
|
||||
setAccounts(accountsResponse.data);
|
||||
|
||||
if (type === 'edit') {
|
||||
const speechCredential = await axios({
|
||||
method: 'get',
|
||||
@@ -119,6 +133,7 @@ const SpeechServicesAddEdit = (props) => {
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
setAccountSid( speechCredential.data.account_sid || '');
|
||||
setVendor( speechCredential.data.vendor || undefined);
|
||||
setServiceKey( serviceKeyJson || '');
|
||||
setDisplayedServiceKey( displayedServiceKeyJson || '');
|
||||
@@ -238,33 +253,6 @@ const SpeechServicesAddEdit = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user already has a speech service with the selected vendor
|
||||
const speechServices = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/SpeechCredentials`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (type === 'add' && speechServices.data.some(speech => speech.vendor === vendor)) {
|
||||
setErrorMessage('You can only have one speech credential per vendor.');
|
||||
setShowLoader(false);
|
||||
if (vendor === 'google') {
|
||||
setInvalidVendorGoogle(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refVendorGoogle.current.focus();
|
||||
}
|
||||
} else if (vendor === 'aws') {
|
||||
setInvalidVendorAws(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refVendorAws.current.focus();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//===============================================
|
||||
// Submit
|
||||
//===============================================
|
||||
@@ -290,6 +278,8 @@ const SpeechServicesAddEdit = (props) => {
|
||||
secret_access_key: vendor === 'aws' ? secretAccessKey : null,
|
||||
use_for_tts: useForTts,
|
||||
use_for_stt: useForStt,
|
||||
service_provider_sid: accountSid ? null : currentServiceProvider,
|
||||
account_sid: accountSid || null,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -383,7 +373,11 @@ const SpeechServicesAddEdit = (props) => {
|
||||
// If successful, go to speech services
|
||||
//===============================================
|
||||
isMounted = false;
|
||||
history.push('/internal/speech-services');
|
||||
if (accountSid) {
|
||||
history.push(`/internal/speech-services?account_sid=${accountSid}`);
|
||||
} else {
|
||||
history.push('/internal/speech-services');
|
||||
}
|
||||
const dispatchMessage = type === 'add'
|
||||
? 'Speech service created successfully'
|
||||
: 'Speech service updated successfully';
|
||||
@@ -452,6 +446,26 @@ const SpeechServicesAddEdit = (props) => {
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<Label htmlFor="account">Used by</Label>
|
||||
<Select
|
||||
name="account"
|
||||
id="account"
|
||||
value={accountSid}
|
||||
onChange={e => setAccountSid(e.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
All accounts
|
||||
</option>
|
||||
{accounts.filter(a => a.service_provider_sid === currentServiceProvider).map(a => (
|
||||
<option
|
||||
key={a.account_sid}
|
||||
value={a.account_sid}
|
||||
>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{vendor === 'google' ? (
|
||||
<>
|
||||
<Label htmlFor="serviceKey">Service Key</Label>
|
||||
|
||||
@@ -41,10 +41,8 @@ const AccountsList = () => {
|
||||
sid: a.account_sid,
|
||||
name: a.name,
|
||||
sip_realm: a.sip_realm,
|
||||
url: a.registration_hook && a.registration_hook.url,
|
||||
method: a.registration_hook && a.registration_hook.method,
|
||||
username: a.registration_hook && a.registration_hook.username,
|
||||
password: a.registration_hook && a.registration_hook.password,
|
||||
url_reg: a.registration_hook && a.registration_hook.url,
|
||||
url_queue: a.queue_event_hook && a.queue_event_hook.url,
|
||||
}));
|
||||
return(simplifiedAccounts);
|
||||
} catch (err) {
|
||||
@@ -75,7 +73,7 @@ const AccountsList = () => {
|
||||
const items = [
|
||||
{ name: 'Name:' , content: account.name || '[none]' },
|
||||
{ name: 'SIP Realm:' , content: account.sip_realm || '[none]' },
|
||||
{ name: 'Registration Webhook:' , content: account.url || '[none]' },
|
||||
{ name: 'Registration Webhook:' , content: account.url_reg || '[none]' },
|
||||
];
|
||||
return items;
|
||||
};
|
||||
@@ -221,7 +219,8 @@ const AccountsList = () => {
|
||||
{ header: 'Name', key: 'name' },
|
||||
{ header: 'AccountSid', key: 'sid' },
|
||||
{ header: 'SIP Realm', key: 'sip_realm' },
|
||||
{ header: 'Registration Webhook', key: 'url' },
|
||||
{ header: 'Registration Webhook', key: 'url_reg' },
|
||||
{ header: 'Queue Event Webhook', key: 'url_queue' },
|
||||
]}
|
||||
formatContentToDelete={formatAccountToDelete}
|
||||
deleteContent={deleteAccount}
|
||||
|
||||
@@ -1,27 +1,97 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useEffect, useContext, useCallback } from 'react';
|
||||
import React, { useEffect, useContext, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
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';
|
||||
import Select from '../../../components/elements/Select';
|
||||
import handleErrors from '../../../helpers/handleErrors';
|
||||
|
||||
const FilterLabel = styled.span`
|
||||
color: #231f20;
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
margin-left: 1rem;
|
||||
margin-right: 0.5rem;
|
||||
`;
|
||||
|
||||
const StyledInputGroup = styled(InputGroup)`
|
||||
padding: 1rem 1rem 0;
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto 1fr;
|
||||
grid-row-gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-row-gap: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const AccountSelect = styled(Select)`
|
||||
min-width: 150px;
|
||||
`;
|
||||
|
||||
const CarriersList = () => {
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
const currentServiceProvider = useContext(ServiceProviderValueContext);
|
||||
const location = useLocation();
|
||||
const locationAccountSid = new URLSearchParams(location.search).get('account_sid');
|
||||
|
||||
const [accountSid, setAccountSid] = useState('');
|
||||
const [accountList, setAccountList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `Carriers | Jambonz | Open Source CPAAS`;
|
||||
}, []);
|
||||
|
||||
//=============================================================================
|
||||
// Get accounts
|
||||
//=============================================================================
|
||||
useEffect(() => {
|
||||
if (currentServiceProvider) {
|
||||
const getAccounts = async () => {
|
||||
try {
|
||||
const accountResponse = await axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (locationAccountSid) {
|
||||
setAccountSid(locationAccountSid);
|
||||
}
|
||||
|
||||
setAccountList((accountResponse.data || []).sort((a, b) => a.name.localeCompare(b.name)));
|
||||
} catch (err) {
|
||||
handleErrors({ err, history, dispatch });
|
||||
}
|
||||
};
|
||||
|
||||
getAccounts();
|
||||
} else {
|
||||
setAccountList([]);
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
|
||||
//=============================================================================
|
||||
// Get sip trunks
|
||||
//=============================================================================
|
||||
const getCarriers = useCallback(async () => {
|
||||
const getCarriers = async () => {
|
||||
try {
|
||||
if (!localStorage.getItem('token')) {
|
||||
history.push('/');
|
||||
@@ -33,6 +103,7 @@ const CarriersList = () => {
|
||||
return;
|
||||
}
|
||||
if(!currentServiceProvider) return [];
|
||||
if (!accountList.length) return [];
|
||||
// Get all SIP trunks
|
||||
const trunkResults = await axios({
|
||||
method: 'get',
|
||||
@@ -43,9 +114,13 @@ const CarriersList = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const trunkResultsFiltered = accountSid ?
|
||||
trunkResults.data.filter(t => t.account_sid === accountSid) :
|
||||
trunkResults.data.filter(t => t.account_sid === null);
|
||||
|
||||
// Add appropriate gateways to each trunk
|
||||
const trunkMap = {};
|
||||
for (const t of trunkResults.data) {
|
||||
for (const t of trunkResultsFiltered) {
|
||||
const gws = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
@@ -57,7 +132,7 @@ const CarriersList = () => {
|
||||
trunkMap[t.voip_carrier_sid] = gws.data;
|
||||
}
|
||||
|
||||
const trunksWithGateways = trunkResults.data.map(t => {
|
||||
const trunksWithGateways = trunkResultsFiltered.map(t => {
|
||||
const gateways = trunkMap[t.voip_carrier_sid] || [];
|
||||
sortSipGateways(gateways);
|
||||
return {
|
||||
@@ -98,7 +173,7 @@ const CarriersList = () => {
|
||||
console.log(err.response || err);
|
||||
}
|
||||
}
|
||||
}, [currentServiceProvider, history, dispatch]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Delete sip trunk
|
||||
@@ -173,6 +248,22 @@ const CarriersList = () => {
|
||||
addButtonLink="/internal/carriers/add"
|
||||
subtitle={<Sbcs />}
|
||||
>
|
||||
<StyledInputGroup flexEnd space>
|
||||
<FilterLabel htmlFor="account">Used By:</FilterLabel>
|
||||
<AccountSelect
|
||||
name="account"
|
||||
id="account"
|
||||
value={accountSid}
|
||||
onChange={e => setAccountSid(e.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
All accounts
|
||||
</option>
|
||||
{accountList.map((acc) => (
|
||||
<option key={acc.account_sid} value={acc.account_sid}>{acc.name}</option>
|
||||
))}
|
||||
</AccountSelect>
|
||||
</StyledInputGroup>
|
||||
<TableContent
|
||||
name="Carrier"
|
||||
urlParam="carriers"
|
||||
|
||||
@@ -58,6 +58,72 @@ const AccountSelect = styled(Select)`
|
||||
min-width: 150px;
|
||||
`;
|
||||
|
||||
const StyledPcapLink = styled.a`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
outline: 0;
|
||||
height: 36px;
|
||||
padding: 10px 26px 8px;
|
||||
border-radius: 0.25rem;
|
||||
background: #D91C5C;
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: #FFF;
|
||||
background: #BD164E;
|
||||
}
|
||||
`;
|
||||
|
||||
const PcapButton = ({call_data, account_sid, jwt_token}) => {
|
||||
const [pcap, setPcap] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/Accounts/${account_sid}/RecentCalls/${call_data.sip_callid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt_token}`,
|
||||
},
|
||||
}).then((result_1) => {
|
||||
if (result_1.status === 200 && result_1.data.total > 0) {
|
||||
axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/Accounts/${account_sid}/RecentCalls/${call_data.sip_callid}/pcap`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt_token}`,
|
||||
},
|
||||
responseType: "blob",
|
||||
}).then((result_2) => {
|
||||
setPcap({
|
||||
dataUrl: URL.createObjectURL(result_2.data),
|
||||
fileName: `callid-${call_data.sip_callid}.pcap`,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [call_data, account_sid, jwt_token, setPcap]);
|
||||
|
||||
if (pcap) {
|
||||
return (
|
||||
<Label>
|
||||
<StyledPcapLink
|
||||
href={pcap.dataUrl}
|
||||
download={pcap.fileName}
|
||||
>
|
||||
Download pcap
|
||||
</StyledPcapLink>
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const RecentCallsIndex = () => {
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
@@ -253,6 +319,7 @@ const RecentCallsIndex = () => {
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<PcapButton call_data={data} account_sid={account} jwt_token={jwt} />
|
||||
</ExpandedSection>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,93 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useContext, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import styled from 'styled-components/macro';
|
||||
|
||||
import { NotificationDispatchContext } from '../../../contexts/NotificationContext';
|
||||
import handleErrors from '../../../helpers/handleErrors';
|
||||
import InternalTemplate from '../../templates/InternalTemplate';
|
||||
import TableContent from '../../../components/blocks/TableContent';
|
||||
import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext';
|
||||
import Sbcs from '../../blocks/Sbcs';
|
||||
import InputGroup from '../../../components/elements/InputGroup';
|
||||
import Select from '../../../components/elements/Select';
|
||||
|
||||
const FilterLabel = styled.span`
|
||||
color: #231f20;
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
margin-left: 1rem;
|
||||
margin-right: 0.5rem;
|
||||
`;
|
||||
|
||||
const StyledInputGroup = styled(InputGroup)`
|
||||
padding: 1rem 1rem 0;
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto 1fr;
|
||||
grid-row-gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-row-gap: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const AccountSelect = styled(Select)`
|
||||
min-width: 150px;
|
||||
`;
|
||||
|
||||
const SpeechServicesList = () => {
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
const currentServiceProvider = useContext(ServiceProviderValueContext);
|
||||
const jwt = localStorage.getItem('token');
|
||||
const location = useLocation();
|
||||
const locationAccountSid = new URLSearchParams(location.search).get('account_sid');
|
||||
|
||||
const [accountSid, setAccountSid] = useState('');
|
||||
const [accountList, setAccountList] = useState([]);
|
||||
|
||||
//=============================================================================
|
||||
// Get accounts
|
||||
//=============================================================================
|
||||
useEffect(() => {
|
||||
if (currentServiceProvider) {
|
||||
const getAccounts = async () => {
|
||||
try {
|
||||
const accountResponse = await axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
|
||||
setAccountList((accountResponse.data || []).sort((a, b) => a.name.localeCompare(b.name)));
|
||||
|
||||
if (locationAccountSid) {
|
||||
setAccountSid(locationAccountSid);
|
||||
}
|
||||
} catch (err) {
|
||||
handleErrors({ err, history, dispatch });
|
||||
}
|
||||
};
|
||||
|
||||
getAccounts();
|
||||
} else {
|
||||
setAccountList([]);
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
|
||||
//=============================================================================
|
||||
// Get speech services
|
||||
//=============================================================================
|
||||
const getSpeechServices = useCallback(async () => {
|
||||
const jwt = localStorage.getItem('token');
|
||||
const getSpeechServices = async () => {
|
||||
try {
|
||||
if (!jwt) {
|
||||
history.push('/');
|
||||
@@ -31,10 +100,14 @@ const SpeechServicesList = () => {
|
||||
}
|
||||
|
||||
if(!currentServiceProvider) return [];
|
||||
|
||||
const speechApiUrl = accountSid ?
|
||||
`/Accounts/${accountSid}/SpeechCredentials` :
|
||||
`/ServiceProviders/${currentServiceProvider}/SpeechCredentials`;
|
||||
const speechServices = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/SpeechCredentials`,
|
||||
url: speechApiUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
@@ -137,7 +210,7 @@ const SpeechServicesList = () => {
|
||||
} catch (err) {
|
||||
handleErrors({ err, history, dispatch, fallbackMessage: 'Unable to get speech services' });
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Delete speech service
|
||||
@@ -149,8 +222,7 @@ const SpeechServicesList = () => {
|
||||
{ name: 'Last Used', content: s.last_used || 'Never' },
|
||||
];
|
||||
};
|
||||
const deleteSpeechService = useCallback(async speechServiceToDelete => {
|
||||
const jwt = localStorage.getItem('token');
|
||||
const deleteSpeechService = async speechServiceToDelete => {
|
||||
try {
|
||||
if (!jwt) {
|
||||
history.push('/');
|
||||
@@ -187,7 +259,7 @@ const SpeechServicesList = () => {
|
||||
return ((err.response && err.response.data && err.response.data.msg) || 'Unable to delete speech service');
|
||||
}
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Render
|
||||
@@ -200,6 +272,22 @@ const SpeechServicesList = () => {
|
||||
addButtonText="Add Speech Service"
|
||||
addButtonLink="/internal/speech-services/add"
|
||||
>
|
||||
<StyledInputGroup flexEnd space>
|
||||
<FilterLabel htmlFor="account">Used By:</FilterLabel>
|
||||
<AccountSelect
|
||||
name="account"
|
||||
id="account"
|
||||
value={accountSid}
|
||||
onChange={e => setAccountSid(e.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
All accounts
|
||||
</option>
|
||||
{accountList.map((acc) => (
|
||||
<option key={acc.account_sid} value={acc.account_sid}>{acc.name}</option>
|
||||
))}
|
||||
</AccountSelect>
|
||||
</StyledInputGroup>
|
||||
<TableContent
|
||||
normalTable
|
||||
name="speech service"
|
||||
|
||||
Reference in New Issue
Block a user