1087 lines
25 KiB
Markdown
1087 lines
25 KiB
Markdown
# 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<number, {
|
|
resolve: (value: any) => void;
|
|
reject: (error: any) => void;
|
|
timeout: NodeJS.Timeout;
|
|
}>();
|
|
private messageHandlers = new Map<string, (data: any) => void>();
|
|
|
|
constructor(private config: MeshCentralConfig) {}
|
|
|
|
/**
|
|
* Kết nối đến MeshCentral Server
|
|
*/
|
|
async connect(): Promise<void> {
|
|
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<void> {
|
|
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<any> {
|
|
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<DeviceGroup> {
|
|
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<DeviceGroup[]> {
|
|
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<string> {
|
|
const response = await this.sendMessage({
|
|
action: 'getmesh',
|
|
meshid: meshId
|
|
});
|
|
|
|
// Agent download URL format:
|
|
// /meshagents?id=<meshid>&installflags=<flags>&exeType=<type>
|
|
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<Device[]> {
|
|
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<void> {
|
|
await this.sendMessage({
|
|
action: 'changedevice',
|
|
nodeid: deviceId,
|
|
enabled: enabled
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Xóa device khỏi group
|
|
*/
|
|
async removeDevice(deviceId: string): Promise<void> {
|
|
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<any> {
|
|
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<void> {
|
|
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<any> {
|
|
return this.sendMessage({
|
|
action: 'runcommand',
|
|
nodeid: deviceId,
|
|
command: command,
|
|
params: parameters || {}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Khởi động lại device
|
|
*/
|
|
async rebootDevice(deviceId: string): Promise<void> {
|
|
await this.sendMessage({
|
|
action: 'poweraction',
|
|
nodeid: deviceId,
|
|
actiontype: 1 // 1 = reboot
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Tắt device
|
|
*/
|
|
async shutdownDevice(deviceId: string): Promise<void> {
|
|
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<MeshCentralService | null>(null);
|
|
const [connected, setConnected] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [deviceGroups, setDeviceGroups] = useState<DeviceGroup[]>([]);
|
|
const [devices, setDevices] = useState<Device[]>([]);
|
|
|
|
// 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 (
|
|
<div className="device-group-manager">
|
|
<h2>Device Groups</h2>
|
|
|
|
{/* Connection Status */}
|
|
<div className={`status ${mesh.connected ? 'connected' : 'disconnected'}`}>
|
|
{mesh.connected ? '🟢 Connected' : '🔴 Disconnected'}
|
|
{mesh.loading && <span> (Connecting...)</span>}
|
|
</div>
|
|
|
|
{/* Error Message */}
|
|
{mesh.error && (
|
|
<div className="error-message">{mesh.error}</div>
|
|
)}
|
|
|
|
{/* Create New Group Form */}
|
|
<div className="create-group-form">
|
|
<h3>Create New Group</h3>
|
|
<input
|
|
type="text"
|
|
placeholder="Group name"
|
|
value={newGroupName}
|
|
onChange={(e) => setNewGroupName(e.target.value)}
|
|
disabled={creating || !mesh.connected}
|
|
/>
|
|
<input
|
|
type="text"
|
|
placeholder="Description (optional)"
|
|
value={newGroupDesc}
|
|
onChange={(e) => setNewGroupDesc(e.target.value)}
|
|
disabled={creating || !mesh.connected}
|
|
/>
|
|
<button
|
|
onClick={handleCreateGroup}
|
|
disabled={creating || !mesh.connected || !newGroupName.trim()}
|
|
>
|
|
{creating ? 'Creating...' : 'Create Group'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Groups List */}
|
|
<div className="groups-list">
|
|
<h3>Existing Groups ({mesh.deviceGroups.length})</h3>
|
|
{mesh.deviceGroups.length === 0 ? (
|
|
<p>No device groups found</p>
|
|
) : (
|
|
<ul>
|
|
{mesh.deviceGroups.map((group: any) => (
|
|
<li key={group._id}>
|
|
<strong>{group.name}</strong>
|
|
{group.desc && <p>{group.desc}</p>}
|
|
<small>ID: {group._id}</small>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
### 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<DeviceManagerProps> = ({ 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 (
|
|
<div className="device-manager">
|
|
<h2>Devices in Group: {meshId}</h2>
|
|
|
|
<button
|
|
onClick={handleGetAgentUrl}
|
|
disabled={!mesh.connected}
|
|
>
|
|
Get Agent URL
|
|
</button>
|
|
|
|
{showAgentUrl && (
|
|
<div className="agent-url">
|
|
<p>Install link:</p>
|
|
<input
|
|
type="text"
|
|
value={`${window.location.origin}${agentUrl}`}
|
|
readOnly
|
|
/>
|
|
<button onClick={() => {
|
|
navigator.clipboard.writeText(`${window.location.origin}${agentUrl}`);
|
|
}}>
|
|
Copy
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
<div className="devices-list">
|
|
<h3>Devices ({mesh.devices.length})</h3>
|
|
{mesh.devices.length === 0 ? (
|
|
<p>No devices</p>
|
|
) : (
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{mesh.devices.map((device: any) => (
|
|
<tr key={device._id}>
|
|
<td>{device.name}</td>
|
|
<td className={`status ${device.conn ? 'online' : 'offline'}`}>
|
|
{device.conn ? '🟢 Online' : '🔴 Offline'}
|
|
</td>
|
|
<td>
|
|
<button onClick={() => handleRebootDevice(device._id)}>
|
|
Reboot
|
|
</button>
|
|
<button
|
|
onClick={() => handleRemoveDevice(device._id)}
|
|
className="danger"
|
|
>
|
|
Remove
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 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<string, string> = {
|
|
'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<void> {
|
|
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<void> {
|
|
await this.sendMessage({
|
|
action: 'removenode',
|
|
nodeids: deviceIds
|
|
});
|
|
}
|
|
|
|
// Add users to mesh
|
|
async addUsersToMesh(meshId: string, users: string[], rights: number): Promise<void> {
|
|
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
|