mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-01-25 02:08:19 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5f2e5fc25 | ||
|
|
6390cc6b81 | ||
|
|
d7db92f0c7 | ||
|
|
f5201d2d69 | ||
|
|
128ca045b0 | ||
|
|
3403996946 | ||
|
|
87dbb461e0 | ||
|
|
9bce9c5510 | ||
|
|
2db5f26dbf | ||
|
|
bfc7cc971c | ||
|
|
35f353c905 | ||
|
|
922d664bf8 | ||
|
|
ff4d6b6e11 | ||
|
|
70387ff4f1 | ||
|
|
7a4c583345 | ||
|
|
d54fbc4782 | ||
|
|
14dd1319d9 | ||
|
|
8538d40696 | ||
|
|
eda1fa0dc4 | ||
|
|
0174315a68 | ||
|
|
b86bf0c403 | ||
|
|
3fc1c800ac | ||
|
|
ee4483288d | ||
|
|
d3f1dbf332 |
2
.env
2
.env
@@ -1 +1 @@
|
||||
REACT_APP_API_BASE_URL=http://[ip]:[port]/v1
|
||||
REACT_APP_API_BASE_URL=http://127.0.0.1:3002/v1
|
||||
|
||||
13
.eslintrc
13
.eslintrc
@@ -2,15 +2,12 @@
|
||||
"extends": "react-app",
|
||||
"rules": {
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-trailing-spaces": [
|
||||
"error"
|
||||
"error",
|
||||
"always"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
51
.github/workflows/docker-publish.yml
vendored
Normal file
51
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
# Publish `main` as Docker `latest` image.
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Publish `v1.2.3` tags as releases.
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
IMAGE_NAME: webapp
|
||||
|
||||
jobs:
|
||||
push:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
||||
|
||||
- name: Log into registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
|
||||
|
||||
# Change all uppercase to lowercase
|
||||
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
|
||||
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
|
||||
# Use Docker `latest` tag convention
|
||||
[ "$VERSION" == "main" ] && VERSION=latest
|
||||
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
|
||||
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
|
||||
docker push $IMAGE_ID:$VERSION
|
||||
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.
|
||||
31805
package-lock.json
generated
31805
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "jambonz-cpaas-ui",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"name": "jambonz-webapp",
|
||||
"version": "v0.7.3",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/jest-dom": "^5.16.1",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"antd": "^4.15.4",
|
||||
"axios": "^0.21.1",
|
||||
@@ -13,8 +12,8 @@
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.1",
|
||||
"serve": "^11.3.0",
|
||||
"react-scripts": "^5.0.0",
|
||||
"serve": "^13.0.2",
|
||||
"styled-components": "^5.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -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')}`,
|
||||
},
|
||||
|
||||
@@ -220,7 +220,8 @@ const TableContent = props => {
|
||||
contentToDelete.name ||
|
||||
contentToDelete.number ||
|
||||
contentToDelete.tenant_fqdn ||
|
||||
contentToDelete.token
|
||||
contentToDelete.token ||
|
||||
contentToDelete.vendor
|
||||
) && (
|
||||
<Modal
|
||||
title={`Are you sure you want to delete the following ${props.name}?`}
|
||||
|
||||
@@ -111,6 +111,7 @@ const Checkbox = (props, ref) => {
|
||||
name={props.id}
|
||||
type="checkbox"
|
||||
checked={props.checked}
|
||||
disabled={props.disabled}
|
||||
onChange={props.onChange}
|
||||
value={props.value}
|
||||
ref={inputRef}
|
||||
|
||||
@@ -2,19 +2,8 @@ import React, { useContext } from 'react';
|
||||
import styled from 'styled-components/macro';
|
||||
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
|
||||
import Button from './Button';
|
||||
import Span from './Span';
|
||||
|
||||
const Span = styled.span`
|
||||
text-align: left;
|
||||
${props => props.hasBorder ? `
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
border: 1px solid #B6B6B6;
|
||||
border-radius: 0.125rem;
|
||||
` : ''}
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
margin-left: 1rem;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ const Select = styled.select`
|
||||
border-radius: 0.125rem;
|
||||
background: #fff;
|
||||
color: inherit;
|
||||
max-width: 230px;
|
||||
&:focus {
|
||||
border-color: #565656;
|
||||
outline: none;
|
||||
|
||||
16
src/components/elements/Span.js
Normal file
16
src/components/elements/Span.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components/macro';
|
||||
|
||||
const Span = styled.span`
|
||||
text-align: left;
|
||||
${props => props.hasBorder ? `
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
border: 1px solid #B6B6B6;
|
||||
border-radius: 0.125rem;
|
||||
` : ''}
|
||||
`;
|
||||
|
||||
export default Span;
|
||||
@@ -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 (
|
||||
<StyledLinkWithTooltip>
|
||||
<Link to="#" onClick={handleLinkClick}>
|
||||
<span ref={triggerRef}>{props.children}</span>
|
||||
</Link>
|
||||
{isActive ? (
|
||||
<span ref={tooltipRef}>
|
||||
{props.tipText}
|
||||
</span>
|
||||
) : null}
|
||||
</StyledLinkWithTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
LinkWithTooltip,
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
|
||||
@@ -2,9 +2,11 @@ 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';
|
||||
import Radio from '../elements/Radio';
|
||||
import Select from '../elements/Select';
|
||||
import InputGroup from '../elements/InputGroup';
|
||||
import PasswordInput from '../elements/PasswordInput';
|
||||
@@ -16,6 +18,7 @@ import Button from '../elements/Button';
|
||||
import Link from '../elements/Link';
|
||||
import Tooltip from '../elements/Tooltip';
|
||||
import CopyableText from '../elements/CopyableText';
|
||||
import Span from '../elements/Span';
|
||||
import handleErrors from "../../helpers/handleErrors";
|
||||
import styled from 'styled-components/macro';
|
||||
|
||||
@@ -49,40 +52,70 @@ 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);
|
||||
const refSubspaceId = useRef(null);
|
||||
const refSubspaceSecret = useRef(null);
|
||||
const refSubspaceOtherSip = 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('');
|
||||
const [ hasSubspace, setHasSubspace ] = useState(false);
|
||||
const [ subspaceId, setSubspaceId ] = useState('');
|
||||
const [ subspaceSecret, setSubspaceSecret ] = useState('');
|
||||
const [ subspaceSipTeleportId, setSubspaceSipTeleportId ] = useState('');
|
||||
const [ subspaceSipTeleportEntryPoints, setSubspaceSipTeleportEntryPoints ] = useState([]);
|
||||
const [ showSubspaceModal, setShowSubspaceModal ] = useState(false);
|
||||
const [ generatingSubspace, setGeneratingSubspace ] = useState(false);
|
||||
const [ subspaceSipRealm, setSubspaceSipRealm ] = useState('');
|
||||
const [ sbcs, setSbcs ] = useState([]);
|
||||
const [ subspaceSipRealmOtherValue, setSubspaceSipRealmOtherValue ] = useState('');
|
||||
const [ subspaceEnable, setSubspaceEnable ] = useState(false);
|
||||
|
||||
// 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);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [ invalidSubspaceId, setInvalidSubspaceId ] = useState(false);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [ invalidSubspaceClient, setInvalidSubspaceClient ] = 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);
|
||||
@@ -97,6 +130,14 @@ const AccountForm = props => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubspaceMenuOpen = sid => {
|
||||
if (menuOpen === sid) {
|
||||
setMenuOpen(null);
|
||||
} else {
|
||||
setMenuOpen(sid);
|
||||
}
|
||||
};
|
||||
|
||||
const copyWebhookSecret = async e => {
|
||||
e.preventDefault();
|
||||
setMenuOpen(null);
|
||||
@@ -151,6 +192,96 @@ const AccountForm = props => {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSubspaceTeleport = (enable, e) => {
|
||||
e.preventDefault();
|
||||
setMenuOpen(null);
|
||||
setSubspaceEnable(enable);
|
||||
setShowSubspaceModal(true);
|
||||
};
|
||||
|
||||
const resetSubspaceState = () => {
|
||||
setMenuOpen(null);
|
||||
setShowSubspaceModal(false);
|
||||
setSubspaceSipRealmOtherValue('');
|
||||
setGeneratingSubspace(false);
|
||||
setSubspaceEnable(false);
|
||||
setSubspaceSipRealm('');
|
||||
};
|
||||
|
||||
const handleSubspaceEnable = async () => {
|
||||
try {
|
||||
setGeneratingSubspace(true);
|
||||
|
||||
const destination = subspaceSipRealm === 'other'
|
||||
? subspaceSipRealmOtherValue
|
||||
: subspaceSipRealm;
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/Accounts/${accountSid}/SubspaceTeleport`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
data: { destination },
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
setSubspaceSipTeleportId(response.data.subspace_sip_teleport_id || '');
|
||||
setSubspaceSipTeleportEntryPoints(response.data.subspace_sip_teleport_destinations || []);
|
||||
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'success',
|
||||
message: 'Successfully enabled subspace teleport.',
|
||||
});
|
||||
}
|
||||
|
||||
resetSubspaceState();
|
||||
} catch (err) {
|
||||
resetSubspaceState();
|
||||
if (err.response.status === 500 && err.response.data.msg === 'Too Many Requests') {
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: 'You have already created the maximum number of SIP Teleports allowed for your Subspace account.',
|
||||
});
|
||||
} else {
|
||||
handleErrors({ err, history, dispatch });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubspaceDisable = async () => {
|
||||
try {
|
||||
setGeneratingSubspace(true);
|
||||
|
||||
const response = await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/Accounts/${accountSid}/SubspaceTeleport`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 204) {
|
||||
setSubspaceSipTeleportId('');
|
||||
setSubspaceSipTeleportEntryPoints([]);
|
||||
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'success',
|
||||
message: `Successfully disabled subspace teleport.`,
|
||||
});
|
||||
}
|
||||
|
||||
resetSubspaceState();
|
||||
} catch (err) {
|
||||
resetSubspaceState();
|
||||
handleErrors({ err, history, dispatch });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const getAccounts = async () => {
|
||||
try {
|
||||
@@ -187,17 +318,15 @@ 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 sbcsPromise = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/Sbcs',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
promiseList.push(sbcsPromise);
|
||||
|
||||
const promiseAllValues = await Promise.all(promiseList);
|
||||
|
||||
@@ -211,11 +340,7 @@ const AccountForm = props => {
|
||||
});
|
||||
setAccountApplications(accountApplicationsData);
|
||||
}
|
||||
|
||||
if (props.type === 'add') {
|
||||
const serviceProviders = (promiseAllValues[1] && promiseAllValues[1].data) || '';
|
||||
setServiceProviderSid(serviceProviders[0].service_provider_sid);
|
||||
}
|
||||
setSbcs(promiseAllValues[2].data);
|
||||
|
||||
if (props.type === 'setup' && accountsData.length > 1) {
|
||||
history.push('/internal/accounts');
|
||||
@@ -250,16 +375,31 @@ 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 || '');
|
||||
|
||||
setSubspaceId(acc.subspace_client_id || '');
|
||||
setSubspaceSecret(acc.subspace_client_secret || '');
|
||||
setSubspaceSipTeleportId(acc.subspace_sip_teleport_id || '');
|
||||
setSubspaceSipTeleportEntryPoints(acc.subspace_sip_teleport_destinations ? JSON.parse(acc.subspace_sip_teleport_destinations) : []);
|
||||
setHasSubspace(acc.subspace_client_id ? true : false);
|
||||
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 +438,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 +485,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 +526,23 @@ 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,
|
||||
subspace_client_id: subspaceId || null,
|
||||
subspace_client_secret: subspaceSecret || null,
|
||||
};
|
||||
|
||||
if (props.type === 'add') {
|
||||
axiosData.service_provider_sid = serviceProviderSid;
|
||||
axiosData.service_provider_sid = currentServiceProvider;
|
||||
}
|
||||
|
||||
if (props.type === 'edit') {
|
||||
@@ -450,6 +615,19 @@ const AccountForm = props => {
|
||||
},
|
||||
];
|
||||
|
||||
const subspaceMenuItems = [
|
||||
{
|
||||
type: 'button',
|
||||
name: 'Enable',
|
||||
action: toggleSubspaceTeleport.bind(toggleSubspaceTeleport, true),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
name: 'Disable',
|
||||
action: toggleSubspaceTeleport.bind(toggleSubspaceTeleport, false),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
showLoader
|
||||
? <Loader
|
||||
@@ -566,27 +744,27 @@ const AccountForm = props => {
|
||||
<Select
|
||||
large={props.type === 'setup'}
|
||||
name="method"
|
||||
id="method"
|
||||
value={method}
|
||||
onChange={e => setMethod(e.target.value)}
|
||||
id="regMethod"
|
||||
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 +772,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,12 +785,211 @@ 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="queueMethod"
|
||||
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>
|
||||
)}
|
||||
|
||||
{ process.env.REACT_APP_ENABLE_SUBSPACE ? (
|
||||
<>
|
||||
<Label htmlFor="subspaceId">Subspace</Label>
|
||||
<InputGroup>
|
||||
<Input
|
||||
large={props.type === 'setup'}
|
||||
name="subspaceId"
|
||||
id="subspaceId"
|
||||
value={subspaceId}
|
||||
onChange={e => setSubspaceId(e.target.value)}
|
||||
placeholder="Client Id for Subspace"
|
||||
ref={refSubspaceId}
|
||||
style={{ margin: '0 4px' }}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
large={props.type === 'setup'}
|
||||
allowShowPassword
|
||||
name="subspaceSecret"
|
||||
id="subspaceSecret"
|
||||
password={subspaceSecret}
|
||||
setPassword={setSubspaceSecret}
|
||||
setErrorMessage={setErrorMessage}
|
||||
placeholder="Client Secret for Subspace"
|
||||
ref={refSubspaceSecret}
|
||||
style={{ margin: '0 4px' }}
|
||||
/>
|
||||
|
||||
<StyledInputGroup>
|
||||
<TableMenu
|
||||
disabled={!hasSubspace}
|
||||
sid="subspace"
|
||||
open={menuOpen === "subspace"}
|
||||
handleMenuOpen={handleSubspaceMenuOpen}
|
||||
menuItems={subspaceSipTeleportId ? [subspaceMenuItems[1]] : [subspaceMenuItems[0]]}
|
||||
/>
|
||||
</StyledInputGroup>
|
||||
</InputGroup>
|
||||
{subspaceSipTeleportId ? (
|
||||
<div style={{ gridColumn: 2, textAlign: 'left' }}>
|
||||
<div>Subspace is now enabled. To send your traffic through Subspace:</div>
|
||||
{subspaceSipTeleportEntryPoints.map(entrypoint => (
|
||||
<div key={entrypoint.transport_type}>
|
||||
<Span>send {entrypoint.transport_type.split('_').join(' and ')} traffic to </Span>
|
||||
<CopyableText text={entrypoint.address} textType="Address" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{showSubspaceModal && (
|
||||
<Modal
|
||||
title={subspaceEnable ? 'Have Subspace send SIP to:' : 'Are you sure you want to delete your Subspace SIP Teleport?'}
|
||||
loader={generatingSubspace}
|
||||
hideButtons={generatingSubspace}
|
||||
maskClosable={!generatingSubspace}
|
||||
actionText={subspaceEnable ? 'Save' : 'Disable'}
|
||||
content={
|
||||
<ModalContainer>
|
||||
{subspaceEnable ? (
|
||||
<>
|
||||
{sipRealm && (
|
||||
<Radio
|
||||
noLeftMargin
|
||||
name="subspaceSipRealm"
|
||||
id="sipRealmAccount"
|
||||
label={sipRealm}
|
||||
checked={subspaceSipRealm === sipRealm}
|
||||
onChange={() => {
|
||||
setSubspaceSipRealm(sipRealm);
|
||||
setSubspaceSipRealmOtherValue('');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{sbcs.map((sbc) => {
|
||||
return (
|
||||
<Radio
|
||||
key={sbc.ipv4}
|
||||
noLeftMargin
|
||||
name="subspaceSipRealm"
|
||||
id={sbc.sbc_address_sid}
|
||||
label={`${sbc.ipv4}:${sbc.port}`}
|
||||
checked={subspaceSipRealm === `${sbc.ipv4}:${sbc.port}`}
|
||||
onChange={() => {
|
||||
setSubspaceSipRealm(`${sbc.ipv4}:${sbc.port}`);
|
||||
setSubspaceSipRealmOtherValue('');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Radio
|
||||
noLeftMargin
|
||||
name="subspaceSipRealm"
|
||||
id="sipRealmOther"
|
||||
label="Other"
|
||||
checked={subspaceSipRealm === 'other'}
|
||||
onChange={() => {
|
||||
setSubspaceSipRealm('other');
|
||||
setTimeout(() => refSubspaceOtherSip.current.focus(), 0);
|
||||
}}
|
||||
/>
|
||||
{subspaceSipRealm === 'other' && (
|
||||
<Input
|
||||
ref={refSubspaceOtherSip}
|
||||
name="subspaceSipRealm"
|
||||
id="sipRealmOtherValue"
|
||||
value={subspaceSipRealmOtherValue}
|
||||
onChange={e => setSubspaceSipRealmOtherValue(e.target.value)}
|
||||
placeholder="IP address or DNS name"
|
||||
style={{ marginTop: '8px' }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</ModalContainer>
|
||||
}
|
||||
handleCancel={() => {
|
||||
setShowSubspaceModal(false);
|
||||
resetSubspaceState();
|
||||
}}
|
||||
handleSubmit={() => {
|
||||
if (subspaceEnable) {
|
||||
handleSubspaceEnable();
|
||||
} else {
|
||||
handleSubspaceDisable();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null }
|
||||
|
||||
{errorMessage && (
|
||||
<FormError grid message={errorMessage} />
|
||||
)}
|
||||
|
||||
@@ -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';
|
||||
@@ -14,12 +15,16 @@ import SpeechSynthesisLanguageGoogle from '../../data/SpeechSynthesisLanguageGoo
|
||||
import SpeechSynthesisLanguageAws from '../../data/SpeechSynthesisLanguageAws';
|
||||
import SpeechRecognizerLanguageGoogle from '../../data/SpeechRecognizerLanguageGoogle';
|
||||
import SpeechRecognizerLanguageAws from '../../data/SpeechRecognizerLanguageAws';
|
||||
import SpeechRecognizerLanguageMicrosoft from '../../data/SpeechRecognizerLanguageMicrosoft';
|
||||
import SpeechSynthesisLanguageMicrosoft from '../../data/SpeechSynthesisLanguageMicrosoft';
|
||||
import SpeechSynthesisLanguageWellSaid from '../../data/SpeechSynthesisLanguageWellSaid';
|
||||
import Loader from '../blocks/Loader';
|
||||
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 +123,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 +498,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}
|
||||
@@ -743,6 +748,14 @@ const ApplicationForm = props => {
|
||||
? SpeechSynthesisLanguageGoogle.find(l => (
|
||||
l.code === speechSynthesisLanguage
|
||||
))
|
||||
: e.target.value === 'microsoft'
|
||||
? SpeechSynthesisLanguageMicrosoft.find(l => (
|
||||
l.code === speechSynthesisLanguage
|
||||
))
|
||||
: e.target.value === 'wellsaid'
|
||||
? SpeechSynthesisLanguageWellSaid.find(l => (
|
||||
l.code === speechSynthesisLanguage
|
||||
))
|
||||
: SpeechSynthesisLanguageAws.find(l => (
|
||||
l.code === speechSynthesisLanguage
|
||||
));
|
||||
@@ -756,9 +769,13 @@ const ApplicationForm = props => {
|
||||
return;
|
||||
}
|
||||
|
||||
newLang = SpeechSynthesisLanguageAws.find(l => (
|
||||
l.code === 'en-US'
|
||||
));
|
||||
newLang = e.target.value === 'aws'
|
||||
? SpeechSynthesisLanguageAws.find(l => (
|
||||
l.code === 'en-US'
|
||||
))
|
||||
: SpeechSynthesisLanguageMicrosoft.find(l => (
|
||||
l.code === 'en-US'
|
||||
));
|
||||
}
|
||||
|
||||
// Update state to reflect first voice option for language
|
||||
@@ -767,6 +784,8 @@ const ApplicationForm = props => {
|
||||
>
|
||||
<option value="google">Google</option>
|
||||
<option value="aws">AWS</option>
|
||||
<option value="microsoft">Microsoft</option>
|
||||
<option value="wellsaid">WellSaid</option>
|
||||
</Select>
|
||||
<Label middle htmlFor="speechSynthesisLanguage">Language</Label>
|
||||
<Select
|
||||
@@ -790,6 +809,14 @@ const ApplicationForm = props => {
|
||||
? SpeechSynthesisLanguageGoogle.find(l => (
|
||||
l.code === e.target.value
|
||||
))
|
||||
: speechSynthesisVendor === 'microsoft'
|
||||
? SpeechSynthesisLanguageMicrosoft.find(l => (
|
||||
l.code === e.target.value
|
||||
))
|
||||
: speechSynthesisVendor === 'wellsaid'
|
||||
? SpeechSynthesisLanguageWellSaid.find(l => (
|
||||
l.code === e.target.value
|
||||
))
|
||||
: SpeechSynthesisLanguageAws.find(l => (
|
||||
l.code === e.target.value
|
||||
));
|
||||
@@ -802,6 +829,14 @@ const ApplicationForm = props => {
|
||||
SpeechSynthesisLanguageGoogle.map(l => (
|
||||
<option key={l.code} value={l.code}>{l.name}</option>
|
||||
))
|
||||
) : speechSynthesisVendor === 'microsoft' ? (
|
||||
SpeechSynthesisLanguageMicrosoft.map(l => (
|
||||
<option key={l.code} value={l.code}>{l.name}</option>
|
||||
))
|
||||
) : speechSynthesisVendor === 'wellsaid' ? (
|
||||
SpeechSynthesisLanguageWellSaid.map(l => (
|
||||
<option key={l.code} value={l.code}>{l.name}</option>
|
||||
))
|
||||
) : (
|
||||
SpeechSynthesisLanguageAws.map(l => (
|
||||
<option key={l.code} value={l.code}>{l.name}</option>
|
||||
@@ -822,6 +857,18 @@ const ApplicationForm = props => {
|
||||
.map(m => m.voices.map(v => (
|
||||
<option key={v.value} value={v.value}>{v.name}</option>
|
||||
)))
|
||||
) : speechSynthesisVendor === 'microsoft' ? (
|
||||
SpeechSynthesisLanguageMicrosoft
|
||||
.filter(l => l.code === speechSynthesisLanguage)
|
||||
.map(m => m.voices.map(v => (
|
||||
<option key={v.value} value={v.value}>{v.name}</option>
|
||||
)))
|
||||
) : speechSynthesisVendor === 'wellsaid' ? (
|
||||
SpeechSynthesisLanguageWellSaid
|
||||
.filter(l => l.code === speechSynthesisLanguage)
|
||||
.map(m => m.voices.map(v => (
|
||||
<option key={v.value} value={v.value}>{v.name}</option>
|
||||
)))
|
||||
) : (
|
||||
SpeechSynthesisLanguageAws
|
||||
.filter(l => l.code === speechSynthesisLanguage)
|
||||
@@ -860,6 +907,7 @@ const ApplicationForm = props => {
|
||||
>
|
||||
<option value="google">Google</option>
|
||||
<option value="aws">AWS</option>
|
||||
<option value="microsoft">Microsoft</option>
|
||||
</Select>
|
||||
<Label middle htmlFor="speechRecognizerLanguage">Language</Label>
|
||||
<Select
|
||||
@@ -873,6 +921,10 @@ const ApplicationForm = props => {
|
||||
SpeechRecognizerLanguageGoogle.map(l => (
|
||||
<option key={l.code} value={l.code}>{l.name}</option>
|
||||
))
|
||||
) : speechRecognizerVendor === 'microsoft' ? (
|
||||
SpeechRecognizerLanguageMicrosoft.map(l => (
|
||||
<option key={l.code} value={l.code}>{l.name}</option>
|
||||
))
|
||||
) : (
|
||||
SpeechRecognizerLanguageAws.map(l => (
|
||||
<option key={l.code} value={l.code}>{l.name}</option>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
@@ -17,7 +18,9 @@ import Code from '../elements/Code';
|
||||
import FormError from '../blocks/FormError';
|
||||
import Button from '../elements/Button';
|
||||
import Loader from '../blocks/Loader';
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext'
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
|
||||
|
||||
import MicrosoftAzureRegions from '../../data/MicrosoftAzureRegions';
|
||||
|
||||
|
||||
const StyledButtonGroup = styled(InputGroup)`
|
||||
@@ -66,10 +69,14 @@ const SpeechServicesAddEdit = (props) => {
|
||||
// Refs
|
||||
const refVendorGoogle = useRef(null);
|
||||
const refVendorAws = useRef(null);
|
||||
const refVendorMs = useRef(null);
|
||||
const refVendorWellSaid = useRef(null);
|
||||
const refAccessKeyId = useRef(null);
|
||||
const refSecretAccessKey = useRef(null);
|
||||
const refUseForTts = useRef(null);
|
||||
const refUseForStt = useRef(null);
|
||||
const refApiKey = useRef(null);
|
||||
const refRegion = useRef(null);
|
||||
|
||||
// Form inputs
|
||||
const [ vendor, setVendor ] = useState('');
|
||||
@@ -79,14 +86,22 @@ 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('');
|
||||
const [ apiKey, setApiKey ] = useState('');
|
||||
const [ region, setRegion ] = useState('');
|
||||
|
||||
// Invalid form inputs
|
||||
const [ invalidVendorGoogle, setInvalidVendorGoogle ] = useState(false);
|
||||
const [ invalidVendorAws, setInvalidVendorAws ] = useState(false);
|
||||
const [ invalidVendorMs, setInvalidVendorMs ] = useState(false);
|
||||
const [ invalidVendorWellSaid, setInvalidVendorWellSaid ] = useState(false);
|
||||
const [ invalidAccessKeyId, setInvalidAccessKeyId ] = useState(false);
|
||||
const [ invalidSecretAccessKey, setInvalidSecretAccessKey ] = useState(false);
|
||||
const [ invalidUseForTts, setInvalidUseForTts ] = useState(false);
|
||||
const [ invalidUseForStt, setInvalidUseForStt ] = useState(false);
|
||||
const [ invalidApiKey, setInvalidApiKey ] = useState(false);
|
||||
const [ invalidRegion, setInvalidRegion ] = useState(false);
|
||||
|
||||
const [ originalTtsValue, setOriginalTtsValue ] = useState(null);
|
||||
const [ originalSttValue, setOriginalSttValue ] = useState(null);
|
||||
@@ -100,6 +115,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,11 +145,14 @@ const SpeechServicesAddEdit = (props) => {
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
setAccountSid( speechCredential.data.account_sid || '');
|
||||
setVendor( speechCredential.data.vendor || undefined);
|
||||
setServiceKey( serviceKeyJson || '');
|
||||
setDisplayedServiceKey( displayedServiceKeyJson || '');
|
||||
setAccessKeyId( speechCredential.data.access_key_id || '');
|
||||
setSecretAccessKey( speechCredential.data.secret_access_key || '');
|
||||
setApiKey( speechCredential.data.api_key || '');
|
||||
setRegion( speechCredential.data.region || '');
|
||||
setUseForTts( speechCredential.data.use_for_tts || false);
|
||||
setUseForStt( speechCredential.data.use_for_stt || false);
|
||||
setOriginalTtsValue( speechCredential.data.use_for_tts || false);
|
||||
@@ -191,10 +220,13 @@ const SpeechServicesAddEdit = (props) => {
|
||||
setErrorMessage('');
|
||||
setInvalidVendorGoogle(false);
|
||||
setInvalidVendorAws(false);
|
||||
setInvalidVendorMs(false);
|
||||
setInvalidVendorWellSaid(false);
|
||||
setInvalidAccessKeyId(false);
|
||||
setInvalidSecretAccessKey(false);
|
||||
setInvalidUseForTts(false);
|
||||
setInvalidUseForStt(false);
|
||||
setInvalidApiKey(false);
|
||||
let errorMessages = [];
|
||||
let focusHasBeenSet = false;
|
||||
|
||||
@@ -202,6 +234,8 @@ const SpeechServicesAddEdit = (props) => {
|
||||
errorMessages.push('Please select a vendor.');
|
||||
setInvalidVendorGoogle(true);
|
||||
setInvalidVendorAws(true);
|
||||
setInvalidVendorMs(true);
|
||||
setInvalidVendorWellSaid(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refVendorGoogle.current.focus();
|
||||
focusHasBeenSet = true;
|
||||
@@ -230,6 +264,33 @@ const SpeechServicesAddEdit = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor === 'microsoft' && !apiKey) {
|
||||
errorMessages.push('Please provide an API key.');
|
||||
setInvalidApiKey(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refApiKey.current.focus();
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor === 'microsoft' && !region) {
|
||||
errorMessages.push('Please select a region.');
|
||||
setInvalidRegion(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refRegion.current.focus();
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor === 'wellsaid' && !apiKey) {
|
||||
errorMessages.push('Please provide an API key.');
|
||||
setInvalidApiKey(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refApiKey.current.focus();
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessages.length > 1) {
|
||||
setErrorMessage(errorMessages);
|
||||
return;
|
||||
@@ -238,33 +299,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
|
||||
//===============================================
|
||||
@@ -288,8 +322,12 @@ const SpeechServicesAddEdit = (props) => {
|
||||
service_key: vendor === 'google' ? JSON.stringify(serviceKey) : null,
|
||||
access_key_id: vendor === 'aws' ? accessKeyId : null,
|
||||
secret_access_key: vendor === 'aws' ? secretAccessKey : null,
|
||||
api_key: ['microsoft', 'wellsaid'].includes(vendor) ? apiKey : null,
|
||||
region: vendor === 'microsoft' ? region : null,
|
||||
use_for_tts: useForTts,
|
||||
use_for_stt: useForStt,
|
||||
service_provider_sid: accountSid ? null : currentServiceProvider,
|
||||
account_sid: accountSid || null,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -383,7 +421,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';
|
||||
@@ -450,8 +492,50 @@ const SpeechServicesAddEdit = (props) => {
|
||||
ref={refVendorAws}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
|
||||
<Radio
|
||||
name="vendor"
|
||||
id="microsoft"
|
||||
label="Microsoft"
|
||||
checked={vendor === 'microsoft'}
|
||||
onChange={() => setVendor('microsoft')}
|
||||
invalid={invalidVendorMs}
|
||||
ref={refVendorMs}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
|
||||
<Radio
|
||||
name="vendor"
|
||||
id="wellsaid"
|
||||
label="WellSaid"
|
||||
checked={vendor === 'wellsaid'}
|
||||
onChange={() => setVendor('wellsaid')}
|
||||
invalid={invalidVendorWellSaid}
|
||||
ref={refVendorWellSaid}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
</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>
|
||||
@@ -498,11 +582,61 @@ const SpeechServicesAddEdit = (props) => {
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
</>
|
||||
) : vendor === 'microsoft' ? (
|
||||
<>
|
||||
<Label htmlFor="apiKey">API Key</Label>
|
||||
<Input
|
||||
name="apiKey"
|
||||
id="apiKey"
|
||||
value={apiKey}
|
||||
onChange={e => setApiKey(e.target.value)}
|
||||
placeholder=""
|
||||
invalid={invalidApiKey}
|
||||
ref={refApiKey}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
|
||||
<Label htmlFor="region">Region</Label>
|
||||
<Select
|
||||
name="region"
|
||||
id="region"
|
||||
value={region}
|
||||
onChange={e => setRegion(e.target.value)}
|
||||
ref={refRegion}
|
||||
invalid={invalidRegion}
|
||||
>
|
||||
<option value="">
|
||||
All regions
|
||||
</option>
|
||||
{MicrosoftAzureRegions.map(r => (
|
||||
<option
|
||||
key={r.value}
|
||||
value={r.value}
|
||||
>
|
||||
{r.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
) : vendor === 'wellsaid' ? (
|
||||
<>
|
||||
<Label htmlFor="apiKey">API Key</Label>
|
||||
<Input
|
||||
name="apiKey"
|
||||
id="apiKey"
|
||||
value={apiKey}
|
||||
onChange={e => setApiKey(e.target.value)}
|
||||
placeholder=""
|
||||
invalid={invalidApiKey}
|
||||
ref={refApiKey}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
null
|
||||
)}
|
||||
|
||||
{vendor === 'google' || vendor === 'aws' ? (
|
||||
{['google', 'aws', 'microsoft', 'wellsaid'].includes(vendor) ? (
|
||||
<>
|
||||
<div/>
|
||||
<Checkbox
|
||||
@@ -521,13 +655,15 @@ const SpeechServicesAddEdit = (props) => {
|
||||
name="useForStt"
|
||||
id="useForStt"
|
||||
label="Use for speech-to-text"
|
||||
disabled={'wellsaid' === vendor}
|
||||
checked={useForStt}
|
||||
onChange={e => setUseForStt(e.target.checked)}
|
||||
invalid={invalidUseForStt}
|
||||
ref={refUseForStt}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
) :
|
||||
(
|
||||
null
|
||||
)}
|
||||
|
||||
|
||||
@@ -41,10 +41,9 @@ 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,
|
||||
subspace_enabled: a.subspace_sip_teleport_id ? 'Enabled' : ''
|
||||
}));
|
||||
return(simplifiedAccounts);
|
||||
} catch (err) {
|
||||
@@ -75,7 +74,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 +220,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}
|
||||
|
||||
@@ -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 = () => {
|
||||
<InternalTemplate
|
||||
type="form"
|
||||
title={pageTitle}
|
||||
subtitle={<Sbcs />}
|
||||
breadcrumbs={[
|
||||
{ name: 'Carriers', url: '/internal/carriers' },
|
||||
{ name: pageTitle },
|
||||
|
||||
@@ -1,27 +1,96 @@
|
||||
/* 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 +102,7 @@ const CarriersList = () => {
|
||||
return;
|
||||
}
|
||||
if(!currentServiceProvider) return [];
|
||||
if (!accountList.length) return [];
|
||||
// Get all SIP trunks
|
||||
const trunkResults = await axios({
|
||||
method: 'get',
|
||||
@@ -43,9 +113,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 +131,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 +172,7 @@ const CarriersList = () => {
|
||||
console.log(err.response || err);
|
||||
}
|
||||
}
|
||||
}, [currentServiceProvider, history, dispatch]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Delete sip trunk
|
||||
@@ -171,8 +245,23 @@ const CarriersList = () => {
|
||||
title="Carriers"
|
||||
addButtonText="Add a Carrier"
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import InternalTemplate from '../../templates/InternalTemplate';
|
||||
import SpeechForm from '../../forms/SpeechForm';
|
||||
import Sbcs from '../../blocks/Sbcs';
|
||||
|
||||
const SpeechServicesAddEdit = () => {
|
||||
let { speech_service_sid } = useParams();
|
||||
@@ -14,7 +13,6 @@ const SpeechServicesAddEdit = () => {
|
||||
<InternalTemplate
|
||||
type="form"
|
||||
title={pageTitle}
|
||||
subtitle={<Sbcs />}
|
||||
breadcrumbs={[
|
||||
{ name: 'Speech Services', url: '/internal/speech-services' },
|
||||
{ name: pageTitle },
|
||||
|
||||
@@ -1,24 +1,92 @@
|
||||
/* 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 +99,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 +209,7 @@ const SpeechServicesList = () => {
|
||||
} catch (err) {
|
||||
handleErrors({ err, history, dispatch, fallbackMessage: 'Unable to get speech services' });
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Delete speech service
|
||||
@@ -149,8 +221,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 +258,7 @@ const SpeechServicesList = () => {
|
||||
return ((err.response && err.response.data && err.response.data.msg) || 'Unable to delete speech service');
|
||||
}
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Render
|
||||
@@ -196,10 +267,25 @@ const SpeechServicesList = () => {
|
||||
<InternalTemplate
|
||||
type="normalTable"
|
||||
title="Speech Services"
|
||||
subtitle={<Sbcs />}
|
||||
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"
|
||||
|
||||
94
src/data/MicrosoftAzureRegions.js
Normal file
94
src/data/MicrosoftAzureRegions.js
Normal file
@@ -0,0 +1,94 @@
|
||||
export default [
|
||||
{
|
||||
name: 'Asia (East)',
|
||||
value: 'eastasia'
|
||||
},
|
||||
{
|
||||
name: 'Asia (Southeast)',
|
||||
value: 'southeastasia'
|
||||
},
|
||||
{
|
||||
name: 'Australia (East)',
|
||||
value: 'australiaeast'
|
||||
},
|
||||
{
|
||||
name: 'Brazil (South)',
|
||||
value: 'brazilsouth'
|
||||
},
|
||||
{
|
||||
name: 'Canada (Central)',
|
||||
value: 'canadacentral'
|
||||
},
|
||||
{
|
||||
name: 'Europe (North)',
|
||||
value: 'northeurope'
|
||||
},
|
||||
{
|
||||
name: 'Europe (West)',
|
||||
value: 'westeurope'
|
||||
},
|
||||
{
|
||||
name: 'France (Central)',
|
||||
value: 'francecentral'
|
||||
},
|
||||
{
|
||||
name: 'Switzerland (North)',
|
||||
value: 'switzerlandnorth'
|
||||
},
|
||||
{
|
||||
name: 'India (Central)',
|
||||
value: 'centralindia'
|
||||
},
|
||||
{
|
||||
name: 'Japan (West)',
|
||||
value: 'japanwest'
|
||||
},
|
||||
{
|
||||
name: 'Japan (East)',
|
||||
value: 'japaneast'
|
||||
},
|
||||
{
|
||||
name: 'Korea (Central)',
|
||||
value: 'koreacentral'
|
||||
},
|
||||
{
|
||||
name: 'South Africa (North)',
|
||||
value: 'southafricanorth'
|
||||
},
|
||||
{
|
||||
name: 'UK (South)',
|
||||
value: 'uksouth'
|
||||
},
|
||||
{
|
||||
name: 'US (Cental)',
|
||||
value: 'centralus'
|
||||
},
|
||||
{
|
||||
name: 'US (West Central)',
|
||||
value: 'westcentralus'
|
||||
},
|
||||
{
|
||||
name: 'US (East)',
|
||||
value: 'eastus'
|
||||
},
|
||||
{
|
||||
name: 'US (East 2)',
|
||||
value: 'eastus2'
|
||||
},
|
||||
{
|
||||
name: 'US (North Central)',
|
||||
value: 'northcentralus'
|
||||
},
|
||||
{
|
||||
name: 'US (South Central)',
|
||||
value: 'southcentralus'
|
||||
},
|
||||
{
|
||||
name: 'US (West)',
|
||||
value: 'westus'
|
||||
},
|
||||
{
|
||||
name: 'US (West 2)',
|
||||
value: 'westus2'
|
||||
},
|
||||
];
|
||||
422
src/data/SpeechRecognizerLanguageMicrosoft.js
Normal file
422
src/data/SpeechRecognizerLanguageMicrosoft.js
Normal file
@@ -0,0 +1,422 @@
|
||||
export default [
|
||||
{
|
||||
name: 'Arabic (Algeria)',
|
||||
code: 'ar-DZ'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Bahrain)',
|
||||
code: 'ar-BH'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Egypt)',
|
||||
code: 'ar-EG'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Iraq)',
|
||||
code: 'ar-IQ'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Israel)',
|
||||
code: 'ar-IL'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Jordan)',
|
||||
code: 'ar-JO'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Kuwait)',
|
||||
code: 'ar-KW'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Lebanon)',
|
||||
code: 'ar-LB'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Libya)',
|
||||
code: 'ar-LY'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Morocco)',
|
||||
code: 'ar-MA'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Oman)',
|
||||
code: 'ar-OM'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Qatar)',
|
||||
code: 'ar-QA'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Saudi Arabia)',
|
||||
code: 'ar-SA'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Palestinian Authority)',
|
||||
code: 'ar-PS'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Syria)',
|
||||
code: 'ar-SY'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Tunisia)',
|
||||
code: 'ar-TN'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (United Arab Emirates)',
|
||||
code: 'ar-AE'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Yemen)',
|
||||
code: 'ar-YE'
|
||||
},
|
||||
{
|
||||
name: 'Bulgarian (Bulgaria)',
|
||||
code: 'bg-BG'
|
||||
},
|
||||
{
|
||||
name: 'Catalan (Spain)',
|
||||
code: 'ca-ES'
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Cantonese, Traditional)',
|
||||
code: 'zh-HK'
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Mandarin, Simplified)',
|
||||
code: 'zh-CN'
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Taiwanese Mandarin)',
|
||||
code: 'zh-TW'
|
||||
},
|
||||
{
|
||||
name: 'Croatian (Croatia)',
|
||||
code: 'hr-HR'
|
||||
},
|
||||
{
|
||||
name: 'Czech (Czech)',
|
||||
code: 'cs-CZ'
|
||||
},
|
||||
{
|
||||
name: 'Danish (Denmark)',
|
||||
code: 'da-DK'
|
||||
},
|
||||
{
|
||||
name: 'Dutch (Netherlands)',
|
||||
code: 'nl-NL'
|
||||
},
|
||||
{
|
||||
name: 'English (Australia)',
|
||||
code: 'en-AU'
|
||||
},
|
||||
{
|
||||
name: 'English (Canada)',
|
||||
code: 'en-CA'
|
||||
},
|
||||
{
|
||||
name: 'English (Ghana)',
|
||||
code: 'en-GH'
|
||||
},
|
||||
{
|
||||
name: 'English (Hong Kong)',
|
||||
code: 'en-HK'
|
||||
},
|
||||
{
|
||||
name: 'English (India)',
|
||||
code: 'en-IN'
|
||||
},
|
||||
{
|
||||
name: 'English (Ireland)',
|
||||
code: 'en-IE'
|
||||
},
|
||||
{
|
||||
name: 'English (Kenya)',
|
||||
code: 'en-KE'
|
||||
},
|
||||
{
|
||||
name: 'English (New Zealand)',
|
||||
code: 'en-NZ'
|
||||
},
|
||||
{
|
||||
name: 'English (Nigeria)',
|
||||
code: 'en-NG'
|
||||
},
|
||||
{
|
||||
name: 'English (Philippines)',
|
||||
code: 'en-PH'
|
||||
},
|
||||
{
|
||||
name: 'English (Singapore)',
|
||||
code: 'en-SG'
|
||||
},
|
||||
{
|
||||
name: 'English (South Africa)',
|
||||
code: 'en-ZA'
|
||||
},
|
||||
{
|
||||
name: 'English (Tanzania)',
|
||||
code: 'en-TZ'
|
||||
},
|
||||
{
|
||||
name: 'English (United Kingdom)',
|
||||
code: 'en-GB'
|
||||
},
|
||||
{
|
||||
name: 'English (United States)',
|
||||
code: 'en-US'
|
||||
},
|
||||
{
|
||||
name: 'Estonian(Estonia)',
|
||||
code: 'et-EE'
|
||||
},
|
||||
{
|
||||
name: 'Filipino (Philippines)',
|
||||
code: 'fil-PH'
|
||||
},
|
||||
{
|
||||
name: 'Finnish (Finland)',
|
||||
code: 'fi-FI'
|
||||
},
|
||||
{
|
||||
name: 'French (Canada)',
|
||||
code: 'fr-CA'
|
||||
},
|
||||
{
|
||||
name: 'French (France)',
|
||||
code: 'fr-FR'
|
||||
},
|
||||
{
|
||||
name: 'French (Switzerland)',
|
||||
code: 'fr-CH'
|
||||
},
|
||||
{
|
||||
name: 'German (Austria)',
|
||||
code: 'de-AT'
|
||||
},
|
||||
{
|
||||
name: 'German (Switzerland)',
|
||||
code: 'de-CH'
|
||||
},
|
||||
{
|
||||
name: 'German (Germany)',
|
||||
code: 'de-DE'
|
||||
},
|
||||
{
|
||||
name: 'Greek (Greece)',
|
||||
code: 'el-GR'
|
||||
},
|
||||
{
|
||||
name: 'Gujarati (Indian)',
|
||||
code: 'gu-IN'
|
||||
},
|
||||
{
|
||||
name: 'Hebrew (Israel)',
|
||||
code: 'he-IL'
|
||||
},
|
||||
{
|
||||
name: 'Hindi (India)',
|
||||
code: 'hi-IN'
|
||||
},
|
||||
{
|
||||
name: 'Hungarian (Hungary)',
|
||||
code: 'hu-HU'
|
||||
},
|
||||
{
|
||||
name: 'Indonesian (Indonesia)',
|
||||
code: 'id-ID'
|
||||
},
|
||||
{
|
||||
name: 'Irish (Ireland)',
|
||||
code: 'ga-IE'
|
||||
},
|
||||
{
|
||||
name: 'Italian (Italy)',
|
||||
code: 'it-IT'
|
||||
},
|
||||
{
|
||||
name: 'Japanese (Japan)',
|
||||
code: 'ja-JP'
|
||||
},
|
||||
{
|
||||
name: 'Kannada (India)',
|
||||
code: 'kn-IN'
|
||||
},
|
||||
{
|
||||
name: 'Korean (Korea)',
|
||||
code: 'ko-KR'
|
||||
},
|
||||
{
|
||||
name: 'Latvian (Latvia)',
|
||||
code: 'lv-LV'
|
||||
},
|
||||
{
|
||||
name: 'Lithuanian (Lithuania)',
|
||||
code: 'lt-LT'
|
||||
},
|
||||
{
|
||||
name: 'Malay (Malaysia)',
|
||||
code: 'ms-MY'
|
||||
},
|
||||
{
|
||||
name: 'Maltese (Malta)',
|
||||
code: 'mt-MT'
|
||||
},
|
||||
{
|
||||
name: 'Marathi (India)',
|
||||
code: 'mr-IN'
|
||||
},
|
||||
{
|
||||
name: 'Norwegian (Bokmål, Norway)',
|
||||
code: 'nb-NO'
|
||||
},
|
||||
{
|
||||
name: 'Persian (Iran)',
|
||||
code: 'fa-IR'
|
||||
},
|
||||
{
|
||||
name: 'Polish (Poland)',
|
||||
code: 'pl-PL'
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Brazil)',
|
||||
code: 'pt-BR'
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Portugal)',
|
||||
code: 'pt-PT'
|
||||
},
|
||||
{
|
||||
name: 'Romanian (Romania)',
|
||||
code: 'ro-RO'
|
||||
},
|
||||
{
|
||||
name: 'Russian (Russia)',
|
||||
code: 'ru-RU'
|
||||
},
|
||||
{
|
||||
name: 'Slovak (Slovakia)',
|
||||
code: 'sk-SK'
|
||||
},
|
||||
{
|
||||
name: 'Slovenian (Slovenia)',
|
||||
code: 'sl-SI'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Argentina)',
|
||||
code: 'es-AR'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Bolivia)',
|
||||
code: 'es-BO'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Chile)',
|
||||
code: 'es-CL'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Colombia)',
|
||||
code: 'es-CO'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Costa Rica)',
|
||||
code: 'es-CR'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Cuba)',
|
||||
code: 'es-CU'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Dominican Republic)',
|
||||
code: 'es-DO'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Ecuador)',
|
||||
code: 'es-EC'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (El Salvador)',
|
||||
code: 'es-SV'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Equatorial Guinea)',
|
||||
code: 'es-GQ'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Guatemala)',
|
||||
code: 'es-GT'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Honduras)',
|
||||
code: 'es-HN'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Mexico)',
|
||||
code: 'es-MX'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Nicaragua)',
|
||||
code: 'es-NI'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Panama)',
|
||||
code: 'es-PA'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Paraguay)',
|
||||
code: 'es-PY'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Peru)',
|
||||
code: 'es-PE'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Puerto Rico)',
|
||||
code: 'es-PR'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Spain)',
|
||||
code: 'es-ES'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Uruguay)',
|
||||
code: 'es-UY'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (USA)',
|
||||
code: 'es-US'
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Venezuela)',
|
||||
code: 'es-VE'
|
||||
},
|
||||
{
|
||||
name: 'Swahili (Kenya)',
|
||||
code: 'sw-KE'
|
||||
},
|
||||
{
|
||||
name: 'Swedish (Sweden)',
|
||||
code: 'sv-SE'
|
||||
},
|
||||
{
|
||||
name: 'Tamil (India)',
|
||||
code: 'ta-IN'
|
||||
},
|
||||
{
|
||||
name: 'Telugu (India)',
|
||||
code: 'te-IN'
|
||||
},
|
||||
{
|
||||
name: 'Thai (Thailand)',
|
||||
code: 'th-TH'
|
||||
},
|
||||
{
|
||||
name: 'Turkish (Turkey)',
|
||||
code: 'tr-TR'
|
||||
},
|
||||
{
|
||||
name: 'Vietnamese (Vietnam)',
|
||||
code: 'vi-VN'
|
||||
},
|
||||
];
|
||||
3474
src/data/SpeechSynthesisLanguageMicrosoft.js
Normal file
3474
src/data/SpeechSynthesisLanguageMicrosoft.js
Normal file
File diff suppressed because it is too large
Load Diff
39
src/data/SpeechSynthesisLanguageWellSaid.js
Normal file
39
src/data/SpeechSynthesisLanguageWellSaid.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export default [
|
||||
{
|
||||
code: 'en-US',
|
||||
name: 'English (US)',
|
||||
voices: [
|
||||
{ value: '3', name: 'Alana B.' },
|
||||
{ value: '4', name: 'Ramona J.' },
|
||||
{ value: '5', name: 'Ramona J. (promo)' },
|
||||
{ value: '7', name: 'Wade C.' },
|
||||
{ value: '8', name: 'Sofia H.' },
|
||||
{ value: '9', name: 'David D.' },
|
||||
{ value: '11', name: 'Isabel V.' },
|
||||
{ value: '12', name: 'Ava H.' },
|
||||
{ value: '13', name: 'Jeremy G.' },
|
||||
{ value: '14', name: 'Nicole L.' },
|
||||
{ value: '15', name: 'Paige L.' },
|
||||
{ value: '16', name: 'Tobin A.' },
|
||||
{ value: '17', name: 'Kai M.' },
|
||||
{ value: '18', name: 'Tristan F.' },
|
||||
{ value: '19', name: 'Patrick K.' },
|
||||
{ value: '20', name: 'Soifia H. (promo)' },
|
||||
{ value: '21', name: 'Damian P. (promo)' },
|
||||
{ value: '22', name: 'Jodi P. (promo)' },
|
||||
{ value: '23', name: 'Lee M. (promo)' },
|
||||
{ value: '24', name: 'Selene R. (promo)' },
|
||||
{ value: '26', name: 'Wade C. (promo)' },
|
||||
{ value: '27', name: 'Joe F.' },
|
||||
{ value: '28', name: 'Joe F. (promo)' },
|
||||
{ value: '29', name: 'Garry J. (character)' },
|
||||
{ value: '33', name: 'Jude D.' },
|
||||
{ value: '34', name: 'Eric S. (promo)' },
|
||||
{ value: '35', name: 'Chase J.' },
|
||||
{ value: '37', name: 'Steve B. (promo)' },
|
||||
{ value: '38', name: 'Bella B. (promo)' },
|
||||
{ value: '39', name: 'Tilda C. (promo)' },
|
||||
{ value: '41', name: 'Paul B. (promo)' }
|
||||
],
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user