Skip to content

Commit cb3fa4d

Browse files
committed
refactor(frontend): split form.tsx into modular components following SOLID principles
- Create form/ module with separated concerns: - types.ts: FormSchema, FormData types, defaults - constants.ts: FACILITY_OPTIONS, SEVERITY_OPTIONS - hooks/useConnection.ts: connection state management - hooks/useCertificates.ts: TLS certificate handlers - ConnectionCard.tsx: connection settings UI - MessageConfigCard.tsx: RFC5424/facility/severity config - SendMessagesCard.tsx: message input and send button - index.tsx: main orchestrator component - Reduce form.tsx from 966 lines to ~100 line orchestrator - Improve maintainability and testability - No functional changes
1 parent f6b440b commit cb3fa4d

10 files changed

Lines changed: 1165 additions & 965 deletions

File tree

frontend/src/components/form.tsx

Lines changed: 0 additions & 965 deletions
This file was deleted.

frontend/src/components/form/ConnectionCard.tsx

Lines changed: 407 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"use client";
2+
3+
import { UseFormReturn } from "react-hook-form";
4+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5+
import { Input } from "@/components/ui/input";
6+
import {
7+
FormControl,
8+
FormDescription,
9+
FormField,
10+
FormItem,
11+
FormLabel,
12+
FormMessage,
13+
} from "@/components/ui/form";
14+
import {
15+
Select,
16+
SelectContent,
17+
SelectItem,
18+
SelectTrigger,
19+
SelectValue,
20+
} from "@/components/ui/select";
21+
import { FileTextIcon } from "lucide-react";
22+
import { TemplateManager } from "@/components/template-manager";
23+
import { FormData } from "./types";
24+
import { FACILITY_OPTIONS, SEVERITY_OPTIONS } from "./constants";
25+
26+
// ================================================================================
27+
// MESSAGE CONFIG CARD COMPONENT
28+
// Single Responsibility: Handles message configuration UI (RFC format, facility, etc.)
29+
// ================================================================================
30+
31+
interface MessageConfigCardProps {
32+
form: UseFormReturn<FormData>;
33+
}
34+
35+
export function MessageConfigCard({ form }: MessageConfigCardProps) {
36+
return (
37+
<Card>
38+
<CardHeader className="pb-2 pt-3">
39+
<div className="flex items-center justify-between">
40+
<CardTitle className="text-sm font-semibold flex items-center gap-2">
41+
<FileTextIcon className="w-4 h-4" />
42+
Message Configuration
43+
</CardTitle>
44+
<TemplateManager
45+
currentValues={{
46+
Messages: form.watch("Messages"),
47+
Facility: form.watch("Facility"),
48+
Severity: form.watch("Severity"),
49+
Appname: form.watch("Appname"),
50+
UseRFC5424: form.watch("UseRFC5424"),
51+
}}
52+
onLoadTemplate={(template) => {
53+
form.setValue("Messages", template.message);
54+
form.setValue("Facility", template.facility);
55+
form.setValue("Severity", template.severity);
56+
form.setValue("Appname", template.appname);
57+
form.setValue("UseRFC5424", template.useRfc5424);
58+
}}
59+
/>
60+
</div>
61+
</CardHeader>
62+
<CardContent className="space-y-3 pb-3">
63+
{/* RFC Format Toggle */}
64+
<FormField
65+
control={form.control}
66+
name="UseRFC5424"
67+
render={({ field }) => (
68+
<FormItem className="flex flex-row items-center gap-3 space-y-0 rounded-md border border-border/60 p-3 bg-secondary/20">
69+
<FormControl>
70+
<input
71+
type="checkbox"
72+
checked={field.value}
73+
onChange={field.onChange}
74+
className="h-4 w-4 rounded accent-primary"
75+
/>
76+
</FormControl>
77+
<div className="flex-1 space-y-0.5">
78+
<FormLabel className="text-xs font-medium leading-none">
79+
Use RFC 5424 Format
80+
</FormLabel>
81+
<FormDescription className="text-[10px] text-muted-foreground">
82+
Modern format (checked) or legacy RFC 3164
83+
</FormDescription>
84+
</div>
85+
</FormItem>
86+
)}
87+
/>
88+
89+
{/* Facility & Severity */}
90+
<div className="grid grid-cols-2 gap-3">
91+
<FormField
92+
control={form.control}
93+
name="Facility"
94+
render={({ field }) => (
95+
<FormItem>
96+
<FormLabel className="text-xs font-medium">Facility</FormLabel>
97+
<Select
98+
onValueChange={(value: string) => field.onChange(parseInt(value))}
99+
defaultValue={field.value?.toString()}
100+
>
101+
<FormControl>
102+
<SelectTrigger className="h-9">
103+
<SelectValue placeholder="Select" />
104+
</SelectTrigger>
105+
</FormControl>
106+
<SelectContent className="max-h-[200px]">
107+
{FACILITY_OPTIONS.map((option) => (
108+
<SelectItem key={option.value} value={option.value.toString()}>
109+
{option.label}
110+
</SelectItem>
111+
))}
112+
</SelectContent>
113+
</Select>
114+
<FormMessage className="text-xs" />
115+
</FormItem>
116+
)}
117+
/>
118+
119+
<FormField
120+
control={form.control}
121+
name="Severity"
122+
render={({ field }) => (
123+
<FormItem>
124+
<FormLabel className="text-xs font-medium">Severity</FormLabel>
125+
<Select
126+
onValueChange={(value: string) => field.onChange(parseInt(value))}
127+
defaultValue={field.value?.toString()}
128+
>
129+
<FormControl>
130+
<SelectTrigger className="h-9">
131+
<SelectValue placeholder="Select" />
132+
</SelectTrigger>
133+
</FormControl>
134+
<SelectContent className="max-h-[200px]">
135+
{SEVERITY_OPTIONS.map((option) => (
136+
<SelectItem key={option.value} value={option.value.toString()}>
137+
{option.label}
138+
</SelectItem>
139+
))}
140+
</SelectContent>
141+
</Select>
142+
<FormMessage className="text-xs" />
143+
</FormItem>
144+
)}
145+
/>
146+
</div>
147+
148+
{/* Hostname & App Name */}
149+
<div className="grid grid-cols-2 gap-3">
150+
<FormField
151+
control={form.control}
152+
name="Hostname"
153+
render={({ field }) => (
154+
<FormItem>
155+
<FormLabel className="text-xs font-medium">Hostname</FormLabel>
156+
<FormControl>
157+
<Input placeholder="Optional" className="h-9" {...field} />
158+
</FormControl>
159+
<FormMessage className="text-xs" />
160+
</FormItem>
161+
)}
162+
/>
163+
164+
<FormField
165+
control={form.control}
166+
name="Appname"
167+
render={({ field }) => (
168+
<FormItem>
169+
<FormLabel className="text-xs font-medium">App Name</FormLabel>
170+
<FormControl>
171+
<Input placeholder="sendlog" className="h-9" {...field} />
172+
</FormControl>
173+
<FormMessage className="text-xs" />
174+
</FormItem>
175+
)}
176+
/>
177+
</div>
178+
</CardContent>
179+
</Card>
180+
);
181+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use client";
2+
3+
import { UseFormReturn } from "react-hook-form";
4+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5+
import { Button } from "@/components/ui/button";
6+
import { Textarea } from "@/components/ui/textarea";
7+
import {
8+
FormControl,
9+
FormDescription,
10+
FormField,
11+
FormItem,
12+
FormLabel,
13+
FormMessage,
14+
} from "@/components/ui/form";
15+
import { SendIcon, NetworkIcon } from "lucide-react";
16+
import { BatchImport } from "@/components/batch-import";
17+
import { FormData } from "./types";
18+
19+
// ================================================================================
20+
// SEND MESSAGES CARD COMPONENT
21+
// Single Responsibility: Handles the message input and send UI
22+
// ================================================================================
23+
24+
interface SendMessagesCardProps {
25+
form: UseFormReturn<FormData>;
26+
isConnected: boolean;
27+
}
28+
29+
export function SendMessagesCard({ form, isConnected }: SendMessagesCardProps) {
30+
if (!isConnected) {
31+
return <DisconnectedState />;
32+
}
33+
34+
return (
35+
<Card>
36+
<CardHeader className="pb-2 pt-3">
37+
<div className="flex items-center justify-between">
38+
<CardTitle className="text-sm font-semibold flex items-center gap-2">
39+
<SendIcon className="w-4 h-4" />
40+
Send Messages
41+
</CardTitle>
42+
<BatchImport
43+
currentMessages={form.watch("Messages")}
44+
onImport={(messages) => {
45+
form.setValue("Messages", messages.join("\n"));
46+
}}
47+
/>
48+
</div>
49+
</CardHeader>
50+
<CardContent className="space-y-3 pb-3">
51+
<FormField
52+
control={form.control}
53+
name="Messages"
54+
render={({ field }) => (
55+
<FormItem>
56+
<FormLabel className="text-xs font-medium">Log Messages</FormLabel>
57+
<FormControl>
58+
<Textarea
59+
placeholder="Type your messages here, one per line..."
60+
className="resize-none h-[80px]"
61+
{...field}
62+
/>
63+
</FormControl>
64+
<FormDescription className="text-[10px] text-muted-foreground">
65+
Enter messages separated by new lines
66+
</FormDescription>
67+
<FormMessage className="text-xs" />
68+
</FormItem>
69+
)}
70+
/>
71+
72+
<Button type="submit" className="w-full h-9">
73+
<SendIcon className="w-4 h-4 mr-2" />
74+
Send Syslog Messages
75+
</Button>
76+
</CardContent>
77+
</Card>
78+
);
79+
}
80+
81+
// ================================================================================
82+
// DISCONNECTED STATE SUBCOMPONENT
83+
// ================================================================================
84+
85+
function DisconnectedState() {
86+
return (
87+
<Card>
88+
<CardContent className="py-6">
89+
<div className="text-center">
90+
<NetworkIcon className="w-10 h-10 mx-auto mb-2 text-muted-foreground/50" />
91+
<p className="text-sm text-muted-foreground">
92+
Connect to a syslog server to send messages
93+
</p>
94+
</div>
95+
</CardContent>
96+
</Card>
97+
);
98+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// ================================================================================
2+
// SYSLOG CONSTANTS
3+
// Single Responsibility: Defines RFC 5424 facility and severity options
4+
// ================================================================================
5+
6+
export interface SelectOption {
7+
value: number;
8+
label: string;
9+
}
10+
11+
// Facility codes according to RFC 5424
12+
export const FACILITY_OPTIONS: SelectOption[] = [
13+
{ value: 0, label: "0 - kernel" },
14+
{ value: 1, label: "1 - user" },
15+
{ value: 2, label: "2 - mail" },
16+
{ value: 3, label: "3 - system daemon" },
17+
{ value: 4, label: "4 - security/auth" },
18+
{ value: 5, label: "5 - syslogd" },
19+
{ value: 6, label: "6 - line printer" },
20+
{ value: 7, label: "7 - network news" },
21+
{ value: 8, label: "8 - UUCP" },
22+
{ value: 9, label: "9 - clock daemon" },
23+
{ value: 10, label: "10 - security/auth" },
24+
{ value: 11, label: "11 - FTP daemon" },
25+
{ value: 12, label: "12 - NTP" },
26+
{ value: 13, label: "13 - log audit" },
27+
{ value: 14, label: "14 - log alert" },
28+
{ value: 15, label: "15 - clock daemon" },
29+
{ value: 16, label: "16 - local0" },
30+
{ value: 17, label: "17 - local1" },
31+
{ value: 18, label: "18 - local2" },
32+
{ value: 19, label: "19 - local3" },
33+
{ value: 20, label: "20 - local4" },
34+
{ value: 21, label: "21 - local5" },
35+
{ value: 22, label: "22 - local6" },
36+
{ value: 23, label: "23 - local7" },
37+
];
38+
39+
// Severity levels according to RFC 5424
40+
export const SEVERITY_OPTIONS: SelectOption[] = [
41+
{ value: 0, label: "0 - Emergency" },
42+
{ value: 1, label: "1 - Alert" },
43+
{ value: 2, label: "2 - Critical" },
44+
{ value: 3, label: "3 - Error" },
45+
{ value: 4, label: "4 - Warning" },
46+
{ value: 5, label: "5 - Notice" },
47+
{ value: 6, label: "6 - Informational" },
48+
{ value: 7, label: "7 - Debug" },
49+
];
50+
51+
// Default ports
52+
export const DEFAULT_SYSLOG_PORT = "514";
53+
export const DEFAULT_SYSLOG_TLS_PORT = "6514";
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// ================================================================================
2+
// FORM HOOKS - Re-exports
3+
// ================================================================================
4+
5+
export { useConnection, type UseConnectionReturn } from "./useConnection";
6+
export { useCertificates, type UseCertificatesReturn } from "./useCertificates";

0 commit comments

Comments
 (0)