Compare commits

...

31 Commits

Author SHA1 Message Date
Dave Horton
3da6594092 fix for APP_ENABLE_ACCOUNT_LIMITS_ALL 2022-12-05 08:30:14 -05:00
Dave Horton
4535820828 add support for tracking add'l account limits 2022-11-27 15:29:57 -05:00
Guilherme Rauen
f743ca47a9 simplify build commands, update node image to the latest and most secure (#149)
Co-authored-by: Guilherme Rauen <g.rauen@cognigy.com>
2022-11-11 11:08:45 -05:00
Dave Horton
b8c67143bd add support for Azure custom vocies 2022-11-04 08:27:40 -04:00
Dave Horton
a67f6ad214 update package-lock.json 2022-10-23 12:26:13 -04:00
Markus Frindt
57dd168272 [snyk] Fix vulnerabilities (#137)
Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
2022-10-20 10:24:58 -04:00
Dave Horton
6b7e4a34bb bump version 2022-10-13 16:03:25 -04:00
Dave Horton
50216d5345 comment out azure custom voice for now.. 2022-10-09 17:12:28 +01:00
Dave Horton
4bdbd6fc0f add support for adding new carrier settings for outbound register 2022-10-07 09:42:50 +01:00
xquanluu
ebcdefb945 Fix: update custome speech label content (#132) 2022-10-04 20:13:40 -07:00
xquanluu
6b9d206f4f Legacy: Microsoft custom TTS and STT endpoints (#120)
* feat: add custom tts and stt endpoints for microsoft

* feat: add custom tts and stt endpoints for microsoft

* fix: send null microsoft custom endpoints incase nothing is provided

* fix: don't sent speech credentials in PUT method

* fix:does not clear the custom stt/tts endpoints value when it's disabled

* use tmp state to store custom tts/stt endpoint when enable/disable it
2022-09-30 10:35:23 +01:00
xquanluu
68d88decb5 feat: add Service provider limits and account limits to settings (#112)
* feat: add Service provider limits and account limits to settings

* fix: review comments

* fix: review comments

* fix: review comments

* fix: review comments

* fix: review comment

* fix: review comment

* fix: review comment

* fix: review comment

* fix: review comments

* fix: review comments

* fix: review comments

* fix: review comments
2022-09-27 08:17:27 +01:00
kitajchuk
1276687cc0 Add package-lock changes for moment lib update 2022-09-22 08:26:41 -07:00
Paulo Telles
b97d5e538f update moment lib (#95)
Co-authored-by: p.souza <p.souza@cognigy.com>
2022-09-07 15:06:02 +02:00
Paulo Telles
2704e97a96 update node image (#94)
Co-authored-by: p.souza <p.souza@cognigy.com>
2022-09-07 13:47:36 +02:00
Dave Horton
4cfdfc3b49 sync package-lock.json 2022-08-28 22:18:17 +02:00
Dave Horton
47d73a7edd bump version to 0.7.6 2022-08-26 20:07:36 +02:00
Lê Hàn Minh Khang
c14fa5db34 remove dummy text from siprec tooltip (#69) 2022-08-06 08:46:28 +01:00
Lê Hàn Minh Khang
668d7f05f6 for #66 (#67) 2022-08-03 17:25:35 +01:00
Dave Horton
5ae88ff13e Dockerfile: update base image 2022-07-28 12:59:18 +01:00
Joan
06c21f2545 Add z-index property to Add button (#63)
* extended AWS region list

* added z-index to add button

Co-authored-by: Joan Salvatella <joan@bookline.io>
2022-07-11 10:33:11 +02:00
Joan
663aabc80c extended AWS region list (#62)
Co-authored-by: Joan Salvatella <joan@bookline.io>
2022-07-01 11:54:32 -04:00
Dave Horton
a7de0a494e update deps 2022-06-11 12:30:33 -04:00
Lê Hàn Minh Khang
b742e67715 Fix speech form dropdown menu reference (#61) 2022-05-26 08:51:49 -04:00
Lê Hàn Minh Khang
013681e7eb Update Azure speech list (#60)
* update azure region list

* format

* more meaningful name

* sorting and prettifying

* making sense of africa server
2022-05-25 11:02:07 -04:00
Lê Hàn Minh Khang
4912758120 Fix issue #57 (#58)
* fix issue #57

* fix further

* fix to phone page
2022-05-24 08:38:13 -04:00
Dave Horton
bb335d0838 Sp apikey UI fix (#56)
* service provider add/view/delete features added

* reverting .env back to port 3002

* fix ui

Co-authored-by: Conner Luzier <connerluzier@protonmail.com>
2022-05-16 11:20:34 -04:00
Dave Horton
38d26dddc8 new Microsoft languages added in 1.21.0 2022-05-12 19:34:49 -04:00
Conner Luzier
c0d531c63f removed radio buttons, replaced with dropdown (#53)
* removed radio buttons, replaced with dropdown

* disabled button if no vendor, moved checkboxes up
2022-05-06 20:37:05 -04:00
Kieron Lawson
420080ba84 Updater FQDN regex to accept numeric characters (#42) 2022-04-27 14:39:06 -04:00
Conner Luzier
c40fb9cc01 fix add account error message (#51)
Co-authored-by: Conner Luzier <connerluzier@outlook.com>
2022-04-27 14:35:40 -04:00
18 changed files with 6324 additions and 4408 deletions

View File

@@ -1,18 +1,23 @@
FROM node:alpine 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 --platform=linux/amd64 node:18.12.1-alpine3.16 as base
FROM node: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"]
RUN apk --update --no-cache add --virtual .builds-deps build-base curl python3
WORKDIR /opt/app/
FROM base as build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM base
COPY --from=build /opt/app /opt/app/
RUN chmod +x /opt/app/entrypoint.sh
ENTRYPOINT ["/opt/app/entrypoint.sh"]

View File

@@ -4,7 +4,9 @@ API_PORT="${API_PORT:-3000}"
API_VERSION="${API_VERSION:-v1}"
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
REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL=${REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL:-0}
echo "REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL=${REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL}" >> /opt/app/.env
cd /opt/app/
TAG="<script>window.JAMBONZ = { APP_API_BASE_URL: '${REACT_APP_API_BASE_URL}'};</script>"
TAG="<script>window.JAMBONZ = { APP_API_BASE_URL: '${REACT_APP_API_BASE_URL}', APP_ENABLE_ACCOUNT_LIMITS_ALL: ${REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL}};</script>"
sed -i -e "\@</head>@i\ $TAG" ./build/index.html
npm run serve

1688
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{
"name": "jambonz-webapp",
"version": "v0.7.5",
"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",

View File

@@ -21,6 +21,7 @@ const StyledLink = styled(FilteredLink)`
border-radius: 50%;
text-decoration: none;
color: #565656;
z-index: 1;
& > span:first-child {
display: flex;

View File

@@ -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 Accounts 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 Accounts 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} />
)}

View File

@@ -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

View File

@@ -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')}`,
},

View File

@@ -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"
/>
)}
</>
)
);
};

View File

@@ -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';
@@ -80,6 +79,8 @@ const SpeechServicesAddEdit = (props) => {
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('');
@@ -94,6 +95,13 @@ const SpeechServicesAddEdit = (props) => {
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);
@@ -107,6 +115,9 @@ const SpeechServicesAddEdit = (props) => {
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);
@@ -163,6 +174,10 @@ const SpeechServicesAddEdit = (props) => {
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) {
@@ -233,6 +248,8 @@ const SpeechServicesAddEdit = (props) => {
setInvalidUseForTts(false);
setInvalidUseForStt(false);
setInvalidApiKey(false);
setInvalidUseCustomTts(false);
setInvalidUseCustomStt(false);
let errorMessages = [];
let focusHasBeenSet = false;
@@ -306,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;
@@ -334,16 +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,
aws_region: vendor === 'aws' ? awsregion : 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,
}),
}
});
@@ -484,53 +532,23 @@ const SpeechServicesAddEdit = (props) => {
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
@@ -552,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>
@@ -607,7 +656,7 @@ const SpeechServicesAddEdit = (props) => {
ref={refAwsRegion}
invalid={invalidAwsRegion}
>
<option value="">
<option value="">
Select a region
</option>
{AwsRegions.map(r => (
@@ -655,6 +704,70 @@ 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 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' ? (
<>
@@ -674,37 +787,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} />
)}
@@ -726,7 +808,7 @@ const SpeechServicesAddEdit = (props) => {
Cancel
</Button>
<Button rounded="true">
<Button rounded="true" disabled={!vendor}>
{type === 'add'
? 'Add Speech Service'
: 'Save'

View File

@@ -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}

View File

@@ -104,7 +104,6 @@ const CarriersList = () => {
return;
}
if(!currentServiceProvider) return [];
if (!accountList.length) return [];
// Get all SIP trunks
const trunkResults = await axios({
method: 'get',

View File

@@ -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>

View File

@@ -1,2 +1,35 @@
const { REACT_APP_API_BASE_URL } = process.env;
const { REACT_APP_API_BASE_URL, REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL } = process.env;
export const APP_API_BASE_URL = (window.JAMBONZ) ? window.JAMBONZ.APP_API_BASE_URL : REACT_APP_API_BASE_URL;
export const APP_ENABLE_ACCOUNT_LIMITS_ALL = (window.JAMBONZ) ? window.JAMBONZ.REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL : JSON.parse(REACT_APP_ENABLE_ACCOUNT_LIMITS_ALL);
export const LIMITS = [
// {
// label: "Max registered devices (0=unlimited)",
// category: "device",
// },
// {
// label: "Max api calls per minute (0=unlimited)",
// category: "api_rate",
// },
{
label: "Max calls",
category: "voice_call_session",
}
];
console.log(`REACT_APP_API_BASE_URL: ${REACT_APP_API_BASE_URL}, APP_ENABLE_ACCOUNT_LIMITS_ALL: ${APP_ENABLE_ACCOUNT_LIMITS_ALL}`);
console.log(`typeof APP_ENABLE_ACCOUNT_LIMITS_ALL: ${typeof APP_ENABLE_ACCOUNT_LIMITS_ALL}`);
if (APP_ENABLE_ACCOUNT_LIMITS_ALL || APP_ENABLE_ACCOUNT_LIMITS_ALL === "true") {
LIMITS.push({
label: "Licensed calls",
category: "voice_call_session_license",
});
LIMITS.push({
label: "Max minutes",
category: "voice_call_minutes",
});
LIMITS.push({
label: "Licensed minutes",
category: "voice_call_minutes_license",
});
}
console.log(`LIMITS: ${JSON.stringify(LIMITS)}`);

View File

@@ -54,6 +54,38 @@ const regions = [
{
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'
}
];

View File

@@ -1,96 +1,124 @@
const regions = [
{
name: 'Asia (East)',
value: 'eastasia'
{
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;

View File

@@ -1,4 +1,12 @@
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 @@ const languages = [
name: 'Bulgarian (Bulgaria)',
code: 'bg-BG'
},
{
name: 'Bengali (India)',
code: 'bn-IN'
},
{
name: 'Catalan (Spain)',
code: 'ca-ES'
@@ -107,6 +119,10 @@ const languages = [
name: 'Dutch (Netherlands)',
code: 'nl-NL'
},
{
name: 'Dutch (Belgium)',
code: 'nl-BE'
},
{
name: 'English (Australia)',
code: 'en-AU'
@@ -179,6 +195,10 @@ const languages = [
name: 'Finnish (Finland)',
code: 'fi-FI'
},
{
name: 'French (Belgium)',
code: 'fr-BE'
},
{
name: 'French (Canada)',
code: 'fr-CA'
@@ -227,6 +247,10 @@ const languages = [
name: 'Indonesian (Indonesia)',
code: 'id-ID'
},
{
name: 'Icelandic (Iceland)',
code: 'is-IS'
},
{
name: 'Irish (Ireland)',
code: 'ga-IE'
@@ -239,10 +263,18 @@ const languages = [
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 @@ const languages = [
name: 'Latvian (Latvia)',
code: 'lv-LV'
},
{
name: 'Lao (Laos)',
code: 'lo-LA'
},
{
name: 'Lithuanian (Lithuania)',
code: 'lt-LT'
@@ -259,6 +295,10 @@ const languages = [
name: 'Malay (Malaysia)',
code: 'ms-MY'
},
{
name: 'Macedonian (North Macedonia)',
code: 'mk-MK'
},
{
name: 'Maltese (Malta)',
code: 'mt-MT'
@@ -267,6 +307,10 @@ const languages = [
name: 'Marathi (India)',
code: 'mr-IN'
},
{
name: 'Burmese (Myanmar)',
code: 'my-MM'
},
{
name: 'Norwegian (Bokmål, Norway)',
code: 'nb-NO'
@@ -395,10 +439,22 @@ const languages = [
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,6 +471,18 @@ const languages = [
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'

File diff suppressed because it is too large Load Diff