Initial commit: NetMesh smart ops platform with 3D topology, inspection reports, RCA and AI ops assistant
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
This commit is contained in:
237
electron/bridges/inspection/adapters/windowsServerAdapter.cjs
Normal file
237
electron/bridges/inspection/adapters/windowsServerAdapter.cjs
Normal file
@@ -0,0 +1,237 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Windows 服务器采集器
|
||||
*
|
||||
* 采集指标:
|
||||
* - cpu_usage CPU 使用率(%)
|
||||
* - memory_usage 内存使用率(%)
|
||||
* - disk_usage 磁盘使用率(%)
|
||||
* - process_count 进程数
|
||||
* - uptime 运行时间
|
||||
* - service_status 关键服务状态
|
||||
*
|
||||
* 实现说明:
|
||||
* 使用 PowerShell Base64 EncodedCommand 方式执行,与 NetMesh System Manager 一致。
|
||||
* 传入 exec 函数执行远程命令(由调度器注入)。
|
||||
*/
|
||||
|
||||
const BaseAdapter = require("./baseAdapter.cjs");
|
||||
|
||||
class WindowsServerAdapter extends BaseAdapter {
|
||||
constructor(connectionInfo, options = {}) {
|
||||
super(connectionInfo, options);
|
||||
this.adapterName = "windows_server";
|
||||
if (options.execFn) {
|
||||
this.execute = options.execFn;
|
||||
}
|
||||
}
|
||||
|
||||
getSupportedMetrics() {
|
||||
return [
|
||||
"cpu_usage",
|
||||
"memory_usage",
|
||||
"disk_usage",
|
||||
"process_count",
|
||||
"uptime",
|
||||
"service_status",
|
||||
];
|
||||
}
|
||||
|
||||
async collectMetrics() {
|
||||
const metrics = {};
|
||||
const raw = {};
|
||||
const DEBUG = process.env.INSPECTION_DEBUG === "1";
|
||||
|
||||
// 1. CPU + 内存 + 运行时间(快速,独立执行)
|
||||
try {
|
||||
const script = this._buildFastStatsScript();
|
||||
const cmd = this._buildPowerShellCmd(script);
|
||||
if (DEBUG) console.log("[WinAdapter] 执行命令:", cmd);
|
||||
raw.stats = await this.execute(cmd);
|
||||
if (DEBUG) console.log("[WinAdapter] 原始输出:", raw.stats);
|
||||
const parsed = this._parseStats(raw.stats);
|
||||
if (DEBUG) console.log("[WinAdapter] 解析结果:", parsed);
|
||||
Object.assign(metrics, parsed);
|
||||
} catch (e) {
|
||||
raw.stats_error = e.message;
|
||||
if (DEBUG) console.error("[WinAdapter] 采集失败:", e.message);
|
||||
}
|
||||
|
||||
// 2. 磁盘使用率(较慢,单独执行,超时放宽到 30s)
|
||||
try {
|
||||
const diskScript = this._buildDiskScript();
|
||||
const diskCmd = this._buildPowerShellCmd(diskScript);
|
||||
if (DEBUG) console.log("[WinAdapter] 磁盘命令:", diskCmd);
|
||||
raw.disk = await this.execute(diskCmd);
|
||||
if (DEBUG) console.log("[WinAdapter] 磁盘原始输出:", raw.disk);
|
||||
metrics.disk_usage = this._parseDisk(raw.disk);
|
||||
} catch (e) {
|
||||
raw.disk_error = e.message;
|
||||
if (DEBUG) console.error("[WinAdapter] 磁盘采集失败:", e.message);
|
||||
}
|
||||
|
||||
// 3. 关键服务状态
|
||||
try {
|
||||
const serviceScript = this._buildServiceScript();
|
||||
const cmd = this._buildPowerShellCmd(serviceScript);
|
||||
if (DEBUG) console.log("[WinAdapter] 服务命令:", cmd);
|
||||
raw.services = await this.execute(cmd);
|
||||
if (DEBUG) console.log("[WinAdapter] 服务原始输出:", raw.services);
|
||||
metrics.service_status = this._parseServices(raw.services);
|
||||
} catch (e) {
|
||||
metrics.service_status = [];
|
||||
raw.services_error = e.message;
|
||||
if (DEBUG) console.error("[WinAdapter] 服务采集失败:", e.message);
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速统计:CPU + 内存(使用更快的 WMI 查询)
|
||||
*/
|
||||
_buildFastStatsScript() {
|
||||
return `
|
||||
try { $cpu = (Get-WmiObject Win32_Processor).LoadPercentage; if (-not $cpu) { $cpu = 0 } } catch { $cpu = 0 }
|
||||
try { $os = Get-WmiObject Win32_OperatingSystem; $total = [math]::Round($os.TotalVisibleMemorySize/1MB,0); $free = [math]::Round($os.FreePhysicalMemory/1MB,0); $usage = if ($total -gt 0) { [math]::Round(($total-$free)/$total*100,1) } else { 0 } } catch { $total = 0; $usage = 0; $free = 0 }
|
||||
@{ cpu = $cpu; mem_usage = $usage; mem_total = $total; mem_free = $free } | ConvertTo-Json -Compress
|
||||
`.trim();
|
||||
}
|
||||
|
||||
/** 磁盘使用率(只查 C: 盘) */
|
||||
_buildDiskScript() {
|
||||
return `
|
||||
try {
|
||||
$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'"
|
||||
if ($disk -and $disk.Size -gt 0) {
|
||||
$s = [math]::Round($disk.Size/1GB,1); $f = [math]::Round($disk.FreeSpace/1GB,1); $u = [math]::Round(($disk.Size-$disk.FreeSpace)/$disk.Size*100,1)
|
||||
@{ d='C:'; s=$s; f=$f; u=$u } | ConvertTo-Json -Compress
|
||||
} else { 'null' }
|
||||
} catch { 'null' }
|
||||
`.trim();
|
||||
}
|
||||
|
||||
_parseDisk(stdout) {
|
||||
try {
|
||||
const json = this._extractJson(stdout);
|
||||
if (!json || json === "null" || json === null) return [];
|
||||
return [{
|
||||
drive: json.d || "C:",
|
||||
size_gb: json.s || 0,
|
||||
free_gb: json.f || 0,
|
||||
usage_percent: json.u || 0,
|
||||
}];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 PowerShell 命令:UTF-16LE Base64 编码 + -EncodedCommand
|
||||
* Node.js Buffer.from(str, "utf16le") 是最可靠的编码方式
|
||||
*/
|
||||
_buildPowerShellCmd(script) {
|
||||
// 用 Node.js 原生 Buffer 进行 UTF-16LE 编码,再转 Base64
|
||||
// UTF-16LE 编码(不带 BOM),然后 Base64
|
||||
const utf16le = Buffer.from(script, "utf16le");
|
||||
const encoded = utf16le.toString("base64");
|
||||
return `powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
|
||||
}
|
||||
|
||||
_buildServiceScript() {
|
||||
return `
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
|
||||
# 关键服务
|
||||
$services = @(
|
||||
"wuauserv", # Windows Update
|
||||
"Spooler", # Print Spooler
|
||||
"TermService", # Remote Desktop
|
||||
"DHCP", # DHCP Client
|
||||
"DNScache" # DNS Client
|
||||
)
|
||||
|
||||
$results = foreach ($svc in $services) {
|
||||
$s = Get-Service -Name $svc -ErrorAction SilentlyContinue
|
||||
if ($s) {
|
||||
[PSCustomObject]@{
|
||||
name = $s.Name
|
||||
display_name = $s.DisplayName
|
||||
status = $s.Status.ToString()
|
||||
start_type = $s.StartType.ToString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results | ConvertTo-Json -Depth 3
|
||||
`.trim();
|
||||
}
|
||||
|
||||
_parseStats(stdout) {
|
||||
try {
|
||||
const json = this._extractJson(stdout);
|
||||
if (!json) return {};
|
||||
|
||||
const result = {};
|
||||
if (json.cpu !== undefined) result.cpu_usage = json.cpu;
|
||||
if (json.mem_usage !== undefined) result.memory_usage = json.mem_usage;
|
||||
if (json.mem_total !== undefined && json.mem_total > 0) {
|
||||
result.memory = {
|
||||
total_mb: json.mem_total,
|
||||
used_mb: Math.round((json.mem_usage / 100) * json.mem_total),
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
_parseServices(stdout) {
|
||||
try {
|
||||
const json = this._extractJson(stdout);
|
||||
if (!json) return [];
|
||||
return Array.isArray(json) ? json : [json];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 PowerShell 输出中提取 JSON(可能有多余的警告/文本)
|
||||
*/
|
||||
_extractJson(text) {
|
||||
if (!text) return null;
|
||||
const start = text.indexOf("{");
|
||||
const startArr = text.indexOf("[");
|
||||
const realStart = startArr === -1 ? start : (start === -1 ? startArr : Math.min(start, startArr));
|
||||
if (realStart === -1) return null;
|
||||
|
||||
let depth = 0;
|
||||
let end = -1;
|
||||
const open = text[realStart];
|
||||
const close = open === "{" ? "}" : "]";
|
||||
|
||||
for (let i = realStart; i < text.length; i++) {
|
||||
if (text[i] === open) depth++;
|
||||
else if (text[i] === close) {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (end === -1) return null;
|
||||
try {
|
||||
return JSON.parse(text.substring(realStart, end + 1));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WindowsServerAdapter;
|
||||
Reference in New Issue
Block a user