mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-01-25 02:08:19 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
1
.env
1
.env
@@ -1 +1,2 @@
|
||||
REACT_APP_API_BASE_URL=http://127.0.0.1:3002/v1
|
||||
GENERATE_SOURCEMAP=false
|
||||
@@ -1,4 +1,4 @@
|
||||
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/
|
||||
@@ -7,7 +7,7 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
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.
|
||||
|
||||
2418
package-lock.json
generated
2418
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "jambonz-webapp",
|
||||
"version": "v0.7.4",
|
||||
"version": "v0.7.7",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.1",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
"@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",
|
||||
|
||||
@@ -21,6 +21,7 @@ const StyledLink = styled(FilteredLink)`
|
||||
border-radius: 50%;
|
||||
text-decoration: none;
|
||||
color: #565656;
|
||||
z-index: 1;
|
||||
|
||||
& > span:first-child {
|
||||
display: flex;
|
||||
|
||||
@@ -22,7 +22,7 @@ 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 } from "../../constants";
|
||||
import { APP_API_BASE_URL, LIMITS } from "../../constants";
|
||||
|
||||
const StyledInputGroup = styled(InputGroup)`
|
||||
position: relative;
|
||||
@@ -73,6 +73,7 @@ const AccountForm = props => {
|
||||
const [ name, setName ] = useState('');
|
||||
const [ sipRealm, setSipRealm ] = useState('');
|
||||
const [ deviceCallingApplication, setDeviceCallingApplication ] = useState('');
|
||||
const [ siprecCallingApplication, setSiprecCallingApplication ] = useState('');
|
||||
const [ regWebhook, setRegWebhook ] = useState('');
|
||||
const [ regMethod, setRegMethod ] = useState('POST');
|
||||
const [ regUser, setRegUser ] = useState('');
|
||||
@@ -93,6 +94,7 @@ const AccountForm = props => {
|
||||
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);
|
||||
@@ -318,6 +320,16 @@ const AccountForm = props => {
|
||||
},
|
||||
});
|
||||
promiseList.push(applicationsPromise);
|
||||
|
||||
const limitsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/Accounts/${props.account_sid}/Limits`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
promiseList.push(limitsPromise);
|
||||
}
|
||||
|
||||
const sbcsPromise = await axios({
|
||||
@@ -336,13 +348,16 @@ const AccountForm = props => {
|
||||
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);
|
||||
}
|
||||
setSbcs(promiseAllValues[2].data);
|
||||
setSbcs(promiseAllValues[3]?.data);
|
||||
|
||||
if (props.type === 'setup' && accountsData.length > 1) {
|
||||
history.push('/internal/accounts');
|
||||
@@ -376,6 +391,7 @@ 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 ) || '');
|
||||
setRegMethod((acc.registration_hook && acc.registration_hook.method ) || 'post');
|
||||
setRegUser((acc.registration_hook && acc.registration_hook.username) || '');
|
||||
@@ -549,13 +565,14 @@ const AccountForm = props => {
|
||||
|
||||
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: APP_API_BASE_URL,
|
||||
url,
|
||||
@@ -564,6 +581,23 @@ const AccountForm = props => {
|
||||
},
|
||||
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;
|
||||
@@ -695,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>
|
||||
@@ -861,6 +923,31 @@ const AccountForm = props => {
|
||||
</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>
|
||||
@@ -991,7 +1078,6 @@ const AccountForm = props => {
|
||||
)}
|
||||
</>
|
||||
) : null }
|
||||
|
||||
{errorMessage && (
|
||||
<FormError grid message={errorMessage} />
|
||||
)}
|
||||
|
||||
@@ -221,6 +221,8 @@ const CarrierForm = (props) => {
|
||||
const refUsername = useRef(null);
|
||||
const refPassword = useRef(null);
|
||||
const refRealm = useRef(null);
|
||||
const refFromUser = useRef(null);
|
||||
const refFromDomain = useRef(null);
|
||||
const refIp = useRef([]);
|
||||
const refPort = useRef([]);
|
||||
const refInbound = useRef([]);
|
||||
@@ -250,6 +252,8 @@ const CarrierForm = (props) => {
|
||||
const [ passwordInvalid, setPasswordInvalid ] = useState(false);
|
||||
const [ realm, setRealm ] = useState('');
|
||||
const [ realmInvalid, setRealmInvalid ] = useState(false);
|
||||
const [ fromUser, setFromUser ] = useState('');
|
||||
const [ fromDomain, setFromDomain ] = useState('');
|
||||
const [ sipGateways, setSipGateways ] = useState([
|
||||
{
|
||||
sip_gateway_sid: '',
|
||||
@@ -276,6 +280,7 @@ const CarrierForm = (props) => {
|
||||
const [suportSIP, setSupportSIP] = useState(false);
|
||||
const [diversion, setDiversion] = useState("");
|
||||
const [carrierActive, setCarrierActive] = useState(true);
|
||||
const [registerHasPublicIp, setRegisterHasPublicIp] = useState(false);
|
||||
const [predefinedCarriers, setPredefinedCarriers] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState('1');
|
||||
const [sbcs, setSbcs] = useState([]);
|
||||
@@ -438,6 +443,8 @@ const CarrierForm = (props) => {
|
||||
setUsername(carrier.register_username || '');
|
||||
setPassword(carrier.register_password || '');
|
||||
setRealm(carrier.register_sip_realm || '');
|
||||
setFromUser(carrier.register_from_user || '');
|
||||
setFromDomain(carrier.register_from_domain || '');
|
||||
setSipGateways(currentSipGateways.map(s => ({
|
||||
sip_gateway_sid: s.sip_gateway_sid,
|
||||
ip: s.ipv4,
|
||||
@@ -473,6 +480,7 @@ const CarrierForm = (props) => {
|
||||
setSupportSIP(carrier.diversion ? true : false);
|
||||
setDiversion(carrier.diversion || '');
|
||||
setCarrierActive(carrier.is_active === 1);
|
||||
setRegisterHasPublicIp(carrier.register_public_ip_in_contact === 1);
|
||||
} else {
|
||||
const result = await axios({
|
||||
method: 'get',
|
||||
@@ -647,7 +655,7 @@ const CarrierForm = (props) => {
|
||||
let errorMessages = [];
|
||||
let focusHasBeenSet = false;
|
||||
const regIp = /^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])$/;
|
||||
const regFqdn = /^([a-zA-Z][^.]*)(\.[^.]+){2,}$/;
|
||||
const regFqdn = /^([a-zA-Z0-9][^.]*)(\.[^.]+){2,}$/;
|
||||
const regFqdnTopLevel = /^([a-zA-Z][^.]*)(\.[^.]+)$/;
|
||||
const regPort = /^[0-9]+$/;
|
||||
|
||||
@@ -1028,6 +1036,9 @@ const CarrierForm = (props) => {
|
||||
register_username: username ? username.trim() : null,
|
||||
register_password: password ? password : null,
|
||||
register_sip_realm: register ? realm.trim() : null,
|
||||
register_from_user: register && fromUser ? fromUser.trim() : null,
|
||||
register_from_domain: register && fromDomain ? fromDomain.trim() : null,
|
||||
register_public_ip_in_contact: register && registerHasPublicIp ? 1 : 0,
|
||||
tech_prefix: techPrefix ? techPrefix.trim() : null,
|
||||
diversion: diversion ? diversion.trim() : null,
|
||||
is_active: carrierActive ? 1 : 0,
|
||||
@@ -1484,6 +1495,33 @@ const CarrierForm = (props) => {
|
||||
invalid={realmInvalid}
|
||||
ref={refRealm}
|
||||
/>
|
||||
<Label htmlFor="fromUser">SIP From User</Label>
|
||||
<Input
|
||||
name="fromUser"
|
||||
id="fromUser"
|
||||
value={fromUser}
|
||||
onChange={e => setFromUser(e.target.value)}
|
||||
placeholder="Optional: specify user part of SIP From header"
|
||||
ref={refFromUser}
|
||||
/>
|
||||
<Label htmlFor="fromDomain">SIP From Domain</Label>
|
||||
<Input
|
||||
name="fromDomain"
|
||||
id="fromDomain"
|
||||
value={fromDomain}
|
||||
onChange={e => setFromDomain(e.target.value)}
|
||||
placeholder="Optional: specify host part of SIP From header"
|
||||
ref={refFromDomain}
|
||||
/>
|
||||
<Label htmlFor="regPublicIp">Use Public IP in Contact</Label>
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
name="regPublicIp"
|
||||
id="regPublicIp"
|
||||
label=""
|
||||
checked={registerHasPublicIp}
|
||||
onChange={e => setRegisterHasPublicIp(e.target.checked)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
null
|
||||
|
||||
@@ -63,7 +63,7 @@ const PhoneNumberForm = props => {
|
||||
const sipTrunksPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/VoipCarriers',
|
||||
url: `ServiceProviders/${currentServiceProvider}/VoipCarriers`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
@@ -71,7 +71,7 @@ const PhoneNumberForm = props => {
|
||||
const accountsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Accounts',
|
||||
url: `ServiceProviders/${currentServiceProvider}/Accounts`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
@@ -79,7 +79,7 @@ const PhoneNumberForm = props => {
|
||||
const applicationsPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/Applications',
|
||||
url: `ServiceProviders/${currentServiceProvider}/Applications`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
@@ -87,7 +87,7 @@ const PhoneNumberForm = props => {
|
||||
const phoneNumbersPromise = axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: '/PhoneNumbers',
|
||||
url: `ServiceProviders/${currentServiceProvider}/PhoneNumbers`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ 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 } from "../../constants";
|
||||
import { APP_API_BASE_URL, LIMITS } from "../../constants";
|
||||
|
||||
const Td = styled.td`
|
||||
padding: 0.5rem 0;
|
||||
@@ -43,23 +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 [ serviceProviderSid, setServiceProviderSid ] = useState('');
|
||||
const [ serviceProviders, setServiceProviders ] = useState([]);
|
||||
const [ confirmDelete, setConfirmDelete ] = useState(false);
|
||||
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 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 () => {
|
||||
@@ -74,14 +87,7 @@ const SettingsForm = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceProvidersResponse = await axios({
|
||||
method: 'get',
|
||||
baseURL: APP_API_BASE_URL,
|
||||
url: `/ServiceProviders`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
const serviceProvidersResponse = await callApi(`/ServiceProviders`, 'get');
|
||||
|
||||
const sps = serviceProvidersResponse.data;
|
||||
const sp = sps.find(s => s.service_provider_sid === currentServiceProvider);
|
||||
@@ -91,6 +97,12 @@ const SettingsForm = () => {
|
||||
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 {
|
||||
@@ -127,19 +139,19 @@ const SettingsForm = () => {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
setConfirmDelete(false);
|
||||
setErrorMessage('');
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'success',
|
||||
message: 'Service Provider Deleted'
|
||||
.then(() => {
|
||||
setConfirmDelete(false);
|
||||
setErrorMessage('');
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'success',
|
||||
message: 'Service Provider Deleted'
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorMessage(error.response.data.msg);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorMessage(error.response.data.msg);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
@@ -208,15 +220,12 @@ const SettingsForm = () => {
|
||||
name: serviceProviderName.trim(),
|
||||
};
|
||||
|
||||
await axios({
|
||||
method: 'put',
|
||||
baseURL: 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();
|
||||
|
||||
@@ -255,115 +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}
|
||||
/>
|
||||
? <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}
|
||||
/>
|
||||
|
||||
<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") || ""}
|
||||
/>
|
||||
<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}
|
||||
/>
|
||||
|
||||
{errorMessage && !confirmDelete && (
|
||||
<FormError grid message={errorMessage} />
|
||||
)}
|
||||
<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") || ""}
|
||||
/>
|
||||
|
||||
<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 && (
|
||||
{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={() => setConfirmDelete(true)}
|
||||
onClick={() => {
|
||||
history.push('/internal/accounts');
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
level: 'info',
|
||||
message: 'Changes canceled',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button grid>Save</Button>
|
||||
</InputGroup>
|
||||
</Form>
|
||||
{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"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
{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"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ 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';
|
||||
@@ -21,6 +20,7 @@ import Button from '../elements/Button';
|
||||
import Loader from '../blocks/Loader';
|
||||
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
|
||||
|
||||
import AwsRegions from '../../data/AwsRegions';
|
||||
import MicrosoftAzureRegions from '../../data/MicrosoftAzureRegions';
|
||||
import { APP_API_BASE_URL } from "../../constants";
|
||||
|
||||
@@ -78,39 +78,54 @@ const SpeechServicesAddEdit = (props) => {
|
||||
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 [ accounts, setAccounts ] = useState([]);
|
||||
const [ accountSid, setAccountSid ] = useState('');
|
||||
const [ apiKey, setApiKey ] = useState('');
|
||||
const [ region, setRegion ] = useState('');
|
||||
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 [ 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 [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 () => {
|
||||
@@ -146,18 +161,23 @@ const SpeechServicesAddEdit = (props) => {
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
setAccountSid( speechCredential.data.account_sid || '');
|
||||
setVendor( speechCredential.data.vendor || undefined);
|
||||
setServiceKey( serviceKeyJson || '');
|
||||
setDisplayedServiceKey( displayedServiceKeyJson || '');
|
||||
setAccessKeyId( speechCredential.data.access_key_id || '');
|
||||
setSecretAccessKey( speechCredential.data.secret_access_key || '');
|
||||
setApiKey( speechCredential.data.api_key || '');
|
||||
setRegion( speechCredential.data.region || '');
|
||||
setUseForTts( speechCredential.data.use_for_tts || false);
|
||||
setUseForStt( speechCredential.data.use_for_stt || false);
|
||||
setOriginalTtsValue( speechCredential.data.use_for_tts || false);
|
||||
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) {
|
||||
@@ -228,6 +248,8 @@ const SpeechServicesAddEdit = (props) => {
|
||||
setInvalidUseForTts(false);
|
||||
setInvalidUseForStt(false);
|
||||
setInvalidApiKey(false);
|
||||
setInvalidUseCustomTts(false);
|
||||
setInvalidUseCustomStt(false);
|
||||
let errorMessages = [];
|
||||
let focusHasBeenSet = false;
|
||||
|
||||
@@ -265,6 +287,15 @@ 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);
|
||||
@@ -292,6 +323,24 @@ const SpeechServicesAddEdit = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -320,15 +369,29 @@ const SpeechServicesAddEdit = (props) => {
|
||||
},
|
||||
data: {
|
||||
vendor,
|
||||
service_key: vendor === 'google' ? JSON.stringify(serviceKey) : null,
|
||||
access_key_id: vendor === 'aws' ? accessKeyId : null,
|
||||
secret_access_key: vendor === 'aws' ? secretAccessKey : null,
|
||||
api_key: ['microsoft', 'wellsaid'].includes(vendor) ? apiKey : null,
|
||||
region: vendor === 'microsoft' ? region : null,
|
||||
use_for_tts: useForTts,
|
||||
use_for_stt: useForStt,
|
||||
service_provider_sid: accountSid ? null : currentServiceProvider,
|
||||
account_sid: accountSid || null,
|
||||
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,
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -463,59 +526,29 @@ 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'}
|
||||
/>
|
||||
|
||||
<Radio
|
||||
name="vendor"
|
||||
id="aws"
|
||||
label="Amazon Web Services"
|
||||
checked={vendor === 'aws'}
|
||||
onChange={() => setVendor('aws')}
|
||||
invalid={invalidVendorAws}
|
||||
ref={refVendorAws}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
|
||||
<Radio
|
||||
name="vendor"
|
||||
id="microsoft"
|
||||
label="Microsoft"
|
||||
checked={vendor === 'microsoft'}
|
||||
onChange={() => setVendor('microsoft')}
|
||||
invalid={invalidVendorMs}
|
||||
ref={refVendorMs}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
|
||||
<Radio
|
||||
name="vendor"
|
||||
id="wellsaid"
|
||||
label="WellSaid"
|
||||
checked={vendor === 'wellsaid'}
|
||||
onChange={() => setVendor('wellsaid')}
|
||||
invalid={invalidVendorWellSaid}
|
||||
ref={refVendorWellSaid}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Label htmlFor="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>
|
||||
|
||||
<Label htmlFor="account">Used by</Label>
|
||||
<Select
|
||||
@@ -537,6 +570,37 @@ const SpeechServicesAddEdit = (props) => {
|
||||
))}
|
||||
</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' ? (
|
||||
<>
|
||||
<Label htmlFor="serviceKey">Service Key</Label>
|
||||
@@ -582,6 +646,28 @@ const SpeechServicesAddEdit = (props) => {
|
||||
ref={refSecretAccessKey}
|
||||
disabled={type === 'edit'}
|
||||
/>
|
||||
|
||||
<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' ? (
|
||||
<>
|
||||
@@ -618,8 +704,74 @@ const SpeechServicesAddEdit = (props) => {
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
{/*
|
||||
<div />
|
||||
<Checkbox
|
||||
noLeftMargin
|
||||
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</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' ? (
|
||||
) : vendor === 'wellsaid' ? (
|
||||
<>
|
||||
<Label htmlFor="apiKey">API Key</Label>
|
||||
<Input
|
||||
@@ -637,37 +789,6 @@ const SpeechServicesAddEdit = (props) => {
|
||||
null
|
||||
)}
|
||||
|
||||
{['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
|
||||
)}
|
||||
|
||||
{errorMessage && (
|
||||
<FormError grid message={errorMessage} />
|
||||
)}
|
||||
@@ -682,14 +803,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'
|
||||
@@ -701,4 +822,4 @@ const SpeechServicesAddEdit = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeechServicesAddEdit;
|
||||
export default SpeechServicesAddEdit;
|
||||
@@ -209,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}
|
||||
|
||||
@@ -104,7 +104,6 @@ const CarriersList = () => {
|
||||
return;
|
||||
}
|
||||
if(!currentServiceProvider) return [];
|
||||
if (!accountList.length) return [];
|
||||
// Get all SIP trunks
|
||||
const trunkResults = await axios({
|
||||
method: 'get',
|
||||
|
||||
@@ -269,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);
|
||||
@@ -301,6 +302,7 @@ const RecentCallsIndex = () => {
|
||||
"remote_host",
|
||||
"sip_status",
|
||||
"trunk",
|
||||
"trace_id"
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,2 +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",
|
||||
},
|
||||
];
|
||||
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;
|
||||
@@ -1,94 +1,124 @@
|
||||
export default [
|
||||
{
|
||||
name: 'Asia (East)',
|
||||
value: 'eastasia'
|
||||
const regions = [
|
||||
{
|
||||
name: 'Australia - East (australiaeast)',
|
||||
value: 'australiaeast'
|
||||
},
|
||||
{
|
||||
name: 'Asia (Southeast)',
|
||||
value: 'southeastasia'
|
||||
{
|
||||
name: 'Brazil - South (brazilsouth)',
|
||||
value: 'brazilsouth'
|
||||
},
|
||||
{
|
||||
name: 'Australia (East)',
|
||||
value: 'australiaeast'
|
||||
{
|
||||
name: 'Canada - Central (canadacentral)',
|
||||
value: 'canadacentral'
|
||||
},
|
||||
{
|
||||
name: 'Brazil (South)',
|
||||
value: 'brazilsouth'
|
||||
{
|
||||
name: 'East Asia (eastasia)',
|
||||
value: 'eastasia'
|
||||
},
|
||||
{
|
||||
name: 'Canada (Central)',
|
||||
value: 'canadacentral'
|
||||
{
|
||||
name: 'Europe - North (northeurope)',
|
||||
value: 'northeurope'
|
||||
},
|
||||
{
|
||||
name: 'Europe (North)',
|
||||
value: 'northeurope'
|
||||
{
|
||||
name: 'Europe - West (westeurope)',
|
||||
value: 'westeurope'
|
||||
},
|
||||
{
|
||||
name: 'Europe (West)',
|
||||
value: 'westeurope'
|
||||
{
|
||||
name: 'France - Central (francecentral)',
|
||||
value: 'francecentral'
|
||||
},
|
||||
{
|
||||
name: 'France (Central)',
|
||||
value: 'francecentral'
|
||||
{
|
||||
name: 'Germany - West Central (germanywestcentral)',
|
||||
value: 'germanywestcentral'
|
||||
},
|
||||
{
|
||||
name: 'Switzerland (North)',
|
||||
value: 'switzerlandnorth'
|
||||
{
|
||||
name: 'India - Central (centralindia)',
|
||||
value: 'centralindia'
|
||||
},
|
||||
{
|
||||
name: 'India (Central)',
|
||||
value: 'centralindia'
|
||||
{
|
||||
name: 'Japan - East (japaneast)',
|
||||
value: 'japaneast'
|
||||
},
|
||||
{
|
||||
name: 'Japan (West)',
|
||||
value: 'japanwest'
|
||||
{
|
||||
name: 'Japan - West (japanwest)',
|
||||
value: 'japanwest'
|
||||
},
|
||||
{
|
||||
name: 'Japan (East)',
|
||||
value: 'japaneast'
|
||||
{
|
||||
name: 'Korea - Central (koreacentral)',
|
||||
value: 'koreacentral'
|
||||
},
|
||||
{
|
||||
name: 'Korea (Central)',
|
||||
value: 'koreacentral'
|
||||
{
|
||||
name: 'Norway - East (norwayeast)',
|
||||
value: 'norwayeast'
|
||||
},
|
||||
{
|
||||
name: 'South Africa (North)',
|
||||
value: 'southafricanorth'
|
||||
{
|
||||
name: 'South Africa - North (southafricanorth)',
|
||||
value: 'southafricanorth'
|
||||
},
|
||||
{
|
||||
name: 'UK (South)',
|
||||
value: 'uksouth'
|
||||
{
|
||||
name: 'Southeast Asia (southeastasia)',
|
||||
value: 'southeastasia'
|
||||
},
|
||||
{
|
||||
name: 'US (Cental)',
|
||||
value: 'centralus'
|
||||
{
|
||||
name: 'Switzerland - North (switzerlandnorth)',
|
||||
value: 'switzerlandnorth'
|
||||
},
|
||||
{
|
||||
name: 'US (West Central)',
|
||||
value: 'westcentralus'
|
||||
{
|
||||
name: 'Switzerland - West (switzerlandwest)',
|
||||
value: 'switzerlandwest'
|
||||
},
|
||||
{
|
||||
name: 'US (East)',
|
||||
value: 'eastus'
|
||||
{
|
||||
name: 'UAE - North (uaenorth)',
|
||||
value: 'uaenorth'
|
||||
},
|
||||
{
|
||||
name: 'US (East 2)',
|
||||
value: 'eastus2'
|
||||
{
|
||||
name: 'UK - South (uksouth)',
|
||||
value: 'uksouth'
|
||||
},
|
||||
{
|
||||
name: 'US (North Central)',
|
||||
value: 'northcentralus'
|
||||
{
|
||||
name: 'US - Central (centralus)',
|
||||
value: 'centralus'
|
||||
},
|
||||
{
|
||||
name: 'US (South Central)',
|
||||
value: 'southcentralus'
|
||||
{
|
||||
name: 'US - East (eastus)',
|
||||
value: 'eastus'
|
||||
},
|
||||
{
|
||||
name: 'US (West)',
|
||||
value: 'westus'
|
||||
{
|
||||
name: 'US - East 2 (eastus2)',
|
||||
value: 'eastus2'
|
||||
},
|
||||
{
|
||||
name: 'US (West 2)',
|
||||
value: 'westus2'
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export default [
|
||||
const languages = [
|
||||
{
|
||||
name: 'Afrikaans (South Africa)',
|
||||
code: 'af-ZA'
|
||||
},
|
||||
{
|
||||
name: 'Amharic (Ethiopia)',
|
||||
code: 'am-ET'
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Algeria)',
|
||||
code: 'ar-DZ'
|
||||
@@ -75,6 +83,10 @@ export default [
|
||||
name: 'Bulgarian (Bulgaria)',
|
||||
code: 'bg-BG'
|
||||
},
|
||||
{
|
||||
name: 'Bengali (India)',
|
||||
code: 'bn-IN'
|
||||
},
|
||||
{
|
||||
name: 'Catalan (Spain)',
|
||||
code: 'ca-ES'
|
||||
@@ -107,6 +119,10 @@ export default [
|
||||
name: 'Dutch (Netherlands)',
|
||||
code: 'nl-NL'
|
||||
},
|
||||
{
|
||||
name: 'Dutch (Belgium)',
|
||||
code: 'nl-BE'
|
||||
},
|
||||
{
|
||||
name: 'English (Australia)',
|
||||
code: 'en-AU'
|
||||
@@ -179,6 +195,10 @@ export default [
|
||||
name: 'Finnish (Finland)',
|
||||
code: 'fi-FI'
|
||||
},
|
||||
{
|
||||
name: 'French (Belgium)',
|
||||
code: 'fr-BE'
|
||||
},
|
||||
{
|
||||
name: 'French (Canada)',
|
||||
code: 'fr-CA'
|
||||
@@ -227,6 +247,10 @@ export default [
|
||||
name: 'Indonesian (Indonesia)',
|
||||
code: 'id-ID'
|
||||
},
|
||||
{
|
||||
name: 'Icelandic (Iceland)',
|
||||
code: 'is-IS'
|
||||
},
|
||||
{
|
||||
name: 'Irish (Ireland)',
|
||||
code: 'ga-IE'
|
||||
@@ -239,10 +263,18 @@ export default [
|
||||
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'
|
||||
@@ -251,6 +283,10 @@ export default [
|
||||
name: 'Latvian (Latvia)',
|
||||
code: 'lv-LV'
|
||||
},
|
||||
{
|
||||
name: 'Lao (Laos)',
|
||||
code: 'lo-LA'
|
||||
},
|
||||
{
|
||||
name: 'Lithuanian (Lithuania)',
|
||||
code: 'lt-LT'
|
||||
@@ -259,6 +295,10 @@ export default [
|
||||
name: 'Malay (Malaysia)',
|
||||
code: 'ms-MY'
|
||||
},
|
||||
{
|
||||
name: 'Macedonian (North Macedonia)',
|
||||
code: 'mk-MK'
|
||||
},
|
||||
{
|
||||
name: 'Maltese (Malta)',
|
||||
code: 'mt-MT'
|
||||
@@ -267,6 +307,10 @@ export default [
|
||||
name: 'Marathi (India)',
|
||||
code: 'mr-IN'
|
||||
},
|
||||
{
|
||||
name: 'Burmese (Myanmar)',
|
||||
code: 'my-MM'
|
||||
},
|
||||
{
|
||||
name: 'Norwegian (Bokmål, Norway)',
|
||||
code: 'nb-NO'
|
||||
@@ -395,10 +439,22 @@ export default [
|
||||
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'
|
||||
@@ -415,8 +471,22 @@ export default [
|
||||
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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
export default [
|
||||
const languages = [
|
||||
{
|
||||
code: 'en-US',
|
||||
name: 'English (US)',
|
||||
@@ -37,3 +37,5 @@ export default [
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
export default languages;
|
||||
|
||||
Reference in New Issue
Block a user