- Add comprehensive database migrations (001-024) for schema evolution - Enhance API schema with expanded type definitions and resolvers - Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth - Implement new services: AI optimization, billing, blockchain, compliance, marketplace - Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage) - Update Crossplane provider with enhanced VM management capabilities - Add comprehensive test suite for API endpoints and services - Update frontend components with improved GraphQL subscriptions and real-time updates - Enhance security configurations and headers (CSP, CORS, etc.) - Update documentation and configuration files - Add new CI/CD workflows and validation scripts - Implement design system improvements and UI enhancements
1035 lines
28 KiB
Markdown
1035 lines
28 KiB
Markdown
# Infrastructure Documentation Dashboard - Complete Implementation Steps
|
|
|
|
## Overview
|
|
|
|
This document provides step-by-step implementation instructions for all enhancements, organized by phase and priority. Each step includes specific actions, file paths, code structure, and time estimates.
|
|
|
|
---
|
|
|
|
## Phase 1: Critical Foundation (Weeks 1-2) - 80-120 hours
|
|
|
|
### Step 1.1: Install Dependencies
|
|
**Time:** 15 minutes
|
|
**Action:** Add required packages to package.json
|
|
|
|
```bash
|
|
npm install html2canvas jspdf xlsx mapbox-gl react-map-gl @tanstack/react-virtual
|
|
```
|
|
|
|
**Files to Modify:**
|
|
- `package.json`
|
|
|
|
**Verification:**
|
|
- Run `npm install`
|
|
- Verify packages in node_modules
|
|
|
|
---
|
|
|
|
### Step 1.2: Create Validation Schemas
|
|
**Time:** 2-3 hours
|
|
**File:** `src/lib/validation/schemas/infrastructure.ts`
|
|
|
|
**Implementation Steps:**
|
|
1. Import Zod
|
|
2. Create `countrySchema`:
|
|
```typescript
|
|
export const countrySchema = z.object({
|
|
name: z.string().min(1),
|
|
region: z.enum(['Africa (Sub-Saharan)', 'Middle East & North Africa', 'Americas', 'Asia-Pacific', 'Europe']),
|
|
relationshipType: z.enum(['Full Diplomatic Relations', 'Official (Non-Diplomatic)', 'Ambassador Level', 'Full Diplomatic Relations (Special Mission)']),
|
|
priority: z.enum(['Critical', 'High', 'Medium', 'Low']),
|
|
cloudflareCoverage: z.boolean(),
|
|
networkInfrastructurePriority: z.string(),
|
|
notes: z.string().optional(),
|
|
coordinates: z.object({
|
|
lat: z.number().min(-90).max(90),
|
|
lng: z.number().min(-180).max(180)
|
|
}).optional()
|
|
})
|
|
```
|
|
|
|
3. Create `topologyNodeSchema`:
|
|
```typescript
|
|
export const topologyNodeSchema = z.object({
|
|
id: z.string(),
|
|
type: z.enum(['region', 'datacenter', 'tunnel', 'vm', 'service']),
|
|
label: z.string().min(1),
|
|
region: z.string(),
|
|
entity: z.string(),
|
|
position: z.object({
|
|
x: z.number(),
|
|
y: z.number()
|
|
}),
|
|
metadata: z.record(z.any())
|
|
})
|
|
```
|
|
|
|
4. Create `topologyEdgeSchema`:
|
|
```typescript
|
|
export const topologyEdgeSchema = z.object({
|
|
id: z.string(),
|
|
source: z.string(),
|
|
target: z.string(),
|
|
type: z.enum(['tunnel', 'peering', 'network-route']),
|
|
metadata: z.record(z.any())
|
|
})
|
|
```
|
|
|
|
5. Create `networkTopologySchema`:
|
|
```typescript
|
|
export const networkTopologySchema = z.object({
|
|
nodes: z.array(topologyNodeSchema),
|
|
edges: z.array(topologyEdgeSchema),
|
|
region: z.string(),
|
|
entity: z.string(),
|
|
lastUpdated: z.string().datetime()
|
|
})
|
|
```
|
|
|
|
6. Create `complianceRequirementSchema`:
|
|
```typescript
|
|
export const complianceRequirementSchema = z.object({
|
|
country: z.string().min(1),
|
|
region: z.string(),
|
|
frameworks: z.array(z.string()).min(1),
|
|
status: z.enum(['Compliant', 'Partial', 'Pending', 'Non-Compliant']),
|
|
requirements: z.array(z.string()),
|
|
lastAuditDate: z.string().datetime().optional(),
|
|
notes: z.string().optional()
|
|
})
|
|
```
|
|
|
|
7. Create `deploymentMilestoneSchema`:
|
|
```typescript
|
|
export const deploymentMilestoneSchema = z.object({
|
|
id: z.string(),
|
|
title: z.string().min(1),
|
|
region: z.string(),
|
|
entity: z.string(),
|
|
priority: z.enum(['Critical', 'High', 'Medium', 'Low']),
|
|
startDate: z.string().datetime(),
|
|
endDate: z.string().datetime(),
|
|
status: z.enum(['Planned', 'In Progress', 'Complete', 'Blocked']),
|
|
dependencies: z.array(z.string()).optional(),
|
|
cost: z.number().min(0).optional(),
|
|
description: z.string().optional()
|
|
}).refine((data) => new Date(data.endDate) > new Date(data.startDate), {
|
|
message: "End date must be after start date",
|
|
path: ["endDate"]
|
|
})
|
|
```
|
|
|
|
8. Create `costEstimateSchema`:
|
|
```typescript
|
|
export const costEstimateSchema = z.object({
|
|
region: z.string(),
|
|
entity: z.string(),
|
|
category: z.enum(['Infrastructure', 'Network', 'Compliance', 'Operations']),
|
|
monthly: z.number().min(0),
|
|
annual: z.number().min(0),
|
|
breakdown: z.object({
|
|
compute: z.number().min(0).optional(),
|
|
storage: z.number().min(0).optional(),
|
|
network: z.number().min(0).optional(),
|
|
licenses: z.number().min(0).optional(),
|
|
personnel: z.number().min(0).optional()
|
|
}),
|
|
currency: z.string().optional(),
|
|
lastUpdated: z.string().datetime().optional()
|
|
}).refine((data) => {
|
|
const breakdownSum = (data.breakdown.compute || 0) +
|
|
(data.breakdown.storage || 0) +
|
|
(data.breakdown.network || 0) +
|
|
(data.breakdown.licenses || 0) +
|
|
(data.breakdown.personnel || 0)
|
|
return Math.abs(breakdownSum - data.monthly) < 0.01
|
|
}, {
|
|
message: "Breakdown sum must equal monthly cost",
|
|
path: ["breakdown"]
|
|
})
|
|
```
|
|
|
|
9. Create input schemas for mutations:
|
|
- `updateTopologyInputSchema`
|
|
- `createMilestoneInputSchema`
|
|
- `updateMilestoneInputSchema`
|
|
- `updateComplianceInputSchema`
|
|
- `updateCostEstimateInputSchema`
|
|
|
|
10. Export all schemas
|
|
|
|
**Testing:**
|
|
- Test each schema with valid data
|
|
- Test each schema with invalid data
|
|
- Verify error messages
|
|
|
|
---
|
|
|
|
### Step 1.3: Create Data Serving API Route
|
|
**Time:** 1-2 hours
|
|
**File:** `src/app/api/infrastructure/data/[filename]/route.ts`
|
|
|
|
**Implementation Steps:**
|
|
1. Create route handler:
|
|
```typescript
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
import * as fs from 'fs'
|
|
import * as path from 'path'
|
|
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: { filename: string } }
|
|
) {
|
|
try {
|
|
const filePath = path.join(process.cwd(), 'docs/infrastructure/data', params.filename)
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
return NextResponse.json(
|
|
{ error: 'File not found' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
const fileContent = fs.readFileSync(filePath, 'utf-8')
|
|
const data = JSON.parse(fileContent)
|
|
const stats = fs.statSync(filePath)
|
|
|
|
const response = NextResponse.json({
|
|
data,
|
|
metadata: {
|
|
lastModified: stats.mtime.toISOString(),
|
|
filename: params.filename
|
|
}
|
|
})
|
|
|
|
// Add caching headers
|
|
response.headers.set('ETag', `"${stats.mtime.getTime()}"`)
|
|
response.headers.set('Last-Modified', stats.mtime.toUTCString())
|
|
response.headers.set('Cache-Control', 'public, max-age=3600')
|
|
|
|
return response
|
|
} catch (error) {
|
|
return NextResponse.json(
|
|
{ error: 'Failed to load file' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Add error handling for invalid JSON
|
|
3. Add security checks (prevent directory traversal)
|
|
4. Add rate limiting (optional)
|
|
5. Test with various filenames
|
|
6. Test error cases
|
|
|
|
**Testing:**
|
|
- Test with valid filename
|
|
- Test with invalid filename
|
|
- Test with missing file
|
|
- Test caching headers
|
|
|
|
---
|
|
|
|
### Step 1.4: Update Data Loading Hook
|
|
**Time:** 2-3 hours
|
|
**File:** `src/lib/hooks/useInfrastructureData.ts`
|
|
|
|
**Implementation Steps:**
|
|
1. Import React Query:
|
|
```typescript
|
|
import { useQuery } from '@tanstack/react-query'
|
|
```
|
|
|
|
2. Update DATA_BASE_PATH:
|
|
```typescript
|
|
const DATA_BASE_PATH = '/api/infrastructure/data'
|
|
```
|
|
|
|
3. Create data fetcher function:
|
|
```typescript
|
|
async function fetchJSONData<T>(filename: string): Promise<T[]> {
|
|
const response = await fetch(`${DATA_BASE_PATH}/${filename}`)
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to load ${filename}: ${response.statusText}`)
|
|
}
|
|
const result = await response.json()
|
|
return result.data || []
|
|
}
|
|
```
|
|
|
|
4. Update useCountries hook:
|
|
```typescript
|
|
export function useCountries(filter?: {...}) {
|
|
return useQuery({
|
|
queryKey: ['countries', filter],
|
|
queryFn: () => fetchJSONData<Country>('smom_countries.json'),
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
retry: 3,
|
|
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
|
|
})
|
|
}
|
|
```
|
|
|
|
5. Apply same pattern to all hooks:
|
|
- useNetworkTopologies
|
|
- useComplianceRequirements
|
|
- useDeploymentMilestones
|
|
- useCostEstimates
|
|
- useInfrastructureSummary
|
|
|
|
6. Add filtering logic after data fetch
|
|
7. Add error handling
|
|
8. Test all hooks
|
|
|
|
**Testing:**
|
|
- Test data loading
|
|
- Test filtering
|
|
- Test error handling
|
|
- Test caching
|
|
|
|
---
|
|
|
|
### Step 1.5: Implement Topology PNG Export
|
|
**Time:** 1-2 hours
|
|
**File:** `src/components/infrastructure/NetworkTopologyDocs.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Import html2canvas:
|
|
```typescript
|
|
import html2canvas from 'html2canvas'
|
|
```
|
|
|
|
2. Add ref to topology container:
|
|
```typescript
|
|
const topologyRef = useRef<HTMLDivElement>(null)
|
|
```
|
|
|
|
3. Implement handleExportPNG:
|
|
```typescript
|
|
const handleExportPNG = async () => {
|
|
if (!topologyRef.current) return
|
|
|
|
setExporting(true)
|
|
try {
|
|
const canvas = await html2canvas(topologyRef.current, {
|
|
backgroundColor: '#000000',
|
|
scale: 2, // High resolution
|
|
logging: false
|
|
})
|
|
|
|
canvas.toBlob((blob) => {
|
|
if (!blob) return
|
|
const url = URL.createObjectURL(blob)
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = `topology-${selectedRegion}-${Date.now()}.png`
|
|
a.click()
|
|
URL.revokeObjectURL(url)
|
|
|
|
toast({
|
|
title: 'Export successful',
|
|
description: 'Topology exported as PNG',
|
|
variant: 'success'
|
|
})
|
|
}, 'image/png')
|
|
} catch (error) {
|
|
toast({
|
|
title: 'Export failed',
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
variant: 'error'
|
|
})
|
|
} finally {
|
|
setExporting(false)
|
|
}
|
|
}
|
|
```
|
|
|
|
4. Add exporting state
|
|
5. Add loading indicator to button
|
|
6. Test export functionality
|
|
|
|
**Testing:**
|
|
- Test PNG export
|
|
- Test with different topologies
|
|
- Test error handling
|
|
- Verify file download
|
|
|
|
---
|
|
|
|
### Step 1.6: Implement Topology SVG Export
|
|
**Time:** 1 hour
|
|
**File:** `src/components/infrastructure/NetworkTopologyDocs.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Implement handleExportSVG:
|
|
```typescript
|
|
const handleExportSVG = () => {
|
|
const svgElement = topologyRef.current?.querySelector('svg')
|
|
if (!svgElement) return
|
|
|
|
try {
|
|
const svgData = new XMLSerializer().serializeToString(svgElement)
|
|
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' })
|
|
const url = URL.createObjectURL(svgBlob)
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = `topology-${selectedRegion}-${Date.now()}.svg`
|
|
a.click()
|
|
URL.revokeObjectURL(url)
|
|
|
|
toast({
|
|
title: 'Export successful',
|
|
description: 'Topology exported as SVG',
|
|
variant: 'success'
|
|
})
|
|
} catch (error) {
|
|
toast({
|
|
title: 'Export failed',
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
variant: 'error'
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Test SVG export
|
|
3. Verify SVG quality
|
|
|
|
---
|
|
|
|
### Step 1.7: Implement Timeline PDF Export
|
|
**Time:** 3-4 hours
|
|
**File:** `src/components/infrastructure/DeploymentTimeline.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Import libraries:
|
|
```typescript
|
|
import jsPDF from 'jspdf'
|
|
import html2canvas from 'html2canvas'
|
|
```
|
|
|
|
2. Create handleExportPDF function:
|
|
```typescript
|
|
const handleExportPDF = async () => {
|
|
setExporting(true)
|
|
try {
|
|
const pdf = new jsPDF('landscape', 'mm', 'a4')
|
|
|
|
// Page 1: Title and metadata
|
|
pdf.setFontSize(20)
|
|
pdf.text('Deployment Timeline', 20, 20)
|
|
pdf.setFontSize(12)
|
|
pdf.text(`Region: ${selectedRegion}`, 20, 30)
|
|
pdf.text(`Entity: ${selectedEntity}`, 20, 35)
|
|
pdf.text(`Export Date: ${new Date().toLocaleDateString()}`, 20, 40)
|
|
|
|
// Page 2: Gantt chart
|
|
const chartElement = document.getElementById('gantt-chart')
|
|
if (chartElement) {
|
|
const canvas = await html2canvas(chartElement)
|
|
const imgData = canvas.toDataURL('image/png')
|
|
pdf.addPage()
|
|
pdf.addImage(imgData, 'PNG', 10, 10, 277, 190)
|
|
}
|
|
|
|
// Page 3: Milestone list
|
|
pdf.addPage()
|
|
pdf.setFontSize(16)
|
|
pdf.text('Milestone List', 20, 20)
|
|
|
|
let y = 30
|
|
milestones.forEach((milestone, index) => {
|
|
if (y > 270) {
|
|
pdf.addPage()
|
|
y = 20
|
|
}
|
|
pdf.setFontSize(12)
|
|
pdf.text(`${index + 1}. ${milestone.title}`, 20, y)
|
|
pdf.setFontSize(10)
|
|
pdf.text(`Status: ${milestone.status} | Priority: ${milestone.priority}`, 20, y + 5)
|
|
y += 15
|
|
})
|
|
|
|
pdf.save(`deployment-timeline-${Date.now()}.pdf`)
|
|
|
|
toast({
|
|
title: 'Export successful',
|
|
description: 'Timeline exported as PDF',
|
|
variant: 'success'
|
|
})
|
|
} catch (error) {
|
|
toast({
|
|
title: 'Export failed',
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
variant: 'error'
|
|
})
|
|
} finally {
|
|
setExporting(false)
|
|
}
|
|
}
|
|
```
|
|
|
|
3. Add exporting state
|
|
4. Test PDF generation
|
|
5. Adjust layout and formatting
|
|
|
|
---
|
|
|
|
### Step 1.8: Implement Cost Estimates Excel Export
|
|
**Time:** 3-4 hours
|
|
**File:** `src/components/infrastructure/CostEstimates.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Import xlsx:
|
|
```typescript
|
|
import * as XLSX from 'xlsx'
|
|
```
|
|
|
|
2. Create handleExportExcel function:
|
|
```typescript
|
|
const handleExportExcel = () => {
|
|
try {
|
|
const workbook = XLSX.utils.book_new()
|
|
|
|
// Summary sheet
|
|
const summaryData = [
|
|
['Total Monthly', (totalMonthly / 1000).toFixed(0) + 'K'],
|
|
['Total Annual', (totalAnnual / 1000000).toFixed(1) + 'M'],
|
|
['Number of Estimates', estimates.length]
|
|
]
|
|
const summarySheet = XLSX.utils.aoa_to_sheet(summaryData)
|
|
XLSX.utils.book_append_sheet(workbook, summarySheet, 'Summary')
|
|
|
|
// Detailed breakdown sheet
|
|
const detailData = estimates.map(e => ({
|
|
Region: e.region,
|
|
Entity: e.entity,
|
|
Category: e.category,
|
|
'Monthly (USD)': e.monthly,
|
|
'Annual (USD)': e.annual,
|
|
'Compute': e.breakdown.compute || 0,
|
|
'Storage': e.breakdown.storage || 0,
|
|
'Network': e.breakdown.network || 0,
|
|
'Licenses': e.breakdown.licenses || 0,
|
|
'Personnel': e.breakdown.personnel || 0
|
|
}))
|
|
const detailSheet = XLSX.utils.json_to_sheet(detailData)
|
|
|
|
// Format currency columns
|
|
const range = XLSX.utils.decode_range(detailSheet['!ref'] || 'A1')
|
|
for (let C = 3; C <= 4; ++C) {
|
|
for (let R = 1; R <= range.e.r; ++R) {
|
|
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C })
|
|
if (!detailSheet[cellAddress]) continue
|
|
detailSheet[cellAddress].z = '$#,##0.00'
|
|
}
|
|
}
|
|
|
|
XLSX.utils.book_append_sheet(workbook, detailSheet, 'Detailed Breakdown')
|
|
|
|
// By region sheet
|
|
const byRegion = estimates.reduce((acc, e) => {
|
|
acc[e.region] = (acc[e.region] || 0) + e.annual
|
|
return acc
|
|
}, {} as Record<string, number>)
|
|
const regionData = Object.entries(byRegion).map(([region, annual]) => ({
|
|
Region: region,
|
|
'Annual Cost (USD)': annual
|
|
}))
|
|
const regionSheet = XLSX.utils.json_to_sheet(regionData)
|
|
XLSX.utils.book_append_sheet(workbook, regionSheet, 'By Region')
|
|
|
|
XLSX.writeFile(workbook, `cost-estimates-${Date.now()}.xlsx`)
|
|
|
|
toast({
|
|
title: 'Export successful',
|
|
description: 'Cost estimates exported as Excel',
|
|
variant: 'success'
|
|
})
|
|
} catch (error) {
|
|
toast({
|
|
title: 'Export failed',
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
variant: 'error'
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
3. Test Excel export
|
|
4. Verify formatting
|
|
|
|
---
|
|
|
|
### Step 1.9: Create Error Boundary Component
|
|
**Time:** 1-2 hours
|
|
**File:** `src/components/infrastructure/InfrastructureErrorBoundary.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Create error boundary class component:
|
|
```typescript
|
|
'use client'
|
|
|
|
import React from 'react'
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Button } from '@/components/ui/button'
|
|
import { AlertTriangle } from 'lucide-react'
|
|
|
|
interface Props {
|
|
children: React.ReactNode
|
|
fallback?: React.ComponentType<{ error: Error; reset: () => void }>
|
|
}
|
|
|
|
interface State {
|
|
hasError: boolean
|
|
error: Error | null
|
|
}
|
|
|
|
export class InfrastructureErrorBoundary extends React.Component<Props, State> {
|
|
constructor(props: Props) {
|
|
super(props)
|
|
this.state = { hasError: false, error: null }
|
|
}
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { hasError: true, error }
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
console.error('Infrastructure error:', error, errorInfo)
|
|
// Log to Sentry if available
|
|
if (typeof window !== 'undefined' && (window as any).Sentry) {
|
|
(window as any).Sentry.captureException(error, { contexts: { react: errorInfo } })
|
|
}
|
|
}
|
|
|
|
reset = () => {
|
|
this.setState({ hasError: false, error: null })
|
|
}
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
if (this.props.fallback) {
|
|
const Fallback = this.props.fallback
|
|
return <Fallback error={this.state.error!} reset={this.reset} />
|
|
}
|
|
|
|
return (
|
|
<Card className="m-4">
|
|
<CardHeader>
|
|
<div className="flex items-center gap-2">
|
|
<AlertTriangle className="h-5 w-5 text-red-400" />
|
|
<CardTitle>Something went wrong</CardTitle>
|
|
</div>
|
|
<CardDescription>
|
|
An error occurred while loading infrastructure data
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{process.env.NODE_ENV === 'development' && this.state.error && (
|
|
<pre className="text-sm text-red-400 mb-4">
|
|
{this.state.error.message}
|
|
{this.state.error.stack}
|
|
</pre>
|
|
)}
|
|
<Button onClick={this.reset}>Try Again</Button>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
return this.props.children
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Test error boundary
|
|
3. Add to all page components
|
|
|
|
---
|
|
|
|
### Step 1.10: Wrap Views with Error Boundary
|
|
**Time:** 30 minutes
|
|
**Files:** All page components
|
|
|
|
**Implementation:**
|
|
1. Import InfrastructureErrorBoundary
|
|
2. Wrap each view:
|
|
```typescript
|
|
export default function TopologyPage() {
|
|
return (
|
|
<InfrastructureErrorBoundary>
|
|
<div className="container mx-auto py-8 px-4">
|
|
<NetworkTopologyDocs />
|
|
</div>
|
|
</InfrastructureErrorBoundary>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2: Edit Mode Implementation (Weeks 2-3) - 60-80 hours
|
|
|
|
### Step 2.1: Create Edit Compliance Form
|
|
**Time:** 3-4 hours
|
|
**File:** `src/components/infrastructure/forms/EditComplianceForm.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Create form component structure:
|
|
```typescript
|
|
'use client'
|
|
|
|
import { useForm } from 'react-hook-form'
|
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
|
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Button } from '@/components/ui/button'
|
|
import { useUpdateComplianceRequirement } from '@/lib/graphql/hooks/useInfrastructure'
|
|
import { useToast } from '@/components/ui/use-toast'
|
|
import { complianceRequirementSchema } from '@/lib/validation/schemas/infrastructure'
|
|
import type { ComplianceRequirement } from '@/lib/types/infrastructure'
|
|
|
|
interface EditComplianceFormProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
requirement: ComplianceRequirement
|
|
}
|
|
|
|
export function EditComplianceForm({ open, onOpenChange, requirement }: EditComplianceFormProps) {
|
|
const { updateCompliance, loading, error } = useUpdateComplianceRequirement()
|
|
const { toast } = useToast()
|
|
|
|
const form = useForm<ComplianceRequirement>({
|
|
resolver: zodResolver(complianceRequirementSchema),
|
|
defaultValues: requirement
|
|
})
|
|
|
|
const onSubmit = async (data: ComplianceRequirement) => {
|
|
try {
|
|
await updateCompliance({
|
|
variables: {
|
|
country: requirement.country,
|
|
input: {
|
|
frameworks: data.frameworks,
|
|
status: data.status,
|
|
requirements: data.requirements,
|
|
lastAuditDate: data.lastAuditDate,
|
|
notes: data.notes
|
|
}
|
|
}
|
|
})
|
|
|
|
toast({
|
|
title: 'Success',
|
|
description: 'Compliance requirement updated',
|
|
variant: 'success'
|
|
})
|
|
|
|
onOpenChange(false)
|
|
} catch (err) {
|
|
toast({
|
|
title: 'Error',
|
|
description: err instanceof Error ? err.message : 'Failed to update',
|
|
variant: 'error'
|
|
})
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle>Edit Compliance Requirement</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="country"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Country</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} disabled />
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
{/* Add all other fields similarly */}
|
|
|
|
<DialogFooter>
|
|
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button type="submit" disabled={loading}>
|
|
{loading ? 'Saving...' : 'Save'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|
|
```
|
|
|
|
2. Add all form fields with proper types
|
|
3. Add multi-select for frameworks
|
|
4. Add date picker for lastAuditDate
|
|
5. Add textarea for requirements (one per line)
|
|
6. Test form validation
|
|
7. Test form submission
|
|
|
|
---
|
|
|
|
### Step 2.2: Create Edit Milestone Form
|
|
**Time:** 4-5 hours
|
|
**File:** `src/components/infrastructure/forms/EditMilestoneForm.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Create form with all fields
|
|
2. Add date range validation
|
|
3. Add dependency selector (multi-select of other milestones)
|
|
4. Add dependency validation (no circular refs)
|
|
5. Add cost field with currency formatting
|
|
6. Handle create vs edit modes
|
|
7. Show dependency graph preview
|
|
8. Test all validations
|
|
|
|
---
|
|
|
|
### Step 2.3: Create Edit Cost Estimate Form
|
|
**Time:** 3-4 hours
|
|
**File:** `src/components/infrastructure/forms/EditCostEstimateForm.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Create form with cost fields
|
|
2. Add auto-calculation (monthly * 12 = annual)
|
|
3. Add breakdown fields
|
|
4. Add validation (breakdown sum = monthly)
|
|
5. Add currency selector
|
|
6. Test calculations
|
|
7. Test validation
|
|
|
|
---
|
|
|
|
### Step 2.4: Create Edit Topology Node Form
|
|
**Time:** 3-4 hours
|
|
**File:** `src/components/infrastructure/forms/EditTopologyNodeForm.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Create form with node fields
|
|
2. Add position X/Y fields
|
|
3. Add metadata editor (JSON or key-value)
|
|
4. Add validation (unique label, valid coordinates)
|
|
5. Integrate with topology update
|
|
6. Test node editing
|
|
|
|
---
|
|
|
|
### Step 2.5: Wire Up Edit Forms
|
|
**Time:** 2-3 hours per component
|
|
**Files:** All infrastructure component files
|
|
|
|
**Implementation Steps:**
|
|
1. Import form components
|
|
2. Add state for dialog open/close
|
|
3. Add state for item being edited
|
|
4. Connect edit buttons:
|
|
```typescript
|
|
<Button onClick={() => {
|
|
setEditingItem(requirement)
|
|
setDialogOpen(true)
|
|
}}>
|
|
Edit
|
|
</Button>
|
|
```
|
|
5. Add form component:
|
|
```typescript
|
|
<EditComplianceForm
|
|
open={dialogOpen}
|
|
onOpenChange={setDialogOpen}
|
|
requirement={editingItem!}
|
|
/>
|
|
```
|
|
6. Refresh data after successful edit
|
|
7. Test edit workflow
|
|
|
|
---
|
|
|
|
### Step 2.6: Implement Topology Edit Mode
|
|
**Time:** 6-8 hours
|
|
**File:** `src/components/infrastructure/NetworkTopologyDocs.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Add edit mode state management
|
|
2. Implement node dragging:
|
|
```typescript
|
|
const onNodeDrag = useCallback((event: NodeDragEvent, node: Node) => {
|
|
// Update node position in state
|
|
setTopology(prev => ({
|
|
...prev,
|
|
nodes: prev.nodes.map(n =>
|
|
n.id === node.id
|
|
? { ...n, position: { x: node.position.x, y: node.position.y } }
|
|
: n
|
|
)
|
|
}))
|
|
}, [])
|
|
```
|
|
|
|
3. Add "Add Node" button and dialog
|
|
4. Implement edge creation (click source, then target)
|
|
5. Add delete functionality
|
|
6. Add context menu
|
|
7. Implement bulk selection
|
|
8. Add save button
|
|
9. Integrate with mutation
|
|
10. Add undo/redo
|
|
|
|
---
|
|
|
|
### Step 2.7: Implement Timeline Drag-and-Drop
|
|
**Time:** 4-5 hours
|
|
**File:** `src/components/infrastructure/DeploymentTimeline.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Set up @dnd-kit:
|
|
```typescript
|
|
import { DndContext, DragEndEvent } from '@dnd-kit/core'
|
|
import { SortableContext, useSortable } from '@dnd-kit/sortable'
|
|
```
|
|
|
|
2. Make milestones draggable
|
|
3. Implement drop handler
|
|
4. Validate date constraints
|
|
5. Check dependencies
|
|
6. Auto-save on drop
|
|
7. Show loading state
|
|
|
|
---
|
|
|
|
## Phase 3: React Flow Integration (Week 3) - 20-25 hours
|
|
|
|
### Step 3.1: Create React Flow Topology Component
|
|
**Time:** 4-5 hours
|
|
**File:** `src/components/infrastructure/topology/ReactFlowTopology.tsx`
|
|
|
|
**Implementation Steps:**
|
|
1. Import React Flow:
|
|
```typescript
|
|
import ReactFlow, {
|
|
Background,
|
|
Controls,
|
|
MiniMap,
|
|
Node,
|
|
Edge,
|
|
Connection,
|
|
useNodesState,
|
|
useEdgesState,
|
|
addEdge,
|
|
onNodesChange,
|
|
onEdgesChange
|
|
} from 'reactflow'
|
|
import 'reactflow/dist/style.css'
|
|
```
|
|
|
|
2. Convert topology data:
|
|
```typescript
|
|
const nodes: Node[] = topology.nodes.map(node => ({
|
|
id: node.id,
|
|
type: node.type,
|
|
position: node.position,
|
|
data: { label: node.label, ...node.metadata }
|
|
}))
|
|
|
|
const edges: Edge[] = topology.edges.map(edge => ({
|
|
id: edge.id,
|
|
source: edge.source,
|
|
target: edge.target,
|
|
type: edge.type,
|
|
label: edge.metadata.bandwidth || edge.type
|
|
}))
|
|
```
|
|
|
|
3. Set up React Flow component
|
|
4. Add minimap and controls
|
|
5. Add background grid
|
|
6. Implement zoom/pan
|
|
7. Add fit view
|
|
|
|
---
|
|
|
|
### Step 3.2-3.4: Create Custom Nodes and Edges
|
|
**Time:** 8-10 hours
|
|
**Files:** Multiple files
|
|
|
|
**Implementation Steps:**
|
|
1. Create base node component
|
|
2. Create specialized nodes (5 files)
|
|
3. Create custom edge component
|
|
4. Register node/edge types
|
|
5. Integrate into main component
|
|
6. Test all node types
|
|
7. Test edit mode with React Flow
|
|
|
|
---
|
|
|
|
## Phase 4: Map Visualization (Week 4) - 8-10 hours
|
|
|
|
### Step 4.1-4.3: Mapbox Integration
|
|
**Time:** 8-10 hours
|
|
**Files:** Multiple files
|
|
|
|
**Implementation Steps:**
|
|
1. Set up Mapbox token
|
|
2. Create ComplianceMapView component
|
|
3. Load country coordinates
|
|
4. Create markers
|
|
5. Add popups
|
|
6. Implement clustering
|
|
7. Add layer controls
|
|
8. Integrate into compliance component
|
|
|
|
---
|
|
|
|
## Phase 5-8: Additional Phases
|
|
|
|
(Continue with similar detailed steps for remaining phases...)
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
After each phase, test:
|
|
- [ ] All exports work correctly
|
|
- [ ] Forms validate properly
|
|
- [ ] Mutations succeed and update UI
|
|
- [ ] Error handling works
|
|
- [ ] Loading states display
|
|
- [ ] Empty states display
|
|
- [ ] Filters work correctly
|
|
- [ ] No console errors
|
|
- [ ] Responsive design works
|
|
- [ ] Accessibility features work
|
|
|
|
---
|
|
|
|
## Deployment Checklist
|
|
|
|
Before deploying:
|
|
- [ ] All dependencies installed
|
|
- [ ] Environment variables set
|
|
- [ ] API routes tested
|
|
- [ ] Error boundaries tested
|
|
- [ ] Performance tested
|
|
- [ ] Security reviewed
|
|
- [ ] Documentation complete
|
|
- [ ] Tests passing
|
|
|