- 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
28 KiB
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
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:
-
Import Zod
-
Create
countrySchema: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() }) -
Create
topologyNodeSchema: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()) }) -
Create
topologyEdgeSchema: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()) }) -
Create
networkTopologySchema:export const networkTopologySchema = z.object({ nodes: z.array(topologyNodeSchema), edges: z.array(topologyEdgeSchema), region: z.string(), entity: z.string(), lastUpdated: z.string().datetime() }) -
Create
complianceRequirementSchema: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() }) -
Create
deploymentMilestoneSchema: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"] }) -
Create
costEstimateSchema: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"] }) -
Create input schemas for mutations:
updateTopologyInputSchemacreateMilestoneInputSchemaupdateMilestoneInputSchemaupdateComplianceInputSchemaupdateCostEstimateInputSchema
-
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:
-
Create route handler:
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 } ) } } -
Add error handling for invalid JSON
-
Add security checks (prevent directory traversal)
-
Add rate limiting (optional)
-
Test with various filenames
-
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:
-
Import React Query:
import { useQuery } from '@tanstack/react-query' -
Update DATA_BASE_PATH:
const DATA_BASE_PATH = '/api/infrastructure/data' -
Create data fetcher function:
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 || [] } -
Update useCountries hook:
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) }) } -
Apply same pattern to all hooks:
- useNetworkTopologies
- useComplianceRequirements
- useDeploymentMilestones
- useCostEstimates
- useInfrastructureSummary
-
Add filtering logic after data fetch
-
Add error handling
-
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:
-
Import html2canvas:
import html2canvas from 'html2canvas' -
Add ref to topology container:
const topologyRef = useRef<HTMLDivElement>(null) -
Implement handleExportPNG:
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) } } -
Add exporting state
-
Add loading indicator to button
-
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:
-
Implement handleExportSVG:
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' }) } } -
Test SVG export
-
Verify SVG quality
Step 1.7: Implement Timeline PDF Export
Time: 3-4 hours
File: src/components/infrastructure/DeploymentTimeline.tsx
Implementation Steps:
-
Import libraries:
import jsPDF from 'jspdf' import html2canvas from 'html2canvas' -
Create handleExportPDF function:
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) } } -
Add exporting state
-
Test PDF generation
-
Adjust layout and formatting
Step 1.8: Implement Cost Estimates Excel Export
Time: 3-4 hours
File: src/components/infrastructure/CostEstimates.tsx
Implementation Steps:
-
Import xlsx:
import * as XLSX from 'xlsx' -
Create handleExportExcel function:
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' }) } } -
Test Excel export
-
Verify formatting
Step 1.9: Create Error Boundary Component
Time: 1-2 hours
File: src/components/infrastructure/InfrastructureErrorBoundary.tsx
Implementation Steps:
-
Create error boundary class component:
'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 } } -
Test error boundary
-
Add to all page components
Step 1.10: Wrap Views with Error Boundary
Time: 30 minutes
Files: All page components
Implementation:
- Import InfrastructureErrorBoundary
- Wrap each view:
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:
-
Create form component structure:
'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> ) } -
Add all form fields with proper types
-
Add multi-select for frameworks
-
Add date picker for lastAuditDate
-
Add textarea for requirements (one per line)
-
Test form validation
-
Test form submission
Step 2.2: Create Edit Milestone Form
Time: 4-5 hours
File: src/components/infrastructure/forms/EditMilestoneForm.tsx
Implementation Steps:
- Create form with all fields
- Add date range validation
- Add dependency selector (multi-select of other milestones)
- Add dependency validation (no circular refs)
- Add cost field with currency formatting
- Handle create vs edit modes
- Show dependency graph preview
- Test all validations
Step 2.3: Create Edit Cost Estimate Form
Time: 3-4 hours
File: src/components/infrastructure/forms/EditCostEstimateForm.tsx
Implementation Steps:
- Create form with cost fields
- Add auto-calculation (monthly * 12 = annual)
- Add breakdown fields
- Add validation (breakdown sum = monthly)
- Add currency selector
- Test calculations
- Test validation
Step 2.4: Create Edit Topology Node Form
Time: 3-4 hours
File: src/components/infrastructure/forms/EditTopologyNodeForm.tsx
Implementation Steps:
- Create form with node fields
- Add position X/Y fields
- Add metadata editor (JSON or key-value)
- Add validation (unique label, valid coordinates)
- Integrate with topology update
- Test node editing
Step 2.5: Wire Up Edit Forms
Time: 2-3 hours per component
Files: All infrastructure component files
Implementation Steps:
- Import form components
- Add state for dialog open/close
- Add state for item being edited
- Connect edit buttons:
<Button onClick={() => { setEditingItem(requirement) setDialogOpen(true) }}> Edit </Button> - Add form component:
<EditComplianceForm open={dialogOpen} onOpenChange={setDialogOpen} requirement={editingItem!} /> - Refresh data after successful edit
- Test edit workflow
Step 2.6: Implement Topology Edit Mode
Time: 6-8 hours
File: src/components/infrastructure/NetworkTopologyDocs.tsx
Implementation Steps:
-
Add edit mode state management
-
Implement node dragging:
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 ) })) }, []) -
Add "Add Node" button and dialog
-
Implement edge creation (click source, then target)
-
Add delete functionality
-
Add context menu
-
Implement bulk selection
-
Add save button
-
Integrate with mutation
-
Add undo/redo
Step 2.7: Implement Timeline Drag-and-Drop
Time: 4-5 hours
File: src/components/infrastructure/DeploymentTimeline.tsx
Implementation Steps:
-
Set up @dnd-kit:
import { DndContext, DragEndEvent } from '@dnd-kit/core' import { SortableContext, useSortable } from '@dnd-kit/sortable' -
Make milestones draggable
-
Implement drop handler
-
Validate date constraints
-
Check dependencies
-
Auto-save on drop
-
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:
-
Import React Flow:
import ReactFlow, { Background, Controls, MiniMap, Node, Edge, Connection, useNodesState, useEdgesState, addEdge, onNodesChange, onEdgesChange } from 'reactflow' import 'reactflow/dist/style.css' -
Convert topology data:
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 })) -
Set up React Flow component
-
Add minimap and controls
-
Add background grid
-
Implement zoom/pan
-
Add fit view
Step 3.2-3.4: Create Custom Nodes and Edges
Time: 8-10 hours
Files: Multiple files
Implementation Steps:
- Create base node component
- Create specialized nodes (5 files)
- Create custom edge component
- Register node/edge types
- Integrate into main component
- Test all node types
- 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:
- Set up Mapbox token
- Create ComplianceMapView component
- Load country coordinates
- Create markers
- Add popups
- Implement clustering
- Add layer controls
- 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