Skip to content

Conversation

@bernoussama
Copy link
Owner

@bernoussama bernoussama commented Jun 10, 2025

Introduce a complete DHCP controller and API client, including configuration management, service control, and validation features. Consolidate testing and enhance the overall architecture for better integration and usability.

Summary by CodeRabbit

  • New Features
    • Introduced comprehensive DHCP server configuration management in backend and frontend, including APIs for configuration, validation, status, and service control.
    • Added an interactive, validated DHCP configuration form in the UI with dynamic subnets, host reservations, and global options.
    • Enabled real-time DHCP service status monitoring and control (start, stop, restart, reload) from the UI.
  • Bug Fixes
    • Enhanced error handling and validation feedback for DHCP configuration inputs.
  • Documentation
    • Added detailed implementation plan and TypeScript type definitions for DHCP configuration and API responses.
  • Tests
    • Added extensive unit tests for DHCP validators, transformers, and schema validation.

…ontroller with config generation, routes with authentication, service management integration, validation and backup functionality
…PI client with auth support, complete DHCP configuration component with tabs, forms, validation, service control, and responsive design
…ered with comprehensive functionality including shared package (types/validators/transformers), backend (controller/routes/service management), frontend (UI with tabs/forms/validation), and integration testing. IP conflict detection and authentication working correctly.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 10, 2025

Walkthrough

This update introduces a complete DHCP configuration management system across backend, shared, and frontend codebases. It adds data types, validation schemas, transformation utilities, backend controllers and routes, frontend API clients, and a comprehensive React UI for DHCP settings. Extensive tests and documentation are also included.

Changes

File(s) Change Summary
apps/backend/src/app.ts, apps/backend/src/routes/dhcpRoutes.ts Integrated new DHCP API routes into the backend application.
apps/backend/src/controllers/dhcpController.ts Added backend controller for DHCP configuration management, validation, and service control.
apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx Rewrote DHCPConfig React component as a controlled, validated, multi-tab form with API integration.
apps/ui/src/lib/api/dhcp.ts, apps/ui/src/lib/api/index.ts Added frontend API client for DHCP endpoints and exported it.
docs/implementation-plan/dhcp-config-implementation-plan.md Added detailed implementation plan for DHCP configuration management.
packages/shared/src/types/dhcp.ts, packages/shared/src/types/index.ts Introduced and exported TypeScript types/interfaces for DHCP configuration and related entities.
packages/shared/src/validators/dhcpFormValidator.ts Added Zod schemas and validation utilities for DHCP configuration forms.
packages/shared/src/validators/dhcpTransformers.ts Added utilities for transforming DHCP form data to API format and vice versa, plus network helpers.
packages/shared/src/validators/tests/dhcpValidators.test.ts Added unit tests for DHCP validators, transformers, and schemas.
packages/shared/src/validators/index.ts, packages/shared/src/index.ts Exported new DHCP validators, transformers, and types from shared package.
.gitignore Removed ignore pattern for all meta directories (**/meta).
apps/backend/drizzle/meta/0000_snapshot.json, apps/backend/drizzle/meta/_journal.json Added new database schema snapshot and journal metadata files related to SQLite schema versioning.

Sequence Diagram(s)

sequenceDiagram
    participant UI as DHCPConfig (React)
    participant API as dhcp.ts (API client)
    participant BE as Backend Express API
    participant Controller as dhcpController
    participant Service as DHCP Service Manager

    UI->>API: getDhcpConfigurationAPI()
    API->>BE: GET /api/dhcp/config
    BE->>Controller: getCurrentDhcpConfiguration()
    Controller-->>BE: DHCP config + status
    BE-->>API: Response
    API-->>UI: DHCP config + status

    UI->>API: updateDhcpConfigurationAPI(formData)
    API->>BE: PUT /api/dhcp/config
    BE->>Controller: updateDhcpConfiguration()
    Controller->>Controller: Validate & Write config
    Controller->>Service: Reload DHCP Service
    Service-->>Controller: Status
    Controller-->>BE: Update response
    BE-->>API: Response
    API-->>UI: Update response

    UI->>API: controlDhcpServiceAPI('restart')
    API->>BE: POST /api/dhcp/service/restart
    BE->>Controller: controlDhcpService()
    Controller->>Service: Restart DHCP Service
    Service-->>Controller: Status
    Controller-->>BE: Status response
    BE-->>API: Status response
    API-->>UI: Status response
Loading

Poem

In burrows deep, a network grows,
With DHCP, the rabbit knows!
Forms and fields, all neat and bright,
Validated, saved just right.
Configs hop from UI to code,
Now every subnet finds its node.
🐇✨ Let the leases flow!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9755f5 and 212ae4e.

📒 Files selected for processing (3)
  • .gitignore (0 hunks)
  • apps/backend/drizzle/meta/0000_snapshot.json (1 hunks)
  • apps/backend/drizzle/meta/_journal.json (1 hunks)
💤 Files with no reviewable changes (1)
  • .gitignore
✅ Files skipped from review due to trivial changes (2)
  • apps/backend/drizzle/meta/_journal.json
  • apps/backend/drizzle/meta/0000_snapshot.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (7)
packages/shared/src/validators/__tests__/dhcpValidators.test.ts (2)

243-243: Consider if empty subnets should always be invalid.

The test marks an empty subnets array as invalid, but there might be legitimate scenarios where a DHCP configuration could temporarily have no subnets defined. Consider whether this validation rule aligns with real-world use cases.


106-140: Consider adding more edge case tests for utility functions.

