homebridge-config-ui-x
Version:
A web based management, configuration and control platform for Homebridge.
434 lines • 19 kB
JavaScript
"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