From bb335d08387d20706ec0308f3cfd4cf098563ea1 Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Mon, 16 May 2022 11:20:34 -0400 Subject: [PATCH] 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 --- src/components/forms/SettingsForm.js | 242 +++++++++--------- .../pages/internal/AccountsAddEdit.js | 2 +- src/components/pages/internal/Settings.js | 207 ++++++++++++++- 3 files changed, 328 insertions(+), 123 deletions(-) diff --git a/src/components/forms/SettingsForm.js b/src/components/forms/SettingsForm.js index ea1f9b9..8f8ec3c 100644 --- a/src/components/forms/SettingsForm.js +++ b/src/components/forms/SettingsForm.js @@ -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 - ? - : ( - <> -
- - setServiceProviderName(e.target.value)} - invalid={invalidServiceProviderName} - ref={refServiceProviderName} - /> -
{/* needed for CSS grid layout */}
- + ? + : ( + <> + + + setServiceProviderName(e.target.value)} + invalid={invalidServiceProviderName} + ref={refServiceProviderName} + /> +
{/* needed for CSS grid layout */}
+ - - 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") || ""} - /> + + 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 && ( - - )} + {errorMessage && !confirmDelete && ( + + )} - - - {serviceProviders.length > 1 && ( + - )} - - - + {serviceProviders.length > 1 && ( + + )} + + + - {confirmDelete && serviceProviders.length > 1 && ( - - - - - - - - - - - - -
Service Provider Name:{serviceProviderName}
SBC Domain Name:{sbcDomainName || '[none]'}
- {errorMessage && ( - - )} - - } - handleCancel={() => { - setConfirmDelete(false); - setErrorMessage(''); - }} - handleSubmit={handleDelete} - actionText="Delete" - /> - )} - - ) + {confirmDelete && serviceProviders.length > 1 && ( + + + + + + + + + + + + +
Service Provider Name:{serviceProviderName}
SBC Domain Name:{sbcDomainName || '[none]'}
+ {errorMessage && ( + + )} + + } + handleCancel={() => { + setConfirmDelete(false); + setErrorMessage(''); + }} + handleSubmit={handleDelete} + actionText="Delete" + /> + )} + + ) ); }; diff --git a/src/components/pages/internal/AccountsAddEdit.js b/src/components/pages/internal/AccountsAddEdit.js index 2ef0c2c..07d52a7 100644 --- a/src/components/pages/internal/AccountsAddEdit.js +++ b/src/components/pages/internal/AccountsAddEdit.js @@ -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} diff --git a/src/components/pages/internal/Settings.js b/src/components/pages/internal/Settings.js index 756767f..b338c3a 100644 --- a/src/components/pages/internal/Settings.js +++ b/src/components/pages/internal/Settings.js @@ -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 ( + )} >