# Tích Hợp MeshCentral vào Hệ Thống React TypeScript ## Tổng Quan MeshCentral cung cấp WebSocket API để tương tác với các chức năng như: - Tạo Device Group (Mesh) - Thêm/xóa thiết bị - Quản lý người dùng - Điều khiển thiết bị --- ## 1. KIẾN TRÚC GIAO TIẾP ### 1.1 WebSocket Communication MeshCentral sử dụng **WebSocket** cho real-time communication. UI của bạn sẽ kết nối qua: ``` ws://meshcentral-server:port/ws ``` ### 1.2 Flow Tương Tác ``` React App (Client) ↓ WebSocket Connection ↓ MeshCentral Server (meshuser.js) ↓ Database (MongoDB) ↓ Response back to Client ``` --- ## 2. SETUP REACT TYPESCRIPT CLIENT ### 2.1 Tạo MeshCentral Service ```typescript // services/meshcentral.service.ts export interface MeshCentralConfig { serverUrl: string; username: string; password: string; domain?: string; } export interface DeviceGroup { _id: string; name: string; desc: string; mtype: number; // 1: FreeAgent, 2: IntelAMT, 3: Mixed } export interface Device { _id: string; name: string; meshid: string; host: string; state: string; // online, offline, unknown rname?: string; // remote name } export class MeshCentralService { private ws: WebSocket | null = null; private messageId = 1; private pendingRequests = new Map void; reject: (error: any) => void; timeout: NodeJS.Timeout; }>(); private messageHandlers = new Map void>(); constructor(private config: MeshCentralConfig) {} /** * Kết nối đến MeshCentral Server */ async connect(): Promise { return new Promise((resolve, reject) => { try { const wsUrl = `${this.config.serverUrl.replace(/^http/, 'ws')}/control.ashx`; this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { console.log('Connected to MeshCentral'); this.authenticate().then(resolve).catch(reject); }; this.ws.onmessage = (event) => this.handleMessage(event.data); this.ws.onerror = (error) => { console.error('WebSocket error:', error); reject(error); }; this.ws.onclose = () => { console.log('Disconnected from MeshCentral'); this.ws = null; }; } catch (error) { reject(error); } }); } /** * Xác thực với server */ private async authenticate(): Promise { const authMessage = { action: 'authCookie', username: this.config.username, password: this.config.password, domain: this.config.domain || '' }; return this.sendMessage(authMessage); } /** * Gửi message và chờ response */ private async sendMessage(message: any): Promise { return new Promise((resolve, reject) => { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { reject(new Error('WebSocket not connected')); return; } const msgId = this.messageId++; message.sessionid = msgId; const timeout = setTimeout(() => { this.pendingRequests.delete(msgId); reject(new Error('Request timeout')); }, 30000); // 30 seconds timeout this.pendingRequests.set(msgId, { resolve, reject, timeout }); try { this.ws.send(JSON.stringify(message)); } catch (error) { this.pendingRequests.delete(msgId); clearTimeout(timeout); reject(error); } }); } /** * Xử lý incoming messages */ private handleMessage(data: string): void { try { const message = JSON.parse(data); // Handle response to pending request if (message.sessionid && this.pendingRequests.has(message.sessionid)) { const { resolve, reject, timeout } = this.pendingRequests.get(message.sessionid)!; this.pendingRequests.delete(message.sessionid); clearTimeout(timeout); if (message.error) { reject(new Error(message.error)); } else { resolve(message); } } // Handle event handlers if (message.action && this.messageHandlers.has(message.action)) { const handler = this.messageHandlers.get(message.action)!; handler(message); } } catch (error) { console.error('Error parsing message:', error); } } /** * Đăng ký handler cho event */ on(action: string, handler: (data: any) => void): void { this.messageHandlers.set(action, handler); } /** * === TẠIDEVICE GROUP === * Tạo một device group (Mesh) mới */ async createDeviceGroup(name: string, desc: string = ''): Promise { const response = await this.sendMessage({ action: 'createmesh', meshname: name, meshdesc: desc, meshtype: 2 // 2 = Intel AMT capable }); if (response.error) { throw new Error(`Failed to create device group: ${response.error}`); } return response.meshid; } /** * Lấy danh sách device groups */ async getDeviceGroups(): Promise { const response = await this.sendMessage({ action: 'meshes' }); return response.meshes || []; } /** * === THÊM DEVICE === * Lấy Agent invite link để install trên device */ async getAgentInviteLink(meshId: string, platform: string = 'linux'): Promise { const response = await this.sendMessage({ action: 'getmesh', meshid: meshId }); // Agent download URL format: // /meshagents?id=&installflags=&exeType= const flags = 3; // Windows install flags return `/meshagents?id=${meshId}&installflags=${flags}&exeType=${platform}`; } /** * Lấy danh sách devices trong group */ async getDevices(meshId: string): Promise { const response = await this.sendMessage({ action: 'getmesh', meshid: meshId }); return response.nodes || []; } /** * === QUẢN LÝ DEVICE === * Vô hiệu hóa/kích hoạt device */ async setDeviceState(deviceId: string, enabled: boolean): Promise { await this.sendMessage({ action: 'changedevice', nodeid: deviceId, enabled: enabled }); } /** * Xóa device khỏi group */ async removeDevice(deviceId: string): Promise { await this.sendMessage({ action: 'removenode', nodeids: [deviceId] }); } /** * === QUẢN LÝ NGƯỜI DÙNG === * Tạo user mới */ async createUser(username: string, password: string, email?: string): Promise { const response = await this.sendMessage({ action: 'createuser', username: username, password: password, email: email || '' }); return response; } /** * Gán quyền truy cập device group cho user * rights: bitmask của permissions * - 1: Edit mesh * - 2: Manage users * - 4: Manage computers * - 8: Remote control * - 16: Agent console * - 32: Server files * - 64: Wake device * - 128: Set notes * - 256: Remote view only */ async addUserToMesh(meshId: string, username: string, rights: number): Promise { await this.sendMessage({ action: 'editmesh', meshid: meshId, usernames: [username], rights: rights }); } /** * === ĐIỀU KHIỂN THIẾT BỊ === * Gửi command đến device */ async sendDeviceCommand(deviceId: string, command: string, parameters?: any): Promise { return this.sendMessage({ action: 'runcommand', nodeid: deviceId, command: command, params: parameters || {} }); } /** * Khởi động lại device */ async rebootDevice(deviceId: string): Promise { await this.sendMessage({ action: 'poweraction', nodeid: deviceId, actiontype: 1 // 1 = reboot }); } /** * Tắt device */ async shutdownDevice(deviceId: string): Promise { await this.sendMessage({ action: 'poweraction', nodeid: deviceId, actiontype: 2 // 2 = shutdown }); } /** * Disconnect user session từ MeshCentral */ disconnect(): void { if (this.ws) { this.ws.close(); this.ws = null; } this.pendingRequests.clear(); this.messageHandlers.clear(); } } ``` ### 2.2 Tạo Custom Hook ```typescript // hooks/useMeshCentral.ts import { useEffect, useState, useCallback, useRef } from 'react'; import { MeshCentralService, DeviceGroup, Device } from '../services/meshcentral.service'; export const useMeshCentral = (config: any) => { const serviceRef = useRef(null); const [connected, setConnected] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [deviceGroups, setDeviceGroups] = useState([]); const [devices, setDevices] = useState([]); // Kết nối const connect = useCallback(async () => { try { setLoading(true); setError(null); const service = new MeshCentralService(config); await service.connect(); serviceRef.current = service; setConnected(true); } catch (err) { setError(err instanceof Error ? err.message : 'Connection failed'); setConnected(false); } finally { setLoading(false); } }, [config]); // Tạo Device Group const createGroup = useCallback(async (name: string, desc: string = '') => { try { setError(null); if (!serviceRef.current) throw new Error('Not connected'); const group = await serviceRef.current.createDeviceGroup(name, desc); return group; } catch (err) { const message = err instanceof Error ? err.message : 'Failed to create group'; setError(message); throw err; } }, []); // Lấy danh sách Device Groups const loadDeviceGroups = useCallback(async () => { try { setError(null); if (!serviceRef.current) throw new Error('Not connected'); const groups = await serviceRef.current.getDeviceGroups(); setDeviceGroups(groups); return groups; } catch (err) { const message = err instanceof Error ? err.message : 'Failed to load groups'; setError(message); return []; } }, []); // Lấy danh sách Devices const loadDevices = useCallback(async (meshId: string) => { try { setError(null); if (!serviceRef.current) throw new Error('Not connected'); const devs = await serviceRef.current.getDevices(meshId); setDevices(devs); return devs; } catch (err) { const message = err instanceof Error ? err.message : 'Failed to load devices'; setError(message); return []; } }, []); // Cleanup useEffect(() => { return () => { if (serviceRef.current) { serviceRef.current.disconnect(); } }; }, []); return { service: serviceRef.current, connected, loading, error, deviceGroups, devices, connect, createGroup, loadDeviceGroups, loadDevices }; }; ``` --- ## 3. REACT COMPONENTS ### 3.1 Device Group Manager ```typescript // components/DeviceGroupManager.tsx import React, { useState, useEffect } from 'react'; import { useMeshCentral } from '../hooks/useMeshCentral'; export const DeviceGroupManager: React.FC = () => { const mesh = useMeshCentral({ serverUrl: 'https://your-meshcentral-server.com', username: 'admin', password: process.env.REACT_APP_MESH_PASSWORD, domain: '' }); const [newGroupName, setNewGroupName] = useState(''); const [newGroupDesc, setNewGroupDesc] = useState(''); const [creating, setCreating] = useState(false); useEffect(() => { // Kết nối khi component mount mesh.connect(); }, []); useEffect(() => { // Load groups khi kết nối thành công if (mesh.connected) { mesh.loadDeviceGroups(); } }, [mesh.connected]); const handleCreateGroup = async () => { if (!newGroupName.trim()) return; setCreating(true); try { await mesh.createGroup(newGroupName, newGroupDesc); setNewGroupName(''); setNewGroupDesc(''); // Reload groups await mesh.loadDeviceGroups(); } finally { setCreating(false); } }; return (

