diff --git a/application/src/components/services/add-service/ServiceForm.tsx b/application/src/components/services/add-service/ServiceForm.tsx index 1fad41a..8e66a92 100644 --- a/application/src/components/services/add-service/ServiceForm.tsx +++ b/application/src/components/services/add-service/ServiceForm.tsx @@ -1,4 +1,3 @@ - import { Form } from "@/components/ui/form"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -47,18 +46,18 @@ export function ServiceForm({ form.reset(formData); // Log for debugging - console.log("Populating form with data:", { - type: formData.type, - url: formData.url, - port: formData.port, - regionalAgent: formData.regionalAgent, - regionalMonitoringEnabled: formData.regionalMonitoringEnabled, - regional_status: initialData.regional_status, - region_name: initialData.region_name, - agent_id: initialData.agent_id, - notification_status: initialData.notification_status, - notificationChannels: formData.notificationChannels - }); + // console.log("Populating form with data:", { + // type: formData.type, + // url: formData.url, + // port: formData.port, + // regionalAgents: formData.regionalAgents, + // regionalMonitoringEnabled: formData.regionalMonitoringEnabled, + // regional_status: initialData.regional_status, + // region_name: initialData.region_name, + // agent_id: initialData.agent_id, + // notification_status: initialData.notification_status, + // notificationChannels: formData.notificationChannels + // }); } }, [initialData, isEdit, form]); @@ -97,7 +96,7 @@ export function ServiceForm({ form.reset(); } } catch (error) { - console.error(`Error ${isEdit ? 'updating' : 'creating'} service:`, error); + // console.error(`Error ${isEdit ? 'updating' : 'creating'} service:`, error); toast({ title: `Failed to ${isEdit ? 'update' : 'create'} service`, description: `An error occurred while ${isEdit ? 'updating' : 'creating'} the service.`, diff --git a/application/src/components/services/add-service/ServiceRegionalFields.tsx b/application/src/components/services/add-service/ServiceRegionalFields.tsx index 39b9e3f..34473b2 100644 --- a/application/src/components/services/add-service/ServiceRegionalFields.tsx +++ b/application/src/components/services/add-service/ServiceRegionalFields.tsx @@ -6,7 +6,9 @@ import { UseFormReturn } from "react-hook-form"; import { ServiceFormData } from "./types"; import { useQuery } from "@tanstack/react-query"; import { regionalService } from "@/services/regionalService"; -import { MapPin, Loader2, X } from "lucide-react"; +import { MapPin, Loader2, X, Plus } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; interface ServiceRegionalFieldsProps { form: UseFormReturn; @@ -14,7 +16,7 @@ interface ServiceRegionalFieldsProps { export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) { const regionalMonitoringEnabled = form.watch("regionalMonitoringEnabled"); - const currentRegionalAgent = form.watch("regionalAgent"); + const currentRegionalAgents = form.watch("regionalAgents") || []; const { data: regionalAgents = [], isLoading } = useQuery({ queryKey: ['regional-services'], @@ -27,15 +29,16 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) { agent.connection === 'online' && agent.agent_id !== "1" ); - // Find the current agent name for display - const getCurrentAgentDisplay = () => { - if (!currentRegionalAgent || currentRegionalAgent === "unassign") { - return "Select a regional agent or unassign"; - } - - const [regionName, agentId] = currentRegionalAgent.split("|"); + // Get available agents (not already selected) + const availableAgents = onlineAgents.filter(agent => + !currentRegionalAgents.includes(`${agent.region_name}|${agent.agent_id}`) + ); + + // Get agent display name + const getAgentDisplayName = (agentValue: string) => { + const [regionName, agentId] = agentValue.split("|"); const agent = onlineAgents.find(agent => - `${agent.region_name}|${agent.agent_id}` === currentRegionalAgent + `${agent.region_name}|${agent.agent_id}` === agentValue ); if (agent) { @@ -43,25 +46,27 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) { } // If agent is not found in online agents, it might be offline but still assigned - // Show the region name from the stored value if (regionName && agentId) { return `${regionName} (Agent ${agentId}) - Offline`; } - return "Select a regional agent or unassign"; + return agentValue; }; - // Get the proper select value - handle both assigned and unassigned cases - const getSelectValue = () => { - if (!regionalMonitoringEnabled) { - return "unassign"; + // Add regional agent + const addRegionalAgent = (agentValue: string) => { + if (agentValue && agentValue !== "select") { + const currentAgents = form.getValues("regionalAgents") || []; + if (!currentAgents.includes(agentValue)) { + form.setValue("regionalAgents", [...currentAgents, agentValue]); + } } - - if (!currentRegionalAgent || currentRegionalAgent === "") { - return "unassign"; - } - - return currentRegionalAgent; + }; + + // Remove regional agent + const removeRegionalAgent = (agentValue: string) => { + const currentAgents = form.getValues("regionalAgents") || []; + form.setValue("regionalAgents", currentAgents.filter(agent => agent !== agentValue)); }; return ( @@ -77,13 +82,19 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) { Regional Monitoring
- Assign this service to a regional monitoring agent for distributed monitoring + Assign this service to regional monitoring agents for distributed monitoring
{ + field.onChange(checked); + // Clear agents when disabling regional monitoring + if (!checked) { + form.setValue("regionalAgents", []); + } + }} /> @@ -93,16 +104,36 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) { {regionalMonitoringEnabled && ( ( - Regional Agent + Regional Agents + + {/* Display selected agents */} + {currentRegionalAgents.length > 0 && ( +
+ {currentRegionalAgents.map((agentValue) => ( + +
+ {getAgentDisplayName(agentValue)} + +
+ ))} +
+ )} + + {/* Add new agent selector */} + + {regionalMonitoringEnabled && onlineAgents.length === 0 && !isLoading && (

No online regional agents found. Services will use default monitoring.

)} - {currentRegionalAgent && currentRegionalAgent !== "" && ( -

- Currently assigned to: {getCurrentAgentDisplay()} + + {currentRegionalAgents.length === 0 && regionalMonitoringEnabled && ( +

+ No regional agents selected. Service will use default monitoring.

)} - {(!currentRegionalAgent || currentRegionalAgent === "") && regionalMonitoringEnabled && ( -

- Service is unassigned and will use default monitoring. + + {currentRegionalAgents.length > 0 && ( +

+ Service assigned to {currentRegionalAgents.length} regional agent{currentRegionalAgents.length > 1 ? 's' : ''}.

)}
diff --git a/application/src/components/services/add-service/serviceFormUtils.ts b/application/src/components/services/add-service/serviceFormUtils.ts index 7d9171d..599768c 100644 --- a/application/src/components/services/add-service/serviceFormUtils.ts +++ b/application/src/components/services/add-service/serviceFormUtils.ts @@ -13,7 +13,7 @@ export const getServiceFormDefaults = (): ServiceFormData => ({ notificationChannels: [], alertTemplate: "", regionalMonitoringEnabled: false, - regionalAgent: "", + regionalAgents: [], }); export const mapServiceToFormData = (service: Service): ServiceFormData => { @@ -40,9 +40,12 @@ export const mapServiceToFormData = (service: Service): ServiceFormData => { // Handle regional monitoring data - check regional_status field const isRegionalEnabled = service.regional_status === "enabled"; - const regionalAgent = isRegionalEnabled && service.region_name && service.agent_id - ? `${service.region_name}|${service.agent_id}` - : ""; + const regionalAgents: string[] = []; + + // For backward compatibility, if there's a single regional agent, add it to the array + if (isRegionalEnabled && service.region_name && service.agent_id) { + regionalAgents.push(`${service.region_name}|${service.agent_id}`); + } // Handle notification channels - convert notification_channel and notificationChannel to array const notificationChannels: string[] = []; @@ -62,7 +65,8 @@ export const mapServiceToFormData = (service: Service): ServiceFormData => { notification_status: service.notification_status, notification_channel: service.notification_channel, notificationChannel: service.notificationChannel, - mappedChannels: notificationChannels + mappedChannels: notificationChannels, + regionalAgents: regionalAgents }); return { @@ -76,21 +80,23 @@ export const mapServiceToFormData = (service: Service): ServiceFormData => { notificationChannels: notificationChannels, alertTemplate: service.alertTemplate === "default" ? "" : service.alertTemplate || "", regionalMonitoringEnabled: isRegionalEnabled, - regionalAgent: regionalAgent, + regionalAgents: regionalAgents, }; }; export const mapFormDataToServiceData = (data: ServiceFormData) => { - // Parse regional agent selection + // Parse regional agent selection - for now, use the first agent for backward compatibility let regionName = ""; let agentId = ""; let regionalStatus: "enabled" | "disabled" = "disabled"; // Set regional status and agent data based on form values - if (data.regionalMonitoringEnabled) { + if (data.regionalMonitoringEnabled && data.regionalAgents && data.regionalAgents.length > 0) { regionalStatus = "enabled"; - if (data.regionalAgent && data.regionalAgent !== "") { - const [parsedRegionName, parsedAgentId] = data.regionalAgent.split("|"); + // Use the first agent for backward compatibility with single-agent database schema + const firstAgent = data.regionalAgents[0]; + if (firstAgent && firstAgent !== "") { + const [parsedRegionName, parsedAgentId] = firstAgent.split("|"); regionName = parsedRegionName || ""; agentId = parsedAgentId || ""; } @@ -109,6 +115,10 @@ export const mapFormDataToServiceData = (data: ServiceFormData) => { regionalStatus: regionalStatus, regionName: regionName, agentId: agentId, + // Store multiple agents as a comment for future use (when backend supports it) + regionalAgentsNote: data.regionalAgents && data.regionalAgents.length > 1 + ? `Multiple agents selected: ${data.regionalAgents.join(', ')}` + : undefined, // Map the URL field to appropriate database field based on service type ...(data.type === "dns" ? { domain: data.url, url: "", host: "", port: undefined } // DNS: store in domain field diff --git a/application/src/components/services/add-service/types.ts b/application/src/components/services/add-service/types.ts index 3d269e5..4269a42 100644 --- a/application/src/components/services/add-service/types.ts +++ b/application/src/components/services/add-service/types.ts @@ -14,9 +14,9 @@ export const serviceSchema = z.object({ notificationStatus: z.enum(["enabled", "disabled"]).optional(), notificationChannels: z.array(z.string()).optional(), alertTemplate: z.string().optional(), - // Regional monitoring fields + // Regional monitoring fields - now supports multiple agents regionalMonitoringEnabled: z.boolean().optional(), - regionalAgent: z.string().optional(), + regionalAgents: z.array(z.string()).optional(), }); -export type ServiceFormData = z.infer; +export type ServiceFormData = z.infer; \ No newline at end of file diff --git a/application/src/components/ui/badge.tsx b/application/src/components/ui/badge.tsx index 8963a4d..f64cf19 100644 --- a/application/src/components/ui/badge.tsx +++ b/application/src/components/ui/badge.tsx @@ -34,4 +34,4 @@ function Badge({ className, variant, ...props }: BadgeProps) { ) } -export { Badge, badgeVariants } +export { Badge, badgeVariants } \ No newline at end of file diff --git a/application/src/components/ui/button.tsx b/application/src/components/ui/button.tsx index 36496a2..fede5fc 100644 --- a/application/src/components/ui/button.tsx +++ b/application/src/components/ui/button.tsx @@ -1,3 +1,4 @@ + import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" @@ -5,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { @@ -53,4 +54,4 @@ const Button = React.forwardRef( ) Button.displayName = "Button" -export { Button, buttonVariants } +export { Button, buttonVariants } \ No newline at end of file