TTMT.ManageWebGUI/INTEGRATION_GUIDE_VI.md

1087 lines
25 KiB
Markdown
Raw Normal View History

2026-03-23 22:08:52 +07:00
# 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