While the current tests are good, consider adding more edge cases:

  • suggestIpRange: Test with different subnet sizes (/16, /8, etc.)
  • calculateBroadcastAddress: Test with non-standard subnet masks
  • generateDefaultDhcpConfig: Consider testing the immutability of returned objects
apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx (1)

111-121: Consider making default subnet values configurable.

The hardcoded default values for new subnets might not be suitable for all network environments. Consider making these configurable or deriving them from existing network configuration.

+  // Consider moving to configuration or calculating from existing subnets
+  const getDefaultSubnetValues = () => ({
+    network: '192.168.1.0',
+    netmask: '255.255.255.0',
+    rangeStart: '192.168.1.100',
+    rangeEnd: '192.168.1.200',
+    defaultGateway: '192.168.1.1',
+    domainNameServers: '8.8.8.8, 8.8.4.4',
+    broadcastAddress: '192.168.1.255',
+    subnetMask: '255.255.255.0'
+  });
+
   const addSubnet = () => {
     appendSubnet({
       id: uuidv4(),
-      network: '192.168.1.0',
-      netmask: '255.255.255.0',
-      rangeStart: '192.168.1.100',
-      rangeEnd: '192.168.1.200',
-      defaultGateway: '192.168.1.1',
-      domainNameServers: '8.8.8.8, 8.8.4.4',
-      broadcastAddress: '192.168.1.255',
-      subnetMask: '255.255.255.0'
+      ...getDefaultSubnetValues()
     });
   };
packages/shared/src/validators/dhcpFormValidator.ts (1)

87-87: Consider validating against reserved DHCP option codes.

The current validation allows codes 1-254, but several DHCP option codes are reserved and shouldn't be user-configurable. For example:

  • Code 0: Pad Option
  • Code 255: End Option
  • Codes 1-12: Basic network parameters (subnet mask, routers, etc.)

Consider maintaining a list of reserved codes and validating against them.

packages/shared/src/validators/dhcpTransformers.ts (1)

208-209: Verify subnet mask calculation for edge cases.

The host bits calculation using Math.log2(256 - maskOctets[rangeOctetIndex]) assumes standard subnet mask patterns. While the input should be validated by isValidNetmask, consider adding a safety check or comment explaining this assumption.

docs/implementation-plan/dhcp-config-implementation-plan.md (2)

7-7: Replace "exact same" for brevity.

Consider shortening the phrasing to improve readability. For example:

- The DHCP implementation will follow the exact same pattern as your existing DNS and HTTP configurations:
+ The DHCP implementation will follow the same pattern as your existing DNS and HTTP configurations:
🧰 Tools
🪛 LanguageTool

[style] ~7-~7: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...The DHCP implementation will follow the exact same pattern as your existing DNS and HTTP c...

(EN_WORDINESS_PREMIUM_EXACT_SAME)


1214-1214: Replace "exact same" for consistency.

Similarly, update the closing sentence:

- This plan follows the exact same patterns as your existing DNS and HTTP implementations, ensuring consistency and maintainability across your codebase.
+ This plan follows the same patterns as your existing DNS and HTTP implementations, ensuring consistency and maintainability across your codebase.
🧰 Tools
🪛 LanguageTool

[style] ~1214-~1214: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ... for development This plan follows the exact same patterns as your existing DNS and HTTP ...

(EN_WORDINESS_PREMIUM_EXACT_SAME)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8628b37 and b9755f5.

📒 Files selected for processing (14)
  • apps/backend/src/app.ts (2 hunks)
  • apps/backend/src/controllers/dhcpController.ts (1 hunks)
  • apps/backend/src/routes/dhcpRoutes.ts (1 hunks)
  • apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx (1 hunks)
  • apps/ui/src/lib/api/dhcp.ts (1 hunks)
  • apps/ui/src/lib/api/index.ts (1 hunks)
  • docs/implementation-plan/dhcp-config-implementation-plan.md (1 hunks)
  • packages/shared/src/index.ts (1 hunks)
  • packages/shared/src/types/dhcp.ts (1 hunks)
  • packages/shared/src/types/index.ts (1 hunks)
  • packages/shared/src/validators/__tests__/dhcpValidators.test.ts (1 hunks)
  • packages/shared/src/validators/dhcpFormValidator.ts (1 hunks)
  • packages/shared/src/validators/dhcpTransformers.ts (1 hunks)
  • packages/shared/src/validators/index.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/backend/src/routes/dhcpRoutes.ts (2)
apps/backend/src/controllers/dhcpController.ts (5)
  • getCurrentDhcpConfiguration (334-400)
  • updateDhcpConfiguration (263-331)
  • validateDhcpConfiguration (403-455)
  • getDhcpServiceStatus (458-493)
  • controlDhcpService (496-563)
apps/backend/src/middlewares/authMiddleware.ts (1)
  • protect (51-51)
apps/ui/src/lib/api/dhcp.ts (1)
packages/shared/src/types/dhcp.ts (3)
  • DhcpConfiguration (55-59)
  • DhcpUpdateResponse (98-106)
  • DhcpServiceResponse (108-116)
🪛 LanguageTool
docs/implementation-plan/dhcp-config-implementation-plan.md

[style] ~7-~7: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...The DHCP implementation will follow the exact same pattern as your existing DNS and HTTP c...

(EN_WORDINESS_PREMIUM_EXACT_SAME)


[style] ~1214-~1214: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ... for development This plan follows the exact same patterns as your existing DNS and HTTP ...

(EN_WORDINESS_PREMIUM_EXACT_SAME)

🔇 Additional comments (23)
apps/ui/src/lib/api/index.ts (1)

