From b36b4b11137e7b27f2d6af9ae1e8a926659cdfb1 Mon Sep 17 00:00:00 2001 From: bachhungcb Date: Mon, 23 Mar 2026 22:08:52 +0700 Subject: [PATCH] add meshcentral skeletons in UI --- INTEGRATION_GUIDE_VI.md | 1086 +++++++++++++++++ .../_auth/rooms/$roomName/connect/index.tsx | 0 src/services/index.ts | 3 + src/services/meshcentral.service.ts | 310 +++++ 4 files changed, 1399 insertions(+) create mode 100644 INTEGRATION_GUIDE_VI.md create mode 100644 src/routes/_auth/rooms/$roomName/connect/index.tsx create mode 100644 src/services/meshcentral.service.ts diff --git a/INTEGRATION_GUIDE_VI.md b/INTEGRATION_GUIDE_VI.md new file mode 100644 index 0000000..6b0383c --- /dev/null +++ b/INTEGRATION_GUIDE_VI.md @@ -0,0 +1,1086 @@ +# 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) => ( + + + + + + ))} + +
NameStatusActions
{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 diff --git a/src/routes/_auth/rooms/$roomName/connect/index.tsx b/src/routes/_auth/rooms/$roomName/connect/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/services/index.ts b/src/services/index.ts index 2c03cf0..5772680 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -15,3 +15,6 @@ export * as permissionService from "./permission.service"; // Role API Services export * as roleService from "./role.service"; + +// Mesh Central API Services +export * as meshCentralService from "./meshcentral.service"; \ No newline at end of file diff --git a/src/services/meshcentral.service.ts b/src/services/meshcentral.service.ts new file mode 100644 index 0000000..7870f08 --- /dev/null +++ b/src/services/meshcentral.service.ts @@ -0,0 +1,310 @@ +// 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: ReturnType; + }>(); + 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 = Window + }); + + 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 = 0; // Windows install flags + return `/meshagents?id=4&meshid=${meshId}&installflags=${flags}`; + } + + /** + * 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(); + } +} \ No newline at end of file