mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-01-25 02:08:19 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8c67143bd | ||
|
|
a67f6ad214 | ||
|
|
57dd168272 | ||
|
|
6b7e4a34bb | ||
|
|
50216d5345 | ||
|
|
4bdbd6fc0f | ||
|
|
ebcdefb945 | ||
|
|
6b9d206f4f | ||
|
|
68d88decb5 | ||
|
|
1276687cc0 | ||
|
|
b97d5e538f | ||
|
|
2704e97a96 | ||
|
|
4cfdfc3b49 | ||
|
|
47d73a7edd | ||
|
|
c14fa5db34 | ||
|
|
668d7f05f6 | ||
|
|
5ae88ff13e | ||
|
|
06c21f2545 | ||
|
|
663aabc80c | ||
|
|
a7de0a494e | ||
|
|
b742e67715 | ||
|
|
013681e7eb | ||
|
|
4912758120 | ||
|
|
bb335d0838 | ||
|
|
38d26dddc8 | ||
|
|
c0d531c63f | ||
|
|
420080ba84 | ||
|
|
c40fb9cc01 | ||
|
|
40143ae79d | ||
|
|
536b183535 | ||
|
|
bf88a27330 | ||
|
|
831450306d | ||
|
|
c8d1034dc9 | ||
|
|
37af9522aa | ||
|
|
6bb81a499b | ||
|
|
58f97dcfb2 | ||
|
|
23a067b6dd | ||
|
|
92db20965e | ||
|
|
8f8d635bd3 | ||
|
|
b075028b7b | ||
|
|
b5f2e5fc25 | ||
|
|
6390cc6b81 | ||
|
|
d7db92f0c7 | ||
|
|
f5201d2d69 | ||
|
|
128ca045b0 | ||
|
|
3403996946 | ||
|
|
87dbb461e0 | ||
|
|
9bce9c5510 | ||
|
|
2db5f26dbf | ||
|
|
bfc7cc971c | ||
|
|
35f353c905 | ||
|
|
922d664bf8 | ||
|
|
ff4d6b6e11 | ||
|
|
70387ff4f1 | ||
|
|
7a4c583345 | ||
|
|
d54fbc4782 | ||
|
|
14dd1319d9 | ||
|
|
8538d40696 | ||
|
|
eda1fa0dc4 | ||
|
|
0174315a68 | ||
|
|
b86bf0c403 | ||
|
|
3fc1c800ac | ||
|
|
ee4483288d | ||
|
|
d3f1dbf332 |
3
.env
3
.env
@@ -1 +1,2 @@
|
||||
REACT_APP_API_BASE_URL=http://[ip]:[port]/v1
|
||||
REACT_APP_API_BASE_URL=http://127.0.0.1:3002/v1
|
||||
GENERATE_SOURCEMAP=false
|
||||
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
|
||||
@@ -1,15 +1,18 @@
|
||||
FROM node:alpine as builder
|
||||
FROM node:18.9.0-alpine3.16 as builder
|
||||
RUN apk update && apk add --no-cache python3 make g++
|
||||
COPY . /opt/app
|
||||
WORKDIR /opt/app/
|
||||
COPY package.json ./
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
RUN npm prune
|
||||
|
||||
FROM node:alpine as webapp
|
||||
FROM node:18.6.0-alpine as webapp
|
||||
RUN apk add curl
|
||||
WORKDIR /opt/app
|
||||
COPY . /opt/app
|
||||
COPY --from=builder /opt/app/node_modules ./node_modules
|
||||
COPY --from=builder /opt/app/build ./build
|
||||
COPY ./entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
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.
|
||||
@@ -25,7 +25,11 @@ If there is an update to this code base, you can update the code without re-depl
|
||||
|
||||
## Development
|
||||
|
||||
Like production, you must specify the IP:port of the Jambonz API you will be hitting.
|
||||
### Local server
|
||||
See [howto-setup-test-environment](./howto-setup-test-environment.md) for details on how to set up a complete local test environment on your laptop.
|
||||
|
||||
### Remote server
|
||||
If you want to test against a remote server, you must specify the IP:port of the Jambonz API you will be hitting.
|
||||
|
||||
1. Copy `.env` to `.env.local`
|
||||
2. In `.env.local`, replace `[ip]:[port]` with the API's IP and port
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
PUBLIC_IPV4="$(curl --fail -qs whatismyip.akamai.com)"
|
||||
API_PORT="${API_PORT:-3000}"
|
||||
API_VERSION="${API_VERSION:-v1}"
|
||||
echo "REACT_APP_API_BASE_URL=${REACT_APP_API_BASE_URL:-http://$PUBLIC_IPV4:$API_PORT/$API_VERSION}" > /opt/app/.env
|
||||
REACT_APP_API_BASE_URL=${REACT_APP_API_BASE_URL:-http://$PUBLIC_IPV4:$API_PORT/$API_VERSION}
|
||||
echo "REACT_APP_API_BASE_URL=${REACT_APP_API_BASE_URL}" > /opt/app/.env
|
||||
cd /opt/app/
|
||||
npm run build
|
||||
npm run serve
|
||||
TAG="<script>window.JAMBONZ = { APP_API_BASE_URL: '${REACT_APP_API_BASE_URL}'};</script>"
|
||||
sed -i -e "\@</head>@i\ $TAG" ./build/index.html
|
||||
npm run serve
|
||||
|
||||
59
howto-setup-test-environment.md
Normal file
59
howto-setup-test-environment.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Setting up a local test environment
|
||||
This document describes how to set up a local development and test environment on your laptop. Testing the jambonz-webapp requires a back-end system to run against, and we use docker-compose to run these back-end components, allowing you to develop and test the react UI locally.
|
||||
|
||||
## Prerequisites
|
||||
- You will need to have docker and docker-compose installed on your laptop.
|
||||
- You need to have cloned the [jambonz-api-server](https://github.com/jambonz/jambonz-api-server) repo to a folder on your laptop.
|
||||
|
||||
## Running the back-end services
|
||||
Make sure the docker daemon is running on your laptop. Open a terminal window and cd into the project folder for jambonz-api-server, then run the following command to start the back-end processes.
|
||||
|
||||
```bash
|
||||
cd jambonz-api-server
|
||||
npm run integration-test
|
||||
```
|
||||
|
||||
This will take a few minutes to start, but eventually a successfull startup will eventually look something like this:
|
||||
```bash
|
||||
$ npm run integration-test
|
||||
|
||||
> jambonz-api-server@v0.7.5 integration-test
|
||||
> NODE_ENV=test JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js
|
||||
|
||||
starting dockerized mysql and redis..
|
||||
mysql is running
|
||||
creating database..
|
||||
creating schema..
|
||||
seeding database..
|
||||
creating admin user..
|
||||
reset_admin_password, initial admin password is admin
|
||||
|
||||
|
||||
sipp exited with non-zero code 1 signal null
|
||||
1
|
||||
ready for testing!
|
||||
{"level":30, "time": "2022-04-14T18:07:49.318Z","pid":5292,"hostname":"MacBook-Pro-2.local","msg":"listening for HTTP traffic on port 3000","v":1}
|
||||
{"level":20, "time": "2022-04-14T18:07:49.325Z","pid":5292,"hostname":"MacBook-Pro-2.local","args":[],"msg":"redis event connect","v":1}
|
||||
{"level":20, "time": "2022-04-14T18:07:49.345Z","pid":5292,"hostname":"MacBook-Pro-2.local","args":[],"msg":"redis event ready","v":1}
|
||||
```
|
||||
|
||||
This starts the a docker-compose network running the following containers:
|
||||
- mysql
|
||||
- redis
|
||||
- influxdb
|
||||
- heplify-server
|
||||
- drachtio
|
||||
- homer-webapp
|
||||
|
||||
Leaving the jambonz-api-server process running, open another terminal window, cd into the folder where you have checked out this project, and start it as shown below:
|
||||
|
||||
```
|
||||
cd jambonz-webapp
|
||||
npm start
|
||||
```
|
||||
This will start the react UI and open a browser page to http://localhost:3001.
|
||||
|
||||
You should now see the login page to the jambonz webapp and can log in with username admin and password admin. You will be forced to change the password, and then you should see the main page of the application.
|
||||
|
||||
From here you can make and test changes locally.
|
||||
|
||||
33506
package-lock.json
generated
33506
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,20 +1,19 @@
|
||||
{
|
||||
"name": "jambonz-cpaas-ui",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"name": "jambonz-webapp",
|
||||
"version": "v0.7.7",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"antd": "^4.15.4",
|
||||
"antd": "^4.21.0",
|
||||
"axios": "^0.21.1",
|
||||
"moment": "^2.29.1",
|
||||
"moment": "^2.29.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"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": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useContext, useEffect, useState, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
@@ -16,6 +17,7 @@ import { Link as ReactRouterLink } from 'react-router-dom';
|
||||
import { ServiceProviderValueContext, ServiceProviderMethodContext } from '../../contexts/ServiceProviderContext';
|
||||
import LogoJambong from "../../images/LogoJambong.svg";
|
||||
import AddModalButton from '../elements/AddModalButton';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
const StyledNav = styled.nav`
|
||||
position: relative;
|
||||
@@ -106,7 +108,7 @@ const Nav = () => {
|
||||
if (history.location.pathname !== '' && jwt) {
|
||||
const serviceProvidersResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/ServiceProviders',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -137,7 +139,7 @@ const Nav = () => {
|
||||
|
||||
const serviceProviderResponse = await axios({
|
||||
method: 'post',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
|
||||
@@ -3,7 +3,8 @@ 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';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
const Container = styled.div`
|
||||
margin-top: 0.25rem;
|
||||
@@ -21,7 +22,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 () => {
|
||||
@@ -37,8 +38,9 @@ const Sbcs = props => {
|
||||
}
|
||||
const sbcResults = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/Sbcs?service_provider_sid=${currentServiceProvider}`,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
// 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}?`}
|
||||
|
||||
@@ -21,6 +21,7 @@ const StyledLink = styled(FilteredLink)`
|
||||
border-radius: 50%;
|
||||
text-decoration: none;
|
||||
color: #565656;
|
||||
z-index: 1;
|
||||
|
||||
& > span:first-child {
|
||||
display: flex;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
/* eslint-disable no-undef */
|
||||
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,8 +19,10 @@ 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';
|
||||
import { APP_API_BASE_URL, LIMITS } from "../../constants";
|
||||
|
||||
const StyledInputGroup = styled(InputGroup)`
|
||||
position: relative;
|
||||
@@ -49,40 +54,72 @@ 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 [ siprecCallingApplication, setSiprecCallingApplication ] = 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);
|
||||
const [localLimits, setLocalLimits] = useState([]);
|
||||
|
||||
// Invalid form inputs
|
||||
const [ invalidName, setInvalidName ] = useState(false);
|
||||
const [ invalidSipRealm, setInvalidSipRealm ] = useState(false);
|
||||
const [ invalidRegWebhook, setInvalidRegWebhook ] = useState(false);
|
||||
const [ invalidUser, setInvalidUser ] = useState(false);
|
||||
const [ invalidPassword, setInvalidPassword ] = useState(false);
|
||||
const [ invalidName, setInvalidName ] = useState(false);
|
||||
const [ invalidSipRealm, setInvalidSipRealm ] = useState(false);
|
||||
const [ invalidRegWebhook, setInvalidRegWebhook ] = useState(false);
|
||||
const [ invalidRegUser, setInvalidRegUser ] = useState(false);
|
||||
const [ invalidRegPassword, setInvalidRegPassword ] = useState(false);
|
||||
const [ invalidQueueWebhook, setInvalidQueueWebhook ] = useState(false);
|
||||
const [ invalidQueueUser, setInvalidQueueUser ] = useState(false);
|
||||
const [ invalidQueuePassword, setInvalidQueuePassword ] = useState(false);
|
||||
// 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 +134,14 @@ const AccountForm = props => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubspaceMenuOpen = sid => {
|
||||
if (menuOpen === sid) {
|
||||
setMenuOpen(null);
|
||||
} else {
|
||||
setMenuOpen(sid);
|
||||
}
|
||||
};
|
||||
|
||||
const copyWebhookSecret = async e => {
|
||||
e.preventDefault();
|
||||
setMenuOpen(null);
|
||||
@@ -128,7 +173,7 @@ const AccountForm = props => {
|
||||
setGeneratingSecret(true);
|
||||
const apiKeyResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${accountSid}/WebhookSecret?regenerate=true`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -151,6 +196,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: 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: 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 {
|
||||
@@ -167,7 +302,7 @@ const AccountForm = props => {
|
||||
const promiseList = [];
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -178,44 +313,51 @@ const AccountForm = props => {
|
||||
if (props.type === 'edit') {
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Applications',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
promiseList.push(applicationsPromise);
|
||||
}
|
||||
|
||||
if (props.type === 'add') {
|
||||
const serviceProvidersPromise = axios({
|
||||
const limitsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/ServiceProviders',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${props.account_sid}/Limits`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
promiseList.push(serviceProvidersPromise);
|
||||
promiseList.push(limitsPromise);
|
||||
}
|
||||
|
||||
const sbcsPromise = await axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Sbcs',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
promiseList.push(sbcsPromise);
|
||||
|
||||
const promiseAllValues = await Promise.all(promiseList);
|
||||
|
||||
const accountsData = (promiseAllValues[0] && promiseAllValues[0].data) || [];
|
||||
setAccounts(accountsData);
|
||||
|
||||
if (props.type === 'edit') {
|
||||
// Application Data
|
||||
const allApplications = (promiseAllValues[1] && promiseAllValues[1].data) || [];
|
||||
const accountApplicationsData = allApplications.filter(app => {
|
||||
return app.account_sid === props.account_sid;
|
||||
});
|
||||
setAccountApplications(accountApplicationsData);
|
||||
// Limits Data
|
||||
setLocalLimits(promiseAllValues[2]?.data);
|
||||
}
|
||||
|
||||
if (props.type === 'add') {
|
||||
const serviceProviders = (promiseAllValues[1] && promiseAllValues[1].data) || '';
|
||||
setServiceProviderSid(serviceProviders[0].service_provider_sid);
|
||||
}
|
||||
setSbcs(promiseAllValues[3]?.data);
|
||||
|
||||
if (props.type === 'setup' && accountsData.length > 1) {
|
||||
history.push('/internal/accounts');
|
||||
@@ -249,17 +391,33 @@ const AccountForm = props => {
|
||||
setName(acc.name || '');
|
||||
setSipRealm(acc.sip_realm || '');
|
||||
setDeviceCallingApplication(acc.device_calling_application_sid || '');
|
||||
setSiprecCallingApplication(acc.siprec_hook_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 +456,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 +503,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,34 +544,60 @@ 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') {
|
||||
axiosData.device_calling_application_sid = deviceCallingApplication || null;
|
||||
axiosData.siprec_hook_sid = siprecCallingApplication || null;
|
||||
}
|
||||
|
||||
const url = props.type === 'add'
|
||||
? `/Accounts`
|
||||
: `/Accounts/${accountSid}`;
|
||||
|
||||
await axios({
|
||||
const accountResp = await axios({
|
||||
method: props.type === 'add' ? 'post' : 'put',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
data: axiosData,
|
||||
});
|
||||
// Update Limits
|
||||
const acc_sid = accountSid ? accountSid : accountResp.data.sid;
|
||||
await Promise.all(
|
||||
localLimits.map(l => {
|
||||
const method = l.quantity === "" ? 'delete' : 'post';
|
||||
const limitUrl = l.quantity === "" ? `/Accounts/${props.account_sid}/Limits?category=${l.category}` : `/Accounts/${acc_sid}/Limits`;
|
||||
return axios({
|
||||
method: method,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: limitUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
...(method === 'post' && {data: l})
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
if (props.type === 'setup') {
|
||||
isMounted = false;
|
||||
@@ -450,6 +651,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
|
||||
@@ -515,33 +729,61 @@ const AccountForm = props => {
|
||||
</StyledInputGroup>
|
||||
|
||||
{props.type === 'edit' && (
|
||||
<React.Fragment>
|
||||
<Label tooltip htmlFor="deviceCallingApplication">
|
||||
<span style={{ position: 'relative' }}>
|
||||
Application for SIP Device Calls
|
||||
<Tooltip large>
|
||||
This application is used to handle incoming calls from SIP users who have registered to the Account’s SIP Realm.
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Label>
|
||||
<Select
|
||||
large={props.type === 'setup'}
|
||||
name="deviceCallingApplication"
|
||||
id="deviceCallingApplication"
|
||||
value={deviceCallingApplication}
|
||||
onChange={e => setDeviceCallingApplication(e.target.value)}
|
||||
>
|
||||
<option value="">-- NONE --</option>
|
||||
{accountApplications && accountApplications.map(app => (
|
||||
<option
|
||||
key={app.application_sid}
|
||||
value={app.application_sid}
|
||||
>
|
||||
{app.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</React.Fragment>
|
||||
<>
|
||||
<React.Fragment>
|
||||
<Label tooltip htmlFor="deviceCallingApplication">
|
||||
<span style={{ position: 'relative' }}>
|
||||
Application for SIP Device Calls
|
||||
<Tooltip large>
|
||||
This application is used to handle incoming calls from SIP users who have registered to the Account’s SIP Realm.
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Label>
|
||||
<Select
|
||||
large={props.type === 'setup'}
|
||||
name="deviceCallingApplication"
|
||||
id="deviceCallingApplication"
|
||||
value={deviceCallingApplication}
|
||||
onChange={e => setDeviceCallingApplication(e.target.value)}
|
||||
>
|
||||
<option value="">-- NONE --</option>
|
||||
{accountApplications && accountApplications.map(app => (
|
||||
<option
|
||||
key={app.application_sid}
|
||||
value={app.application_sid}
|
||||
>
|
||||
{app.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</React.Fragment>
|
||||
|
||||
<React.Fragment>
|
||||
<Label tooltip htmlFor="siprecCallingApplication">
|
||||
<span style={{ position: 'relative' }}>
|
||||
Application for SIPREC Calls
|
||||
</span>
|
||||
</Label>
|
||||
<Select
|
||||
large={props.type === 'setup'}
|
||||
name="siprecCallingApplication"
|
||||
id="siprecCallingApplication"
|
||||
value={siprecCallingApplication}
|
||||
onChange={e => setSiprecCallingApplication(e.target.value)}
|
||||
right
|
||||
>
|
||||
<option value="">-- NONE --</option>
|
||||
{accountApplications && accountApplications.map(app => (
|
||||
<option
|
||||
key={app.application_sid}
|
||||
value={app.application_sid}
|
||||
>
|
||||
{app.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Label htmlFor="regWebhook">Registration Webhook</Label>
|
||||
@@ -566,27 +808,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 +836,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 +849,235 @@ 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>
|
||||
)}
|
||||
|
||||
{LIMITS.map(({ label, category }) => {
|
||||
const quantity = localLimits?.find(l => l.category === category)?.quantity;
|
||||
return <React.Fragment key={category}>
|
||||
<Label htmlFor={category}>{label}</Label>
|
||||
<Input
|
||||
name={category}
|
||||
id={category}
|
||||
type="number"
|
||||
placeholder="Enter Quantity (0=unlimited)"
|
||||
min="0"
|
||||
value={quantity >= 0 ? quantity : ""}
|
||||
onChange={e => {
|
||||
const limit = localLimits.find(l => l.category === category);
|
||||
const value = e.target.value ? Number(e.target.value) : "";
|
||||
if (limit) {
|
||||
setLocalLimits(localLimits.map(l => l.category === category ? {...l, quantity: value} : l));
|
||||
} else {
|
||||
setLocalLimits([...localLimits, {category, quantity: value}]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
</Input>
|
||||
|
||||
</React.Fragment>;
|
||||
})}
|
||||
{ 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} />
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable no-undef */
|
||||
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 +16,17 @@ 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';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
const ApplicationForm = props => {
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
const currentServiceProvider = useContext(ServiceProviderValueContext);
|
||||
|
||||
// Refs
|
||||
const refName = useRef(null);
|
||||
@@ -98,7 +105,7 @@ const ApplicationForm = props => {
|
||||
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -106,7 +113,7 @@ const ApplicationForm = props => {
|
||||
});
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Applications',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -118,7 +125,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);
|
||||
@@ -389,7 +396,7 @@ const ApplicationForm = props => {
|
||||
|
||||
await axios({
|
||||
method,
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -493,7 +500,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 +750,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 +771,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 +786,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 +811,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 +831,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 +859,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 +909,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 +923,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
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
@@ -10,6 +11,7 @@ import InputGroup from '../elements/InputGroup';
|
||||
import FormError from '../blocks/FormError';
|
||||
import Loader from '../blocks/Loader';
|
||||
import Button from '../elements/Button';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
const MsTeamsTenantForm = props => {
|
||||
|
||||
@@ -54,7 +56,7 @@ const MsTeamsTenantForm = props => {
|
||||
|
||||
const tenantsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/MicrosoftTeamsTenants',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -62,7 +64,7 @@ const MsTeamsTenantForm = props => {
|
||||
});
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -70,7 +72,7 @@ const MsTeamsTenantForm = props => {
|
||||
});
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Applications',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -86,7 +88,7 @@ const MsTeamsTenantForm = props => {
|
||||
if (props.type === 'add') {
|
||||
promises.push(axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/ServiceProviders',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -247,7 +249,7 @@ const MsTeamsTenantForm = props => {
|
||||
|
||||
await axios({
|
||||
method,
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable no-undef */
|
||||
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';
|
||||
@@ -11,11 +13,13 @@ import FormError from '../blocks/FormError';
|
||||
import Loader from '../blocks/Loader';
|
||||
import Button from '../elements/Button';
|
||||
import phoneNumberFormat from '../../helpers/phoneNumberFormat';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
const PhoneNumberForm = props => {
|
||||
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
const currentServiceProvider = useContext(ServiceProviderValueContext);
|
||||
|
||||
// Refs
|
||||
const refPhoneNumber = useRef(null);
|
||||
@@ -58,32 +62,32 @@ const PhoneNumberForm = props => {
|
||||
|
||||
const sipTrunksPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/VoipCarriers',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `ServiceProviders/${currentServiceProvider}/VoipCarriers`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/Applications',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `ServiceProviders/${currentServiceProvider}/Applications`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
const phoneNumbersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: '/PhoneNumbers',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `ServiceProviders/${currentServiceProvider}/PhoneNumbers`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
@@ -274,7 +278,7 @@ const PhoneNumberForm = props => {
|
||||
|
||||
await axios({
|
||||
method,
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -358,7 +362,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 +375,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 +398,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,8 @@
|
||||
/* eslint-disable no-undef */
|
||||
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,8 +13,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";
|
||||
import { APP_API_BASE_URL, LIMITS } from "../../constants";
|
||||
|
||||
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();
|
||||
@@ -26,22 +43,36 @@ const SettingsForm = () => {
|
||||
const refServiceProviderName = useRef(null);
|
||||
|
||||
// Form inputs
|
||||
const [ enableMsTeams, setEnableMsTeams ] = useState(false);
|
||||
const [ sbcDomainName, setSbcDomainName ] = useState('');
|
||||
const [enableMsTeams, setEnableMsTeams] = useState(false);
|
||||
const [sbcDomainName, setSbcDomainName] = useState('');
|
||||
const [serviceProviderName, setServiceProviderName] = useState('');
|
||||
|
||||
// For when user has data in sbcDomainName and then taps the checkbox to disable MsTeams
|
||||
const [ savedSbcDomainName, setSavedSbcDomainName ] = useState('');
|
||||
const [savedSbcDomainName, setSavedSbcDomainName] = useState('');
|
||||
|
||||
// Invalid form inputs
|
||||
const [ invalidEnableMsTeams, setInvalidEnableMsTeams ] = useState(false);
|
||||
const [ invalidSbcDomainName, setInvalidSbcDomainName ] = useState(false);
|
||||
const [invalidEnableMsTeams, setInvalidEnableMsTeams] = useState(false);
|
||||
const [invalidSbcDomainName, setInvalidSbcDomainName] = useState(false);
|
||||
const [invalidServiceProviderName, setInvalidServiceProviderName] = useState(false);
|
||||
|
||||
const [ showLoader, setShowLoader ] = useState(true);
|
||||
const [ errorMessage, setErrorMessage ] = useState('');
|
||||
const [showLoader, setShowLoader] = useState(true);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [serviceProviderSid, setServiceProviderSid] = useState('');
|
||||
const [serviceProviders, setServiceProviders] = useState([]);
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
const [localLimits, setLocalLimits] = useState([]);
|
||||
|
||||
const [ serviceProviderSid, setServiceProviderSid ] = useState('');
|
||||
const callApi = async (path, method, data) => {
|
||||
return await axios({
|
||||
method: method,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: path,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
...(data && {data})
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const getSettingsData = async () => {
|
||||
@@ -56,21 +87,22 @@ const SettingsForm = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceProvidersResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
const serviceProvidersResponse = await callApi(`/ServiceProviders`, 'get');
|
||||
|
||||
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);
|
||||
setSbcDomainName(sp.ms_teams_fqdn || '');
|
||||
|
||||
// Fetch Service provider Limits
|
||||
if (sp.service_provider_sid) {
|
||||
const serviceProvidersLimitsResponse = await callApi(`/ServiceProviders/${sp.service_provider_sid}/Limits`, 'get');
|
||||
setLocalLimits(serviceProvidersLimitsResponse.data);
|
||||
}
|
||||
} catch (err) {
|
||||
handleErrors({ err, history, dispatch });
|
||||
} finally {
|
||||
@@ -96,6 +128,32 @@ const SettingsForm = () => {
|
||||
setEnableMsTeams(e.target.checked);
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
setErrorMessage('');
|
||||
|
||||
axios({
|
||||
method: 'delete',
|
||||
baseURL: 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 {
|
||||
@@ -162,15 +220,12 @@ const SettingsForm = () => {
|
||||
name: serviceProviderName.trim(),
|
||||
};
|
||||
|
||||
await axios({
|
||||
method: 'put',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${serviceProviderSid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
data,
|
||||
});
|
||||
await callApi(`/ServiceProviders/${serviceProviderSid}`, 'put', data);
|
||||
await Promise.all(
|
||||
localLimits.map(l => l.quantity === "" ?
|
||||
callApi(`/ServiceProviders/${serviceProviderSid}/Limits?category=${l.category}`, 'delete') :
|
||||
callApi(`/ServiceProviders/${serviceProviderSid}/Limits`, 'post', l))
|
||||
);
|
||||
|
||||
refreshMsTeamsData();
|
||||
|
||||
@@ -209,70 +264,140 @@ 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}
|
||||
/>
|
||||
|
||||
<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} />
|
||||
)}
|
||||
|
||||
<InputGroup flexEnd spaced>
|
||||
<Button
|
||||
grid
|
||||
gray
|
||||
type="button"
|
||||
onClick={() => {
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'info',
|
||||
message: 'Changes canceled',
|
||||
});
|
||||
}}
|
||||
? <Loader height="365px" />
|
||||
: (
|
||||
<>
|
||||
<Form
|
||||
large
|
||||
wideLabel
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Label htmlFor="serviceProviderName">Service Provider Name</Label>
|
||||
<Input
|
||||
name="serviceProviderName"
|
||||
id="serviceProviderName"
|
||||
value={serviceProviderName}
|
||||
onChange={e => setServiceProviderName(e.target.value)}
|
||||
invalid={invalidServiceProviderName}
|
||||
ref={refServiceProviderName}
|
||||
/>
|
||||
|
||||
<Button grid>Save</Button>
|
||||
</InputGroup>
|
||||
</Form>
|
||||
<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") || ""}
|
||||
/>
|
||||
|
||||
{LIMITS.map(({ label, category }) => {
|
||||
const quantity = localLimits?.find(l => l.category === category)?.quantity;
|
||||
return <React.Fragment key={category}>
|
||||
<Label htmlFor={category}>{label}</Label>
|
||||
<Input
|
||||
name={category}
|
||||
id={category}
|
||||
type="number"
|
||||
placeholder="Enter Quantity (0=unlimited)"
|
||||
min="0"
|
||||
value={quantity >= 0 ? quantity : ""}
|
||||
onChange={e => {
|
||||
const limit = localLimits.find(l => l.category === category);
|
||||
const value = e.target.value ? Number(e.target.value) : "";
|
||||
if (limit) {
|
||||
setLocalLimits(localLimits.map(l => l.category === category ? {...l, quantity: value} : l));
|
||||
} else {
|
||||
setLocalLimits([...localLimits, {category, quantity: value}]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>;
|
||||
})}
|
||||
|
||||
{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',
|
||||
});
|
||||
}}
|
||||
>
|
||||
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('');
|
||||
}}
|
||||
handleSubmit={handleDelete}
|
||||
actionText="Delete"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
@@ -8,17 +9,20 @@ 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';
|
||||
import Checkbox from '../elements/Checkbox';
|
||||
import FileUpload from '../elements/FileUpload';
|
||||
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 AwsRegions from '../../data/AwsRegions';
|
||||
import MicrosoftAzureRegions from '../../data/MicrosoftAzureRegions';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
const StyledButtonGroup = styled(InputGroup)`
|
||||
@media (max-width: 576.98px) {
|
||||
@@ -66,44 +70,82 @@ 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);
|
||||
const refAwsRegion = useRef(null);
|
||||
const refUseCustomTts = useRef(null);
|
||||
const refUseCustomStt = useRef(null);
|
||||
|
||||
// Form inputs
|
||||
const [ vendor, setVendor ] = useState('');
|
||||
const [ serviceKey, setServiceKey ] = useState('');
|
||||
const [ displayedServiceKey, setDisplayedServiceKey ] = useState('');
|
||||
const [ accessKeyId, setAccessKeyId ] = useState('');
|
||||
const [ secretAccessKey, setSecretAccessKey ] = useState('');
|
||||
const [ useForTts, setUseForTts ] = useState(false);
|
||||
const [ useForStt, setUseForStt ] = useState(false);
|
||||
const [vendor, setVendor] = useState('');
|
||||
const [serviceKey, setServiceKey] = useState('');
|
||||
const [displayedServiceKey, setDisplayedServiceKey] = useState('');
|
||||
const [accessKeyId, setAccessKeyId] = useState('');
|
||||
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('');
|
||||
const [awsregion, setAwsRegion] = useState('');
|
||||
const [useCustomTts, setUseCustomTts] = useState(false);
|
||||
const [useCustomStt, setUseCustomStt] = useState(false);
|
||||
const [customTtsEndpoint, setCustomTtsEndpoint] = useState('');
|
||||
const [customSttEndpoint, setCustomSttEndpoint] = useState('');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [tmpCustomTtsEndpoint, setTmpCustomTtsEndpoint] = useState('');
|
||||
const [tmpCustomSttEndpoint, setTmpCustomSttEndpoint] = useState('');
|
||||
|
||||
// Invalid form inputs
|
||||
const [ invalidVendorGoogle, setInvalidVendorGoogle ] = useState(false);
|
||||
const [ invalidVendorAws, setInvalidVendorAws ] = useState(false);
|
||||
const [ invalidAccessKeyId, setInvalidAccessKeyId ] = useState(false);
|
||||
const [ invalidSecretAccessKey, setInvalidSecretAccessKey ] = useState(false);
|
||||
const [ invalidUseForTts, setInvalidUseForTts ] = useState(false);
|
||||
const [ invalidUseForStt, setInvalidUseForStt ] = useState(false);
|
||||
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 [invalidAwsRegion, setInvalidAwsRegion] = useState(false);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [invalidUseCustomTts, setInvalidUseCustomTts] = useState(false);
|
||||
const [invalidUseCustomStt, setInvalidUseCustomStt] = useState(false);
|
||||
|
||||
const [ originalTtsValue, setOriginalTtsValue ] = useState(null);
|
||||
const [ originalSttValue, setOriginalSttValue ] = useState(null);
|
||||
const [originalTtsValue, setOriginalTtsValue] = useState(null);
|
||||
const [originalSttValue, setOriginalSttValue] = useState(null);
|
||||
|
||||
const [ validServiceKey, setValidServiceKey ] = useState(false);
|
||||
const [validServiceKey, setValidServiceKey] = useState(false);
|
||||
|
||||
const [ showLoader, setShowLoader ] = useState(true);
|
||||
const [ errorMessage, setErrorMessage ] = useState('');
|
||||
const [showLoader, setShowLoader] = useState(true);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const getAPIData = async () => {
|
||||
let isMounted = true;
|
||||
try {
|
||||
const accountsResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
|
||||
setAccounts(accountsResponse.data);
|
||||
|
||||
if (type === 'edit') {
|
||||
const speechCredential = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/SpeechCredentials/${speech_service_sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -119,15 +161,23 @@ const SpeechServicesAddEdit = (props) => {
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
setVendor( speechCredential.data.vendor || undefined);
|
||||
setServiceKey( serviceKeyJson || '');
|
||||
setDisplayedServiceKey( displayedServiceKeyJson || '');
|
||||
setAccessKeyId( speechCredential.data.access_key_id || '');
|
||||
setSecretAccessKey( speechCredential.data.secret_access_key || '');
|
||||
setUseForTts( speechCredential.data.use_for_tts || false);
|
||||
setUseForStt( speechCredential.data.use_for_stt || false);
|
||||
setOriginalTtsValue( speechCredential.data.use_for_tts || false);
|
||||
setOriginalSttValue( speechCredential.data.use_for_stt || false);
|
||||
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 || '');
|
||||
setAwsRegion(speechCredential.data.aws_region || '');
|
||||
setUseForTts(speechCredential.data.use_for_tts || false);
|
||||
setUseForStt(speechCredential.data.use_for_stt || false);
|
||||
setOriginalTtsValue(speechCredential.data.use_for_tts || false);
|
||||
setOriginalSttValue(speechCredential.data.use_for_stt || false);
|
||||
setUseCustomTts(speechCredential.data.use_custom_tts || false);
|
||||
setCustomTtsEndpoint(speechCredential.data.custom_tts_endpoint || '');
|
||||
setUseCustomStt(speechCredential.data.use_custom_stt || false);
|
||||
setCustomSttEndpoint(speechCredential.data.custom_stt_endpoint || '');
|
||||
}
|
||||
setShowLoader(false);
|
||||
} catch (err) {
|
||||
@@ -191,10 +241,15 @@ const SpeechServicesAddEdit = (props) => {
|
||||
setErrorMessage('');
|
||||
setInvalidVendorGoogle(false);
|
||||
setInvalidVendorAws(false);
|
||||
setInvalidVendorMs(false);
|
||||
setInvalidVendorWellSaid(false);
|
||||
setInvalidAccessKeyId(false);
|
||||
setInvalidSecretAccessKey(false);
|
||||
setInvalidUseForTts(false);
|
||||
setInvalidUseForStt(false);
|
||||
setInvalidApiKey(false);
|
||||
setInvalidUseCustomTts(false);
|
||||
setInvalidUseCustomStt(false);
|
||||
let errorMessages = [];
|
||||
let focusHasBeenSet = false;
|
||||
|
||||
@@ -202,6 +257,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 +287,60 @@ const SpeechServicesAddEdit = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor === 'aws' && !awsregion) {
|
||||
errorMessages.push('Please select a region.');
|
||||
setInvalidAwsRegion(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refAwsRegion.current.focus();
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
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 (useCustomTts && !customTtsEndpoint) {
|
||||
errorMessages.push('Please provide a custom voice endpoint.');
|
||||
setInvalidUseCustomTts(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refUseCustomTts.current.focus();
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (useCustomStt && !customSttEndpoint) {
|
||||
errorMessages.push('Please provide a custom speech endpoint Id.');
|
||||
setInvalidUseCustomStt(true);
|
||||
if (!focusHasBeenSet) {
|
||||
refUseCustomStt.current.focus();
|
||||
focusHasBeenSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessages.length > 1) {
|
||||
setErrorMessage(errorMessages);
|
||||
return;
|
||||
@@ -238,33 +349,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
|
||||
//===============================================
|
||||
@@ -278,18 +362,36 @@ const SpeechServicesAddEdit = (props) => {
|
||||
|
||||
const postResults = await axios({
|
||||
method,
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
data: {
|
||||
vendor,
|
||||
service_key: vendor === 'google' ? JSON.stringify(serviceKey) : null,
|
||||
access_key_id: vendor === 'aws' ? accessKeyId : null,
|
||||
secret_access_key: vendor === 'aws' ? secretAccessKey : null,
|
||||
service_provider_sid: accountSid ? null : currentServiceProvider,
|
||||
account_sid: accountSid || null,
|
||||
use_for_tts: useForTts,
|
||||
use_for_stt: useForStt,
|
||||
...(vendor === 'google' && method === 'post' && {
|
||||
service_key: serviceKey ? JSON.stringify(serviceKey) : null,
|
||||
}),
|
||||
...(vendor === 'aws' && {
|
||||
...(method === 'post' && {
|
||||
access_key_id: accessKeyId || null,
|
||||
secret_access_key: secretAccessKey || null}),
|
||||
aws_region: awsregion || null
|
||||
}),
|
||||
...(vendor === 'microsoft' && {
|
||||
region: region || null,
|
||||
use_custom_tts: useCustomTts ? 1 : 0,
|
||||
use_custom_stt: useCustomStt ? 1 : 0,
|
||||
custom_tts_endpoint: customTtsEndpoint || null,
|
||||
custom_stt_endpoint: customSttEndpoint || null,
|
||||
}),
|
||||
...(['wellsaid', 'microsoft'].includes(vendor) && method === 'post' && {
|
||||
api_key: apiKey || null,
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -307,7 +409,7 @@ const SpeechServicesAddEdit = (props) => {
|
||||
if (useForTts || useForStt) {
|
||||
const testResults = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/SpeechCredentials/${speech_service_sid}/test`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -352,7 +454,7 @@ const SpeechServicesAddEdit = (props) => {
|
||||
if (type === 'add') {
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/SpeechCredentials/${speech_service_sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -363,7 +465,7 @@ const SpeechServicesAddEdit = (props) => {
|
||||
if (type === 'edit') {
|
||||
await axios({
|
||||
method,
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -383,7 +485,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';
|
||||
@@ -420,37 +526,80 @@ const SpeechServicesAddEdit = (props) => {
|
||||
|
||||
return (
|
||||
showLoader ? (
|
||||
<Loader height={props.type === 'add' ? '424px' : '376px'}/>
|
||||
<Loader height={props.type === 'add' ? '424px' : '376px'} />
|
||||
) : (
|
||||
<Form
|
||||
large
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Label htmlFor="name">Vendor</Label>
|
||||
<InputGroup>
|
||||
<Radio
|
||||
noLeftMargin
|
||||
name="vendor"
|
||||
id="google"
|
||||
label="Google"
|
||||
checked={vendor === 'google'}
|
||||
onChange={() => setVendor('google')}
|
||||
invalid={invalidVendorGoogle}
|
||||
ref={refVendorGoogle}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
<Label htmlFor="vendor">Vendor</Label>
|
||||
<Select
|
||||
name="vendor"
|
||||
id="vendor"
|
||||
value={vendor}
|
||||
onChange={e => setVendor(e.target.value)}
|
||||
{...[refVendorGoogle, refVendorAws, refVendorMs, refVendorWellSaid]}
|
||||
invalid={[invalidVendorGoogle, invalidVendorAws, invalidVendorMs, invalidVendorWellSaid].includes(true)}
|
||||
>
|
||||
<option value="">
|
||||
Select a Vendor
|
||||
</option>
|
||||
<option value="google">Google</option>
|
||||
<option value="aws">AWS</option>
|
||||
<option value="microsoft">Microsoft</option>
|
||||
<option value="wellsaid">WellSaid</option>
|
||||
</Select>
|
||||
|
||||
<Radio
|
||||
name="vendor"
|
||||
id="aws"
|
||||
label="Amazon Web Services"
|
||||
checked={vendor === 'aws'}
|
||||
onChange={() => setVendor('aws')}
|
||||
invalid={invalidVendorAws}
|
||||
ref={refVendorAws}
|
||||
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>
|
||||
|
||||
{['google', 'aws', 'microsoft', 'wellsaid'].includes(vendor) ? (
|
||||
<>
|
||||
<div />
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
name="useForTts"
|
||||
id="useForTts"
|
||||
label="Use for text-to-speech"
|
||||
checked={useForTts}
|
||||
onChange={e => setUseForTts(e.target.checked)}
|
||||
invalid={invalidUseForTts}
|
||||
ref={refUseForTts}
|
||||
/>
|
||||
<div />
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
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
|
||||
)}
|
||||
|
||||
{vendor === 'google' ? (
|
||||
<>
|
||||
@@ -497,34 +646,141 @@ const SpeechServicesAddEdit = (props) => {
|
||||
ref={refSecretAccessKey}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
null
|
||||
)}
|
||||
|
||||
{vendor === 'google' || vendor === 'aws' ? (
|
||||
<Label htmlFor="regions">Region</Label>
|
||||
<Select
|
||||
name="regions"
|
||||
id="regions"
|
||||
value={awsregion}
|
||||
onChange={e => setAwsRegion(e.target.value)}
|
||||
ref={refAwsRegion}
|
||||
invalid={invalidAwsRegion}
|
||||
>
|
||||
<option value="">
|
||||
Select a region
|
||||
</option>
|
||||
{AwsRegions.map(r => (
|
||||
<option
|
||||
key={r.value}
|
||||
value={r.value}
|
||||
>
|
||||
{r.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
) : vendor === 'microsoft' ? (
|
||||
<>
|
||||
<div/>
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
name="useForTts"
|
||||
id="useForTts"
|
||||
label="Use for text-to-speech"
|
||||
checked={useForTts}
|
||||
onChange={e => setUseForTts(e.target.checked)}
|
||||
invalid={invalidUseForTts}
|
||||
ref={refUseForTts}
|
||||
<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'}
|
||||
/>
|
||||
<div/>
|
||||
|
||||
<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>
|
||||
<div />
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
name="useForStt"
|
||||
id="useForStt"
|
||||
label="Use for speech-to-text"
|
||||
checked={useForStt}
|
||||
onChange={e => setUseForStt(e.target.checked)}
|
||||
invalid={invalidUseForStt}
|
||||
ref={refUseForStt}
|
||||
name="useACustomVoice"
|
||||
id="useACustomVoice"
|
||||
label="Use a custom voice"
|
||||
checked={useCustomTts}
|
||||
onChange={e => {
|
||||
setUseCustomTts(e.target.checked);
|
||||
if (e.target.checked && tmpCustomTtsEndpoint) {
|
||||
setCustomTtsEndpoint(tmpCustomTtsEndpoint);
|
||||
}
|
||||
|
||||
if (!e.target.checked) {
|
||||
setTmpCustomTtsEndpoint(customTtsEndpoint);
|
||||
setCustomTtsEndpoint("");
|
||||
}
|
||||
}}
|
||||
invalid={invalidUseCustomTts}
|
||||
ref={refUseCustomTts}
|
||||
/>
|
||||
<Label htmlFor="customVoiceEndpoint">Custom voice endpoint Id</Label>
|
||||
<Input
|
||||
name="customVoiceEndpoint"
|
||||
id="customVoiceEndpoint"
|
||||
value={customTtsEndpoint}
|
||||
onChange={e => setCustomTtsEndpoint(e.target.value)}
|
||||
placeholder="Custom voice endpoint"
|
||||
invalid={invalidUseCustomTts}
|
||||
ref={refUseCustomTts}
|
||||
disabled={!useCustomTts}
|
||||
/>
|
||||
<div />
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
name="useACustomSpeechModel"
|
||||
id="useACustomSpeechModel"
|
||||
label="Use a custom speech model"
|
||||
checked={useCustomStt}
|
||||
onChange={e => {
|
||||
setUseCustomStt(e.target.checked);
|
||||
if(e.target.checked && tmpCustomSttEndpoint) {
|
||||
setCustomSttEndpoint(tmpCustomSttEndpoint);
|
||||
}
|
||||
|
||||
if(!e.target.checked) {
|
||||
setTmpCustomSttEndpoint(customSttEndpoint);
|
||||
setCustomSttEndpoint("");
|
||||
}
|
||||
}}
|
||||
invalid={invalidUseCustomStt}
|
||||
ref={refUseCustomStt}
|
||||
/>
|
||||
<Label htmlFor="customSpeechEndpoint">Custom speech endpoint Id</Label>
|
||||
<Input
|
||||
name="customSpeechEndpoint"
|
||||
id="customSpeechEndpoint"
|
||||
value={customSttEndpoint}
|
||||
onChange={e => setCustomSttEndpoint(e.target.value)}
|
||||
placeholder="Custom speech endpoint Id"
|
||||
invalid={invalidUseCustomStt}
|
||||
ref={refUseCustomStt}
|
||||
disabled={!useCustomStt}
|
||||
/>
|
||||
</>
|
||||
) : 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'}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@@ -545,14 +801,14 @@ const SpeechServicesAddEdit = (props) => {
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'info',
|
||||
message: type === 'add' ? 'New speech service canceled' :'Changes canceled',
|
||||
message: type === 'add' ? 'New speech service canceled' : 'Changes canceled',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button rounded="true">
|
||||
<Button rounded="true" disabled={!vendor}>
|
||||
{type === 'add'
|
||||
? 'Add Speech Service'
|
||||
: 'Save'
|
||||
@@ -564,4 +820,4 @@ const SpeechServicesAddEdit = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeechServicesAddEdit;
|
||||
export default SpeechServicesAddEdit;
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
@@ -7,6 +8,7 @@ import Button from '../elements/Button';
|
||||
import Input from '../elements/Input';
|
||||
import PasswordInput from '../elements/PasswordInput';
|
||||
import FormError from '../blocks/FormError';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
const Login = props => {
|
||||
let history = useHistory();
|
||||
@@ -58,7 +60,7 @@ const Login = props => {
|
||||
// Log in
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/login',
|
||||
data: { username, password },
|
||||
});
|
||||
@@ -84,7 +86,7 @@ const Login = props => {
|
||||
//-----------------------------------------------------------------------------
|
||||
const serviceProvidersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/serviceProviders',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -93,7 +95,7 @@ const Login = props => {
|
||||
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -102,7 +104,7 @@ const Login = props => {
|
||||
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/applications',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -111,7 +113,7 @@ const Login = props => {
|
||||
|
||||
const voipCarriersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/voipCarriers',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useEffect, useContext } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
@@ -5,6 +6,7 @@ import { NotificationDispatchContext } from '../../../contexts/NotificationConte
|
||||
import InternalTemplate from '../../templates/InternalTemplate';
|
||||
import AccountForm from '../../forms/AccountForm';
|
||||
import TableContent from '../../blocks/TableContent.js';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const AccountsAddEdit = () => {
|
||||
let history = useHistory();
|
||||
@@ -31,7 +33,7 @@ const AccountsAddEdit = () => {
|
||||
}
|
||||
const results = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${account_sid}/ApiKeys`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -112,7 +114,7 @@ const AccountsAddEdit = () => {
|
||||
}
|
||||
const result = await axios({
|
||||
method: 'post',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Apikeys',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -167,7 +169,7 @@ const AccountsAddEdit = () => {
|
||||
}
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Apikeys/${apiKeyToDelete.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -207,7 +209,7 @@ const AccountsAddEdit = () => {
|
||||
name="API key"
|
||||
getContent={getApiKeys}
|
||||
columns={[
|
||||
{ header: 'API Key', key: 'token', width: '27rem', fontWeight: 'normal' },
|
||||
{ header: 'Account API Keys', key: 'token', width: '27rem', fontWeight: 'normal' },
|
||||
{ header: 'Last Used', key: 'last_used', width: '10rem' },
|
||||
]}
|
||||
addContent={createApiKey}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useEffect, useContext } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
@@ -5,6 +6,7 @@ import { NotificationDispatchContext } from '../../../contexts/NotificationConte
|
||||
import InternalTemplate from '../../templates/InternalTemplate';
|
||||
import TableContent from '../../blocks/TableContent.js';
|
||||
import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const AccountsList = () => {
|
||||
let history = useHistory();
|
||||
@@ -31,7 +33,7 @@ const AccountsList = () => {
|
||||
if(!currentServiceProvider) return [];
|
||||
const results = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -41,10 +43,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 +76,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;
|
||||
};
|
||||
@@ -95,7 +96,7 @@ const AccountsList = () => {
|
||||
// or if the account has any API keys
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Applications',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -103,7 +104,7 @@ const AccountsList = () => {
|
||||
});
|
||||
const phoneNumbersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/PhoneNumbers',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -111,7 +112,7 @@ const AccountsList = () => {
|
||||
});
|
||||
const msTeamsTenantsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/MicrosoftTeamsTenants',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -119,7 +120,7 @@ const AccountsList = () => {
|
||||
});
|
||||
const apiKeysPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${accountToDelete.sid}/ApiKeys`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -180,7 +181,7 @@ const AccountsList = () => {
|
||||
// Delete account
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${accountToDelete.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -221,7 +222,8 @@ const AccountsList = () => {
|
||||
{ header: 'Name', key: 'name' },
|
||||
{ header: 'AccountSid', key: 'sid' },
|
||||
{ header: 'SIP Realm', key: 'sip_realm' },
|
||||
{ header: 'Registration Webhook', key: 'url' },
|
||||
{ header: 'Registration Webhook', key: 'url_reg' },
|
||||
{ header: 'Queue Event Webhook', key: 'url_queue' }
|
||||
]}
|
||||
formatContentToDelete={formatAccountToDelete}
|
||||
deleteContent={deleteAccount}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
@@ -14,6 +15,7 @@ import Select from "../../../components/elements/Select";
|
||||
import AntdTable from "../../../components/blocks/AntdTable";
|
||||
import handleErrors from "../../../helpers/handleErrors";
|
||||
import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
& > span {
|
||||
@@ -115,7 +117,7 @@ const AlertsIndex = () => {
|
||||
|
||||
const alerts = await axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${account}/Alerts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -171,7 +173,7 @@ const AlertsIndex = () => {
|
||||
setLoading(true);
|
||||
const accountResponse = await axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useEffect, useContext, useState, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
@@ -10,6 +11,7 @@ import Select from "../../../components/elements/Select";
|
||||
import InputGroup from "../../../components/elements/InputGroup";
|
||||
import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext';
|
||||
import handleErrors from "../../../helpers/handleErrors";
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const FilterLabel = styled.span`
|
||||
color: #231f20;
|
||||
@@ -57,7 +59,7 @@ const ApplicationsList = () => {
|
||||
try {
|
||||
const accountResponse = await axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -100,7 +102,7 @@ const ApplicationsList = () => {
|
||||
}
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${account}/Applications`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -173,7 +175,7 @@ const ApplicationsList = () => {
|
||||
// check if any account or Microsoft Teams Tenant uses this application
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -181,7 +183,7 @@ const ApplicationsList = () => {
|
||||
});
|
||||
const msTeamsTenantsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/MicrosoftTeamsTenants',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -226,7 +228,7 @@ const ApplicationsList = () => {
|
||||
// Delete application
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Applications/${applicationToDelete.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
|
||||
@@ -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,98 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useEffect, useContext, useCallback } from 'react';
|
||||
/* eslint-disable no-undef */
|
||||
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';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
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: 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('/');
|
||||
@@ -36,19 +107,23 @@ const CarriersList = () => {
|
||||
// Get all SIP trunks
|
||||
const trunkResults = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/VoipCarriers`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
|
||||
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,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/SipGateways?voip_carrier_sid=${t.voip_carrier_sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -57,7 +132,7 @@ const CarriersList = () => {
|
||||
trunkMap[t.voip_carrier_sid] = gws.data;
|
||||
}
|
||||
|
||||
const trunksWithGateways = trunkResults.data.map(t => {
|
||||
const trunksWithGateways = trunkResultsFiltered.map(t => {
|
||||
const gateways = trunkMap[t.voip_carrier_sid] || [];
|
||||
sortSipGateways(gateways);
|
||||
return {
|
||||
@@ -98,7 +173,7 @@ const CarriersList = () => {
|
||||
console.log(err.response || err);
|
||||
}
|
||||
}
|
||||
}, [currentServiceProvider, history, dispatch]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Delete sip trunk
|
||||
@@ -129,7 +204,7 @@ const CarriersList = () => {
|
||||
for (const sid of carrierToDelete.gatewaysSid) {
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/SipGateways/${sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -139,7 +214,7 @@ const CarriersList = () => {
|
||||
// delete sip trunk
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/VoipCarriers/${carrierToDelete.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -171,8 +246,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"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useEffect, useContext } from 'react';
|
||||
import axios from 'axios';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { NotificationDispatchContext } from '../../../contexts/NotificationContext';
|
||||
import InternalTemplate from '../../templates/InternalTemplate';
|
||||
import TableContent from '../../blocks/TableContent.js';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const MsTeamsTenantsList = () => {
|
||||
let history = useHistory();
|
||||
@@ -28,7 +30,7 @@ const MsTeamsTenantsList = () => {
|
||||
}
|
||||
const msTeamsTenantsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/MicrosoftTeamsTenants',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -36,7 +38,7 @@ const MsTeamsTenantsList = () => {
|
||||
});
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -44,7 +46,7 @@ const MsTeamsTenantsList = () => {
|
||||
});
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Applications',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -114,7 +116,7 @@ const MsTeamsTenantsList = () => {
|
||||
}
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/MicrosoftTeamsTenants/${tenant.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useState, useEffect, useContext, useCallback } from 'react';
|
||||
import axios from 'axios';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
@@ -6,6 +7,7 @@ import InternalTemplate from '../../templates/InternalTemplate';
|
||||
import TableContent from '../../blocks/TableContent.js';
|
||||
import phoneNumberFormat from '../../../helpers/phoneNumberFormat';
|
||||
import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const PhoneNumbersList = () => {
|
||||
let history = useHistory();
|
||||
@@ -33,7 +35,7 @@ const PhoneNumbersList = () => {
|
||||
if(!currentServiceProvider) return [];
|
||||
const phoneNumbersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/PhoneNumbers`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -41,7 +43,7 @@ const PhoneNumbersList = () => {
|
||||
});
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -49,7 +51,7 @@ const PhoneNumbersList = () => {
|
||||
});
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Applications`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -57,7 +59,7 @@ const PhoneNumbersList = () => {
|
||||
});
|
||||
const sipTrunksPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/VoipCarriers`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -150,7 +152,7 @@ const PhoneNumbersList = () => {
|
||||
}
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/PhoneNumbers/${phoneNumber.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -192,7 +194,7 @@ const PhoneNumbersList = () => {
|
||||
for (const sid of phoneNumberSids) {
|
||||
await axios({
|
||||
method: 'put',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/PhoneNumbers/${sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
@@ -15,6 +16,7 @@ import InputGroup from "../../../components/elements/InputGroup";
|
||||
import Select from "../../../components/elements/Select";
|
||||
import handleErrors from "../../../helpers/handleErrors";
|
||||
import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const FilterLabel = styled.span`
|
||||
color: #231f20;
|
||||
@@ -58,6 +60,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: 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: 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);
|
||||
@@ -176,7 +244,7 @@ const RecentCallsIndex = () => {
|
||||
setLoading(true);
|
||||
const result = await axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${account}/RecentCalls`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -201,6 +269,7 @@ const RecentCallsIndex = () => {
|
||||
to: phoneNumberFormat(item.to),
|
||||
status: item.answered ? "answered" : item.termination_reason,
|
||||
duration: timeFormat(item.duration),
|
||||
trace_id: item.trace_id
|
||||
}));
|
||||
|
||||
setRecentCallsData(recentCalls);
|
||||
@@ -233,6 +302,7 @@ const RecentCallsIndex = () => {
|
||||
"remote_host",
|
||||
"sip_status",
|
||||
"trunk",
|
||||
"trace_id"
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -253,6 +323,7 @@ const RecentCallsIndex = () => {
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<PcapButton call_data={data} account_sid={account} jwt_token={jwt} />
|
||||
</ExpandedSection>
|
||||
);
|
||||
};
|
||||
@@ -309,7 +380,7 @@ const RecentCallsIndex = () => {
|
||||
setLoading(true);
|
||||
const accountResponse = await axios({
|
||||
method: "get",
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
|
||||
@@ -1,16 +1,221 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useContext } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { NotificationDispatchContext } from '../../../contexts/NotificationContext';
|
||||
import InternalTemplate from '../../templates/InternalTemplate';
|
||||
import SettingsForm from '../../forms/SettingsForm';
|
||||
import TableContent from '../../blocks/TableContent.js';
|
||||
import axios from 'axios';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
import { ServiceProviderValueContext } from '../../../contexts/ServiceProviderContext';
|
||||
|
||||
|
||||
const Settings = () => {
|
||||
let history = useHistory();
|
||||
const dispatch = useContext(NotificationDispatchContext);
|
||||
let service_provider_sid = useContext(ServiceProviderValueContext);
|
||||
const pageTitle = 'Settings';
|
||||
useEffect(() => {
|
||||
document.title = `${pageTitle} | Jambonz | Open Source CPAAS`;
|
||||
});
|
||||
|
||||
//=============================================================================
|
||||
// Get API keys
|
||||
//=============================================================================
|
||||
const getApiKeys = async () => {
|
||||
try {
|
||||
if (!localStorage.getItem('token')) {
|
||||
history.push('/');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: 'You must log in to view that page.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const results = await axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${service_provider_sid}/ApiKeys`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
const simplifiedApiKeys = results.data.map(a => {
|
||||
const { token } = a;
|
||||
const maskLength = token.length - 4;
|
||||
const maskedPortion = token.substring(0, maskLength).replace(/[a-zA-Z0-9]/g, '*');
|
||||
const revealedPortion = token.substring(maskLength);
|
||||
const maskedToken = `${maskedPortion}${revealedPortion}`;
|
||||
|
||||
const { last_used } = a;
|
||||
let lastUsedString = 'Never used';
|
||||
if (last_used) {
|
||||
const currentDate = new Date();
|
||||
const lastUsedDate = new Date(last_used);
|
||||
currentDate.setHours(0, 0, 0, 0);
|
||||
lastUsedDate.setHours(0, 0, 0, 0);
|
||||
const daysDifference = Math.round((currentDate - lastUsedDate) / 1000 / 60 / 60 / 24);
|
||||
lastUsedString = daysDifference > 1
|
||||
? `${daysDifference} days ago`
|
||||
: daysDifference === 1
|
||||
? 'Yesterday'
|
||||
: daysDifference === 0
|
||||
? 'Today'
|
||||
: 'Never used';
|
||||
}
|
||||
|
||||
return {
|
||||
sid: a.api_key_sid,
|
||||
token: {
|
||||
type: 'masked',
|
||||
masked: maskedToken,
|
||||
revealed: token,
|
||||
},
|
||||
last_used: {
|
||||
type: 'normal',
|
||||
content: lastUsedString,
|
||||
},
|
||||
};
|
||||
});
|
||||
return (simplifiedApiKeys);
|
||||
} catch (err) {
|
||||
if (err.response && err.response.status === 401) {
|
||||
localStorage.removeItem('token');
|
||||
sessionStorage.clear();
|
||||
history.push('/');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: 'Your session has expired. Please log in and try again.',
|
||||
});
|
||||
} else {
|
||||
console.log(err.response || err);
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: (err.response && err.response.data && err.response.data.msg) || 'Unable to get API key data',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Create API key
|
||||
//=============================================================================
|
||||
const createApiKey = async () => {
|
||||
try {
|
||||
if (!localStorage.getItem('token')) {
|
||||
history.push('/');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: 'You must log in to view that page.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const result = await axios({
|
||||
method: 'post',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/ApiKeys',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
data: {
|
||||
"service_provider_sid": service_provider_sid,
|
||||
}
|
||||
});
|
||||
return result.data.token;
|
||||
} catch (err) {
|
||||
if (err.response && err.response.status === 401) {
|
||||
localStorage.removeItem('token');
|
||||
sessionStorage.clear();
|
||||
history.push('/');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: 'Your session has expired. Please log in and try again.',
|
||||
});
|
||||
} else {
|
||||
console.log(err.response || err);
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: (err.response && err.response.data && err.response.data.msg) || 'Unable to create API key',
|
||||
});
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Delete API key
|
||||
//=============================================================================
|
||||
const formatApiKeyToDelete = apiKey => {
|
||||
const items = [
|
||||
{ name: 'API Key:', content: apiKey.token.masked || '[none]' },
|
||||
{ name: 'Last Used:', content: apiKey.last_used.content || 'Never used' },
|
||||
];
|
||||
return items;
|
||||
};
|
||||
const deleteApiKey = async apiKeyToDelete => {
|
||||
try {
|
||||
if (!localStorage.getItem('token')) {
|
||||
history.push('/');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: 'You must log in to view that page.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Apikeys/${apiKeyToDelete.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
return 'success';
|
||||
} catch (err) {
|
||||
if (err.response && err.response.status === 401) {
|
||||
localStorage.removeItem('token');
|
||||
sessionStorage.clear();
|
||||
history.push('/');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'error',
|
||||
message: 'Your session has expired. Please log in and try again.',
|
||||
});
|
||||
} else {
|
||||
console.log(err.response || err);
|
||||
return ((err.response && err.response.data && err.response.data.msg) || 'Unable to delete API key');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InternalTemplate
|
||||
type="form"
|
||||
title={pageTitle}
|
||||
breadcrumbs={[
|
||||
{ name: 'Service Provider', url: '/internal/settings' },
|
||||
{ name: pageTitle },
|
||||
]}
|
||||
additionalTable={service_provider_sid && (
|
||||
<TableContent
|
||||
name="API key"
|
||||
getContent={getApiKeys}
|
||||
columns={[
|
||||
{ header: 'Service Provider API Keys', key: 'token', width: '27rem', fontWeight: 'normal' },
|
||||
{ header: 'Last Used', key: 'last_used', width: '10rem' },
|
||||
]}
|
||||
addContent={createApiKey}
|
||||
formatContentToDelete={formatApiKeyToDelete}
|
||||
deleteContent={deleteApiKey}
|
||||
rowsHaveDeleteButtons
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<SettingsForm />
|
||||
</InternalTemplate>
|
||||
|
||||
@@ -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,94 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useContext, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
/* eslint-disable no-undef */
|
||||
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';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
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: 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 +101,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`,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: speechApiUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
@@ -44,7 +118,7 @@ const SpeechServicesList = () => {
|
||||
if (s.use_for_stt || s.use_for_tts) {
|
||||
return axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/SpeechCredentials/${s.speech_credential_sid}/test`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -137,7 +211,7 @@ const SpeechServicesList = () => {
|
||||
} catch (err) {
|
||||
handleErrors({ err, history, dispatch, fallbackMessage: 'Unable to get speech services' });
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Delete speech service
|
||||
@@ -149,8 +223,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('/');
|
||||
@@ -165,7 +238,7 @@ const SpeechServicesList = () => {
|
||||
// Delete speech service
|
||||
await axios({
|
||||
method: 'delete',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders/${currentServiceProvider}/SpeechCredentials/${speechServiceToDelete.sid}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
@@ -187,7 +260,7 @@ const SpeechServicesList = () => {
|
||||
return ((err.response && err.response.data && err.response.data.msg) || 'Unable to delete speech service');
|
||||
}
|
||||
}
|
||||
}, [currentServiceProvider]);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Render
|
||||
@@ -196,10 +269,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"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
@@ -8,6 +9,7 @@ import Button from '../../elements/Button';
|
||||
import Input from '../../elements/Input';
|
||||
import FormError from '../../blocks/FormError';
|
||||
import Loader from '../../blocks/Loader';
|
||||
import { APP_API_BASE_URL } from "../../../constants";
|
||||
|
||||
const CreatePassword = () => {
|
||||
let history = useHistory();
|
||||
@@ -63,7 +65,7 @@ const CreatePassword = () => {
|
||||
//-----------------------------------------------------------------------------
|
||||
const serviceProvidersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/serviceProviders',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -72,7 +74,7 @@ const CreatePassword = () => {
|
||||
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -81,7 +83,7 @@ const CreatePassword = () => {
|
||||
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/applications',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -90,7 +92,7 @@ const CreatePassword = () => {
|
||||
|
||||
const voipCarriersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/voipCarriers',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -235,7 +237,7 @@ const CreatePassword = () => {
|
||||
|
||||
const response = await axios({
|
||||
method: 'put',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Users/${user_sid}`,
|
||||
data: {
|
||||
old_password,
|
||||
|
||||
16
src/constants.js
Normal file
16
src/constants.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { REACT_APP_API_BASE_URL } = process.env;
|
||||
export const APP_API_BASE_URL = (window.JAMBONZ) ? window.JAMBONZ.APP_API_BASE_URL : REACT_APP_API_BASE_URL;
|
||||
export const LIMITS = [
|
||||
// {
|
||||
// label: "Max registered devices (0=unlimited)",
|
||||
// category: "device",
|
||||
// },
|
||||
// {
|
||||
// label: "Max api calls per minute (0=unlimited)",
|
||||
// category: "api_rate",
|
||||
// },
|
||||
{
|
||||
label: "Max concurrent calls (0=unlimited)",
|
||||
category: "voice_call_session",
|
||||
},
|
||||
];
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, createContext, useContext } from 'react';
|
||||
import axios from 'axios';
|
||||
import { NotificationDispatchContext } from './NotificationContext';
|
||||
import { APP_API_BASE_URL } from "../constants";
|
||||
|
||||
export const ShowMsTeamsStateContext = createContext();
|
||||
export const ShowMsTeamsDispatchContext = createContext();
|
||||
@@ -13,7 +14,7 @@ export function ShowMsTeamsProvider(props) {
|
||||
try {
|
||||
const serviceProvidersResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/ServiceProviders',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
|
||||
92
src/data/AwsRegions.js
Normal file
92
src/data/AwsRegions.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const regions = [
|
||||
{
|
||||
name: 'US East (N. Virginia)',
|
||||
value: 'us-east-1'
|
||||
},
|
||||
{
|
||||
name: 'US East (Ohio)',
|
||||
value: 'us-east-2'
|
||||
},
|
||||
{
|
||||
name: 'US West (N. California)',
|
||||
value: 'us-west-1'
|
||||
},
|
||||
{
|
||||
name: 'US West (Oregon)',
|
||||
value: 'us-west-2'
|
||||
},
|
||||
{
|
||||
name: 'Africa (Cape Town)',
|
||||
value: 'af-south-1'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Hong Kong)',
|
||||
value: 'ap-east-1'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Jakarta)',
|
||||
value: 'ap-southeast-3'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Mumbai)',
|
||||
value: 'ap-south-1'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Osaka)',
|
||||
value: 'ap-northeast-3'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Seoul)',
|
||||
value: 'ap-northeast-2'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Singapore)',
|
||||
value: 'ap-southeast-1'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Sydney)',
|
||||
value: 'ap-southeast-2'
|
||||
},
|
||||
{
|
||||
name: 'Asia Pacific (Tokyo)',
|
||||
value: 'ap-northeast-1'
|
||||
},
|
||||
{
|
||||
name: 'Canada (Central)',
|
||||
value: 'ca-central-1'
|
||||
},
|
||||
{
|
||||
name: 'Europe (Frankfurt)',
|
||||
value: 'eu-central-1'
|
||||
},
|
||||
{
|
||||
name: 'Europe (Ireland)',
|
||||
value: 'eu-west-1'
|
||||
},
|
||||
{
|
||||
name: 'Europe (London)',
|
||||
value: 'eu-west-2'
|
||||
},
|
||||
{
|
||||
name: 'Europe (Milan)',
|
||||
value: 'eu-south-1'
|
||||
},
|
||||
{
|
||||
name: 'Europe (Paris)',
|
||||
value: 'eu-west-3'
|
||||
},
|
||||
{
|
||||
name: 'Europe (Stockholm)',
|
||||
value: 'eu-north-1'
|
||||
},
|
||||
{
|
||||
name: 'Middle East (Bahrain)',
|
||||
value: 'me-south-1'
|
||||
},
|
||||
{
|
||||
name: 'South America (São Paulo)',
|
||||
value: 'sa-east-1'
|
||||
}
|
||||
];
|
||||
|
||||
export default regions;
|
||||
124
src/data/MicrosoftAzureRegions.js
Normal file
124
src/data/MicrosoftAzureRegions.js
Normal file
@@ -0,0 +1,124 @@
|
||||
const regions = [
|
||||
{
|
||||
name: 'Australia - East (australiaeast)',
|
||||
value: 'australiaeast'
|
||||
},
|
||||
{
|
||||
name: 'Brazil - South (brazilsouth)',
|
||||
value: 'brazilsouth'
|
||||
},
|
||||
{
|
||||
name: 'Canada - Central (canadacentral)',
|
||||
value: 'canadacentral'
|
||||
},
|
||||
{
|
||||
name: 'East Asia (eastasia)',
|
||||
value: 'eastasia'
|
||||
},
|
||||
{
|
||||
name: 'Europe - North (northeurope)',
|
||||
value: 'northeurope'
|
||||
},
|
||||
{
|
||||
name: 'Europe - West (westeurope)',
|
||||
value: 'westeurope'
|
||||
},
|
||||
{
|
||||
name: 'France - Central (francecentral)',
|
||||
value: 'francecentral'
|
||||
},
|
||||
{
|
||||
name: 'Germany - West Central (germanywestcentral)',
|
||||
value: 'germanywestcentral'
|
||||
},
|
||||
{
|
||||
name: 'India - Central (centralindia)',
|
||||
value: 'centralindia'
|
||||
},
|
||||
{
|
||||
name: 'Japan - East (japaneast)',
|
||||
value: 'japaneast'
|
||||
},
|
||||
{
|
||||
name: 'Japan - West (japanwest)',
|
||||
value: 'japanwest'
|
||||
},
|
||||
{
|
||||
name: 'Korea - Central (koreacentral)',
|
||||
value: 'koreacentral'
|
||||
},
|
||||
{
|
||||
name: 'Norway - East (norwayeast)',
|
||||
value: 'norwayeast'
|
||||
},
|
||||
{
|
||||
name: 'South Africa - North (southafricanorth)',
|
||||
value: 'southafricanorth'
|
||||
},
|
||||
{
|
||||
name: 'Southeast Asia (southeastasia)',
|
||||
value: 'southeastasia'
|
||||
},
|
||||
{
|
||||
name: 'Switzerland - North (switzerlandnorth)',
|
||||
value: 'switzerlandnorth'
|
||||
},
|
||||
{
|
||||
name: 'Switzerland - West (switzerlandwest)',
|
||||
value: 'switzerlandwest'
|
||||
},
|
||||
{
|
||||
name: 'UAE - North (uaenorth)',
|
||||
value: 'uaenorth'
|
||||
},
|
||||
{
|
||||
name: 'UK - South (uksouth)',
|
||||
value: 'uksouth'
|
||||
},
|
||||
{
|
||||
name: 'US - Central (centralus)',
|
||||
value: 'centralus'
|
||||
},
|
||||
{
|
||||
name: 'US - East (eastus)',
|
||||
value: 'eastus'
|
||||
},
|
||||
{
|
||||
name: 'US - East 2 (eastus2)',
|
||||
value: 'eastus2'
|
||||
},
|
||||
{
|
||||
name: 'US - Gov Arizona (usgovarizona)',
|
||||
value: 'usgovarizona'
|
||||
},
|
||||
{
|
||||
name: 'US - Gov Virginia (usgovvirginia)',
|
||||
value: 'usgovvirginia'
|
||||
},
|
||||
{
|
||||
name: 'US - North Central (northcentralus)',
|
||||
value: 'northcentralus'
|
||||
},
|
||||
{
|
||||
name: 'US - South Central (southcentralus)',
|
||||
value: 'southcentralus'
|
||||
},
|
||||
{
|
||||
name: 'US - West Central (westcentralus)',
|
||||
value: 'westcentralus'
|
||||
},
|
||||
{
|
||||
name: 'US - West (westus)',
|
||||
value: 'westus'
|
||||
},
|
||||
{
|
||||
name: 'US - West 2 (westus2)',
|
||||
value: 'westus2'
|
||||
},
|
||||
{
|
||||
name: 'US - West 3 (westus3)',
|
||||
value: 'westus3'
|
||||
}
|
||||
];
|
||||
|
||||
export default regions;
|
||||
@@ -1,4 +1,4 @@
|
||||
export default [
|
||||
const languages = [
|
||||
{ name: 'Australian English', 'code': 'en-AU' },
|
||||
{ name: 'British English', 'code': 'en-GB' },
|
||||
{ name: 'US English', 'code': 'en-US' },
|
||||
@@ -8,3 +8,5 @@ export default [
|
||||
{ name: 'Italian', 'code': 'it-IT' },
|
||||
{ name: 'US Spanish', 'code': 'es-US' },
|
||||
];
|
||||
|
||||
export default languages;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default [
|
||||
const languages = [
|
||||
{ name: 'Afrikaans (South Africa)', code: 'af-ZA', },
|
||||
{ name: 'Albanian (Albania)', code: 'sq-AL', },
|
||||
{ name: 'Amharic (Ethiopia)', code: 'am-ET', },
|
||||
@@ -128,3 +128,5 @@ export default [
|
||||
{ name: 'Vietnamese (Vietnam)', code: 'vi-VN', },
|
||||
{ name: 'Zulu (South Africa)', code: 'zu-ZA', },
|
||||
];
|
||||
|
||||
export default languages;
|
||||
|
||||
492
src/data/SpeechRecognizerLanguageMicrosoft.js
Normal file
492
src/data/SpeechRecognizerLanguageMicrosoft.js
Normal file
@@ -0,0 +1,492 @@
|
||||
const languages = [
|
||||
{
|
||||
name: 'Afrikaans (South Africa)',
|
||||
code: 'af-ZA'
|
||||
},
|
||||
{
|
||||
name: 'Amharic (Ethiopia)',
|
||||
code: 'am-ET'
|
||||
},
|
||||
{
|
||||
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: 'Bengali (India)',
|
||||
code: 'bn-IN'
|
||||
},
|
||||
{
|
||||
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: 'Dutch (Belgium)',
|
||||
code: 'nl-BE'
|
||||
},
|
||||
{
|
||||
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 (Belgium)',
|
||||
code: 'fr-BE'
|
||||
},
|
||||
{
|
||||
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: 'Icelandic (Iceland)',
|
||||
code: 'is-IS'
|
||||
},
|
||||
{
|
||||
name: 'Irish (Ireland)',
|
||||
code: 'ga-IE'
|
||||
},
|
||||
{
|
||||
name: 'Italian (Italy)',
|
||||
code: 'it-IT'
|
||||
},
|
||||
{
|
||||
name: 'Japanese (Japan)',
|
||||
code: 'ja-JP'
|
||||
},
|
||||
{
|
||||
name: 'Javanese (Indonesia)',
|
||||
code: 'jv-ID'
|
||||
},
|
||||
{
|
||||
name: 'Kannada (India)',
|
||||
code: 'kn-IN'
|
||||
},
|
||||
{
|
||||
name: 'Khmer (Cambodia)',
|
||||
code: 'km-KH'
|
||||
},
|
||||
{
|
||||
name: 'Korean (Korea)',
|
||||
code: 'ko-KR'
|
||||
},
|
||||
{
|
||||
name: 'Latvian (Latvia)',
|
||||
code: 'lv-LV'
|
||||
},
|
||||
{
|
||||
name: 'Lao (Laos)',
|
||||
code: 'lo-LA'
|
||||
},
|
||||
{
|
||||
name: 'Lithuanian (Lithuania)',
|
||||
code: 'lt-LT'
|
||||
},
|
||||
{
|
||||
name: 'Malay (Malaysia)',
|
||||
code: 'ms-MY'
|
||||
},
|
||||
{
|
||||
name: 'Macedonian (North Macedonia)',
|
||||
code: 'mk-MK'
|
||||
},
|
||||
{
|
||||
name: 'Maltese (Malta)',
|
||||
code: 'mt-MT'
|
||||
},
|
||||
{
|
||||
name: 'Marathi (India)',
|
||||
code: 'mr-IN'
|
||||
},
|
||||
{
|
||||
name: 'Burmese (Myanmar)',
|
||||
code: 'my-MM'
|
||||
},
|
||||
{
|
||||
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: 'Swahili (Tanzania)',
|
||||
code: 'sw-TZ'
|
||||
},
|
||||
{
|
||||
name: 'Sinhala (Sri Lanka)',
|
||||
code: 'si-LK'
|
||||
},
|
||||
{
|
||||
name: 'Swedish (Sweden)',
|
||||
code: 'sv-SE'
|
||||
},
|
||||
{
|
||||
name: 'Serbian (Serbia)',
|
||||
code: 'sr-RS'
|
||||
},
|
||||
{
|
||||
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: 'Ukrainian (Ukraine)',
|
||||
code: 'uk-UA'
|
||||
},
|
||||
{
|
||||
name: 'Uzbek (Uzbekistan)',
|
||||
code: 'uz-UZ'
|
||||
},
|
||||
{
|
||||
name: 'Zulu (South Africa)',
|
||||
code: 'zu-ZA'
|
||||
},
|
||||
{
|
||||
name: 'Vietnamese (Vietnam)',
|
||||
code: 'vi-VN'
|
||||
},
|
||||
];
|
||||
|
||||
export default languages;
|
||||
@@ -1,4 +1,4 @@
|
||||
export default [
|
||||
const languages = [
|
||||
{
|
||||
code: 'arb',
|
||||
name: 'Arabic',
|
||||
@@ -235,3 +235,5 @@ export default [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default languages;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default [
|
||||
const languages = [
|
||||
{
|
||||
code: 'ar-XA',
|
||||
name: 'Arabic',
|
||||
@@ -408,3 +408,6 @@ export default [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export default languages;
|
||||
|
||||
4451
src/data/SpeechSynthesisLanguageMicrosoft.js
Normal file
4451
src/data/SpeechSynthesisLanguageMicrosoft.js
Normal file
File diff suppressed because it is too large
Load Diff
41
src/data/SpeechSynthesisLanguageWellSaid.js
Normal file
41
src/data/SpeechSynthesisLanguageWellSaid.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const languages = [
|
||||
{
|
||||
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)' }
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
export default languages;
|
||||
Reference in New Issue
Block a user