mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-07-04 19:21:58 +00:00
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>
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user