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:
Dave Horton
2022-05-16 11:20:34 -04:00
committed by GitHub
parent 38d26dddc8
commit bb335d0838
3 changed files with 328 additions and 123 deletions
+121 -121
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"
/>
)}
</>
)
);
};
@@ -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}
+206 -1
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>