27-27: LGTM! Clean integration following established patterns.

The DHCP API export follows the same pattern as other API modules and maintains alphabetical ordering.

apps/backend/src/app.ts (2)

11-11: LGTM! Import follows established patterns.

The DHCP routes import is properly placed with other route imports.


38-38: LGTM! Route mounting follows API conventions.

The DHCP routes are properly mounted under /api/dhcp, maintaining consistency with other service routes.

packages/shared/src/types/index.ts (1)

3-3: LGTM! Consistent with established export patterns.

The DHCP types export maintains alphabetical ordering and follows the same pattern as other type exports.

apps/backend/src/routes/dhcpRoutes.ts (1)

1-28: Excellent route structure with appropriate security controls.

The DHCP routes are well-organized with a sensible security model:

  • Read operations (GET /config, GET /status) are publicly accessible
  • Write/control operations (PUT /config, POST /validate, POST /service/:action) are properly protected with authentication middleware
  • The route structure follows RESTful conventions and aligns well with the controller functions

The imports and route definitions are clean and consistent with the existing codebase patterns.

packages/shared/src/validators/__tests__/dhcpValidators.test.ts (2)

25-104: Excellent validator test coverage with comprehensive edge cases.

The validation function tests are thorough and well-organized:

  • IP address validation covers all major scenarios including edge cases
  • MAC address validation tests multiple formats (colon and dash separated)
  • Netmask validation properly tests contiguous bit requirements
  • Hostname validation covers RFC compliance rules
  • Network membership tests validate subnet calculations

The test structure is clean and easy to understand.


141-202: Robust round-trip transformation testing ensures data integrity.

The round-trip transformation test is particularly valuable as it verifies that data can be converted from form format to API format and back without loss of information. This is critical for maintaining data consistency across the frontend and backend.

The test covers all major configuration elements including subnets, host reservations, and global options.

packages/shared/src/index.ts (1)

28-50: LGTM! Well-organized DHCP exports.

The DHCP-related exports are comprehensive and well-structured. The naming convention with prefixes (e.g., dhcpIsValidIpAddress) effectively prevents naming conflicts with existing validators.

packages/shared/src/validators/index.ts (2)

10-11: LGTM! Consistent import pattern.

The DHCP validator imports follow the established pattern used for other validator modules in this file.


54-81: LGTM! Well-structured DHCP re-exports.

The DHCP validator and transformer re-exports are comprehensive and follow the established pattern. The aliasing for conflicting names (e.g., parseStringToArray as dhcpParseStringToArray) and type exports are properly handled.

apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx (1)

1-16: LGTM! Comprehensive imports for DHCP component.

The imports are well-organized and include all necessary dependencies for a feature-rich DHCP configuration component.

apps/ui/src/lib/api/dhcp.ts (6)

21-36: LGTM! Consistent error handling pattern.

The centralized error handling function provides consistent error formatting and proper error propagation throughout the API client.


39-57: LGTM! Well-structured GET configuration endpoint.

The function properly handles authentication, error cases, and response parsing with appropriate TypeScript typing.


59-79: LGTM! Proper PUT endpoint implementation.

The update function correctly sends form data and handles responses with proper error handling.


81-101: LGTM! Validation endpoint follows consistent pattern.

The validation endpoint maintains consistency with other API functions and provides proper error handling.


103-119: LGTM! Service status endpoint with proper error handling.

The status endpoint correctly handles authentication and provides appropriate error messages.


121-137: LGTM! Service control endpoint with dynamic actions.

The control endpoint properly parameterizes actions and maintains consistent error handling patterns.

packages/shared/src/types/dhcp.ts (3)

1-59: LGTM! Comprehensive DHCP configuration types.

The type definitions are well-structured with clear separation of concerns:

  • Core configuration interfaces (DhcpServerConfig, DhcpSubnet, etc.)
  • Proper use of optional properties
  • Logical composition with DhcpConfiguration extending base config

61-95: LGTM! Well-designed form types for UI compatibility.

The form types appropriately use string representations for numeric values (lease times) and flattened structures for easier form handling with react-hook-form. This separation from API types is a good design decision.


97-126: LGTM! Comprehensive API response types.

The response types provide good coverage for all API operations with:

  • Proper error handling structures
  • Service status definitions
  • Clear success/failure indicators
packages/shared/src/validators/dhcpFormValidator.ts (2)

4-13: LGTM!

The IPv4 address validation is correctly implemented with both format and range checks.


15-18: LGTM!

The MAC address validation correctly handles both colon and hyphen separators.

apps/backend/src/controllers/dhcpController.ts (1)

23-26:

Details

❌ Incorrect review comment

Ensure test directories exist in development environment.

The non-production paths use './test/dhcp/config' which might not exist. Consider creating these directories on startup or using a configuration-based approach.

Run this script to check if the test directories are properly created:


🏁 Script executed:

#!/bin/bash
# Check if test directories exist or are created by the application
fd -t d "test" -x echo "Found test directory: {}"
rg -A 5 "ensureDirectoryExists.*test" 

Length of output: 376


🏁 Script executed:

#!/bin/bash
set -e

# Show fs operations in dhcpController
echo "---- fs operations in dhcpController.ts ----"
rg -n "fs\." -C2 apps/backend/src/controllers/dhcpController.ts || true

# Search for mkdir usage anywhere in backend sources
echo "---- mkdir invocations in apps/backend/src ----"
rg -n "mkdir" -C2 apps/backend/src || true

# Search for ensureDir (fs-extra) usage
echo "---- ensureDir invocations in apps/backend/src ----"
rg -n "ensureDir" -C2 apps/backend/src || true

