Skip to main content
Glama

Fonoster MCP Server

Official
by fonoster
MIT License
118
7,391
  • Apple
  • Linux
create-trunk.form.tsx17.1 kB
/** * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com) * http://github.com/fonoster/fonoster * * This file is part of Fonoster * * Licensed under the MIT License (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://opensource.org/licenses/MIT * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useFieldArray, useForm } from "react-hook-form"; import { Form, FormControl, FormField, FormItem } from "~/core/components/design-system/forms"; import { Input } from "~/core/components/design-system/ui/input/input"; import { FormRoot } from "~/core/components/design-system/forms/form-root"; import { zodResolver } from "@hookform/resolvers/zod"; import { useCallback, useState } from "react"; import { schema, type Schema } from "./create-trunk.schema"; import type { Trunk } from "@fonoster/types"; import { Box } from "@mui/material"; import { Typography } from "~/core/components/design-system/ui/typography/typography"; import { Select } from "~/core/components/design-system/ui/select/select"; import { ResourceIdField } from "~/core/components/design-system/ui/resource-id-field/resource-id-field"; import { useAcls } from "~/acls/services/acls.service"; import { ModalTrigger } from "~/core/components/general/modal-trigger"; import { useCredentials } from "~/credentials/services/credentials.service"; import { CreateTrunkCredentialsModal } from "./create-trunk-credentials-modal.modal"; import { CreateTrunkAclsModal } from "./create-trunk-acls-modal.modal"; import { Checkbox } from "~/core/components/design-system/ui/checkbox/checkbox"; import { CreateTrunkUrisModal } from "./create-trunk-uris-modal.modal"; import { Tooltip } from "~/core/components/design-system/ui/tooltip/tooltip"; import { useFormContextSync } from "~/core/hooks/use-form-context-sync"; import type { Acl } from "@fonoster/types"; import type { Credentials } from "@fonoster/types"; /** * Props interface for the CreateTrunkForm component. */ export interface CreateTrunkFormProps extends React.PropsWithChildren { /** Optional initial values to populate the form fields with. */ initialValues?: Schema; /** Callback triggered on successful form submission. */ onSubmit: (data: Schema) => Promise<Trunk | void | null>; /** Whether this form is for editing an existing trunk. */ isEdit?: boolean; } /** * CreateTrunkForm component. * * Renders a form for creating a trunk, including fields for: * - Friendly Name * - Inbound URI * - Access Control List * - Credentials * - URIs * * Integrates: * - React Hook Form for state management * - Zod for schema validation * - FormContext for state synchronization * - Nested modals for creating related resources (ACLs, Credentials) * * Note: For nested modals that create resources, we use the onFormSubmit callback * to update the select fields with the real ref after the resource is created. * This ensures that the select shows the newly created resource instead of * the temporary ref from optimistic updates. * * @param {CreateTrunkFormProps} props - Props including onSubmit handler and optional initial values. * @returns {JSX.Element} The rendered Create Trunk form. */ export function CreateTrunkForm({ onSubmit, initialValues, isEdit }: CreateTrunkFormProps) { const [isTrunkCredentialsModalOpen, setIsTrunkCredentialsModalOpen] = useState(false); const [isTrunkAclsModalOpen, setIsTrunkAclsModalOpen] = useState(false); const [isTrunkUrisModalOpen, setIsTrunkUrisModalOpen] = useState(false); // Restore data hooks with stable query keys const { data: acls = [], isLoading: isAclsLoading } = useAcls(); const { data: credentials = [], isLoading: isLoadingCredentials } = useCredentials(); /** Initializes the React Hook Form with Zod validation and initial values. */ const form = useForm<Schema>({ resolver: zodResolver(schema), defaultValues: { ref: null, name: "", sendRegister: false, inboundUri: "", accessControlListRef: "", inboundCredentialsRef: "", outboundCredentialsRef: "", uris: [], ...initialValues }, mode: "onChange" }); const { fields: uris, append: appendURI, remove: removeURI } = useFieldArray<Schema>({ name: "uris", control: form.control }); /** Sync form state with FormContext */ useFormContextSync(form, onSubmit, isEdit); // Simple callbacks for updating selects with real refs const handleAclFormSubmit = useCallback( (acl: Acl) => { form.setValue("accessControlListRef", acl.ref); }, [form] ); const handleCredentialsFormSubmit = useCallback( (credentials: Credentials, fieldName?: string) => { // Update the correct field based on which one triggered the modal if (fieldName === "outboundCredentialsRef") { form.setValue("outboundCredentialsRef", credentials.ref); } else { // Default to inbound credentials form.setValue("inboundCredentialsRef", credentials.ref); } }, [form] ); const handleUrisFormSubmit = useCallback( (uri: any) => { appendURI(uri); }, [appendURI] ); // Simple close callbacks const handleCloseCredentialsModal = useCallback(() => { setIsTrunkCredentialsModalOpen(false); }, []); const handleCloseAclsModal = useCallback(() => { setIsTrunkAclsModalOpen(false); }, []); const handleCloseUrisModal = useCallback(() => { setIsTrunkUrisModalOpen(false); }, []); // State to track which field triggered the credentials modal const [credentialsModalField, setCredentialsModalField] = useState<string>( "inboundCredentialsRef" ); // Handlers to open credentials modal with field context const handleOpenInboundCredentialsModal = useCallback(() => { setCredentialsModalField("inboundCredentialsRef"); setIsTrunkCredentialsModalOpen(true); }, []); const handleOpenOutboundCredentialsModal = useCallback(() => { setCredentialsModalField("outboundCredentialsRef"); setIsTrunkCredentialsModalOpen(true); }, []); /** * Builds the displayed values for the Select, each formatted as "type:name". */ const selectValues = uris.map( ({ transport, host, port, enabled }) => `${transport.toLowerCase()}:${host}:${port} (${enabled ? "enabled" : "disabled"})` ); /** * Builds the Select options, matching the Select values. */ const selectOptions = uris.map(({ transport, host, port, enabled }) => ({ value: `${transport.toLowerCase()}:${host}:${port} (${enabled ? "enabled" : "disabled"})`, label: `${transport.toLowerCase()}:${host}:${port} (${enabled ? "enabled" : "disabled"})` })); const handleDelete = useCallback( (oldValues: string[], newValues: string[]) => { const deleted = oldValues.find((val) => !newValues.includes(val)); if (deleted) { const index = uris.findIndex( ({ transport, host, port, enabled }) => `${transport.toLowerCase()}:${host}:${port} (${enabled ? "enabled" : "disabled"})` === deleted ); if (index !== -1) { removeURI(index); } } }, [uris, removeURI] ); /** * Renders the form with individual fields wrapped in FormField and FormItem components. */ return ( <> <Form {...form}> <FormRoot onSubmit={form.handleSubmit(onSubmit)}> {/* Trunk ID - Only show in edit mode */} {isEdit && initialValues?.ref && ( <ResourceIdField value={initialValues.ref} label="Trunk Ref" /> )} {/* Friendly Name Field */} <FormField control={form.control} name="name" render={({ field }) => ( <FormItem> <FormControl> <Input type="text" label="Friendly Name" {...field} /> </FormControl> </FormItem> )} /> {/* Text to Speech Section */} <Box sx={{ mt: "8px" }}> <Typography variant="mono-medium" color="base.03"> INBOUND </Typography> <Typography variant="body-micro" color="base.03"> Outgoing traffic from your communications infrastructure to the PSTN. In order to use a Trunk for termination it must have a Termination SIP URI and at least one authentication scheme (IP Access Control Lists and/or Credential Lists). </Typography> </Box> {/* Friendly Name Field */} <FormField control={form.control} name="inboundUri" render={({ field }) => ( <FormItem> <FormControl> <Input type="text" label="Inbound SIP URI" {...field} /> </FormControl> </FormItem> )} /> <FormField control={form.control} name="accessControlListRef" render={({ field }) => ( <FormItem> <FormControl> <Box sx={{ display: "flex", flexDirection: "column", gap: "12px" }} > <Select label="Access Control List (ACL)" options={acls.map(({ ref, name }) => ({ value: ref, label: name }))} disabled={isAclsLoading || acls.length === 0} placeholder={ isAclsLoading ? "Loading ACLs..." : acls.length === 0 ? "No ACLs found. Create one first." : "" } allowClear={true} {...field} /> {/* Modal trigger to open rule creation */} <ModalTrigger onClick={() => setIsTrunkAclsModalOpen(true)} label="Create New Access Control List" /> </Box> </FormControl> </FormItem> )} /> <FormField control={form.control} name="inboundCredentialsRef" render={({ field }) => ( <FormItem> <FormControl> <Box sx={{ display: "flex", flexDirection: "column", gap: "12px" }} > <Select {...field} label="Inbound Credentials" options={credentials.map(({ ref, name }) => ({ value: ref, label: name }))} disabled={ isLoadingCredentials || credentials.length === 0 } placeholder={ isLoadingCredentials ? "Loading credentials..." : credentials.length === 0 ? "No credentials found. Create one first." : "Select credentials" } allowClear={true} /> <ModalTrigger onClick={handleOpenInboundCredentialsModal} label="Create New Inbound Credentials" /> </Box> </FormControl> </FormItem> )} /> {/* Text to Speech Section */} <Box sx={{ mt: "8px" }}> <Typography variant="mono-medium" color="base.03"> OUTBOUND </Typography> <Typography variant="body-micro" color="base.03"> Outgoing traffic from your communications infrastructure to the PSTN. In order to use a Trunk for termination it must have a Termination SIP URI and at least one authentication scheme (IP Access Control Lists and/or Credential Lists). </Typography> </Box> <FormField control={form.control} name="outboundCredentialsRef" render={({ field }) => ( <FormItem> <FormControl> <Box sx={{ display: "flex", flexDirection: "column", gap: "12px" }} > <Select {...field} label="Outbound Credentials" options={credentials.map(({ ref, name }) => ({ value: ref, label: name }))} disabled={ isLoadingCredentials || credentials.length === 0 } placeholder={ isLoadingCredentials ? "Loading credentials..." : credentials.length === 0 ? "No credentials found. Create one first." : "Select credentials" } allowClear={true} /> <ModalTrigger onClick={handleOpenOutboundCredentialsModal} label="Create New Outbound Credentials" /> </Box> </FormControl> </FormItem> )} /> <FormField control={form.control} name="uris" render={() => ( <FormItem> <FormControl> <Box sx={{ display: "flex", flexDirection: "column", gap: "12px" }} > {/* Read-only Select showing current rules */} <Select label="Outbound SIP URIs" placeholder="Click below to add SIP URIs" multiple value={selectValues} options={selectOptions} disabled onChange={(event) => { const newValues = event.target.value as string[]; handleDelete(selectValues, newValues); }} /> {/* Modal trigger to open rule creation */} <ModalTrigger onClick={() => setIsTrunkUrisModalOpen(true)} label="Create Outbound SIP URI" /> </Box> </FormControl> </FormItem> )} /> <Box sx={{ mt: "12px" }}> <FormField control={form.control} name="sendRegister" render={({ field }) => ( <FormItem> <FormControl> <Checkbox checked={field.value} onChange={(e) => field.onChange(e.target.checked)} disabled > <Tooltip title="This feature is not yet available. (Coming soon!)"> <span>Send SIP Register requests for this trunk</span> </Tooltip> </Checkbox> </FormControl> </FormItem> )} /> </Box> </FormRoot> </Form> {/* Credentials Modal */} <CreateTrunkCredentialsModal isOpen={isTrunkCredentialsModalOpen} onClose={handleCloseCredentialsModal} onFormSubmit={handleCredentialsFormSubmit} fieldName={credentialsModalField} /> <CreateTrunkAclsModal isOpen={isTrunkAclsModalOpen} onClose={handleCloseAclsModal} onFormSubmit={handleAclFormSubmit} /> <CreateTrunkUrisModal isOpen={isTrunkUrisModalOpen} onClose={handleCloseUrisModal} onFormSubmit={handleUrisFormSubmit} /> </> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fonoster/fonoster'

If you have feedback or need assistance with the MCP directory API, please join our Discord server