UNPKG

homebridge-config-ui-x

Version:

A web based management, configuration and control platform for Homebridge.

434 lines • 19 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StatusService = exports.HomebridgeStatus = void 0; const node_child_process_1 = require("node:child_process"); const node_os_1 = require("node:os"); const node_path_1 = require("node:path"); const node_process_1 = __importDefault(require("node:process")); const node_util_1 = require("node:util"); const axios_1 = require("@nestjs/axios"); const common_1 = require("@nestjs/common"); const fs_extra_1 = require("fs-extra"); const node_cache_1 = __importDefault(require("node-cache")); const rxjs_1 = require("rxjs"); const semver_1 = require("semver"); const systeminformation_1 = require("systeminformation"); const config_service_1 = require("../../core/config/config.service"); const homebridge_ipc_service_1 = require("../../core/homebridge-ipc/homebridge-ipc.service"); const logger_service_1 = require("../../core/logger/logger.service"); const plugins_service_1 = require("../plugins/plugins.service"); const server_service_1 = require("../server/server.service"); var HomebridgeStatus; (function (HomebridgeStatus) { HomebridgeStatus["OK"] = "ok"; HomebridgeStatus["UP"] = "up"; HomebridgeStatus["DOWN"] = "down"; })(HomebridgeStatus || (exports.HomebridgeStatus = HomebridgeStatus = {})); const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec); let StatusService = class StatusService { constructor(httpService, logger, configService, pluginsService, serverService, homebridgeIpcService) { this.httpService = httpService; this.logger = logger; this.configService = configService; this.pluginsService = pluginsService; this.serverService = serverService; this.homebridgeIpcService = homebridgeIpcService; this.statusCache = new node_cache_1.default({ stdTTL: 3600 }); this.homebridgeStatus = HomebridgeStatus.DOWN; this.homebridgeStatusChange = new rxjs_1.Subject(); this.cpuLoadHistory = []; this.memoryUsageHistory = []; this.rpiGetThrottledMapping = { 0: 'Under-voltage detected', 1: 'Arm frequency capped', 2: 'Currently throttled', 3: 'Soft temperature limit active', 16: 'Under-voltage has occurred', 17: 'Arm frequency capping has occurred', 18: 'Throttled has occurred', 19: 'Soft temperature limit has occurred', }; if ((0, node_os_1.platform)() === 'freebsd') { this.getCpuLoadPoint = this.getCpuLoadPointAlt; this.getCpuTemp = this.getCpuTempAlt; } if (this.configService.ui.disableServerMetricsMonitoring !== true) { setInterval(async () => { this.getCpuLoadPoint(); this.getMemoryUsagePoint(); }, 10000); } else { this.logger.warn('Server metrics monitoring disabled.'); } if (this.configService.serviceMode) { this.homebridgeIpcService.on('serverStatusUpdate', (data) => { this.homebridgeStatus = data.status === HomebridgeStatus.OK ? HomebridgeStatus.UP : data.status; if (data?.setupUri) { this.serverService.setupCode = data.setupUri; this.serverService.paired = data.paired; } this.homebridgeStatusChange.next(this.homebridgeStatus); }); } } async getCpuLoadPoint() { const load = (await (0, systeminformation_1.currentLoad)()).currentLoad; this.cpuLoadHistory = this.cpuLoadHistory.slice(-60); this.cpuLoadHistory.push(load); } async getMemoryUsagePoint() { const memory = await (0, systeminformation_1.mem)(); this.memoryInfo = memory; const memoryFreePercent = ((memory.total - memory.available) / memory.total) * 100; this.memoryUsageHistory = this.memoryUsageHistory.slice(-60); this.memoryUsageHistory.push(memoryFreePercent); } async getCpuLoadPointAlt() { const load = ((0, node_os_1.loadavg)()[0] * 100 / (0, node_os_1.cpus)().length); this.cpuLoadHistory = this.cpuLoadHistory.slice(-60); this.cpuLoadHistory.push(load); } async getCpuTemp() { const cpuTempData = await (0, systeminformation_1.cpuTemperature)(); if (cpuTempData.main === -1 && this.configService.ui.temp) { return this.getCpuTempLegacy(); } return cpuTempData; } async getCpuTempLegacy() { try { const tempData = await (0, fs_extra_1.readFile)(this.configService.ui.temp, 'utf-8'); const cpuTemp = Number.parseInt(tempData, 10) / 1000; return { main: cpuTemp, cores: [], max: cpuTemp, }; } catch (e) { this.logger.error(`Failed to read temp from ${this.configService.ui.temp} as ${e.message}.`); return this.getCpuTempAlt(); } } async getCpuTempAlt() { return { main: -1, cores: [], max: -1, }; } async getCurrentNetworkUsage(netInterfaces) { if (!netInterfaces || !netInterfaces.length) { netInterfaces = [await (0, systeminformation_1.networkInterfaceDefault)()]; } const net = await (0, systeminformation_1.networkStats)(netInterfaces.join(',')); const txRxSec = (net[0].tx_sec + net[0].rx_sec) / 1024 / 1024; return { net: net[0], point: txRxSec }; } async getDashboardLayout() { if (!this.dashboardLayout) { try { const layout = await (0, fs_extra_1.readJson)((0, node_path_1.resolve)(this.configService.storagePath, '.uix-dashboard.json')); this.dashboardLayout = layout; return layout; } catch (e) { return []; } } else { return this.dashboardLayout; } } async setDashboardLayout(layout) { (0, fs_extra_1.writeJsonSync)((0, node_path_1.resolve)(this.configService.storagePath, '.uix-dashboard.json'), layout); this.dashboardLayout = layout; return { status: 'ok' }; } async getServerCpuInfo() { if (!this.memoryUsageHistory.length) { await this.getCpuLoadPoint(); } return { cpuTemperature: await this.getCpuTemp(), currentLoad: this.cpuLoadHistory.slice(-1)[0], cpuLoadHistory: this.cpuLoadHistory, }; } async getServerMemoryInfo() { if (!this.memoryUsageHistory.length) { await this.getMemoryUsagePoint(); } return { mem: this.memoryInfo, memoryUsageHistory: this.memoryUsageHistory, }; } async getServerUptimeInfo() { return { time: (0, systeminformation_1.time)(), processUptime: node_process_1.default.uptime(), }; } async getHomebridgePairingPin() { return { pin: this.configService.homebridgeConfig.bridge.pin, setupUri: await this.serverService.getSetupCode(), paired: this.serverService.paired, }; } async getHomebridgeStatus() { return { status: this.homebridgeStatus, consolePort: this.configService.ui.port, name: this.configService.homebridgeConfig.bridge.name, port: this.configService.homebridgeConfig.bridge.port, pin: this.configService.homebridgeConfig.bridge.pin, setupUri: this.serverService.setupCode, packageVersion: this.configService.package.version, paired: this.serverService.paired, }; } async watchStats(client) { let homebridgeStatusChangeSub; let homebridgeStatusInterval; client.emit('homebridge-status', await this.getHomebridgeStats()); if (this.configService.serviceMode) { homebridgeStatusChangeSub = this.homebridgeStatusChange.subscribe(async () => { client.emit('homebridge-status', await this.getHomebridgeStats()); }); } else { homebridgeStatusInterval = setInterval(async () => { client.emit('homebridge-status', await this.getHomebridgeStats()); }, 10000); } const onEnd = () => { client.removeAllListeners('end'); client.removeAllListeners('disconnect'); if (homebridgeStatusInterval) { clearInterval(homebridgeStatusInterval); } if (homebridgeStatusChangeSub) { homebridgeStatusChangeSub.unsubscribe(); } }; client.on('end', onEnd.bind(this)); client.on('disconnect', onEnd.bind(this)); } async getHomebridgeStats() { return { consolePort: this.configService.ui.port, port: this.configService.homebridgeConfig.bridge.port, pin: this.configService.homebridgeConfig.bridge.pin, setupUri: await this.serverService.getSetupCode(), paired: this.serverService.paired, packageVersion: this.configService.package.version, status: await this.checkHomebridgeStatus(), }; } async checkHomebridgeStatus() { if (this.configService.serviceMode) { return this.homebridgeStatus; } try { await (0, rxjs_1.firstValueFrom)(this.httpService.get(`http://localhost:${this.configService.homebridgeConfig.bridge.port}`, { validateStatus: () => true, })); this.homebridgeStatus = HomebridgeStatus.UP; } catch (e) { this.homebridgeStatus = HomebridgeStatus.DOWN; } return this.homebridgeStatus; } async getDefaultInterface() { const cachedResult = this.statusCache.get('defaultInterface'); if (cachedResult) { return cachedResult; } const defaultInterfaceName = await (0, systeminformation_1.networkInterfaceDefault)(); const defaultInterface = defaultInterfaceName ? (await (0, systeminformation_1.networkInterfaces)()).find(x => x.iface === defaultInterfaceName) : undefined; if (defaultInterface) { this.statusCache.set('defaultInterface', defaultInterface); } return defaultInterface; } async getOsInfo() { const cachedResult = this.statusCache.get('osInfo'); if (cachedResult) { return cachedResult; } const osInformation = await (0, systeminformation_1.osInfo)(); this.statusCache.set('osInfo', osInformation, 86400); return osInformation; } getGlibcVersion() { if ((0, node_os_1.platform)() !== 'linux') { return ''; } const cachedResult = this.statusCache.get('glibcVersion'); if (cachedResult) { return cachedResult; } try { const glibcVersion = (0, node_child_process_1.execSync)('getconf GNU_LIBC_VERSION 2>/dev/null').toString().split('glibc')[1].trim(); this.statusCache.set('glibcVersion', glibcVersion, 86400); return glibcVersion; } catch (e) { this.logger.debug(`Could not check glibc version as ${e.message}.`); return ''; } } async getHomebridgeServerInfo() { return { serviceUser: (0, node_os_1.userInfo)().username, homebridgeConfigJsonPath: this.configService.configPath, homebridgeStoragePath: this.configService.storagePath, homebridgeInsecureMode: this.configService.homebridgeInsecureMode, homebridgeCustomPluginPath: this.configService.customPluginPath, homebridgePluginPath: (0, node_path_1.resolve)(node_process_1.default.env.UIX_BASE_PATH, '..'), homebridgeRunningInDocker: this.configService.runningInDocker, homebridgeRunningInSynologyPackage: this.configService.runningInSynologyPackage, homebridgeRunningInPackageMode: this.configService.runningInPackageMode, homebridgeServiceMode: this.configService.serviceMode, nodeVersion: node_process_1.default.version, os: await this.getOsInfo(), glibcVersion: this.getGlibcVersion(), time: (0, systeminformation_1.time)(), network: await this.getDefaultInterface() || {}, }; } async getHomebridgeVersion() { return this.pluginsService.getHomebridgePackage(); } async getNodeJsVersionInfo() { const cachedResult = this.statusCache.get('nodeJsVersion'); if (cachedResult) { return cachedResult; } try { const versionList = (await (0, rxjs_1.firstValueFrom)(this.httpService.get('https://nodejs.org/dist/index.json'))).data; const latest18 = versionList.filter((x) => x.version.startsWith('v18'))[0]; const latest20 = versionList.filter((x) => x.version.startsWith('v20'))[0]; const latest22 = versionList.filter((x) => x.version.startsWith('v22'))[0]; let updateAvailable = false; let latestVersion = node_process_1.default.version; let showNodeUnsupportedWarning = false; let showGlibcUnsupportedWarning = false; switch (node_process_1.default.version.split('.')[0]) { case 'v18': { if ((0, node_os_1.platform)() === 'linux') { const glibcVersion = this.getGlibcVersion(); if (glibcVersion) { if (Number.parseFloat(glibcVersion) >= 2.31) { updateAvailable = true; latestVersion = latest20.version; } else { if ((0, semver_1.gt)(latest18.version, node_process_1.default.version)) { updateAvailable = true; latestVersion = latest18.version; } if (Number.parseFloat(glibcVersion) < 2.31) { showGlibcUnsupportedWarning = true; } } } } else { updateAvailable = true; latestVersion = latest20.version; } break; } case 'v20': { if ((0, semver_1.gt)(latest20.version, node_process_1.default.version)) { updateAvailable = true; latestVersion = latest20.version; } break; } case 'v22': { if ((0, semver_1.gt)(latest22.version, node_process_1.default.version)) { updateAvailable = true; latestVersion = latest22.version; } break; } default: { showNodeUnsupportedWarning = true; } } const versionInformation = { currentVersion: node_process_1.default.version, latestVersion, updateAvailable, showNodeUnsupportedWarning, showGlibcUnsupportedWarning, installPath: (0, node_path_1.dirname)(node_process_1.default.execPath), }; this.statusCache.set('nodeJsVersion', versionInformation, 86400); return versionInformation; } catch (e) { this.logger.log(`Failed to check for Node.js version updates (check your internet connection) as ${e.message}.`); const versionInformation = { currentVersion: node_process_1.default.version, latestVersion: node_process_1.default.version, updateAvailable: false, showNodeUnsupportedWarning: false, showGlibcUnsupportedWarning: false, }; this.statusCache.set('nodeJsVersion', versionInformation, 3600); return versionInformation; } } async getRaspberryPiThrottledStatus() { if (!this.configService.runningOnRaspberryPi) { throw new common_1.BadRequestException('This command is only available on Raspberry Pi'); } const output = {}; for (const bit of Object.keys(this.rpiGetThrottledMapping)) { output[this.rpiGetThrottledMapping[bit]] = false; } try { const { stdout } = await execAsync('vcgencmd get_throttled'); const throttledHex = Number.parseInt(stdout.trim().replace('throttled=', '')); if (!Number.isNaN(throttledHex)) { for (const bit of Object.keys(this.rpiGetThrottledMapping)) { output[this.rpiGetThrottledMapping[bit]] = !!((throttledHex >> Number.parseInt(bit, 10)) & 1); } } } catch (e) { this.logger.debug(`Could not check vcgencmd get_throttled as ${e.message}.`); } return output; } }; exports.StatusService = StatusService; exports.StatusService = StatusService = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [axios_1.HttpService, logger_service_1.Logger, config_service_1.ConfigService, plugins_service_1.PluginsService, server_service_1.ServerService, homebridge_ipc_service_1.HomebridgeIpcService]) ], StatusService); //# sourceMappingURL=status.service.js.map