import React, {useState, useEffect, useContext} from 'react'; import {useNavigate} from 'react-router-dom'; import {UserContext} from '@/common/contexts/UserContext.jsx'; import {useToast} from '@/common/contexts/ToastContext.jsx'; import {getRequest, postRequest, deleteRequest} from '@/common/utils/RequestUtil.js'; import Button from '@/common/components/Button'; import Input from '@/common/components/Input'; import Modal, {ModalActions} from '@/common/components/Modal'; import Card, {CardHeader, CardBody} from '@/common/components/Card'; import Badge from '@/common/components/Badge'; import Grid from '@/common/components/Grid'; import LoadingSpinner from '@/common/components/LoadingSpinner'; import EmptyState from '@/common/components/EmptyState'; import PageHeader from '@/common/components/PageHeader'; import DetailItem, {DetailList} from '@/common/components/DetailItem'; import { PlusIcon, TrashIcon, ComputerTowerIcon, CalendarIcon, IdentificationCardIcon, UserIcon, QrCodeIcon, CopyIcon, ClockIcon } from '@phosphor-icons/react'; import './styles.sass'; export const Machines = () => { const {user: currentUser} = useContext(UserContext); const toast = useToast(); const navigate = useNavigate(); const [machines, setMachines] = useState([]); const [loading, setLoading] = useState(true); const [showCreateModal, setShowCreateModal] = useState(false); const [showProvisioningModal, setShowProvisioningModal] = useState(false); const [selectedMachine, setSelectedMachine] = useState(null); const [provisioningCode, setProvisioningCode] = useState(null); const [formData, setFormData] = useState({name: ''}); const [formErrors, setFormErrors] = useState({}); const [submitting, setSubmitting] = useState(false); useEffect(() => { fetchMachines(); }, []); const fetchMachines = async () => { try { setLoading(true); const response = await getRequest('machines'); setMachines(response); } catch (error) { console.error('Failed to fetch machines:', error); toast.error('Failed to load machines. Please try again.'); } finally { setLoading(false); } }; const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); }; const formatUuid = (uuid) => { // Show first 8 characters of UUID for display return uuid.substring(0, 8).toUpperCase(); }; const openCreateModal = () => { setFormData({name: ''}); setFormErrors({}); setShowCreateModal(true); }; const closeCreateModal = () => { setShowCreateModal(false); setFormData({name: ''}); setFormErrors({}); }; const openProvisioningModal = (machine) => { setSelectedMachine(machine); setProvisioningCode(null); setShowProvisioningModal(true); }; const closeProvisioningModal = () => { setShowProvisioningModal(false); setSelectedMachine(null); setProvisioningCode(null); }; const validateForm = () => { const errors = {}; if (!formData.name.trim()) { errors.name = 'Machine name is required'; } else if (formData.name.length < 3) { errors.name = 'Machine name must be at least 3 characters'; } setFormErrors(errors); return Object.keys(errors).length === 0; }; const handleCreateSubmit = async (e) => { e.preventDefault(); if (!validateForm()) { return; } setSubmitting(true); try { await postRequest('machines/register', { name: formData.name.trim() }); toast.success(`Machine "${formData.name}" registered successfully`); closeCreateModal(); fetchMachines(); } catch (error) { console.error('Failed to register machine:', error); const errorMessage = error.error || 'Failed to register machine. Please try again.'; toast.error(errorMessage); } finally { setSubmitting(false); } }; const handleGenerateProvisioningCode = async () => { if (!selectedMachine) return; setSubmitting(true); try { const response = await postRequest('machines/provisioning-code', { machine_id: selectedMachine.id }); setProvisioningCode(response); toast.success('Provisioning code generated successfully'); } catch (error) { console.error('Failed to generate provisioning code:', error); const errorMessage = error.error || 'Failed to generate provisioning code. Please try again.'; toast.error(errorMessage); } finally { setSubmitting(false); } }; const handleCopyCode = async (code) => { try { await navigator.clipboard.writeText(code); toast.success('Provisioning code copied to clipboard'); } catch (error) { console.error('Failed to copy to clipboard:', error); toast.error('Failed to copy to clipboard'); } }; const handleDelete = async (machineId, machineName) => { if (!window.confirm(`Are you sure you want to delete machine "${machineName}"? This action cannot be undone.`)) { return; } try { await deleteRequest(`machines/${machineId}`); toast.success(`Machine "${machineName}" deleted successfully`); fetchMachines(); } catch (error) { console.error('Failed to delete machine:', error); toast.error('Failed to delete machine. Please try again.'); } }; const handleMachineClick = (machineId) => { navigate(`/machines/${machineId}`); }; const handleActionClick = (e) => { e.stopPropagation(); // Prevent navigation when clicking action buttons }; const handleInputChange = (e) => { const {name, value} = e.target; setFormData(prev => ({ ...prev, [name]: value })); if (formErrors[name]) { setFormErrors(prev => ({ ...prev, [name]: '' })); } }; if (loading) { return (
); } return (
} onClick={openCreateModal} > Register Machine } /> {machines.map(machine => ( handleMachineClick(machine.id)} style={{ cursor: 'pointer' }} >

{machine.name}

{formatUuid(machine.uuid)}
}> Owner: {currentUser?.role === 'admin' ? `User ${machine.user_id}` : 'You'} }> Registered: {formatDate(machine.created_at)}
))}
{machines.length === 0 && ( } title="No machines registered" description="Register your first machine to get started with backup management" action={ } /> )} {/* Create Machine Modal */}

Register a new machine to enable backup management. This will create a machine entry that you can later generate provisioning codes for.

{/* Provisioning Code Modal */}

Generate a provisioning code for {selectedMachine?.name}. This code can be used to register a client machine with the backup system.

{!provisioningCode ? (
) : (
}> Expires: {formatDate(provisioningCode.expires_at)} }> Raw Code: {provisioningCode.raw_code}
This code expires in 1 hour
)} {provisioningCode && ( )}
); };