EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
AssignDeviceModal.jsx
Go to the documentation of this file.
1import { useState, useEffect } from 'react';
2import { apiFetch } from '../lib/api';
3import './Modal.css';
4
5export default function AssignDeviceModal({ isOpen, onClose, device, onAssigned }) {
6 const [tenants, setTenants] = useState([]);
7 const [customers, setCustomers] = useState([]);
8 const [selectedTenant, setSelectedTenant] = useState('');
9 const [selectedCustomer, setSelectedCustomer] = useState('');
10 const [moveMesh, setMoveMesh] = useState(true);
11 const [loading, setLoading] = useState(false);
12 const [error, setError] = useState('');
13
14 useEffect(() => {
15 if (isOpen) {
16 fetchTenants();
17 }
18 }, [isOpen]);
19
20 useEffect(() => {
21 if (selectedTenant) {
22 fetchCustomers(selectedTenant);
23 } else {
24 setCustomers([]);
25 setSelectedCustomer('');
26 }
27 }, [selectedTenant]);
28
29 const fetchTenants = async () => {
30 try {
31 const res = await apiFetch('/tenants');
32 if (!res.ok) throw new Error('Failed to fetch tenants');
33 const data = await res.json();
34 setTenants(data.tenants || []);
35 } catch (err) {
36 console.error('Error fetching tenants:', err);
37 setError('Failed to load tenants');
38 }
39 };
40
41 const fetchCustomers = async (tenantId) => {
42 try {
43 const res = await apiFetch(`/customers?tenant=${tenantId}`);
44 if (!res.ok) throw new Error('Failed to fetch customers');
45 const data = await res.json();
46 setCustomers(data.customers || []);
47 } catch (err) {
48 console.error('Error fetching customers:', err);
49 setCustomers([]);
50 }
51 };
52
53 const handleAssign = async () => {
54 if (!selectedTenant) {
55 setError('Please select a tenant');
56 return;
57 }
58
59 setLoading(true);
60 setError('');
61
62 try {
63 const res = await apiFetch('/monitoring/assign-device', {
64 method: 'POST',
65 headers: { 'Content-Type': 'application/json' },
66 body: JSON.stringify({
67 nodeId: device.meshcentral_nodeid,
68 tenantId: selectedTenant,
69 customerId: selectedCustomer || null,
70 moveMesh: moveMesh
71 })
72 });
73
74 if (!res.ok) {
75 const errData = await res.json();
76 throw new Error(errData.error || 'Failed to assign device');
77 }
78
79 const result = await res.json();
80 console.log('Device assigned:', result);
81
82 if (onAssigned) {
83 onAssigned(result);
84 }
85
86 onClose();
87 } catch (err) {
88 console.error('Error assigning device:', err);
89 setError(err.message);
90 } finally {
91 setLoading(false);
92 }
93 };
94
95 if (!isOpen) return null;
96
97 return (
98 <div className="modal-overlay" onClick={onClose}>
99 <div className="modal-content" onClick={(e) => e.stopPropagation()}>
100 <div className="modal-header">
101 <h2>Assign Device to Tenant</h2>
102 <button className="modal-close" onClick={onClose}>
103 <span className="material-symbols-outlined">close</span>
104 </button>
105 </div>
106
107 <div className="modal-body">
108 {error && (
109 <div className="error-message" style={{ marginBottom: '1rem' }}>
110 {error}
111 </div>
112 )}
113
114 <div style={{ marginBottom: '1rem', padding: '1rem', background: 'var(--bg-secondary)', borderRadius: '8px' }}>
115 <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}>
116 <span className="material-symbols-outlined" style={{ color: 'var(--primary)' }}>
117 computer
118 </span>
119 <strong>{device.hostname || 'Unknown Device'}</strong>
120 </div>
121 <div style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>
122 <div>Platform: {device.platform || device.os || 'Unknown'}</div>
123 <div style={{ fontSize: '0.75rem', fontFamily: 'monospace', marginTop: '0.25rem' }}>
124 Node ID: {device.meshcentral_nodeid?.substring(0, 40)}...
125 </div>
126 </div>
127 </div>
128
129 <div className="form-group">
130 <label htmlFor="tenant">
131 Tenant <span style={{ color: 'red' }}>*</span>
132 </label>
133 <select
134 id="tenant"
135 value={selectedTenant}
136 onChange={(e) => setSelectedTenant(e.target.value)}
137 disabled={loading}
138 >
139 <option value="">-- Select Tenant --</option>
140 {tenants.map((tenant) => (
141 <option key={tenant.tenant_id} value={tenant.tenant_id}>
142 {tenant.name}
143 </option>
144 ))}
145 </select>
146 </div>
147
148 <div className="form-group">
149 <label htmlFor="customer">Customer (Optional)</label>
150 <select
151 id="customer"
152 value={selectedCustomer}
153 onChange={(e) => setSelectedCustomer(e.target.value)}
154 disabled={loading || !selectedTenant}
155 >
156 <option value="">-- No Customer --</option>
157 {customers.map((customer) => (
158 <option key={customer.customer_id} value={customer.customer_id}>
159 {customer.name}
160 </option>
161 ))}
162 </select>
163 {!selectedTenant && (
164 <small style={{ color: 'var(--text-secondary)', fontSize: '0.75rem' }}>
165 Select a tenant first to load customers
166 </small>
167 )}
168 </div>
169
170 <div className="form-group">
171 <label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer' }}>
172 <input
173 type="checkbox"
174 checked={moveMesh}
175 onChange={(e) => setMoveMesh(e.target.checked)}
176 disabled={loading}
177 />
178 <span>Move device to tenant's mesh group</span>
179 </label>
180 <small style={{ color: 'var(--text-secondary)', fontSize: '0.75rem', marginLeft: '1.5rem' }}>
181 Recommended: Organizes devices by tenant in MeshCentral
182 </small>
183 </div>
184 </div>
185
186 <div className="modal-footer">
187 <button className="btn" onClick={onClose} disabled={loading}>
188 Cancel
189 </button>
190 <button
191 className="btn primary"
192 onClick={handleAssign}
193 disabled={loading || !selectedTenant}
194 >
195 {loading ? 'Assigning...' : 'Assign Device'}
196 </button>
197 </div>
198 </div>
199 </div>
200 );
201}