Skip to main content
Glama

Fonoster MCP Server

Official
by fonoster
MIT License
118
7,387
  • Apple
  • Linux
create-domain.form.tsx10.5 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, useEffect, useMemo } from "react"; import { schema, type Schema } from "./create-domain.schema"; import { useAcls } from "~/acls/services/acls.service"; 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 { ModalTrigger } from "~/core/components/general/modal-trigger"; import { Box } from "@mui/material"; import { useNumbers } from "~/numbers/services/numbers.service"; import { CreateRuleModal } from "./create-domain-rules-modal.modal"; import { CreateDomainAclsModal } from "./create-domain-acls-modal.modal"; import { useFormContextSync } from "~/core/hooks"; import type { Acl } from "@fonoster/types"; /** * Props interface for the CreateDomainForm component. */ export interface CreateDomainFormProps extends React.PropsWithChildren { /** Optional initial values to populate the form fields with. */ initialValues?: Schema; /** Callback triggered on successful form submission. */ onSubmit: (data: Schema) => Promise<void>; /** Whether this form is for editing an existing domain. */ isEdit?: boolean; /** Ref to the form element */ formRef?: React.RefObject<HTMLFormElement>; } /** * CreateDomainForm component. * * Renders a form for creating a domain, including fields for: * - Friendly Name * - Domain URI * - Access Control List * - Egress Policies * * Integrates: * - React Hook Form for state management * - Zod for schema validation * - FormContext for state synchronization * * @param {CreateDomainFormProps} props - Props including onSubmit handler and optional initial values. * @returns {JSX.Element} The rendered Create Domain form. */ export function CreateDomainForm({ onSubmit, initialValues, isEdit, formRef }: CreateDomainFormProps) { const [isRulesModalOpen, setIsRulesModalOpen] = useState(false); const [isDomainAclsModalOpen, setIsDomainAclsModalOpen] = useState(false); const { data: aclsData, isLoading: isAclsLoading, refetch: refetchAcls } = useAcls(); const [acls, setAcls] = useState<Acl[]>([]); const { data: numbers, isLoading: isNumbersLoading } = useNumbers(); /** Initializes the React Hook Form with Zod validation and initial values. */ const form = useForm<Schema>({ resolver: zodResolver(schema), defaultValues: { ref: null, name: "", domainUri: "", accessControlListRef: "", egressPolicies: [], ...initialValues, // Extract ACL reference from the full ACL object if in edit mode ...(initialValues && "accessControlList" in initialValues && (initialValues as any).accessControlList && { accessControlListRef: (initialValues as any).accessControlList.ref }) }, mode: "onChange" }); /** React Hook Form's useFieldArray for dynamic list of rules. */ const { fields: policies, append: appendEgressPolicy } = useFieldArray({ name: "egressPolicies", control: form.control }); const getNumberName = useCallback( (numberRef: string) => { if (!numbers || isNumbersLoading) { return "Loading..."; } const number = numbers.find((num) => num.ref === numberRef); return number ? number.name : "Unknown Number"; }, [numbers, isNumbersLoading] ); /** * Builds the displayed values for the Select, each formatted as "type:name". */ const selectValues = useMemo(() => { return policies.map( (item) => `${getNumberName(item.numberRef)} (${item.rule})` ); }, [policies, getNumberName]); /** * Builds the Select options, matching the Select values. */ const selectOptions = useMemo(() => { return policies.map(({ rule, numberRef }) => ({ value: `${rule}:${numberRef}`, label: `${getNumberName(numberRef)} (${rule})` })); }, [policies, getNumberName]); // Stable callbacks for modals const handleCloseRulesModal = useCallback( () => setIsRulesModalOpen(false), [] ); const handleCloseAclsModal = useCallback( () => setIsDomainAclsModalOpen(false), [] ); const handleRuleFormSubmit = useCallback( (rule: any) => { appendEgressPolicy(rule); setIsRulesModalOpen(false); }, [appendEgressPolicy] ); const handleAclFormSubmit = useCallback( (newAcl: Acl) => { // Add the new ACL to the local list and select it setAcls((prev) => [...(prev || []), newAcl]); form.setValue("accessControlListRef", newAcl.ref); }, [form] ); // Keep local ACLs in sync with remote data useEffect(() => { if (aclsData) { setAcls(aclsData); } }, [aclsData]); /** Sync form state with FormContext */ useFormContextSync(form, onSubmit, isEdit); /** * Renders the form with individual fields wrapped in FormField and FormItem components. */ return ( <> <Form {...form}> <FormRoot ref={formRef} onSubmit={form.handleSubmit(onSubmit)}> {/* Domain ID - Only show in edit mode */} {isEdit && initialValues?.ref && ( <ResourceIdField value={initialValues.ref} label="Domain Ref" /> )} {/* Friendly Name Field */} <FormField control={form.control} name="name" render={({ field }) => ( <FormItem> <FormControl> <Input type="text" label="Friendly Name" {...field} /> </FormControl> </FormItem> )} /> <FormField control={form.control} name="domainUri" render={({ field }) => ( <FormItem> <FormControl> <Input type="text" label="Domain 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 || acls.length === 0} placeholder={ isAclsLoading ? "Loading ACLs..." : !acls || acls.length === 0 ? "No ACLs found. Create one first." : "" } allowClear={true} {...field} /> <ModalTrigger onClick={() => setIsDomainAclsModalOpen(true)} label="Create New Access Control List" /> </Box> </FormControl> </FormItem> )} /> <FormField control={form.control} name="egressPolicies" render={() => ( <FormItem> <FormControl> <Box sx={{ display: "flex", flexDirection: "column", gap: "12px" }} > {/* Read-only Select showing current rules */} <Select label="Egress Rules" placeholder="Click below to add rules (e.g., .*)." multiple value={selectValues} options={selectOptions} disabled onChange={(event) => { const selectedValues = event.target.value as string[]; // Update the form state with selected values form.setValue( "egressPolicies", selectedValues.map((value) => { const [rule, numberRef] = value.split(":"); return { rule, numberRef }; }) ); }} /> {/* Modal trigger to open rule creation */} <ModalTrigger onClick={() => setIsRulesModalOpen(true)} label="Create New Egress Rule" /> </Box> </FormControl> </FormItem> )} /> </FormRoot> </Form> {/* Modal for creating new ACL rules */} <CreateRuleModal isOpen={isRulesModalOpen} onClose={handleCloseRulesModal} onFormSubmit={handleRuleFormSubmit} /> <CreateDomainAclsModal isOpen={isDomainAclsModalOpen} onClose={handleCloseAclsModal} onFormSubmit={handleAclFormSubmit} /> </> ); }

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