diff --git a/src/api/constants.ts b/src/api/constants.ts index 3f6c910..292d771 100644 --- a/src/api/constants.ts +++ b/src/api/constants.ts @@ -131,6 +131,8 @@ export const SIP_GATEWAY_PROTOCOL_OPTIONS = [ /** * Record bucket type */ +export const BUCKET_VENDOR_AWS = "aws_s3"; +export const BUCKET_VENDOR_GOOGLE = "google"; export const BUCKET_VENDOR_OPTIONS = [ { name: "NONE", @@ -138,7 +140,11 @@ export const BUCKET_VENDOR_OPTIONS = [ }, { name: "AWS S3", - value: "aws_s3", + value: BUCKET_VENDOR_AWS, + }, + { + name: "Google Cloud Storage", + value: BUCKET_VENDOR_GOOGLE, }, ]; diff --git a/src/api/types.ts b/src/api/types.ts index 4c60481..9553f43 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -269,6 +269,7 @@ export interface BucketCredential { access_key_id?: null | string; secret_access_key?: null | string; tags?: null | AwsTag[]; + service_key?: null | string; } export interface Application { diff --git a/src/containers/internal/views/accounts/form.tsx b/src/containers/internal/views/accounts/form.tsx index e51937b..ca5cfbe 100644 --- a/src/containers/internal/views/accounts/form.tsx +++ b/src/containers/internal/views/accounts/form.tsx @@ -21,10 +21,13 @@ import { Message, ApplicationSelect, LocalLimits, + FileUpload, } from "src/components/forms"; import { ROUTE_INTERNAL_ACCOUNTS } from "src/router/routes"; import { AUDIO_FORMAT_OPTIONS, + BUCKET_VENDOR_AWS, + BUCKET_VENDOR_GOOGLE, BUCKET_VENDOR_OPTIONS, CRED_OK, DEFAULT_WEBHOOK, @@ -47,6 +50,8 @@ import type { } from "src/api/types"; import { hasLength, hasValue } from "src/utils"; import { useRegionVendors } from "src/vendor"; +import { GoogleServiceKey } from "src/vendor/types"; +import { getObscuredGoogleServiceKey } from "../speech-services/utils"; type AccountFormProps = { apps?: Application[]; @@ -81,13 +86,19 @@ export const AccountForm = ({ const [initialCheckRecordAllCall, setInitialCheckRecordAllCall] = useState(false); const [bucketVendor, setBucketVendor] = useState(""); + const [tmpBucketVendor, setTmpBucketVendor] = useState(""); const [recordFormat, setRecordFormat] = useState("mp3"); const [bucketRegion, setBucketRegion] = useState("us-east-1"); const [bucketName, setBucketName] = useState(""); + const [tmpBucketName, setTmpBucketName] = useState(""); const [bucketAccessKeyId, setBucketAccessKeyId] = useState(""); const [bucketSecretAccessKey, setBucketSecretAccessKey] = useState(""); const [bucketCredentialChecked, setBucketCredentialChecked] = useState(false); const [bucketTags, setBucketTags] = useState([]); + const [bucketGoogleServiceKey, setBucketGoogleServiceKey] = + useState(null); + const [tmpBucketGoogleServiceKey, setTmpBucketGoogleServiceKey] = + useState(null); const regions = useRegionVendors(); /** This lets us map and render the same UI for each... */ @@ -134,15 +145,49 @@ export const AccountForm = ({ setModal(false); }; + const handleFile = (file: File) => { + const handleError = () => { + setBucketGoogleServiceKey(null); + setTmpBucketGoogleServiceKey(null); + toastError("Invalid service key file, could not parse as JSON."); + }; + + file + .text() + .then((text) => { + try { + const json: GoogleServiceKey = JSON.parse(text); + + if (json.private_key && json.client_email) { + setBucketGoogleServiceKey(json); + setTmpBucketGoogleServiceKey(json); + } else { + setBucketGoogleServiceKey(null); + setTmpBucketGoogleServiceKey(null); + } + } catch (error) { + handleError(); + } + }) + .catch(() => { + handleError(); + }); + }; + const handleTestBucketCredential = (e: React.FormEvent) => { e.preventDefault(); if (!account || !account.data) return; const cred: BucketCredential = { vendor: bucketVendor, - region: bucketRegion, name: bucketName, - access_key_id: bucketAccessKeyId, - secret_access_key: bucketSecretAccessKey, + ...(bucketVendor === BUCKET_VENDOR_AWS && { + region: bucketRegion, + access_key_id: bucketAccessKeyId, + secret_access_key: bucketSecretAccessKey, + }), + ...(bucketVendor === BUCKET_VENDOR_GOOGLE && { + service_key: JSON.stringify(bucketGoogleServiceKey), + }), }; postAccountBucketCredentialTest(account?.data?.account_sid, cred).then( @@ -256,7 +301,7 @@ export const AccountForm = ({ device_calling_application_sid: appId || null, record_all_calls: recordAllCalls ? 1 : 0, record_format: recordFormat ? recordFormat : "mp3", - ...(bucketVendor === "aws_s3" && { + ...(bucketVendor === BUCKET_VENDOR_AWS && { bucket_credential: { vendor: bucketVendor || null, region: bucketRegion || "us-east-1", @@ -266,6 +311,14 @@ export const AccountForm = ({ ...(hasLength(bucketTags) && { tags: bucketTags }), }, }), + ...(bucketVendor === BUCKET_VENDOR_GOOGLE && { + bucket_credential: { + vendor: bucketVendor || null, + service_key: JSON.stringify(bucketGoogleServiceKey), + name: bucketName || null, + ...(hasLength(bucketTags) && { tags: bucketTags }), + }, + }), ...(!bucketCredentialChecked && { record_all_calls: 0, bucket_credential: { @@ -347,11 +400,15 @@ export const AccountForm = ({ } } - if (account.data.bucket_credential?.vendor) { + if (tmpBucketVendor) { + setBucketVendor(tmpBucketVendor); + } else if (account.data.bucket_credential?.vendor) { setBucketVendor(account.data.bucket_credential?.vendor); } - if (account.data.bucket_credential?.name) { + if (tmpBucketName) { + setBucketName(tmpBucketName); + } else if (account.data.bucket_credential?.name) { setBucketName(account.data.bucket_credential?.name); } @@ -378,6 +435,13 @@ export const AccountForm = ({ if (account.data.record_format) { setRecordFormat(account.data.record_format || "mp3"); } + if (tmpBucketGoogleServiceKey) { + setBucketGoogleServiceKey(tmpBucketGoogleServiceKey); + } else if (account.data.bucket_credential?.service_key) { + setBucketGoogleServiceKey( + JSON.parse(account.data.bucket_credential?.service_key) + ); + } setInitialCheckRecordAllCall( hasValue(bucketVendor) && bucketVendor.length !== 0 ); @@ -639,25 +703,27 @@ export const AccountForm = ({ options={BUCKET_VENDOR_OPTIONS} onChange={(e) => { setBucketVendor(e.target.value); + setTmpBucketVendor(e.target.value); }} /> - {bucketVendor === "aws_s3" && ( + + { + setBucketName(e.target.value); + setTmpBucketName(e.target.value); + }} + /> + {bucketVendor === BUCKET_VENDOR_AWS && ( <> - - { - setBucketName(e.target.value); - }} - /> {regions && regions["aws"] && ( <>