([]);
+ const [loading, setLoading] = useState(false);
+ const [lastRefresh, setLastRefresh] = useState(new Date());
+
+ useEffect(() => {
+ fetchHealthData();
+ const interval = setInterval(fetchHealthData, 30000); // Refresh every 30 seconds
+ return () => clearInterval(interval);
+ }, []);
+
+ const fetchHealthData = async () => {
+ try {
+ setLoading(true);
+ // API calls would go here
+ // const response = await apiClient.get('/admin/health');
+
+ // Using mock data for development
+ setServices([
+ {
+ name: 'Backend API',
+ status: 'healthy',
+ responseTime: 45,
+ uptime: 99.98,
+ lastCheck: new Date().toISOString(),
+ },
+ {
+ name: 'PostgreSQL Database',
+ status: 'healthy',
+ responseTime: 12,
+ uptime: 99.99,
+ lastCheck: new Date().toISOString(),
+ },
+ {
+ name: 'Redis Cache',
+ status: 'healthy',
+ responseTime: 3,
+ uptime: 100,
+ lastCheck: new Date().toISOString(),
+ },
+ {
+ name: 'MongoDB',
+ status: 'healthy',
+ responseTime: 18,
+ uptime: 99.95,
+ lastCheck: new Date().toISOString(),
+ },
+ {
+ name: 'MinIO Storage',
+ status: 'degraded',
+ responseTime: 250,
+ uptime: 98.5,
+ lastCheck: new Date().toISOString(),
+ },
+ {
+ name: 'WebSocket Server',
+ status: 'healthy',
+ responseTime: 8,
+ uptime: 99.97,
+ lastCheck: new Date().toISOString(),
+ },
+ ]);
+
+ setMetrics([
+ {
+ name: 'CPU Usage',
+ value: 45,
+ max: 100,
+ unit: '%',
+ status: 'normal',
+ },
+ {
+ name: 'Memory Usage',
+ value: 3.2,
+ max: 8,
+ unit: 'GB',
+ status: 'normal',
+ },
+ {
+ name: 'Disk Usage',
+ value: 42,
+ max: 100,
+ unit: 'GB',
+ status: 'normal',
+ },
+ {
+ name: 'Network I/O',
+ value: 125,
+ max: 1000,
+ unit: 'Mbps',
+ status: 'normal',
+ },
+ {
+ name: 'Database Connections',
+ value: 85,
+ max: 100,
+ unit: '',
+ status: 'warning',
+ },
+ {
+ name: 'Redis Memory',
+ value: 450,
+ max: 512,
+ unit: 'MB',
+ status: 'warning',
+ },
+ ]);
+
+ setErrorLogs([
+ {
+ id: '1',
+ timestamp: new Date(Date.now() - 5 * 60000).toISOString(),
+ severity: 'warning',
+ service: 'MinIO',
+ message: 'High response time detected',
+ count: 3,
+ },
+ {
+ id: '2',
+ timestamp: new Date(Date.now() - 15 * 60000).toISOString(),
+ severity: 'error',
+ service: 'Backend API',
+ message: 'Rate limit exceeded for IP 192.168.1.105',
+ count: 12,
+ },
+ {
+ id: '3',
+ timestamp: new Date(Date.now() - 30 * 60000).toISOString(),
+ severity: 'info',
+ service: 'Database',
+ message: 'Automatic vacuum completed',
+ count: 1,
+ },
+ {
+ id: '4',
+ timestamp: new Date(Date.now() - 45 * 60000).toISOString(),
+ severity: 'warning',
+ service: 'Redis',
+ message: 'Memory usage above 85%',
+ count: 2,
+ },
+ {
+ id: '5',
+ timestamp: new Date(Date.now() - 60 * 60000).toISOString(),
+ severity: 'info',
+ service: 'System',
+ message: 'Daily backup completed successfully',
+ count: 1,
+ },
+ ]);
+
+ setLastRefresh(new Date());
+ } catch (error) {
+ console.error('Failed to fetch health data:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'healthy':
+ case 'normal':
+ return 'success';
+ case 'degraded':
+ case 'warning':
+ return 'warning';
+ case 'down':
+ case 'critical':
+ return 'error';
+ default:
+ return 'default';
+ }
+ };
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case 'healthy':
+ case 'normal':
+ return ;
+ case 'degraded':
+ case 'warning':
+ return ;
+ case 'down':
+ case 'critical':
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ const getSeverityColor = (severity: string) => {
+ switch (severity) {
+ case 'error':
+ return 'error';
+ case 'warning':
+ return 'warning';
+ case 'info':
+ return 'info';
+ default:
+ return 'default';
+ }
+ };
+
+ const formatTimestamp = (timestamp: string) => {
+ const date = new Date(timestamp);
+ const now = new Date();
+ const diff = now.getTime() - date.getTime();
+ const minutes = Math.floor(diff / 60000);
+
+ if (minutes < 1) return 'Just now';
+ if (minutes < 60) return `${minutes}m ago`;
+ if (minutes < 1440) return `${Math.floor(minutes / 60)}h ago`;
+ return date.toLocaleString();
+ };
+
+ const overallHealth = services.every(s => s.status === 'healthy')
+ ? 'healthy'
+ : services.some(s => s.status === 'down')
+ ? 'critical'
+ : 'degraded';
+
+ return (
+
+
+
+
+
+ System Health
+
+
+ Monitor system services and performance metrics
+
+
+
+
+ Last updated: {formatTimestamp(lastRefresh.toISOString())}
+
+
+
+
+
+
+
+
+ {/* Overall Status Alert */}
+
+
+ System Status:{' '}
+ {overallHealth === 'healthy'
+ ? 'All Systems Operational'
+ : overallHealth === 'critical'
+ ? 'Critical Issues Detected'
+ : 'Degraded Performance'}
+
+ {overallHealth !== 'healthy' && (
+
+ {services.filter(s => s.status !== 'healthy').length} service(s) experiencing issues
+
+ )}
+
+
+ {/* Service Status */}
+
+ Service Status
+
+
+ {services.map((service) => (
+
+
+
+
+
+ {service.name}
+
+
+
+
+
+
+ Response Time
+
+
+ {service.responseTime}ms
+
+
+
+
+ Uptime
+
+
+ {service.uptime}%
+
+
+
+
+
+
+ ))}
+
+
+ {/* System Metrics */}
+
+ System Metrics
+
+
+ {metrics.map((metric) => (
+
+
+
+
+ {metric.name}
+
+ {metric.status !== 'normal' && (
+
+ )}
+
+
+ {metric.value}{metric.unit}
+
+ {' '}/ {metric.max}{metric.unit}
+
+
+
+
+
+ ))}
+
+
+ {/* Recent Error Logs */}
+
+ Recent Events
+
+
+
+
+
+ Time
+ Severity
+ Service
+ Message
+ Count
+
+
+
+ {errorLogs.map((log) => (
+
+ {formatTimestamp(log.timestamp)}
+
+
+
+ {log.service}
+ {log.message}
+
+ {log.count > 1 && (
+
+ )}
+
+
+ ))}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/parentflow-admin/src/app/settings/page.tsx b/parentflow-admin/src/app/settings/page.tsx
new file mode 100644
index 0000000..d3c7b02
--- /dev/null
+++ b/parentflow-admin/src/app/settings/page.tsx
@@ -0,0 +1,595 @@
+'use client';
+
+import { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ TextField,
+ Button,
+ Switch,
+ FormControlLabel,
+ FormGroup,
+ Divider,
+ Grid,
+ Alert,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Tabs,
+ Tab,
+ Paper,
+} from '@mui/material';
+import {
+ Save,
+ Security,
+ Notifications,
+ Email,
+ Storage,
+ Api,
+} from '@mui/icons-material';
+import AdminLayout from '@/components/AdminLayout';
+
+interface TabPanelProps {
+ children?: React.ReactNode;
+ index: number;
+ value: number;
+}
+
+function TabPanel(props: TabPanelProps) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+ {value === index && {children}}
+
+ );
+}
+
+export default function SettingsPage() {
+ const [tabValue, setTabValue] = useState(0);
+ const [saveSuccess, setSaveSuccess] = useState(false);
+ const [settings, setSettings] = useState({
+ // General Settings
+ siteName: 'ParentFlow',
+ adminEmail: 'admin@parentflowapp.com',
+ supportEmail: 'support@parentflowapp.com',
+ timezone: 'America/New_York',
+ language: 'en',
+
+ // Security Settings
+ enforcePasswordPolicy: true,
+ minPasswordLength: 8,
+ requireUppercase: true,
+ requireNumbers: true,
+ requireSpecialChars: true,
+ sessionTimeout: 30,
+ maxLoginAttempts: 5,
+ enableTwoFactor: false,
+
+ // Notification Settings
+ enableEmailNotifications: true,
+ enablePushNotifications: true,
+ adminNotifications: true,
+ errorAlerts: true,
+ newUserAlerts: true,
+ systemHealthAlerts: true,
+
+ // Email Settings
+ smtpHost: 'smtp.gmail.com',
+ smtpPort: 587,
+ smtpUser: 'noreply@parentflowapp.com',
+ smtpPassword: '********',
+ emailFrom: 'ParentFlow ',
+
+ // Storage Settings
+ maxFileSize: 10,
+ allowedFileTypes: 'jpg,jpeg,png,pdf,doc,docx',
+ storageProvider: 'minio',
+ s3Bucket: 'parentflow-files',
+ retentionDays: 90,
+
+ // API Settings
+ rateLimit: 100,
+ rateLimitWindow: 60,
+ apiTimeout: 30,
+ enableGraphQL: true,
+ enableWebSockets: true,
+ corsOrigins: 'https://web.parentflowapp.com',
+ });
+
+ const handleSave = () => {
+ // Save settings logic here
+ setSaveSuccess(true);
+ setTimeout(() => setSaveSuccess(false), 3000);
+ };
+
+ return (
+
+
+
+ Settings
+
+
+ Configure system settings and preferences
+
+
+
+ {saveSuccess && (
+
+ Settings saved successfully!
+
+ )}
+
+
+ setTabValue(newValue)}
+ sx={{ borderBottom: 1, borderColor: 'divider', px: 2 }}
+ >
+
+ } iconPosition="start" />
+ } iconPosition="start" />
+ } iconPosition="start" />
+ } iconPosition="start" />
+ } iconPosition="start" />
+
+
+
+ {/* General Settings */}
+
+
+
+ setSettings({ ...settings, siteName: e.target.value })}
+ />
+
+
+ setSettings({ ...settings, adminEmail: e.target.value })}
+ />
+
+
+ setSettings({ ...settings, supportEmail: e.target.value })}
+ />
+
+
+
+ Timezone
+
+
+
+
+
+ Language
+
+
+
+
+
+
+ {/* Security Settings */}
+
+
+
+ setSettings({ ...settings, enforcePasswordPolicy: e.target.checked })
+ }
+ />
+ }
+ label="Enforce Password Policy"
+ />
+ {settings.enforcePasswordPolicy && (
+
+
+
+
+ setSettings({ ...settings, minPasswordLength: parseInt(e.target.value) })
+ }
+ />
+
+
+
+
+ setSettings({ ...settings, requireUppercase: e.target.checked })
+ }
+ />
+ }
+ label="Require Uppercase Letters"
+ />
+
+ setSettings({ ...settings, requireNumbers: e.target.checked })
+ }
+ />
+ }
+ label="Require Numbers"
+ />
+
+ setSettings({ ...settings, requireSpecialChars: e.target.checked })
+ }
+ />
+ }
+ label="Require Special Characters"
+ />
+
+
+
+
+ )}
+
+
+
+
+ setSettings({ ...settings, sessionTimeout: parseInt(e.target.value) })
+ }
+ />
+
+
+
+ setSettings({ ...settings, maxLoginAttempts: parseInt(e.target.value) })
+ }
+ />
+
+
+
+ setSettings({ ...settings, enableTwoFactor: e.target.checked })
+ }
+ />
+ }
+ label="Enable Two-Factor Authentication"
+ sx={{ mt: 2 }}
+ />
+
+
+
+ {/* Notification Settings */}
+
+
+
+ setSettings({ ...settings, enableEmailNotifications: e.target.checked })
+ }
+ />
+ }
+ label="Enable Email Notifications"
+ />
+
+ setSettings({ ...settings, enablePushNotifications: e.target.checked })
+ }
+ />
+ }
+ label="Enable Push Notifications"
+ />
+
+
+ Admin Notifications
+
+
+ setSettings({ ...settings, adminNotifications: e.target.checked })
+ }
+ />
+ }
+ label="Receive Admin Notifications"
+ />
+
+ setSettings({ ...settings, errorAlerts: e.target.checked })
+ }
+ />
+ }
+ label="Error Alerts"
+ />
+
+ setSettings({ ...settings, newUserAlerts: e.target.checked })
+ }
+ />
+ }
+ label="New User Registration Alerts"
+ />
+
+ setSettings({ ...settings, systemHealthAlerts: e.target.checked })
+ }
+ />
+ }
+ label="System Health Alerts"
+ />
+
+
+
+ {/* Email Settings */}
+
+
+
+ setSettings({ ...settings, smtpHost: e.target.value })}
+ />
+
+
+
+ setSettings({ ...settings, smtpPort: parseInt(e.target.value) })
+ }
+ />
+
+
+ setSettings({ ...settings, smtpUser: e.target.value })}
+ />
+
+
+ setSettings({ ...settings, smtpPassword: e.target.value })}
+ />
+
+
+ setSettings({ ...settings, emailFrom: e.target.value })}
+ />
+
+
+
+
+
+
+
+ {/* Storage Settings */}
+
+
+
+
+ setSettings({ ...settings, maxFileSize: parseInt(e.target.value) })
+ }
+ />
+
+
+ setSettings({ ...settings, allowedFileTypes: e.target.value })}
+ helperText="Comma-separated list of extensions"
+ />
+
+
+
+ Storage Provider
+
+
+
+
+ setSettings({ ...settings, s3Bucket: e.target.value })}
+ />
+
+
+
+ setSettings({ ...settings, retentionDays: parseInt(e.target.value) })
+ }
+ />
+
+
+
+
+ {/* API Settings */}
+
+
+
+
+ setSettings({ ...settings, rateLimit: parseInt(e.target.value) })
+ }
+ />
+
+
+
+ setSettings({ ...settings, rateLimitWindow: parseInt(e.target.value) })
+ }
+ />
+
+
+
+ setSettings({ ...settings, apiTimeout: parseInt(e.target.value) })
+ }
+ />
+
+
+
+
+ setSettings({ ...settings, enableGraphQL: e.target.checked })
+ }
+ />
+ }
+ label="Enable GraphQL API"
+ />
+
+ setSettings({ ...settings, enableWebSockets: e.target.checked })
+ }
+ />
+ }
+ label="Enable WebSockets"
+ />
+
+
+
+ setSettings({ ...settings, corsOrigins: e.target.value })}
+ helperText="One origin per line"
+ />
+
+
+
+
+ {/* Save Button */}
+
+ }
+ onClick={handleSave}
+ >
+ Save Settings
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/parentflow-admin/src/app/users/page.tsx b/parentflow-admin/src/app/users/page.tsx
new file mode 100644
index 0000000..dc10d5d
--- /dev/null
+++ b/parentflow-admin/src/app/users/page.tsx
@@ -0,0 +1,453 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Paper,
+ IconButton,
+ Chip,
+ Button,
+ TextField,
+ InputAdornment,
+ TablePagination,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Avatar,
+ Grid,
+ Switch,
+ FormControlLabel,
+} from '@mui/material';
+import {
+ Search,
+ Edit,
+ Delete,
+ Visibility,
+ PersonAdd,
+ Block,
+ CheckCircle,
+} from '@mui/icons-material';
+import AdminLayout from '@/components/AdminLayout';
+import apiClient from '@/lib/api-client';
+
+interface User {
+ id: string;
+ email: string;
+ name: string;
+ createdAt: string;
+ lastActiveAt: string;
+ isActive: boolean;
+ familyCount: number;
+ childrenCount: number;
+ deviceCount: number;
+}
+
+export default function UsersPage() {
+ const [users, setUsers] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(10);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [selectedUser, setSelectedUser] = useState(null);
+ const [viewDialogOpen, setViewDialogOpen] = useState(false);
+ const [editDialogOpen, setEditDialogOpen] = useState(false);
+
+ useEffect(() => {
+ fetchUsers();
+ }, []);
+
+ const fetchUsers = async () => {
+ try {
+ setLoading(true);
+ const response = await apiClient.get('/admin/users');
+ setUsers(response.data);
+ } catch (error) {
+ console.error('Failed to fetch users:', error);
+ // Using mock data for development
+ setUsers([
+ {
+ id: '1',
+ email: 'john.doe@example.com',
+ name: 'John Doe',
+ createdAt: '2024-01-15T10:00:00Z',
+ lastActiveAt: '2024-10-06T08:30:00Z',
+ isActive: true,
+ familyCount: 1,
+ childrenCount: 2,
+ deviceCount: 3,
+ },
+ {
+ id: '2',
+ email: 'jane.smith@example.com',
+ name: 'Jane Smith',
+ createdAt: '2024-02-20T14:30:00Z',
+ lastActiveAt: '2024-10-05T18:45:00Z',
+ isActive: true,
+ familyCount: 1,
+ childrenCount: 1,
+ deviceCount: 2,
+ },
+ {
+ id: '3',
+ email: 'bob.johnson@example.com',
+ name: 'Bob Johnson',
+ createdAt: '2024-03-10T09:15:00Z',
+ lastActiveAt: '2024-09-30T12:00:00Z',
+ isActive: false,
+ familyCount: 1,
+ childrenCount: 3,
+ deviceCount: 1,
+ },
+ ]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleViewUser = (user: User) => {
+ setSelectedUser(user);
+ setViewDialogOpen(true);
+ };
+
+ const handleEditUser = (user: User) => {
+ setSelectedUser(user);
+ setEditDialogOpen(true);
+ };
+
+ const handleToggleUserStatus = async (user: User) => {
+ try {
+ await apiClient.patch(`/admin/users/${user.id}`, {
+ isActive: !user.isActive,
+ });
+ fetchUsers();
+ } catch (error) {
+ console.error('Failed to update user status:', error);
+ }
+ };
+
+ const handleDeleteUser = async (userId: string) => {
+ if (window.confirm('Are you sure you want to delete this user?')) {
+ try {
+ await apiClient.delete(`/admin/users/${userId}`);
+ fetchUsers();
+ } catch (error) {
+ console.error('Failed to delete user:', error);
+ }
+ }
+ };
+
+ const filteredUsers = users.filter(
+ (user) =>
+ user.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ user.email.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const formatDate = (dateString: string) => {
+ return new Date(dateString).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ };
+
+ return (
+
+
+
+ User Management
+
+
+ Manage ParentFlow users, their accounts, and permissions
+
+
+
+ {/* Stats Cards */}
+
+
+
+
+
+ Total Users
+
+
+ {users.length}
+
+
+
+
+
+
+
+
+ Active Users
+
+
+ {users.filter(u => u.isActive).length}
+
+
+
+
+
+
+
+
+ Total Families
+
+
+ {users.reduce((sum, u) => sum + u.familyCount, 0)}
+
+
+
+
+
+
+
+
+ Total Children
+
+
+ {users.reduce((sum, u) => sum + u.childrenCount, 0)}
+
+
+
+
+
+
+ {/* Search and Actions */}
+
+ setSearchQuery(e.target.value)}
+ sx={{ flexGrow: 1 }}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+ }
+ onClick={() => {
+ // Handle add user
+ }}
+ >
+ Add User
+
+
+
+ {/* Users Table */}
+
+
+
+
+ User
+ Created
+ Last Active
+ Status
+ Families
+ Children
+ Devices
+ Actions
+
+
+
+ {filteredUsers
+ .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
+ .map((user) => (
+
+
+
+
+ {user.name.charAt(0)}
+
+
+ {user.name}
+
+ {user.email}
+
+
+
+
+ {formatDate(user.createdAt)}
+ {formatDate(user.lastActiveAt)}
+
+ : }
+ />
+
+ {user.familyCount}
+ {user.childrenCount}
+ {user.deviceCount}
+
+ handleViewUser(user)}
+ title="View"
+ >
+
+
+ handleEditUser(user)}
+ title="Edit"
+ >
+
+
+ handleToggleUserStatus(user)}
+ title={user.isActive ? 'Deactivate' : 'Activate'}
+ >
+ {user.isActive ? : }
+
+ handleDeleteUser(user.id)}
+ title="Delete"
+ color="error"
+ >
+
+
+
+
+ ))}
+
+
+ setPage(newPage)}
+ onRowsPerPageChange={(e) => {
+ setRowsPerPage(parseInt(e.target.value, 10));
+ setPage(0);
+ }}
+ />
+
+
+ {/* View User Dialog */}
+
+
+ {/* Edit User Dialog */}
+
+
+ );
+}
\ No newline at end of file