feat: google storage for record all call (#292)

* feat: google storage for record all call

* fix

* wip

* wip

* wip

* wip

* fix

* fix

* fix

* fix

* fix
This commit is contained in:
Hoan Luu Huu
2023-07-28 23:03:54 +07:00
committed by GitHub
parent 16629ba508
commit 61bd1f9bab
3 changed files with 208 additions and 105 deletions
+7 -1
View File
@@ -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,
},
];
+1
View File
@@ -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 {
+200 -104
View File
@@ -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<AwsTag[]>([]);
const [bucketGoogleServiceKey, setBucketGoogleServiceKey] =
useState<GoogleServiceKey | null>(null);
const [tmpBucketGoogleServiceKey, setTmpBucketGoogleServiceKey] =
useState<GoogleServiceKey | null>(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);
}}
/>
</div>
{bucketVendor === "aws_s3" && (
<label htmlFor="bucket_name">
Bucket Name<span>*</span>
</label>
<input
id="bucket_name"
required
type="text"
name="bucket_name"
placeholder="Bucket"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value);
setTmpBucketName(e.target.value);
}}
/>
{bucketVendor === BUCKET_VENDOR_AWS && (
<>
<label htmlFor="bucket_name">
Bucket Name<span>*</span>
</label>
<input
id="bucket_name"
required
type="text"
name="bucket_name"
placeholder="Bucket"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value);
}}
/>
{regions && regions["aws"] && (
<>
<label htmlFor="bucket_aws_region">
@@ -705,86 +771,120 @@ export const AccountForm = ({
setBucketSecretAccessKey(e.target.value);
}}
/>
<label htmlFor="aws_s3_tags">S3 Tags</label>
{hasLength(bucketTags) &&
bucketTags.map((b, i) => (
<div key={`s3_tags_${i}`} className="bucket_tag">
<div>
<div>
<input
id={`bucket_tag_name_${i}`}
name={`bucket_tag_name_${i}`}
type="text"
placeholder="Name"
required
value={b.Key}
onChange={(e) => {
updateBucketTags(i, "Key", e.target.value);
}}
/>
</div>
<div>
<input
id={`bucket_tag_value_${i}`}
name={`bucket_tag_value_${i}`}
type="text"
placeholder="Value"
required
value={b.Value}
onChange={(e) => {
updateBucketTags(
i,
"Value",
e.target.value
);
}}
/>
</div>
</div>
<button
className="btnty"
title="Delete Aws Tag"
type="button"
onClick={() => {
setBucketTags(
bucketTags.filter((g2, i2) => i2 !== i)
);
}}
>
<Icon>
<Icons.Trash2 />
</Icon>
</button>
</div>
))}
<ButtonGroup left>
<button
className="btnty"
type="button"
onClick={addBucketTag}
title="Add S3 Tags"
>
<Icon subStyle="teal">
<Icons.Plus />
</Icon>
</button>
</ButtonGroup>
<ButtonGroup left>
<Button
onClick={handleTestBucketCredential}
small
disabled={
!bucketName ||
!bucketAccessKeyId ||
!bucketSecretAccessKey
}
>
Test
</Button>
</ButtonGroup>
</>
)}
{bucketVendor === BUCKET_VENDOR_GOOGLE && (
<>
<label htmlFor="google_service_key">
Service key<span>*</span>
<Tooltip text="Provide a JSON key for a Service Account with APIs enabled for Cloud Storage and Storage Transfer API">
{" "}
</Tooltip>
</label>
<FileUpload
id="google_service_key"
name="google_service_key"
handleFile={handleFile}
placeholder="Choose a file"
required={!bucketGoogleServiceKey}
/>
{bucketGoogleServiceKey && (
<pre>
<code>
{JSON.stringify(
getObscuredGoogleServiceKey(
bucketGoogleServiceKey
),
null,
2
)}
</code>
</pre>
)}
</>
)}
<label htmlFor="aws_s3_tags">
{bucketVendor === BUCKET_VENDOR_AWS
? "S3"
: bucketVendor === BUCKET_VENDOR_GOOGLE
? "Google Cloud Storage"
: ""}{" "}
Tags
</label>
{hasLength(bucketTags) &&
bucketTags.map((b, i) => (
<div key={`s3_tags_${i}`} className="bucket_tag">
<div>
<div>
<input
id={`bucket_tag_name_${i}`}
name={`bucket_tag_name_${i}`}
type="text"
placeholder="Name"
required
value={b.Key}
onChange={(e) => {
updateBucketTags(i, "Key", e.target.value);
}}
/>
</div>
<div>
<input
id={`bucket_tag_value_${i}`}
name={`bucket_tag_value_${i}`}
type="text"
placeholder="Value"
required
value={b.Value}
onChange={(e) => {
updateBucketTags(i, "Value", e.target.value);
}}
/>
</div>
</div>
<button
className="btnty"
title="Delete Aws Tag"
type="button"
onClick={() => {
setBucketTags(
bucketTags.filter((g2, i2) => i2 !== i)
);
}}
>
<Icon>
<Icons.Trash2 />
</Icon>
</button>
</div>
))}
<ButtonGroup left>
<button
className="btnty"
type="button"
onClick={addBucketTag}
title="Add S3 Tags"
>
<Icon subStyle="teal">
<Icons.Plus />
</Icon>
</button>
</ButtonGroup>
<ButtonGroup left>
<Button
onClick={handleTestBucketCredential}
small
disabled={
!bucketName ||
(bucketVendor === BUCKET_VENDOR_AWS &&
(!bucketAccessKeyId || !bucketSecretAccessKey)) ||
(bucketVendor === BUCKET_VENDOR_GOOGLE &&
!bucketGoogleServiceKey)
}
>
Test
</Button>
</ButtonGroup>
<label htmlFor="record_all_call" className="chk">
<input
id="record_all_call"
@@ -793,11 +893,7 @@ export const AccountForm = ({
onChange={(e) => setRecordAllCalls(e.target.checked)}
checked={recordAllCalls}
/>
<div></div>
<Tooltip
text="You can also record calls only to specific applications"
subStyle="info"
>
<Tooltip text="You can also record calls only to specific applications">
Record all calls for this account
</Tooltip>
</label>