Device Groups

{/* Connection Status */}
{mesh.connected ? '🟢 Connected' : '🔴 Disconnected'} {mesh.loading && (Connecting...)}
{/* Error Message */} {mesh.error && (
{mesh.error}
)} {/* Create New Group Form */}

Create New Group

setNewGroupName(e.target.value)} disabled={creating || !mesh.connected} /> setNewGroupDesc(e.target.value)} disabled={creating || !mesh.connected} />
{/* Groups List */}

Existing Groups ({mesh.deviceGroups.length})

{mesh.deviceGroups.length === 0 ? (

No device groups found

) : (
    {mesh.deviceGroups.map((group: any) => (
  • {group.name} {group.desc &&

    {group.desc}

    } ID: {group._id}
  • ))}
)}
); }; ``` ### 3.2 Device Manager ```typescript // components/DeviceManager.tsx import React, { useState, useEffect } from 'react'; import { useMeshCentral } from '../hooks/useMeshCentral'; interface DeviceManagerProps { meshId: string; } export const DeviceManager: React.FC = ({ meshId }) => { const mesh = useMeshCentral({ serverUrl: 'https://your-meshcentral-server.com', username: 'admin', password: process.env.REACT_APP_MESH_PASSWORD }); const [agentUrl, setAgentUrl] = useState(''); const [showAgentUrl, setShowAgentUrl] = useState(false); useEffect(() => { mesh.connect(); }, []); useEffect(() => { if (mesh.connected) { mesh.loadDevices(meshId); } }, [mesh.connected, meshId]); const handleGetAgentUrl = async () => { try { const url = await mesh.service?.getAgentInviteLink(meshId, 'linux'); setAgentUrl(url || ''); setShowAgentUrl(true); } catch (err) { console.error('Error getting agent URL:', err); } }; const handleRemoveDevice = async (deviceId: string) => { if (!window.confirm('Are you sure?')) return; try { await mesh.service?.removeDevice(deviceId); await mesh.loadDevices(meshId); } catch (err) { console.error('Error removing device:', err); } }; const handleRebootDevice = async (deviceId: string) => { if (!window.confirm('Reboot device?')) return; try { await mesh.service?.rebootDevice(deviceId); } catch (err) { console.error('Error rebooting device:', err); } }; return (

