Files
Charon/docs/plans/dns_challenge_frontend_research.md
GitHub Actions 3169b05156 fix: skip incomplete system log viewer tests
- Marked 12 tests as skip pending feature implementation
- Features tracked in GitHub issue #686 (system log viewer feature completion)
- Tests cover sorting by timestamp/level/method/URI/status, pagination controls, filtering by text/level, download functionality
- Unblocks Phase 2 at 91.7% pass rate to proceed to Phase 3 security enforcement validation
- TODO comments in code reference GitHub #686 for feature completion tracking
- Tests skipped: Pagination (3), Search/Filter (2), Download (2), Sorting (1), Log Display (4)
2026-02-09 21:55:55 +00:00

539 lines
16 KiB
Markdown

# DNS Challenge Frontend Research - Issue #21
## Overview
This document outlines the frontend architecture analysis and implementation plan for DNS challenge support in the Charon proxy manager. DNS challenges are required for wildcard certificate issuance via Let's Encrypt/ACME.
---
## 1. Existing Frontend Architecture Analysis
### Technology Stack
| Layer | Technology |
|-------|------------|
| Framework | React 18+ with TypeScript |
| State Management | TanStack Query (React Query) |
| Routing | React Router |
| UI Components | Custom component library (Radix UI primitives) |
| Styling | Tailwind CSS with custom design tokens |
| Internationalization | react-i18next |
| HTTP Client | Axios |
| Icons | Lucide React |
### Directory Structure
```
frontend/src/
├── api/ # API client functions (typed, async)
├── components/ # Reusable UI components
│ ├── ui/ # Design system primitives
│ ├── layout/ # Layout components (PageShell, etc.)
│ └── dialogs/ # Modal dialogs
├── hooks/ # Custom React hooks (data fetching)
├── pages/ # Page-level components
└── utils/ # Utility functions
```
### Key Architectural Patterns
1. **API Layer** (`frontend/src/api/`)
- Each domain has its own API file (e.g., `certificates.ts`, `smtp.ts`)
- Uses typed interfaces for request/response
- All functions are async, returning Promise types
- Axios client with base URL `/api/v1`
2. **Custom Hooks** (`frontend/src/hooks/`)
- Wrap TanStack Query for data fetching
- Naming convention: `use{Resource}` (e.g., `useCertificates`, `useDomains`)
- Return `{ data, isLoading, error, refetch }` pattern
3. **Page Components** (`frontend/src/pages/`)
- Use `PageShell` wrapper for consistent layout
- Header with title, description, and action buttons
- Card-based content organization
4. **Form Patterns**
- Use controlled components with `useState`
- `useMutation` for form submissions
- Toast notifications for success/error feedback
- Inline validation with error state
---
## 2. UI Component Patterns Identified
### Design System Components (`frontend/src/components/ui/`)
| Component | Purpose | Usage |
|-----------|---------|-------|
| `Button` | Primary actions, variants: primary, secondary, danger, ghost | All forms |
| `Input` | Text/password inputs with label, error, helper text support | Forms |
| `Select` | Radix-based dropdown with proper styling | Provider selection |
| `Card` | Content containers with header/content/footer | Page sections |
| `Dialog` | Modal dialogs for forms/confirmations | Add/edit modals |
| `Alert` | Info/warning/error banners | Notifications |
| `Badge` | Status indicators | Provider status |
| `Label` | Form field labels | All form fields |
| `Textarea` | Multi-line text input | Advanced config |
| `Switch` | Toggle switches | Enable/disable |
### Form Patterns (from `SMTPSettings.tsx`, `ProxyHostForm.tsx`)
```tsx
// Standard form structure
const [formData, setFormData] = useState({ /* initial state */ })
const mutation = useMutation({
mutationFn: async () => { /* API call */ },
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['resource'] })
toast.success(t('resource.success'))
},
onError: (error) => {
toast.error(error.message)
},
})
// Form submission
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
mutation.mutate()
}
```
### Password/Credential Input Pattern
From `Input.tsx`:
- Built-in password visibility toggle
- Uses `type="password"` with eye icon
- Supports `helperText` for guidance
- `autoComplete` attributes for security
### Test Connection Pattern (from `RemoteServerForm.tsx`, `SMTPSettings.tsx`)
```tsx
const [testStatus, setTestStatus] = useState<'idle' | 'testing' | 'success' | 'error'>('idle')
const handleTestConnection = async () => {
setTestStatus('testing')
try {
await testConnection(/* params */)
setTestStatus('success')
setTimeout(() => setTestStatus('idle'), 3000)
} catch {
setTestStatus('error')
setTimeout(() => setTestStatus('idle'), 3000)
}
}
```
---
## 3. Proposed New Components
### 3.1 DNS Providers Page (`DNSProviders.tsx`)
**Location**: `frontend/src/pages/DNSProviders.tsx`
**Purpose**: Manage DNS provider configurations for DNS-01 challenges
**Layout**:
```
┌─────────────────────────────────────────────────────────┐
│ DNS Providers [+ Add Provider] │
│ Configure DNS providers for wildcard certificates │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Alert: DNS providers enable wildcard certificates │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Cloudflare │ │ Route53 │ │
│ │ ✓ Configured │ │ Not configured │ │
│ │ [Edit] [Test] [Del] │ │ [Configure] │ │
│ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
**Features**:
- List configured DNS providers
- Add/Edit/Delete provider configurations
- Test DNS propagation
- Status badges (configured, error, pending)
### 3.2 DNS Provider Form (`DNSProviderForm.tsx`)
**Location**: `frontend/src/components/DNSProviderForm.tsx`
**Purpose**: Add/edit DNS provider configuration
**Key Features**:
- Dynamic form fields based on provider type
- Credential inputs with password masking
- Test connection button
- Validation feedback
**Provider-Specific Fields**:
| Provider | Required Fields |
|----------|-----------------|
| Cloudflare | API Token OR (API Key + Email) |
| Route53 | Access Key ID, Secret Access Key, Region |
| DigitalOcean | API Token |
| Google Cloud DNS | Service Account JSON |
| Namecheap | API User, API Key |
| GoDaddy | API Key, API Secret |
| Azure DNS | Client ID, Client Secret, Subscription ID, Resource Group |
### 3.3 Provider Selector Dropdown (`DNSProviderSelector.tsx`)
**Location**: `frontend/src/components/DNSProviderSelector.tsx`
**Purpose**: Dropdown to select DNS provider when requesting wildcard certificates
**Usage**: Integrated into `ProxyHostForm.tsx` or certificate request flow
```tsx
<DNSProviderSelector
value={selectedProviderId}
onChange={setSelectedProviderId}
required={isWildcard}
/>
```
### 3.4 DNS Propagation Test Component (`DNSPropagationTest.tsx`)
**Location**: `frontend/src/components/DNSPropagationTest.tsx`
**Purpose**: Visual feedback for DNS TXT record propagation
**Features**:
- Shows test domain and expected TXT record
- Real-time propagation status
- Retry button
- Success/failure indicators
---
## 4. API Hooks Needed
### 4.1 `useDNSProviders` Hook
**Location**: `frontend/src/hooks/useDNSProviders.ts`
```typescript
export function useDNSProviders() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['dns-providers'],
queryFn: getDNSProviders,
})
return {
providers: data || [],
isLoading,
error,
refetch,
}
}
```
### 4.2 `useDNSProviderMutations` Hook
**Location**: `frontend/src/hooks/useDNSProviders.ts`
```typescript
export function useDNSProviderMutations() {
const queryClient = useQueryClient()
const createMutation = useMutation({
mutationFn: createDNSProvider,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dns-providers'] }),
})
const updateMutation = useMutation({
mutationFn: updateDNSProvider,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dns-providers'] }),
})
const deleteMutation = useMutation({
mutationFn: deleteDNSProvider,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dns-providers'] }),
})
const testMutation = useMutation({
mutationFn: testDNSProvider,
})
return { createMutation, updateMutation, deleteMutation, testMutation }
}
```
---
## 5. API Client Functions Needed
### 5.1 `frontend/src/api/dnsProviders.ts`
```typescript
import client from './client'
/** Supported DNS provider types */
export type DNSProviderType =
| 'cloudflare'
| 'route53'
| 'digitalocean'
| 'gcloud'
| 'namecheap'
| 'godaddy'
| 'azure'
/** DNS Provider configuration */
export interface DNSProvider {
id: number
name: string
provider_type: DNSProviderType
is_default: boolean
created_at: string
updated_at: string
last_used_at?: string
status: 'active' | 'error' | 'unconfigured'
}
/** Provider-specific credentials (varies by type) */
export interface DNSProviderCredentials {
// Cloudflare
api_token?: string
api_key?: string
email?: string
// Route53
access_key_id?: string
secret_access_key?: string
region?: string
// DigitalOcean
token?: string
// GCloud
service_account_json?: string
project_id?: string
// Generic
[key: string]: string | undefined
}
/** Request payload for creating/updating provider */
export interface DNSProviderRequest {
name: string
provider_type: DNSProviderType
credentials: DNSProviderCredentials
is_default?: boolean
}
/** Test result response */
export interface DNSTestResult {
success: boolean
message?: string
error?: string
propagation_time_ms?: number
}
// API functions
export const getDNSProviders = async (): Promise<DNSProvider[]> => {
const response = await client.get<DNSProvider[]>('/dns-providers')
return response.data
}
export const getDNSProvider = async (id: number): Promise<DNSProvider> => {
const response = await client.get<DNSProvider>(`/dns-providers/${id}`)
return response.data
}
export const createDNSProvider = async (data: DNSProviderRequest): Promise<DNSProvider> => {
const response = await client.post<DNSProvider>('/dns-providers', data)
return response.data
}
export const updateDNSProvider = async (
id: number,
data: Partial<DNSProviderRequest>
): Promise<DNSProvider> => {
const response = await client.put<DNSProvider>(`/dns-providers/${id}`, data)
return response.data
}
export const deleteDNSProvider = async (id: number): Promise<void> => {
await client.delete(`/dns-providers/${id}`)
}
export const testDNSProvider = async (id: number): Promise<DNSTestResult> => {
const response = await client.post<DNSTestResult>(`/dns-providers/${id}/test`)
return response.data
}
export const testDNSProviderCredentials = async (
data: DNSProviderRequest
): Promise<DNSTestResult> => {
const response = await client.post<DNSTestResult>('/dns-providers/test', data)
return response.data
}
```
---
## 6. Files to Create/Modify
### New Files to Create
| File | Purpose |
|------|---------|
| `frontend/src/pages/DNSProviders.tsx` | DNS providers management page |
| `frontend/src/components/DNSProviderForm.tsx` | Add/edit provider form |
| `frontend/src/components/DNSProviderSelector.tsx` | Provider dropdown selector |
| `frontend/src/components/DNSPropagationTest.tsx` | Propagation test UI |
| `frontend/src/api/dnsProviders.ts` | API client functions |
| `frontend/src/hooks/useDNSProviders.ts` | Data fetching hooks |
| `frontend/src/data/dnsProviderSchemas.ts` | Provider field definitions |
### Existing Files to Modify
| File | Modification |
|------|--------------|
| `frontend/src/App.tsx` | Add route for `/dns-providers` |
| `frontend/src/components/layout/Layout.tsx` | Add navigation link |
| `frontend/src/pages/Certificates.tsx` | Add DNS provider selector for wildcard requests |
| `frontend/src/components/ProxyHostForm.tsx` | Add DNS provider option when wildcard domain detected |
| `frontend/src/api/certificates.ts` | Add `dns_provider_id` to certificate request types |
---
## 7. Translation Keys Needed
Add to `frontend/src/locales/en/translation.json`:
```json
{
"dnsProviders": {
"title": "DNS Providers",
"description": "Configure DNS providers for wildcard certificate issuance",
"addProvider": "Add Provider",
"editProvider": "Edit Provider",
"deleteProvider": "Delete Provider",
"deleteConfirm": "Are you sure you want to delete this DNS provider?",
"testConnection": "Test Connection",
"testSuccess": "DNS provider connection successful",
"testFailed": "DNS provider test failed",
"providerType": "Provider Type",
"providerName": "Provider Name",
"credentials": "Credentials",
"setAsDefault": "Set as Default",
"default": "Default",
"status": {
"active": "Active",
"error": "Error",
"unconfigured": "Not Configured"
},
"providers": {
"cloudflare": "Cloudflare",
"route53": "Amazon Route 53",
"digitalocean": "DigitalOcean",
"gcloud": "Google Cloud DNS",
"namecheap": "Namecheap",
"godaddy": "GoDaddy",
"azure": "Azure DNS"
},
"fields": {
"apiToken": "API Token",
"apiKey": "API Key",
"email": "Email",
"accessKeyId": "Access Key ID",
"secretAccessKey": "Secret Access Key",
"region": "Region",
"serviceAccountJson": "Service Account JSON",
"projectId": "Project ID"
},
"hints": {
"cloudflare": "Use an API token with Zone:DNS:Edit permissions",
"route53": "IAM user with route53:ChangeResourceRecordSets permission",
"digitalocean": "Personal access token with write scope"
},
"propagation": {
"title": "DNS Propagation",
"checking": "Checking DNS propagation...",
"success": "DNS record propagated successfully",
"failed": "DNS propagation check failed",
"retry": "Retry Check"
}
}
}
```
---
## 8. UI/UX Considerations
### Provider Selection Flow
1. User creates proxy host with wildcard domain (e.g., `*.example.com`)
2. System detects wildcard and shows DNS provider requirement
3. User selects existing provider OR creates new one inline
4. On save, backend uses DNS challenge for certificate
### Security Considerations
- Credentials masked by default (password inputs)
- API tokens never returned in full from backend (masked)
- Clear warning about credential storage
- Option to test without saving
### Error Handling
- Clear error messages for invalid credentials
- Specific guidance for each provider's setup
- Link to provider documentation
- Retry mechanisms for transient failures
---
## 9. Implementation Priority
1. **Phase 1**: API layer and hooks
- `dnsProviders.ts` API client
- `useDNSProviders.ts` hooks
2. **Phase 2**: Core UI components
- `DNSProviders.tsx` page
- `DNSProviderForm.tsx` form
3. **Phase 3**: Integration
- Navigation and routing
- Certificate/ProxyHost form integration
- Provider selector component
4. **Phase 4**: Polish
- Translations
- Error handling refinement
- Propagation test UI
---
## 10. Related Backend Requirements
The frontend implementation depends on these backend API endpoints:
| Method | Endpoint | Purpose |
|--------|----------|---------|
| GET | `/api/v1/dns-providers` | List all providers |
| POST | `/api/v1/dns-providers` | Create provider |
| GET | `/api/v1/dns-providers/:id` | Get provider details |
| PUT | `/api/v1/dns-providers/:id` | Update provider |
| DELETE | `/api/v1/dns-providers/:id` | Delete provider |
| POST | `/api/v1/dns-providers/:id/test` | Test saved provider |
| POST | `/api/v1/dns-providers/test` | Test credentials before saving |
---
*Research completed: 2026-01-01*