2026-10-15 13:53:04 +08:00
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设备扩展数据存储(巡检相关配置)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 关联 NetMesh 的主机数据,扩展巡检相关字段:
|
|
|
|
|
|
* - 设备类型(catalog_id)
|
|
|
|
|
|
* - 设备层级(core/aggregation/access)
|
|
|
|
|
|
* - 巡检周期
|
|
|
|
|
|
* - 启用状态
|
|
|
|
|
|
* - 自定义告警阈值
|
|
|
|
|
|
* - 上次巡检信息
|
|
|
|
|
|
*
|
|
|
|
|
|
* 周期优先级(从高到低):
|
|
|
|
|
|
* 1. 设备独立设置 inspection_interval
|
|
|
|
|
|
* 2. 设备层级的默认周期 DEVICE_TIER_DEFAULT_INTERVAL
|
|
|
|
|
|
* 3. 系统默认周期 SYS_CONFIG_DEFAULTS.inspection_default_interval
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
STORAGE_KEY_DEVICE_EXTENSION,
|
|
|
|
|
|
STORAGE_KEY_SYS_CONFIG,
|
|
|
|
|
|
} = require("./storageKeys.cjs");
|
|
|
|
|
|
const {
|
|
|
|
|
|
DEVICE_TIERS,
|
|
|
|
|
|
DEVICE_TIER_LABELS,
|
|
|
|
|
|
DEVICE_TIER_DEFAULT_INTERVAL,
|
|
|
|
|
|
SYS_CONFIG_DEFAULTS,
|
|
|
|
|
|
} = require("./types.cjs");
|
|
|
|
|
|
const { getPresetCatalog } = require("./deviceCatalogStore.cjs");
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 内存缓存 ----------
|
|
|
|
|
|
let _extensions = null; // { [hostId]: { ... } }
|
|
|
|
|
|
let _sysConfig = null; // { inspection_default_interval: 5, ... }
|
|
|
|
|
|
let _catalog = null; // 设备类型列表
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从存储加载数据(首次调用时加载,之后用缓存)
|
|
|
|
|
|
* @param {function} readFn - 读取函数 (key) => rawString
|
|
|
|
|
|
*/
|
|
|
|
|
|
function _ensureLoaded(readFn) {
|
|
|
|
|
|
if (_extensions === null) {
|
|
|
|
|
|
const raw = readFn(STORAGE_KEY_DEVICE_EXTENSION);
|
|
|
|
|
|
_extensions = raw ? _safeParse(raw, {}) : {};
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_sysConfig === null) {
|
|
|
|
|
|
const raw = readFn(STORAGE_KEY_SYS_CONFIG);
|
|
|
|
|
|
_sysConfig = raw ? _safeParse(raw, {}) : { ...SYS_CONFIG_DEFAULTS };
|
|
|
|
|
|
// 合并默认值
|
|
|
|
|
|
for (const k of Object.keys(SYS_CONFIG_DEFAULTS)) {
|
|
|
|
|
|
if (_sysConfig[k] === undefined) _sysConfig[k] = SYS_CONFIG_DEFAULTS[k];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_catalog === null) {
|
|
|
|
|
|
_catalog = getPresetCatalog();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _safeParse(text, def) {
|
|
|
|
|
|
if (text === null || text === undefined) return def;
|
2026-10-19 21:11:57 +08:00
|
|
|
|
// 已经是对象/数组 → 直接返回
|
|
|
|
|
|
if (typeof text === "object") return text;
|
|
|
|
|
|
if (typeof text !== "string") return def;
|
2026-10-15 13:53:04 +08:00
|
|
|
|
let parsed;
|
|
|
|
|
|
try { parsed = JSON.parse(text); }
|
|
|
|
|
|
catch (e) { return def; }
|
|
|
|
|
|
// 防御二次/三次 stringify:inspectionStorage.writeStorage 先 JSON.stringify 一次,
|
|
|
|
|
|
// _save 又 JSON.stringify 一次 → 文件里值是 "\"{...}\"" 这种嵌套字符串
|
|
|
|
|
|
// 连续 parse 直到拿到真正的对象/数组(最多 3 层避免死循环)
|
|
|
|
|
|
let depth = 0;
|
|
|
|
|
|
while (typeof parsed === "string" && parsed.length > 0 && depth < 3) {
|
|
|
|
|
|
let next;
|
|
|
|
|
|
try { next = JSON.parse(parsed); } catch { break; }
|
|
|
|
|
|
if (typeof next === "string" && next === parsed) break;
|
|
|
|
|
|
parsed = next;
|
|
|
|
|
|
depth++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return (parsed !== null && typeof parsed === "object") ? parsed : def;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _save(writeFn, key, data) {
|
|
|
|
|
|
writeFn(key, JSON.stringify(data));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 系统配置 ----------
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取系统配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getSysConfig(readFn) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
return { ..._sysConfig };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新系统配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
function updateSysConfig(readFn, writeFn, updates) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
_sysConfig = { ..._sysConfig, ...updates };
|
|
|
|
|
|
_save(writeFn, STORAGE_KEY_SYS_CONFIG, _sysConfig);
|
|
|
|
|
|
return { ..._sysConfig };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 设备扩展 ----------
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取所有设备扩展配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getAllExtensions(readFn) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
return { ..._extensions };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取单个设备的扩展配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getExtension(readFn, hostId) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
return _extensions[hostId] || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建/更新设备扩展配置
|
|
|
|
|
|
* @param {string} hostId - NetMesh 主机 ID
|
|
|
|
|
|
* @param {object} data - 扩展配置
|
|
|
|
|
|
* @param {string} data.catalog_id - 设备类型 ID
|
|
|
|
|
|
* @param {string} [data.tier] - 设备层级 core/aggregation/access
|
|
|
|
|
|
* @param {number} [data.inspection_interval] - 巡检周期(分钟),null 表示继承
|
|
|
|
|
|
* @param {boolean} [data.inspection_enabled] - 是否启用巡检
|
|
|
|
|
|
* @param {object} [data.custom_thresholds] - 自定义告警阈值
|
|
|
|
|
|
* @param {string[]} [data.inspection_tags] - 巡检标签
|
|
|
|
|
|
* @param {string} [data.hostname] - 主机名(IP 或域名),用于调度器关联 vault host SSH 配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
function upsertExtension(readFn, writeFn, hostId, data) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
|
|
|
|
|
|
const existing = _extensions[hostId] || {
|
|
|
|
|
|
host_id: hostId,
|
|
|
|
|
|
hostname: null,
|
|
|
|
|
|
catalog_id: "",
|
|
|
|
|
|
tier: null,
|
|
|
|
|
|
inspection_interval: null,
|
|
|
|
|
|
inspection_enabled: false,
|
|
|
|
|
|
custom_thresholds: [],
|
|
|
|
|
|
inspection_tags: [],
|
|
|
|
|
|
last_scan_at: null,
|
|
|
|
|
|
last_scan_status: null,
|
|
|
|
|
|
last_scan_error: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_extensions[hostId] = {
|
|
|
|
|
|
...existing,
|
|
|
|
|
|
...data,
|
|
|
|
|
|
updated_at: Date.now(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_save(writeFn, STORAGE_KEY_DEVICE_EXTENSION, _extensions);
|
|
|
|
|
|
return { ..._extensions[hostId] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除设备扩展配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
function deleteExtension(readFn, writeFn, hostId) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
if (_extensions[hostId]) {
|
|
|
|
|
|
delete _extensions[hostId];
|
|
|
|
|
|
_save(writeFn, STORAGE_KEY_DEVICE_EXTENSION, _extensions);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算设备的实际巡检周期(考虑三级继承)
|
|
|
|
|
|
* @param {string} hostId
|
|
|
|
|
|
* @returns {number} 周期(分钟)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getEffectiveInterval(readFn, hostId) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
const ext = _extensions[hostId];
|
|
|
|
|
|
if (!ext) return _sysConfig.inspection_default_interval;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 设备独立设置
|
|
|
|
|
|
if (typeof ext.inspection_interval === "number" && ext.inspection_interval > 0) {
|
|
|
|
|
|
return ext.inspection_interval;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 设备层级默认
|
|
|
|
|
|
if (ext.tier && DEVICE_TIER_DEFAULT_INTERVAL[ext.tier]) {
|
|
|
|
|
|
return DEVICE_TIER_DEFAULT_INTERVAL[ext.tier];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 系统默认
|
|
|
|
|
|
return _sysConfig.inspection_default_interval;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取所有启用了巡检的设备列表(带有效周期)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getEnabledDevices(readFn) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
const result = [];
|
|
|
|
|
|
|
|
|
|
|
|
for (const hostId of Object.keys(_extensions)) {
|
|
|
|
|
|
const ext = _extensions[hostId];
|
|
|
|
|
|
if (ext.inspection_enabled) {
|
|
|
|
|
|
result.push({
|
|
|
|
|
|
...ext,
|
|
|
|
|
|
effective_interval: getEffectiveInterval(readFn, hostId),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新上次巡检信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
function updateLastScan(readFn, writeFn, hostId, status, error = null) {
|
|
|
|
|
|
_ensureLoaded(readFn);
|
|
|
|
|
|
if (!_extensions[hostId]) return false;
|
|
|
|
|
|
|
|
|
|
|
|
_extensions[hostId].last_scan_at = Date.now();
|
|
|
|
|
|
_extensions[hostId].last_scan_status = status;
|
|
|
|
|
|
_extensions[hostId].last_scan_error = error;
|
|
|
|
|
|
|
|
|
|
|
|
_save(writeFn, STORAGE_KEY_DEVICE_EXTENSION, _extensions);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 设备类型 ----------
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取设备类型列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getCatalog() {
|
|
|
|
|
|
if (_catalog === null) {
|
|
|
|
|
|
_catalog = getPresetCatalog();
|
|
|
|
|
|
}
|
|
|
|
|
|
return [..._catalog];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据 ID 获取设备类型
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getCatalogById(catalogId) {
|
|
|
|
|
|
const list = getCatalog();
|
|
|
|
|
|
return list.find((c) => c.id === catalogId) || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 设备层级 ----------
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取设备层级列表(带中文名称和默认周期)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getTiers() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ key: DEVICE_TIERS.CORE, label: DEVICE_TIER_LABELS.core, default_interval: DEVICE_TIER_DEFAULT_INTERVAL.core },
|
|
|
|
|
|
{ key: DEVICE_TIERS.AGGREGATION, label: DEVICE_TIER_LABELS.aggregation, default_interval: DEVICE_TIER_DEFAULT_INTERVAL.aggregation },
|
|
|
|
|
|
{ key: DEVICE_TIERS.ACCESS, label: DEVICE_TIER_LABELS.access, default_interval: DEVICE_TIER_DEFAULT_INTERVAL.access },
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 重置(测试用) ----------
|
|
|
|
|
|
function _resetForTest() {
|
|
|
|
|
|
_extensions = null;
|
|
|
|
|
|
_sysConfig = null;
|
|
|
|
|
|
_catalog = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
// 系统配置
|
|
|
|
|
|
getSysConfig,
|
|
|
|
|
|
updateSysConfig,
|
|
|
|
|
|
// 设备扩展
|
|
|
|
|
|
getAllExtensions,
|
|
|
|
|
|
getExtension,
|
|
|
|
|
|
upsertExtension,
|
|
|
|
|
|
deleteExtension,
|
|
|
|
|
|
getEffectiveInterval,
|
|
|
|
|
|
getEnabledDevices,
|
|
|
|
|
|
updateLastScan,
|
|
|
|
|
|
// 设备类型
|
|
|
|
|
|
getCatalog,
|
|
|
|
|
|
getCatalogById,
|
|
|
|
|
|
// 设备层级
|
|
|
|
|
|
getTiers,
|
|
|
|
|
|
// 内部
|
|
|
|
|
|
_resetForTest,
|
|
|
|
|
|
};
|