Devices in Group: {meshId}

{showAgentUrl && (

Install link:

)}

Devices ({mesh.devices.length})

{mesh.devices.length === 0 ? (

No devices

) : ( {mesh.devices.map((device: any) => ( ))}
Name Status Actions
{device.name} {device.conn ? '🟢 Online' : '🔴 Offline'}
)}
); }; ``` --- ## 4. AUTHENTICATION ### 4.1 Methods MeshCentral hỗ trợ: 1. **Basic Auth** (credentials embed in WebSocket message) ```typescript { action: 'authCookie', username: 'admin', password: 'password', domain: '' } ``` 2. **Token Auth** (nếu MeshCentral cấu hình OAuth/SSO) ```typescript { action: 'authToken', token: 'jwt-token-here' } ``` ### 4.2 Secure Setup ```typescript // .env.local REACT_APP_MESH_SERVER=https://your-meshcentral-server.com REACT_APP_MESH_USER=your-api-user REACT_APP_MESH_PASSWORD=your-secure-password // hoặc sử dụng backend proxy REACT_APP_MESH_API_ENDPOINT=/api/mesh/ ``` --- ## 5. WEBSOCKET MESSAGE PROTOCOL ### 5.1 Request Format ```typescript { action: 'command-name', sessionid: number, // auto-increment per client meshid?: string, nodeid?: string, ...otherParams } ``` ### 5.2 Response Format ```typescript { action: 'command-response', sessionid: number, // matches request error?: string, // if error occurred ... response data } ``` ### 5.3 Common Actions | Action | Purpose | Parameters | |--------|---------|------------| | `createmesh` | Tạo device group | meshname, meshdesc, meshtype | | `meshes` | Lấy danh sách groups | (none) | | `getmesh` | Chi tiết group | meshid | | `editmesh` | Sửa group | meshid, name, desc | | `deletemesh` | Xóa group | meshid, meshname | | `nodes` | Lấy danh sách devices | meshid | | `removenode` | Xóa device | nodeids[] | | `changedevice` | Sửa device | nodeid, name, desc, etc | | `poweraction` | Power control | nodeid, actiontype (1=reboot, 2=shutdown) | | `auth*` | Authentication | (see Auth section) | --- ## 6. ERROR HANDLING ```typescript // service error response examples { error: 'Invalid credentials', sessionid: 1 } { error: 'Permission denied', sessionid: 2 } { error: 'Device not found', sessionid: 3 } ``` ### Custom Error Handling ```typescript private handleMessage(data: string): void { try { const message = JSON.parse(data); if (message.sessionid && this.pendingRequests.has(message.sessionid)) { const { resolve, reject, timeout } = this.pendingRequests.get(message.sessionid)!; this.pendingRequests.delete(message.sessionid); clearTimeout(timeout); if (message.error) { // Map error codes to user-friendly messages const errorMap: Record = { 'Invalid credentials': 'Username or password incorrect', 'Permission denied': 'You do not have permission for this action', 'Device not found': 'Device does not exist', 'Mesh not found': 'Device group does not exist' }; const userMessage = errorMap[message.error] || message.error; reject(new Error(userMessage)); } else { resolve(message); } } } catch (error) { console.error('Error parsing message:', error); } } ``` --- ## 7. BEST PRACTICES ### 7.1 Connection Management ```typescript // Reconnect logic private async reconnect(): Promise { const maxRetries = 3; let retries = 0; while (retries < maxRetries) { try { await this.connect(); return; } catch (error) { retries++; if (retries < maxRetries) { await new Promise(resolve => setTimeout(resolve, 1000 * retries)); } } } throw new Error('Failed to connect after retries'); } ``` ### 7.2 Event Subscriptions ```typescript // Subscribe to device updates mesh.on('nodechanged', (data) => { console.log('Device updated:', data); // Reload device list mesh.loadDevices(selectedMeshId); }); // Subscribe to user login mesh.on('userlogin', (data) => { console.log('User logged in:', data.username); }); ``` ### 7.3 Batch Operations ```typescript // Xóa multiple devices async removeMultipleDevices(deviceIds: string[]): Promise { await this.sendMessage({ action: 'removenode', nodeids: deviceIds }); } // Add users to mesh async addUsersToMesh(meshId: string, users: string[], rights: number): Promise { await this.sendMessage({ action: 'editmesh', meshid: meshId, usernames: users, rights: rights }); } ``` ### 7.4 Permissions Check ```typescript // MESHRIGHT constants const MESHRIGHT_EDITMESH = 0x00000001; const MESHRIGHT_MANAGEUSERS = 0x00000002; const MESHRIGHT_MANAGECOMPUTERS = 0x00000004; const MESHRIGHT_REMOTECONTROL = 0x00000008; function hasPermission(userRights: number, requiredRight: number): boolean { return (userRights & requiredRight) === requiredRight; } // Sử dụng if (hasPermission(userRights, MESHRIGHT_MANAGECOMPUTERS)) { // Allow device management } ``` --- ## 8. CONFIGURATION EXAMPLE ### 8.1 MeshCentral Server config.json ```json { "settings": { "cert": "meshcentral", "mongodb": "mongodb://127.0.0.1:27017/meshcentral", "domain": { "": { "title": "MeshCentral", "title2": "My System", "minpasswordlen": 8, "https": true } } } } ``` ### 8.2 React App config ```typescript // config/meshcentral.config.ts export const meshCentralConfig = { production: { serverUrl: 'https://meshcentral.example.com', wsUrl: 'wss://meshcentral.example.com' }, development: { serverUrl: 'http://localhost:8081', wsUrl: 'ws://localhost:8081' } }; ``` --- ## 9. TYPESCRIPT TYPES (Full API) ```typescript // Complete types export interface MeshCentralMessage { action: string; sessionid?: number; error?: string; } export interface CreateMeshRequest { action: 'createmesh'; meshname: string; meshdesc?: string; meshtype?: number; } export interface CreateMeshResponse { action: 'meshchange'; mesh: DeviceGroup; } export interface GetMeshesResponse { action: 'meshes'; meshes: DeviceGroup[]; } export interface DeviceChangeEvent { action: 'nodechanged'; node: Device; } export interface PowerActionRequest { action: 'poweraction'; nodeid: string; actiontype: 1 | 2 | 5; // 1=reboot, 2=shutdown, 5=wake } ``` --- ## 10. TROUBLESHOOTING ### Issue: WebSocket Connection Failed ``` Solution: Check CORS settings in MeshCentral config Add to config.json: "https": { "headers": { "Access-Control-Allow-Origin": "*" } } ``` ### Issue: Authentication Failed ``` Solution: Verify credentials and domain check MeshCentral admin panel for user ensure domain name matches in request ``` ### Issue: Permission Denied ``` Solution: Check user permissions in MeshCentral Admin > Users > Select user > Mesh access Add necessary permissions (edit mesh, manage computers, etc) ``` ### Issue: Device Not Responding ``` Solution: Verify device agent status 1. Check device is online in MeshCentral UI 2. Verify agent has internet connectivity 3. Check firewall/NAT rules allow agent communication ``` --- ## 11. EXAMPLES ### Complete Example: Create Group and Add Devices ```typescript async function setupNewTeam(teamName: string, deviceIds: string[]) { const mesh = new MeshCentralService({ serverUrl: 'https://meshcentral.example.com', username: 'admin', password: process.env.MESH_PASSWORD }); try { // 1. Connect await mesh.connect(); console.log('Connected'); // 2. Create group const group = await mesh.createDeviceGroup(teamName, `Team: ${teamName}`); console.log('Group created:', group); // 3. Get agent URL const agentUrl = await mesh.getAgentInviteLink(group._id, 'windows'); console.log('Agent URL:', agentUrl); // 4. Add users const readOnlyRights = 256; // MESHRIGHT_REMOTEVIEWONLY const adminRights = 0xFFFFFFFF; await mesh.addUserToMesh(group._id, 'user@example.com', readOnlyRights); console.log('User added'); // 5. Deploy agents (manual step or via script) console.log(`Deploy agents using: ${agentUrl}`); return group; } finally { mesh.disconnect(); } } ``` --- ## 📚 Tài Liệu Tham Khảo - MeshCentral GitHub: https://github.com/Ylianst/MeshCentral - Official Docs: https://meshcentral.com/info/ - WebSocket Protocol: Xem `meshuser.js` trong MeshCentral source