Length of output: 10764


Directories auto-created by existing helper
In apps/backend/src/controllers/dhcpController.ts, the calls

  • await ensureDirectoryExists(DHCPD_CONF_DIR) (line 278)
  • await ensureDirectoryExists(DHCPD_BACKUP_DIR) (line 279)
    automatically create ./test/dhcp/... directories at runtime. No manual setup is required.

Likely an incorrect or invalid review comment.

Comment on lines 18 to 675
export function DHCPConfig() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h4 className="text-sm font-medium leading-none">DHCP Server Status</h4>
<p className="text-sm text-muted-foreground">Enable or disable the DHCP server</p>
const [isLoading, setIsLoading] = React.useState(true);
const [isSaving, setIsSaving] = React.useState(false);
const [serviceStatus, setServiceStatus] = React.useState<'running' | 'stopped' | 'unknown'>('unknown');

const form = useForm<DhcpConfigFormValues>({
resolver: zodResolver(dhcpConfigSchema),
defaultValues: {
dhcpServerStatus: false,
domainName: 'local',
domainNameServers: '8.8.8.8, 8.8.4.4',
defaultLeaseTime: '86400',
maxLeaseTime: '604800',
authoritative: true,
ddnsUpdateStyle: 'none' as const,
subnets: [],
hostReservations: [],
globalOptions: []
}
});

const { fields: subnetFields, append: appendSubnet, remove: removeSubnet } = useFieldArray({
control: form.control,
name: 'subnets'
});

const { fields: hostFields, append: appendHost, remove: removeHost } = useFieldArray({
control: form.control,
name: 'hostReservations'
});

const { fields: optionFields, append: appendOption, remove: removeOption } = useFieldArray({
control: form.control,
name: 'globalOptions'
});

