Source: management-system/src/backend/shared-electron-server/network/machines/machineInfo.js

import { getMachines, updateMachine } from '../../data/machines.js';
import {
  machineEndpoint,
  configurationEndpoint,
  loggingEndpoint,
} from '../ms-engine-communication/module.js';
import {
  checkAvailability,
  getCompleteMachineInformation,
  getUpdatedDeploymentInfo,
} from './machineInfoRequests.js';
import logger from '../../logging.js';
import eventHandler from '../../../../frontend/backend-api/event-system/EventHandler.js';
import helpers from '../../../../shared-frontend-backend/helpers/javascriptHelpers.js';
const { isSubset } = helpers;

import { getBackendConfig } from '../../data/config.js';

// number of seconds between two request for machine information
let updateInterval = getBackendConfig().machinePollingInterval;

if (typeof updateInterval === 'string') {
  updateInterval = parseInt(updateInterval, 10);
}

eventHandler.on('backendConfigChange.machinePollingInterval', ({ newValue }) => {
  updateInterval = newValue;
});

/**
 * Allows changing the time between two consecutive machine information requests
 *
 * @param {Number} newInterval the new time between two requests (in seconds)
 */
export function setUpdateInterval(newInterval) {
  updateInterval = newInterval;
}

/**
 * Returns the time between two requests for machine information
 *
 * @returns {Number} the interval time
 */
export function getUpdateInterval() {
  return updateInterval;
}

// contains the machineIds of machines that were subscribed to and which information was requested
let subscribedToMachines = {};

let pollMachines = false;
let pollDeployments = false;
let polling = false;

/**
 * Activates a polling loop that requests information from all machines
 */
export function startPolling() {
  pollMachines = true;
  logger.debug('Start polling machines for up to date information');
  if (!polling) {
    pollMachinesInformation();
  }
}

/**
 * Deactivates the machine polling loop
 */
export function stopPolling() {
  logger.debug('Stop polling machines for information');
  pollMachines = false;
}

/**
 * Activates request for deployment information for all machines in request loop
 */
export function getDeployments() {
  pollDeployments = true;
}

/**
 * Deactivate request for deployment information in request loop
 */
export function dontGetDeployments() {
  pollDeployments = false;
}

/**
 * Occasionally polls all machines for up to date informations
 */
async function pollMachinesInformation() {
  polling = true;
  const machines = getMachines();

  const pollPromises = machines.map(async (machine) => {
    let newInfo = {};

    try {
      // check if machines are reachable
      const running = await checkAvailability(machine);

      if (running) {
        let { optionalName, hostname, id, name } = machine;

        // request updated information for subscribed to machines
        if (subscribedToMachines[machine.id]) {
          logger.debug(
            `Requesting up to date information for machine ${
              optionalName || name || hostname || id
            }.`
          );
          const properties = await machineEndpoint.getProperties(machine);

          eventHandler.dispatch('newMachineInfo', {
            id: machine.id,
            info: {
              cpu: properties.cpu,
              mem: properties.mem,
              disk: properties.disk,
              battery: properties.battery,
            },
          });

          //delete some values that change to often to be stored (or just don't make sense to store)
          const { cores, physicalCores, processors, speed } = properties.cpu;

          properties.cpu = { cores, physicalCores, processors, speed };
          properties.mem = { total: properties.mem.total };
          properties.disk = properties.disk.map(({ type, total }) => ({ type, total }));
          properties.battery = { hasBattery: properties.battery.hasBattery };

          // see if log information is requested too
          if (subscribedToMachines[machine.id].logs) {
            logger.debug(`Requesting logs from machine ${optionalName || name || hostname || id}.`);
            const logs = await loggingEndpoint.getLogs(machine);
            eventHandler.dispatch('newMachineLogs', {
              id: machine.id,
              logs,
            });
          }

          newInfo.machine = properties;
          newInfo.id = properties.id;
          newInfo.name = properties.name;
          newInfo.hostname = properties.hostname;
          newInfo.description = properties.description;
        }

        // request deployment information if it is generally requested or specifically for this machine
        if (pollDeployments || subscribedToMachines[machine.id]) {
          const changedDeployments = await getUpdatedDeploymentInfo(machine);

          if (changedDeployments) {
            newInfo.deployedProcesses = changedDeployments;
          }
        }

        // log if machine was connected to for the first time
        if (machine.status === 'DISCONNECTED') {
          logger.info(
            `Established first connection to machine ${
              optionalName || name || hostname || id
            }. Requesting information.`
          );

          newInfo = await getCompleteMachineInformation(machine);
        }

        newInfo.status = 'CONNECTED';

        // check if machine information changed and update if it did
        if (!isSubset(machine, newInfo)) {
          await updateMachine(machine.id, newInfo);
        }

        return;
      }
    } catch (err) {
      console.log(err);
      // log if connection failed but was connected before
      if (machine.status === 'CONNECTED') {
        const { optionalName, hostname, id, name } = machine;
        logger.info(`Lost connection to machine ${optionalName || name || hostname || id}.`);
        newInfo.online = false;
      }
    }

    newInfo.status = 'DISCONNECTED';

    // see if information changed and update if it did
    if (!isSubset(machine, newInfo)) {
      await updateMachine(machine.id, newInfo);
    }
  });

  await Promise.all(pollPromises);

  setTimeout(() => {
    // repeat after timeout if polling is still requested then
    if (pollMachines) {
      pollMachinesInformation();
    } else {
      // set flag to allow loop to be started again in the future
      polling = false;
    }
  }, updateInterval * 1000);
}

/**
 * Set flag that we want extended information for a specific machine
 *
 * @param {String} machineId machine that more information is requested for
 */
export function addMachineSubscription(machineId) {
  if (!subscribedToMachines[machineId]) {
    subscribedToMachines[machineId] = { logs: false };
  }
}

/**
 * Remove flag for extended information about a specific machine
 *
 * @param {String} machineId machine for which extended information is not needed anymore
 */
export function removeMachineSubscription(machineId) {
  delete subscribedToMachines[machineId];
}

/**
 * Set flag that logs for a machine are requested
 *
 * @param {String} machineId the machine for which logs are requested
 */
export function addMachineLogsSubscription(machineId) {
  subscribedToMachines[machineId].logs = true;
}

/**
 * Remove flag so that logs for the machine are not requested anymore
 *
 * @param {String} machineId the machine for which logs are not needed anymore
 */
export function removeMachineLogsSubscription(machineId) {
  if (subscribedToMachines[machineId]) {
    subscribedToMachines[machineId].logs = false;
  }
}

/**
 * Request deployment information from every connected machine
 * @returns {Promise<void[]>} Resolves if all deployment information was requested
 */
export function requestDeploymentInformation() {
  const machines = getMachines();

  const requestPromises = machines
    .filter((machine) => machine.status === 'CONNECTED')
    .map(async (machine) => {
      let machineNewInfo = {};

      try {
        // check if machines are reachable
        const running = await checkAvailability(machine);

        if (running) {
          // request deployment information if it is generally requested or specifically for this machine
          const changedDeployments = await getUpdatedDeploymentInfo(machine);

          if (changedDeployments) {
            machineNewInfo.deployedProcesses = changedDeployments;
            await updateMachine(machine.id, machineNewInfo);
          }
        }
      } catch (err) {
        console.log(err);

        const { optionalName, hostname, id, name } = machine;
        logger.info(
          `Could not request deployment information for machine: ${
            optionalName || name || hostname || id
          }.`
        );
      }
    });

  return Promise.all(requestPromises);
}