Compare commits

...

14 Commits

Author SHA1 Message Date
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
13 changed files with 1401 additions and 861 deletions

View File

@@ -1,4 +1,4 @@
FROM node:alpine as builder
FROM node:18.8.0-alpine 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

1502
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.6",
"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

@@ -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('');
@@ -376,6 +377,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,6 +551,7 @@ 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'
@@ -695,33 +698,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>

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

@@ -43,23 +43,23 @@ 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);
useEffect(() => {
const getSettingsData = async () => {
@@ -127,19 +127,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) => {
@@ -255,115 +255,115 @@ 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}
/>
<div>{/* needed for CSS grid layout */}</div>
<Checkbox
noLeftMargin
id="enableMsTeams"
label="Enable Microsoft Teams Direct Routing"
checked={enableMsTeams}
onChange={toggleMsTeams}
invalid={invalidEnableMsTeams}
ref={refEnableMsTeams}
/>
<Label htmlFor="sbcDomainName">SBC Domain Name</Label>
<Input
name="sbcDomainName"
id="sbcDomainName"
value={sbcDomainName}
onChange={e => setSbcDomainName(e.target.value)}
placeholder="Fully qualified domain name used for Microsoft Teams"
invalid={invalidSbcDomainName}
autoFocus={enableMsTeams}
ref={refSbcDomainName}
disabled={!enableMsTeams}
title={(!enableMsTeams && "You must enable Microsoft Teams Direct Routing in order to provide an SBC Domain Name") || ""}
/>
<Label htmlFor="sbcDomainName">SBC Domain Name</Label>
<Input
name="sbcDomainName"
id="sbcDomainName"
value={sbcDomainName}
onChange={e => setSbcDomainName(e.target.value)}
placeholder="Fully qualified domain name used for Microsoft Teams"
invalid={invalidSbcDomainName}
autoFocus={enableMsTeams}
ref={refSbcDomainName}
disabled={!enableMsTeams}
title={(!enableMsTeams && "You must enable Microsoft Teams Direct Routing in order to provide an SBC Domain Name") || ""}
/>
{errorMessage && !confirmDelete && (
<FormError grid message={errorMessage} />
)}
{errorMessage && !confirmDelete && (
<FormError grid message={errorMessage} />
)}
<InputGroup flexEnd spaced>
<Button
grid
gray
type="button"
onClick={() => {
history.push('/internal/accounts');
dispatch({
type: 'ADD',
level: 'info',
message: 'Changes canceled',
});
}}
>
Cancel
</Button>
{serviceProviders.length > 1 && (
<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

@@ -489,7 +489,7 @@ const SpeechServicesAddEdit = (props) => {
id="vendor"
value={vendor}
onChange={e => setVendor(e.target.value)}
ref={[refVendorGoogle, refVendorAws, refVendorMs, refVendorWellSaid]}
{...[refVendorGoogle, refVendorAws, refVendorMs, refVendorWellSaid]}
invalid={[invalidVendorGoogle, invalidVendorAws, invalidVendorMs, invalidVendorWellSaid].includes(true)}
>
<option value="">

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

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