// Load existing configuration and service status
React.useEffect(() => {
const loadConfiguration = async () => {
try {
const [configResponse, statusResponse] = await Promise.all([
getDhcpConfigurationAPI(),
getDhcpServiceStatusAPI().catch(() => ({ data: { status: 'unknown' } }))
]);

const formData = transformDhcpApiToForm(configResponse.data);
form.reset(formData as any); // Type coercion for form reset
setServiceStatus(statusResponse.data.status as any);
} catch (error) {
console.error('Failed to load DHCP configuration:', error);
toast({
title: "Error",
description: "Failed to load DHCP configuration",
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};

loadConfiguration();
}, [form]);

// Submit handler
const onSubmit = async (data: DhcpConfigFormValues) => {
setIsSaving(true);
try {
await updateDhcpConfigurationAPI(data);
toast({
title: "Success",
description: "DHCP configuration updated successfully",
});

// Refresh service status
try {
const statusResponse = await getDhcpServiceStatusAPI();
setServiceStatus(statusResponse.data.status as any);
} catch (error) {
console.warn('Failed to refresh service status:', error);
}
} catch (error: any) {
toast({
title: "Error",
description: error?.data?.message || "Failed to update DHCP configuration",
variant: "destructive",
});
} finally {
setIsSaving(false);
}
};

// Add new subnet
const addSubnet = () => {
appendSubnet({
id: uuidv4(),
network: '192.168.1.0',
netmask: '255.255.255.0',
rangeStart: '192.168.1.100',
rangeEnd: '192.168.1.200',
defaultGateway: '192.168.1.1',
domainNameServers: '8.8.8.8, 8.8.4.4',
broadcastAddress: '192.168.1.255',
subnetMask: '255.255.255.0'
});
};

// Add new host reservation
const addHostReservation = () => {
appendHost({
id: uuidv4(),
hostname: '',
macAddress: '',
fixedAddress: ''
});
};

// Add new global option
const addGlobalOption = () => {
appendOption({
id: uuidv4(),
name: '',
value: ''
});
};

// Control service
const handleServiceAction = async (action: string) => {
try {
await controlDhcpServiceAPI(action);
const statusResponse = await getDhcpServiceStatusAPI();
setServiceStatus(statusResponse.data.status as any);
toast({
title: "Success",
description: `DHCP service ${action} completed successfully`,
});
} catch (error: any) {
toast({
title: "Error",
description: error?.data?.message || `Failed to ${action} DHCP service`,
variant: "destructive",
});
}
};

if (isLoading) {
return (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto"></div>
<p className="mt-2 text-sm text-muted-foreground">Loading DHCP configuration...</p>
</div>
<Switch />
</div>
<Separator />
<div className="grid gap-4">
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="start-ip">Start IP</Label>
<Input id="start-ip" placeholder="192.168.1.100" />
</div>
<div className="grid gap-2">
<Label htmlFor="end-ip">End IP</Label>
<Input id="end-ip" placeholder="192.168.1.200" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="subnet-mask">Subnet Mask</Label>
<Input id="subnet-mask" placeholder="255.255.255.0" />
);
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold">DHCP Configuration</h2>
<p className="text-muted-foreground">Configure your DHCP server settings</p>
</div>
<div className="grid gap-2">
<Label htmlFor="lease-time">Lease Time (hours)</Label>
<Input id="lease-time" type="number" placeholder="24" />
<div className="flex items-center gap-2">
<div className={`px-2 py-1 rounded-full text-xs font-medium ${
serviceStatus === 'running' ? 'bg-green-100 text-green-800' :
serviceStatus === 'stopped' ? 'bg-red-100 text-red-800' :
'bg-gray-100 text-gray-800'
}`}>
Service: {serviceStatus}
</div>
{serviceStatus === 'stopped' && (
<Button
type="button"
variant="outline"
size="sm"
onClick={() => handleServiceAction('start')}
>
Start Service
</Button>
)}
{serviceStatus === 'running' && (
<Button
type="button"
variant="outline"
size="sm"
onClick={() => handleServiceAction('restart')}
>
Restart Service
</Button>
)}
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="gateway">Default Gateway</Label>
<Input id="gateway" placeholder="192.168.1.1" />
</div>
<div className="grid gap-2">
<Label>Static Leases</Label>
<ScrollArea className="h-[200px] w-full rounded-md border">
<div className="p-4 space-y-4">
{Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="grid grid-cols-3 gap-2">
<Input placeholder="MAC Address" defaultValue={i === 0 ? "00:11:22:33:44:55" : "66:77:88:99:AA:BB"} />
<Input placeholder="IP Address" defaultValue={i === 0 ? "192.168.1.10" : "192.168.1.11"} />
<Button variant="outline" size="icon" className="w-full">+</Button>

<Tabs defaultValue="general" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="subnets">Subnets</TabsTrigger>
<TabsTrigger value="reservations">Host Reservations</TabsTrigger>
<TabsTrigger value="options">Global Options</TabsTrigger>
</TabsList>

<TabsContent value="general" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Server className="h-5 w-5" />
DHCP Server Settings
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<FormField
control={form.control}
name="dhcpServerStatus"
render={({ field }) => (
<FormItem className="flex items-center justify-between">
<div>
<FormLabel>DHCP Server Status</FormLabel>
<FormDescription>
Enable or disable the DHCP server
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>

<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="domainName"
render={({ field }) => (
<FormItem>
<FormLabel>Domain Name</FormLabel>
<FormControl>
<Input placeholder="example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="domainNameServers"
render={({ field }) => (
<FormItem>
<FormLabel>DNS Servers</FormLabel>
<FormControl>
<Input placeholder="8.8.8.8, 8.8.4.4" {...field} />
</FormControl>
<FormDescription>Comma-separated list of DNS servers</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
))}

<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="defaultLeaseTime"
render={({ field }) => (
<FormItem>
<FormLabel>Default Lease Time (seconds)</FormLabel>
<FormControl>
<Input placeholder="86400" {...field} />
</FormControl>
<FormDescription>Default: 86400 (24 hours)</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="maxLeaseTime"
render={({ field }) => (
<FormItem>
<FormLabel>Max Lease Time (seconds)</FormLabel>
<FormControl>
<Input placeholder="604800" {...field} />
</FormControl>
<FormDescription>Default: 604800 (7 days)</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>

<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="authoritative"
render={({ field }) => (
<FormItem className="flex items-center justify-between">
<div>
<FormLabel>Authoritative Server</FormLabel>
<FormDescription>
Act as the authoritative DHCP server for this network
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>

<FormField
control={form.control}
name="ddnsUpdateStyle"
render={({ field }) => (
<FormItem>
<FormLabel>DDNS Update Style</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select DDNS style" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value="interim">Interim</SelectItem>
<SelectItem value="standard">Standard</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
</TabsContent>

<TabsContent value="subnets" className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Network Subnets</h3>
<Button type="button" onClick={addSubnet} variant="outline">
<PlusCircle className="h-4 w-4 mr-2" />
Add Subnet
</Button>
</div>
</ScrollArea>

{subnetFields.map((subnet, index) => (
<Card key={subnet.id}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Network className="h-4 w-4" />
Subnet {index + 1}
</CardTitle>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => removeSubnet(index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name={`subnets.${index}.network`}
render={({ field }) => (
<FormItem>
<FormLabel>Network Address</FormLabel>
<FormControl>
<Input placeholder="192.168.1.0" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name={`subnets.${index}.netmask`}
render={({ field }) => (
<FormItem>
<FormLabel>Subnet Mask</FormLabel>
<FormControl>
<Input placeholder="255.255.255.0" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name={`subnets.${index}.rangeStart`}
render={({ field }) => (
<FormItem>
<FormLabel>Range Start</FormLabel>
<FormControl>
<Input placeholder="192.168.1.100" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name={`subnets.${index}.rangeEnd`}
render={({ field }) => (
<FormItem>
<FormLabel>Range End</FormLabel>
<FormControl>
<Input placeholder="192.168.1.200" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name={`subnets.${index}.defaultGateway`}
render={({ field }) => (
<FormItem>
<FormLabel>Default Gateway</FormLabel>
<FormControl>
<Input placeholder="192.168.1.1" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name={`subnets.${index}.domainNameServers`}
render={({ field }) => (
<FormItem>
<FormLabel>DNS Servers</FormLabel>
<FormControl>
<Input placeholder="8.8.8.8, 8.8.4.4" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
))}

{subnetFields.length === 0 && (
<Card className="border-dashed">
<CardContent className="flex flex-col items-center justify-center py-8">
<Network className="h-8 w-8 text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground">No subnets configured</p>
<Button type="button" onClick={addSubnet} variant="ghost" className="mt-2">
Add your first subnet
</Button>
</CardContent>
</Card>
)}
</TabsContent>

<TabsContent value="reservations" className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Static Host Reservations</h3>
<Button type="button" onClick={addHostReservation} variant="outline">
<PlusCircle className="h-4 w-4 mr-2" />
Add Reservation
</Button>
</div>

{hostFields.map((host, index) => (
<Card key={host.id}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Users className="h-4 w-4" />
Host Reservation {index + 1}
</CardTitle>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => removeHost(index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-3 gap-4">
<FormField
control={form.control}
name={`hostReservations.${index}.hostname`}
render={({ field }) => (
<FormItem>
<FormLabel>Hostname</FormLabel>
<FormControl>
<Input placeholder="printer-01" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name={`hostReservations.${index}.macAddress`}
render={({ field }) => (
<FormItem>
<FormLabel>MAC Address</FormLabel>
<FormControl>
<Input placeholder="00:11:22:33:44:55" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name={`hostReservations.${index}.fixedAddress`}
render={({ field }) => (
<FormItem>
<FormLabel>Fixed IP Address</FormLabel>
<FormControl>
<Input placeholder="192.168.1.10" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
))}

{hostFields.length === 0 && (
<Card className="border-dashed">
<CardContent className="flex flex-col items-center justify-center py-8">
<Users className="h-8 w-8 text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground">No host reservations configured</p>
<Button type="button" onClick={addHostReservation} variant="ghost" className="mt-2">
Add your first reservation
</Button>
</CardContent>
</Card>
)}
</TabsContent>

<TabsContent value="options" className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Global DHCP Options</h3>
<Button type="button" onClick={addGlobalOption} variant="outline">
<PlusCircle className="h-4 w-4 mr-2" />
Add Option
</Button>
</div>

{optionFields.map((option, index) => (
<Card key={option.id}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Settings className="h-4 w-4" />
Option {index + 1}
</CardTitle>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => removeOption(index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name={`globalOptions.${index}.name`}
render={({ field }) => (
<FormItem>
<FormLabel>Option Name</FormLabel>
<FormControl>
<Input placeholder="ntp-servers" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name={`globalOptions.${index}.value`}
render={({ field }) => (
<FormItem>
<FormLabel>Option Value</FormLabel>
<FormControl>
<Input placeholder="pool.ntp.org" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
))}

{optionFields.length === 0 && (
<Card className="border-dashed">
<CardContent className="flex flex-col items-center justify-center py-8">
<Settings className="h-8 w-8 text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground">No global options configured</p>
<Button type="button" onClick={addGlobalOption} variant="ghost" className="mt-2">
Add your first option
</Button>
</CardContent>
</Card>
)}
</TabsContent>
</Tabs>

<div className="flex gap-4">
<Button type="submit" disabled={isSaving}>
{isSaving ? 'Saving...' : 'Save DHCP Configuration'}
</Button>
<Button
type="button"
variant="outline"
onClick={() => form.reset()}
disabled={isSaving}
>
Reset
</Button>
</div>
</div>
<Button>Save DHCP Configuration</Button>
</div>
</form>
</Form>
);
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider component decomposition for maintainability.

This component is quite large (675 lines) and handles multiple responsibilities. Consider breaking it down into smaller, focused components for better maintainability.

Potential decomposition:

  • DHCPGeneralSettings - General configuration tab
  • DHCPSubnetsTab - Subnet management
  • DHCPHostReservationsTab - Host reservations management
  • DHCPGlobalOptionsTab - Global options management
  • DHCPServiceStatus - Service status and controls
  • DHCPSubnetCard - Individual subnet configuration
  • DHCPHostReservationCard - Individual host reservation

This would improve:

  • Code reusability
  • Testing granularity
  • Maintainability
  • Performance (smaller re-render scope)
🤖 Prompt for AI Agents
In apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx around lines 18 to
675, the DHCPConfig component is very large and handles multiple
responsibilities, making it hard to maintain. Refactor by decomposing this
component into smaller focused components such as DHCPGeneralSettings for the
general tab, DHCPSubnetsTab for subnet management, DHCPHostReservationsTab for
host reservations, DHCPGlobalOptionsTab for global options, DHCPServiceStatus
for service status controls, and individual card components like DHCPSubnetCard
and DHCPHostReservationCard. Move the relevant JSX and logic into these
components and update DHCPConfig to compose them, improving maintainability,
reusability, and performance.

try {
await controlDhcpServiceAPI(action);
const statusResponse = await getDhcpServiceStatusAPI();
setServiceStatus(statusResponse.data.status as any);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type coercion for service status.

Remove the type coercion and ensure proper typing.

-      setServiceStatus(statusResponse.data.status as any);
+      setServiceStatus(statusResponse.data.status);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setServiceStatus(statusResponse.data.status as any);
setServiceStatus(statusResponse.data.status);
🤖 Prompt for AI Agents
In apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx at line 148, remove
the use of 'as any' type coercion on statusResponse.data.status. Instead, define
or use the correct TypeScript type for the service status and apply it directly
to ensure proper typing and type safety.

// Refresh service status
try {
const statusResponse = await getDhcpServiceStatusAPI();
setServiceStatus(statusResponse.data.status as any);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type coercion for service status.

Another instance of unnecessary type coercion that should be resolved properly.

-        setServiceStatus(statusResponse.data.status as any);
+        setServiceStatus(statusResponse.data.status);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setServiceStatus(statusResponse.data.status as any);
setServiceStatus(statusResponse.data.status);
🤖 Prompt for AI Agents
In apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx at line 94, the code
uses an unnecessary type coercion with 'as any' when setting the service status.
To fix this, replace the 'as any' cast by properly typing the statusResponse
data or by defining the expected type for service status and ensuring the
assignment matches that type without coercion.

Comment on lines +64 to +65
form.reset(formData as any); // Type coercion for form reset
setServiceStatus(statusResponse.data.status as any);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Eliminate type coercion with proper typing.

The as any type coercion is a code smell that bypasses TypeScript's type safety. This suggests a mismatch between the form data structure and the expected types.

-        const formData = transformDhcpApiToForm(configResponse.data);
-        form.reset(formData as any); // Type coercion for form reset
-        setServiceStatus(statusResponse.data.status as any);
+        const formData = transformDhcpApiToForm(configResponse.data);
+        form.reset(formData);
+        setServiceStatus(statusResponse.data.status);

Verify that the transformDhcpApiToForm function returns data compatible with DhcpConfigFormValues:


🏁 Script executed:

#!/bin/bash
# Description: Check the return type of transformDhcpApiToForm function
ast-grep --pattern 'export function transformDhcpApiToForm($_): $TYPE {
  $$$
}'

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Locate the definition and signature of transformDhcpApiToForm to verify its return type
rg --context 3 "transformDhcpApiToForm" -n .

Length of output: 8447


🏁 Script executed:

#!/bin/bash
# Display initializations around form and serviceStatus in DHCPConfig.tsx
sed -n '1,80p' apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx

Length of output: 3239


🏁 Script executed:

#!/bin/bash
# Locate and show the definition of getDhcpServiceStatusAPI to verify its return type
rg --context 5 "getDhcpServiceStatusAPI" -n apps/ui/src/lib/api/dhcp

Length of output: 133


🏁 Script executed:

#!/bin/bash
# Search for getDhcpServiceStatusAPI definition across the repo
rg --context 5 "getDhcpServiceStatusAPI" -n .

Length of output: 5392


🏁 Script executed:

#!/bin/bash
# Display imports and DhcpServiceResponse usage in DHCP API wrapper
sed -n '1,80p' apps/ui/src/lib/api/dhcp.ts

Length of output: 2279


🏁 Script executed:

#!/bin/bash
# Verify the status field type in DhcpServiceResponse
rg --context 3 "export (interface|type) DhcpServiceResponse" -n packages/shared/src

Length of output: 494


Update typing and remove unnecessary any casts

The transformDhcpApiToForm helper already returns a DhcpConfigFormValues, so you can call form.reset(formData) without as any. However, the DHCP service status may also return "failed", which isn’t included in the current state type ('running' | 'stopped' | 'unknown'). You should either expand the state type to match DhcpServiceResponse['status'] or map "failed" to one of the existing values.

Files to update:

  • apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx

Suggested diff:

--- a/apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx
+++ b/apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx
@@ -6,7 +6,8 @@
 import { v4 as uuidv4 } from 'uuid';
-import { dhcpConfigSchema, transformDhcpApiToForm, type DhcpConfigFormValues } from '@server-manager/shared/validators';
+import {
+  dhcpConfigSchema,
+  transformDhcpApiToForm,
+  type DhcpConfigFormValues,
+  type DhcpServiceResponse
+} from '@server-manager/shared/validators';
 
 export function DHCPConfig() {
   const [isLoading, setIsLoading] = React.useState(true);
@@ -8,7 +9,8 @@ export function DHCPConfig() {
   const [isSaving, setIsSaving] = React.useState(false);
-  const [serviceStatus, setServiceStatus] = React.useState<'running' | 'stopped' | 'unknown'>('unknown');
+  // Include all possible statuses from the API
+  const [serviceStatus, setServiceStatus] = React.useState<DhcpServiceResponse['status']>('unknown');
 
   const form = useForm<DhcpConfigFormValues>({
     resolver: zodResolver(dhcpConfigSchema),
@@ -60,7 +62,7 @@   React.useEffect(() => {
         const formData = transformDhcpApiToForm(configResponse.data);
-        form.reset(formData as any); // Type coercion for form reset
-        setServiceStatus(statusResponse.data.status as any);
+        form.reset(formData);
+        setServiceStatus(statusResponse.data.status);
       } catch (error) {
         console.error('Failed to load DHCP configuration:', error);
  • Remove the as any casts on form.reset and setServiceStatus
  • Import and use DhcpServiceResponse['status'] for the React state type
  • Adjust mapping if you prefer to treat "failed" differently
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
form.reset(formData as any); // Type coercion for form reset
setServiceStatus(statusResponse.data.status as any);
import { v4 as uuidv4 } from 'uuid';
import {
dhcpConfigSchema,
transformDhcpApiToForm,
type DhcpConfigFormValues,
type DhcpServiceResponse
} from '@server-manager/shared/validators';
export function DHCPConfig() {
const [isLoading, setIsLoading] = React.useState(true);
const [isSaving, setIsSaving] = React.useState(false);
// Include all possible statuses from the API
const [serviceStatus, setServiceStatus] = React.useState<DhcpServiceResponse['status']>('unknown');
const form = useForm<DhcpConfigFormValues>({
resolver: zodResolver(dhcpConfigSchema),
});
React.useEffect(() => {
// ...fetch configResponse & statusResponse...
const formData = transformDhcpApiToForm(configResponse.data);
form.reset(formData);
setServiceStatus(statusResponse.data.status);
}, []);
// ...rest of component...
}
🤖 Prompt for AI Agents
In apps/ui/src/features/configuration/dhcp/DHCPConfig.tsx around lines 64-65,
remove the unnecessary 'as any' type coercions on form.reset and
setServiceStatus by ensuring formData is typed as DhcpConfigFormValues and the
service status state uses DhcpServiceResponse['status'] type. Update the React
state type for serviceStatus to include all possible status values from
DhcpServiceResponse, including "failed", or map "failed" to an existing status
if preferred, to maintain type safety without bypassing TypeScript checks.

Comment on lines +13 to +19
function getAuthHeaders() {
const token = localStorage.getItem('authToken');
return {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` })
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add SSR compatibility check for localStorage access.

Accessing localStorage directly can cause issues in server-side rendering environments where localStorage is undefined.

 function getAuthHeaders() {
-  const token = localStorage.getItem('authToken');
+  const token = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null;
   return {
     'Content-Type': 'application/json',
     ...(token && { 'Authorization': `Bearer ${token}` })
   };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getAuthHeaders() {
const token = localStorage.getItem('authToken');
return {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` })
};
}
function getAuthHeaders() {
const token = typeof window !== 'undefined'
? localStorage.getItem('authToken')
: null;
return {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` })
};
}
🤖 Prompt for AI Agents
In apps/ui/src/lib/api/dhcp.ts around lines 13 to 19, the function
getAuthHeaders accesses localStorage directly, which breaks in server-side
rendering environments where localStorage is undefined. To fix this, add a check
to ensure localStorage is available before accessing it, such as verifying if
typeof window !== 'undefined' and window.localStorage exists. Only retrieve the
token if localStorage is accessible; otherwise, return headers without the
Authorization field.

Comment on lines +110 to +132
netmask: z.string().refine(isValidNetmask, {
message: "Invalid subnet mask"
}),
rangeStart: z.string().refine(isValidIpAddress, {
message: "Invalid start IP address"
}),
rangeEnd: z.string().refine(isValidIpAddress, {
message: "Invalid end IP address"
}),
defaultGateway: z.string().refine(isValidIpAddress, {
message: "Invalid gateway IP address"
}),
domainNameServers: z.string().min(1, "At least one DNS server is required"),
broadcastAddress: z.string().refine((val) => {
return val === '' || isValidIpAddress(val);
}, {
message: "Invalid broadcast address"
}),
subnetMask: z.string().refine((val) => {
return val === '' || isValidNetmask(val);
}, {
message: "Invalid subnet mask"
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove redundant subnet mask field to avoid confusion.

The schema defines both netmask (required) and subnetMask (optional) fields, which appear to serve the same purpose. This redundancy could lead to confusion and potential inconsistencies.

Consider removing the subnetMask field or clearly documenting the distinction if they serve different purposes.

🤖 Prompt for AI Agents
In packages/shared/src/validators/dhcpFormValidator.ts between lines 110 and
132, remove the redundant subnetMask field from the schema since netmask already
serves the purpose of defining the subnet mask. This will avoid confusion and
potential inconsistencies by having only one field for the subnet mask
validation.

Comment on lines +496 to +507
export const controlDhcpService = async (req: AuthRequest, res: Response) => {
try {
const { action } = req.params;
const validActions = ['start', 'stop', 'restart', 'reload', 'status'];

if (!validActions.includes(action)) {
return res.status(400).json({
success: false,
message: `Invalid action: ${action}. Valid actions are: ${validActions.join(', ')}`
});
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add rate limiting for service control actions.

The service control endpoint lacks rate limiting, which could allow rapid start/stop/restart actions that might destabilize the service. Consider implementing rate limiting to prevent abuse.

Would you like me to generate a rate limiting implementation using a token bucket or sliding window approach?

🤖 Prompt for AI Agents
In apps/backend/src/controllers/dhcpController.ts around lines 496 to 507, the
controlDhcpService function lacks rate limiting, allowing rapid repeated actions
that could destabilize the service. Implement rate limiting middleware or logic
using a token bucket or sliding window algorithm to limit how frequently a
client can invoke start/stop/restart actions. Integrate this rate limiting
before processing the action parameter and return an appropriate error response
when the limit is exceeded.

Comment on lines +78 to +89
if (existsSync(filePath)) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupFilePath = path.join(backupDir, `${filename}.${timestamp}.backup`);

try {
const existingContent = await readFile(filePath, 'utf8');
await writeFile(backupFilePath, existingContent, 'utf8');
logger.info(`Backup created: ${backupFilePath}`);
} catch (error) {
logger.warn(`Failed to create backup for ${filePath}:`, error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement backup rotation to prevent disk space issues.

The current implementation creates unlimited timestamped backups which could fill disk space over time. Consider implementing a rotation strategy to keep only the N most recent backups.

Add backup rotation logic:

   // Create backup if file exists
   if (existsSync(filePath)) {
     const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
     const backupFilePath = path.join(backupDir, `${filename}.${timestamp}.backup`);
     
     try {
       const existingContent = await readFile(filePath, 'utf8');
       await writeFile(backupFilePath, existingContent, 'utf8');
       logger.info(`Backup created: ${backupFilePath}`);
+      
+      // Rotate backups - keep only the 10 most recent
+      const backups = await fs.promises.readdir(backupDir);
+      const fileBackups = backups
+        .filter(f => f.startsWith(`${filename}.`) && f.endsWith('.backup'))
+        .sort()
+        .reverse();
+      
+      // Remove old backups
+      for (let i = 10; i < fileBackups.length; i++) {
+        await fs.promises.unlink(path.join(backupDir, fileBackups[i]));
+        logger.info(`Removed old backup: ${fileBackups[i]}`);
+      }
     } catch (error) {
       logger.warn(`Failed to create backup for ${filePath}:`, error);
     }
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/backend/src/controllers/dhcpController.ts around lines 78 to 89, the
backup creation code currently saves unlimited timestamped backups, risking disk
space exhaustion. Implement backup rotation by listing existing backups in the
backupDir matching the filename pattern, sorting them by creation or timestamp,
and deleting the oldest backups to keep only a fixed number (e.g., the N most
recent). Add this rotation logic after successfully creating a new backup to
maintain a controlled number of backup files.

…apshot and journal JSON files for SQLite schema versioning and tracking user table structure.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants