mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2025-12-19 05:37:43 +00:00
support applicatin env vars (#509)
* support applicatin env vars * wip * wip * wip * wip * wip
This commit is contained in:
2
.env
2
.env
@@ -1,4 +1,4 @@
|
|||||||
# VITE_API_BASE_URL=http://127.0.0.1:3000/v1
|
#VITE_API_BASE_URL=http://127.0.0.1:3000/v1
|
||||||
#VITE_DEV_BASE_URL=http://127.0.0.1:3000/v1
|
#VITE_DEV_BASE_URL=http://127.0.0.1:3000/v1
|
||||||
|
|
||||||
## enables choosing units and lisenced account call limits
|
## enables choosing units and lisenced account call limits
|
||||||
|
|||||||
@@ -447,3 +447,4 @@ export const API_SUBSCRIPTIONS = `${API_BASE_URL}/Subscriptions`;
|
|||||||
export const API_CHANGE_PASSWORD = `${API_BASE_URL}/change-password`;
|
export const API_CHANGE_PASSWORD = `${API_BASE_URL}/change-password`;
|
||||||
export const API_SIGNIN = `${API_BASE_URL}/signin`;
|
export const API_SIGNIN = `${API_BASE_URL}/signin`;
|
||||||
export const API_GOOGLE_CUSTOM_VOICES = `${API_BASE_URL}/GoogleCustomVoices`;
|
export const API_GOOGLE_CUSTOM_VOICES = `${API_BASE_URL}/GoogleCustomVoices`;
|
||||||
|
export const API_APP_ENV = `${API_BASE_URL}/AppEnv`;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
API_CHANGE_PASSWORD,
|
API_CHANGE_PASSWORD,
|
||||||
API_SIGNIN,
|
API_SIGNIN,
|
||||||
API_GOOGLE_CUSTOM_VOICES,
|
API_GOOGLE_CUSTOM_VOICES,
|
||||||
|
API_APP_ENV,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
import { ROUTE_LOGIN } from "src/router/routes";
|
import { ROUTE_LOGIN } from "src/router/routes";
|
||||||
import {
|
import {
|
||||||
@@ -94,6 +95,7 @@ import type {
|
|||||||
GoogleCustomVoice,
|
GoogleCustomVoice,
|
||||||
GoogleCustomVoicesQuery,
|
GoogleCustomVoicesQuery,
|
||||||
SpeechSupportedLanguagesAndVoices,
|
SpeechSupportedLanguagesAndVoices,
|
||||||
|
AppEnv,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { Availability, StatusCodes } from "./types";
|
import { Availability, StatusCodes } from "./types";
|
||||||
import { JaegerRoot } from "./jaeger-types";
|
import { JaegerRoot } from "./jaeger-types";
|
||||||
@@ -812,6 +814,11 @@ export const getGoogleCustomVoices = (
|
|||||||
const qryStr = getQuery<Partial<GoogleCustomVoicesQuery>>(query);
|
const qryStr = getQuery<Partial<GoogleCustomVoicesQuery>>(query);
|
||||||
return getFetch<GoogleCustomVoice[]>(`${API_GOOGLE_CUSTOM_VOICES}?${qryStr}`);
|
return getFetch<GoogleCustomVoice[]>(`${API_GOOGLE_CUSTOM_VOICES}?${qryStr}`);
|
||||||
};
|
};
|
||||||
|
// ENV VARS
|
||||||
|
|
||||||
|
export const getAppEnvSchema = (url: string) => {
|
||||||
|
return getFetch<AppEnv>(`${API_APP_ENV}?url=${url}`);
|
||||||
|
};
|
||||||
|
|
||||||
/** Wrappers for APIs that can have a mock dev server response */
|
/** Wrappers for APIs that can have a mock dev server response */
|
||||||
|
|
||||||
|
|||||||
@@ -338,6 +338,7 @@ export interface Application {
|
|||||||
fallback_speech_recognizer_vendor: null | string;
|
fallback_speech_recognizer_vendor: null | string;
|
||||||
fallback_speech_recognizer_language: null | string;
|
fallback_speech_recognizer_language: null | string;
|
||||||
fallback_speech_recognizer_label: null | string;
|
fallback_speech_recognizer_label: null | string;
|
||||||
|
env_vars: null | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PhoneNumber {
|
export interface PhoneNumber {
|
||||||
@@ -781,3 +782,14 @@ export interface CartesiaOptions {
|
|||||||
speed: number;
|
speed: number;
|
||||||
emotion: CartesiaEmotions;
|
emotion: CartesiaEmotions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppEnvProperty {
|
||||||
|
description: string;
|
||||||
|
type: string;
|
||||||
|
required?: boolean;
|
||||||
|
default?: string | number | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppEnv {
|
||||||
|
[key: string]: AppEnvProperty;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Button, ButtonGroup, MS } from "@jambonz/ui-kit";
|
import { Button, ButtonGroup, MS } from "@jambonz/ui-kit";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { toastError, toastSuccess, useSelectState } from "src/store";
|
import { toastError, toastSuccess, useSelectState } from "src/store";
|
||||||
import { ClipBoard, Section } from "src/components";
|
import { ClipBoard, Section, Tooltip } from "src/components";
|
||||||
import {
|
import {
|
||||||
Selector,
|
Selector,
|
||||||
Checkzone,
|
Checkzone,
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
putApplication,
|
putApplication,
|
||||||
useServiceProviderData,
|
useServiceProviderData,
|
||||||
useApiData,
|
useApiData,
|
||||||
|
getAppEnvSchema,
|
||||||
} from "src/api";
|
} from "src/api";
|
||||||
import {
|
import {
|
||||||
ROUTE_INTERNAL_ACCOUNTS,
|
ROUTE_INTERNAL_ACCOUNTS,
|
||||||
@@ -48,6 +49,7 @@ import type {
|
|||||||
WebhookMethod,
|
WebhookMethod,
|
||||||
UseApiDataMap,
|
UseApiDataMap,
|
||||||
SpeechCredential,
|
SpeechCredential,
|
||||||
|
AppEnv,
|
||||||
} from "src/api/types";
|
} from "src/api/types";
|
||||||
import { MSG_REQUIRED_FIELDS, MSG_WEBHOOK_FIELDS } from "src/constants";
|
import { MSG_REQUIRED_FIELDS, MSG_WEBHOOK_FIELDS } from "src/constants";
|
||||||
import { hasLength, isUserAccountScope, useRedirect } from "src/utils";
|
import { hasLength, isUserAccountScope, useRedirect } from "src/utils";
|
||||||
@@ -122,6 +124,12 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
|||||||
useState("");
|
useState("");
|
||||||
const [initalCheckFallbackSpeech, setInitalCheckFallbackSpeech] =
|
const [initalCheckFallbackSpeech, setInitalCheckFallbackSpeech] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [appEnv, setAppEnv] = useState<AppEnv | null>(null);
|
||||||
|
const appEnvTimeoutRef = useRef<number | null>(null);
|
||||||
|
const [envVars, setEnvVars] = useState<Record<
|
||||||
|
string,
|
||||||
|
string | number | boolean
|
||||||
|
> | null>(null);
|
||||||
|
|
||||||
/** This lets us map and render the same UI for each... */
|
/** This lets us map and render the same UI for each... */
|
||||||
const webhooks = [
|
const webhooks = [
|
||||||
@@ -134,6 +142,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
|||||||
tmpStateSet: setTmpCallWebhook,
|
tmpStateSet: setTmpCallWebhook,
|
||||||
initialCheck: initialCallWebhook,
|
initialCheck: initialCallWebhook,
|
||||||
required: true,
|
required: true,
|
||||||
|
webhookEnv: appEnv,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Call status",
|
label: "Call status",
|
||||||
@@ -197,6 +206,15 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
|||||||
speech_recognizer_label: recogLabel || null,
|
speech_recognizer_label: recogLabel || null,
|
||||||
record_all_calls: recordAllCalls ? 1 : 0,
|
record_all_calls: recordAllCalls ? 1 : 0,
|
||||||
use_for_fallback_speech: useForFallbackSpeech ? 1 : 0,
|
use_for_fallback_speech: useForFallbackSpeech ? 1 : 0,
|
||||||
|
env_vars: envVars
|
||||||
|
? JSON.stringify(
|
||||||
|
Object.keys(envVars).reduce(
|
||||||
|
(acc, key) =>
|
||||||
|
appEnv && appEnv[key] ? { ...acc, [key]: envVars[key] } : acc,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
fallback_speech_synthesis_vendor: useForFallbackSpeech
|
fallback_speech_synthesis_vendor: useForFallbackSpeech
|
||||||
? fallbackSpeechSynthsisVendor || null
|
? fallbackSpeechSynthsisVendor || null
|
||||||
: null,
|
: null,
|
||||||
@@ -513,6 +531,9 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
|||||||
application.data.fallback_speech_synthesis_voice,
|
application.data.fallback_speech_synthesis_voice,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (application.data.env_vars) {
|
||||||
|
setEnvVars(JSON.parse(application.data.env_vars));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [application]);
|
}, [application]);
|
||||||
|
|
||||||
@@ -548,6 +569,33 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
|||||||
setFallbackSpeechRecognizerLabel(tmp);
|
setFallbackSpeechRecognizerLabel(tmp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (callWebhook && callWebhook.url) {
|
||||||
|
// Clear any existing timeout to prevent multiple requests
|
||||||
|
if (appEnvTimeoutRef.current) {
|
||||||
|
clearTimeout(appEnvTimeoutRef.current);
|
||||||
|
appEnvTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
appEnvTimeoutRef.current = setTimeout(() => {
|
||||||
|
getAppEnvSchema(callWebhook.url)
|
||||||
|
.then(({ json }) => {
|
||||||
|
setAppEnv(json);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setMessage(error.msg);
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (appEnvTimeoutRef.current) {
|
||||||
|
clearTimeout(appEnvTimeoutRef.current);
|
||||||
|
appEnvTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [callWebhook]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section slim>
|
<Section slim>
|
||||||
<form
|
<form
|
||||||
@@ -699,6 +747,100 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Checkzone>
|
</Checkzone>
|
||||||
|
|
||||||
|
{webhook.webhookEnv &&
|
||||||
|
Object.keys(webhook.webhookEnv).length > 0 && (
|
||||||
|
<>
|
||||||
|
{Object.keys(webhook.webhookEnv).map((key) => {
|
||||||
|
const envType = webhook.webhookEnv![key].type;
|
||||||
|
const isBoolean = envType === "boolean";
|
||||||
|
const isNumber = envType === "number";
|
||||||
|
const defaultValue = webhook.webhookEnv![key].default;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="inp" key={key}>
|
||||||
|
{isBoolean ? (
|
||||||
|
// Boolean input as checkbox
|
||||||
|
<label htmlFor={`env_${key}`} className="chk">
|
||||||
|
<input
|
||||||
|
id={`env_${key}`}
|
||||||
|
type="checkbox"
|
||||||
|
name={`env_${key}`}
|
||||||
|
required={webhook.webhookEnv![key].required}
|
||||||
|
checked={
|
||||||
|
envVars && envVars[key] !== undefined
|
||||||
|
? Boolean(envVars[key])
|
||||||
|
: Boolean(defaultValue)
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
setEnvVars({
|
||||||
|
...(envVars || {}),
|
||||||
|
[key]: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
text={webhook.webhookEnv![key].description}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
{webhook.webhookEnv![key].required && (
|
||||||
|
<span>*</span>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
) : (
|
||||||
|
// Text or number input
|
||||||
|
<>
|
||||||
|
<label htmlFor={`env_${key}`}>
|
||||||
|
<Tooltip
|
||||||
|
text={webhook.webhookEnv![key].description}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
{webhook.webhookEnv![key].required && (
|
||||||
|
<span>*</span>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id={`env_${key}`}
|
||||||
|
type={isNumber ? "number" : "text"}
|
||||||
|
name={`env_${key}`}
|
||||||
|
placeholder={
|
||||||
|
webhook.webhookEnv![key].description
|
||||||
|
}
|
||||||
|
required={webhook.webhookEnv![key].required}
|
||||||
|
value={
|
||||||
|
envVars && envVars[key] !== undefined
|
||||||
|
? String(envVars[key])
|
||||||
|
: defaultValue !== undefined
|
||||||
|
? String(defaultValue)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
// Convert to proper type based on schema
|
||||||
|
let newValue;
|
||||||
|
if (isNumber) {
|
||||||
|
newValue =
|
||||||
|
e.target.value === ""
|
||||||
|
? ""
|
||||||
|
: Number(e.target.value);
|
||||||
|
} else {
|
||||||
|
newValue = e.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnvVars({
|
||||||
|
...(envVars || {}),
|
||||||
|
[key]: newValue,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user