Elius94 / console-gui-tools

A simple library to draw option menu or other popup inputs and layout on Node.js console.
MIT License
123 stars 18 forks source link

[Bug]: Using winston logging results in broken box #90

Open rjjrbatarao opened 3 months ago

rjjrbatarao commented 3 months ago

What happened?

A bug happened! Broken box

What should have happened?

Box should be not broken

Code

image

/**
 * !Logger
 */
const os = require("node:os");
const chalk = require("chalk");
const winston = require("winston");
const { combine, timestamp, json, printf, label, errors } = winston.format;
/**
 * !console
 */
const {
  ConsoleManager,
  OptionPopup,
  InputPopup,
  PageBuilder,
  ButtonPopup,
  ConfirmPopup,
  CustomPopup,
  FileSelectorPopup,
  Progress,
  Control,
  InPageWidgetBuilder,
  Button,
  Box,
  EOL,
} = require("console-gui-tools");

const myFormat = printf(({ level, message, label, timestamp }) => {
  if (level === "info") {
    console.log(
      `${chalk.blue(timestamp)} ${chalk.yellow(
        "[" + label + "]"
      )} ${chalk.green(level)}: ${message}`
    );
  } else if (level === "warn") {
    console.log(
      `${chalk.blue(timestamp)} ${chalk.yellow("[" + label + "]")} ${chalk.red(
        level
      )}: ${message}`
    );
  } else {
    console.log(
      `${chalk.blue(timestamp)} ${chalk.yellow("[" + label + "]")} ${chalk.red(
        level
      )}: ${message}`
    );
  }
  return `${timestamp} [${label}] ${level}: ${message}`;
});
const logger = winston.createLogger({
  level: "info",
  format: combine(label({ label: "radiman" }), timestamp(), myFormat),
  transports: [
    //new winston.transports.Console(),
    new winston.transports.File({
      filename: "radiman.log",
    }),
    //new LogtailTransport(logtail), // onlien logging
  ],
});

const opt = {
  title: "Radiffy Radius Manager", // Title of the console
  logPageSize: 30, // Number of lines to show in logs page
  logLocation: 1, // Location of the logs page
  enableMouse: false, // Enable mouse support
  layoutOptions: {
    boxed: true, // Set to true to enable boxed layout
    showTitle: true, // Set to false to hide title
    //changeFocusKey: "ctrl+l", // Change layout with ctrl+l to switch to the logs page
    type: "double", // Set to 'double' to enable double layout
    direction: "vertical", // Set to 'horizontal' to enable horizontal layout
    boxColor: "yellow",
    boxStyle: "bold",
  },
};
const GUI = new ConsoleManager(opt);

let period = 2000;
let mode = "debug";
let valueEmitter = null;
let min = 9;
let max = 12;
let cpu_brand = "";
let cpu_speed = 0;
let cpu_cores = 0;
const periodList = [
  2000, 5000, 10000, 20000, 30000, 60000, 120000, 300000, 600000, 900000,
  1800000, 3600000, 7200000, 14400000, 28800000, 43200000, 86400000,
];

const numberOfCores = os.cpus().length;
const cores = [];
for (let i = 0; i < numberOfCores; i++) {
  cores.push(
    new Progress({
      id: `htop-cpu-${i}`,
      x: 2,
      y: 1 + i,
      interactive: false,
      draggable: false,
      label: i > 9 ? `${i} ` : `${i}  `,
      length: 40,
      min: 0,
      max: 100,
      style: {
        boxed: true,
        theme: "htop",
        showMinMax: false,
        showValue: false,
      },
    })
  );
}

const mem = new Progress({
  id: "htop-mem",
  x: 2,
  y: 1 + numberOfCores,
  label: "Mem",
  length: 40,
  min: 0,
  max: os.totalmem() / (1024 * 1024 * 1024),
  unit: "G",
  style: {
    boxed: true,
    theme: "htop",
    showMinMax: false,
  },
});

//Create function to get CPU information
function cpuAverage(core) {
  //Initialise sum of idle and time of cores and fetch CPU info
  let totalIdle = 0,
    totalTick = 0;

  //Select CPU core
  const cpu = os.cpus()[core];

  //Total up the time in the cores tick
  for (let i = 0, len = Object.keys(cpu.times).length; i < len; i++) {
    totalTick += Object.values(cpu.times)[i];
  }

  //Total up the idle time of the core
  totalIdle += cpu.times.idle;

  //Return the average Idle and Tick times
  return { idle: totalIdle, total: totalTick };
}

function getCPULoadAVG(core, avgTime = 1000, delay = 500) {
  return new Promise((resolve, reject) => {
    const n = ~~(avgTime / delay);
    if (n <= 1) {
      reject("Error: interval to small");
    }
    let i = 0;
    const samples = [];
    const avg1 = cpuAverage(core);

    const interval = setInterval(() => {
      //GUI.log("CPU Interval: " + i)

      if (i >= n) {
        clearInterval(interval);
        resolve(
          ~~((samples.reduce((a, b) => a + b, 0) / samples.length) * 100)
        );
      }

      const avg2 = cpuAverage(core);
      const totalDiff = avg2.total - avg1.total;
      const idleDiff = avg2.idle - avg1.idle;

      samples[i] = 1 - idleDiff / totalDiff;
      i++;
    }, delay);
  });
}

const getSystemInfo = async () => {
  const coresPercent = [];

  for (let i = 0; i < numberOfCores; i++) {
    const load = await getCPULoadAVG(i).catch((err) => console.error(err));
    coresPercent.push(load);
  }
  const memUsage = os.totalmem() - os.freemem();
  return {
    cpuUsage: coresPercent,
    memUsage,
  };
};

const modeList = [
  "debug",
  "info",
  "error",
  "warning",
  "notice",
  "crit",
  "alert",
  "emerg",
];

const fetchSystemInformation = async () => {
  getSystemInfo().then((info) => {
    info.cpuUsage.forEach((core, i) => {
      cores[i].setValue(core);
    });
    mem.setValue(info.memUsage / (1024 * 1024 * 1024));
  });
  drawGui();
};

const frame = async () => {
  await fetchSystemInformation();
};

/**
 * @description Updates the console screen
 *
 */
const updateConsole = async () => {
  const uptime = os.uptime();
  const hours = Math.floor(uptime / 3600);
  const minutes = Math.floor((uptime % 3600) / 60);
  const seconds = Math.floor(uptime % 60);
  const uptimeText = `${hours}:${minutes}:${seconds}s`;
  const p = new PageBuilder();
  p.addSpacer(numberOfCores + 1);

  if (!valueEmitter) {
    p.addRow(
      { text: "Sysinfo is not running! ", color: "red" },
      { text: "press 'space' to start", color: "white" }
    );
  } else {
    p.addRow(
      { text: "Sysinfo is running! ", color: "green" },
      { text: "press 'space' to stop", color: "white" }
    );
  }

  // Print mode:
  p.addRow(
    { text: "Logging Mode: ", color: "magenta" },
    { text: `${mode}`, color: "white" }
  );

  GUI.setPage(p, 0);
  GUI.refresh();
};

GUI.on("exit", () => {
  closeApp();
});

GUI.on("keypressed", (key) => {
  switch (key.name) {
    case "space":
      if (valueEmitter) {
        clearInterval(valueEmitter);
        valueEmitter = null;
        drawGui();
      } else {
        valueEmitter = setInterval(frame, period);
        drawGui();
      }
      break;
    case "m":
      new OptionPopup({
        id: "popupSelectMode",
        title: "Select logging mode",
        options: modeList,
        selected: mode,
      })
        .show()
        .on("confirm", (_mode) => {
          mode = _mode;
          //logger.transports.console.level = mode;
          GUI.warn(`NEW MODE: ${mode}`);
          drawGui();
        });
      break;
    case "s":
      new OptionPopup({
        id: "popupSelectPeriod",
        title: "Select update period",
        options: periodList,
        selected: period,
      })
        .show()
        .on("confirm", (_period) => {
          const msgMultiLine = `Changing period from ${period} to ${_period} ms.${EOL}This will restart the monitor.${EOL}Do you want to continue?`;
          new ButtonPopup({
            id: "popupConfirmPeriod",
            title: "Confirm period",
            message: msgMultiLine,
            buttons: ["Yes", "No", "?"],
          })
            .show()
            .on("confirm", (answer) => {
              if (answer === "Yes") {
                period = _period;
                GUI.warn(`NEW PERIOD: ${period}`);
              } else if (answer === "?") {
                GUI.info("Choose ok to confirm period");
              }
              drawGui();
            });
        });
      break;
    case "a":
      new InputPopup({
        id: "popupTypeMax",
        title: "Type max value",
        value: max,
        numeric: true,
      })
        .show()
        .on("confirm", (_max) => {
          max = _max;
          GUI.warn(`NEW MAX VALUE: ${max}`);
          drawGui();
        });
      break;
    case "d":
      new InputPopup({
        id: "popupTypeMin",
        title: "Type min value",
        value: min,
        numeric: true,
      })
        .show()
        .on("confirm", (_min) => {
          min = _min;
          GUI.warn(`NEW MIN VALUE: ${min}`);
          drawGui();
        });
      break;
    case "1":
      {
        const p = new PageBuilder(5); // Add a scroll limit so it will be scrollable with up and down
        p.addRow({
          text: "Example of a custom popup content!",
          color: "yellow",
        });
        p.addRow({ text: "This is a custom popup!", color: "green" });
        p.addRow({
          text: "It can be used to show a message,",
          color: "green",
        });
        p.addRow({ text: "or to show variables.", color: "green" });
        p.addRow(
          { text: "TCP Message sent: ", color: "green" },
          { text: `${12}`, color: "white" }
        );
        p.addRow(
          { text: "Connected clients: ", color: "green" },
          { text: `${20}`, color: "white" }
        );
        p.addRow(
          { text: "Mode: ", color: "green" },
          { text: `${"radsec"}`, color: "white" }
        );
        p.addRow(
          { text: "Message period: ", color: "green" },
          { text: `${100} ms`, color: "white" }
        );
        new CustomPopup({
          id: "popupCustom1",
          title: "See that values",
          content: p,
          width: 32,
        }).show();
      }
      break;
    case "q":
      new ConfirmPopup({
        id: "popupQuit",
        title: "Are you sure you want to quit?",
      })
        .show()
        .on("confirm", () => closeApp());
      break;
    default:
      break;
  }
});

const closeApp = () => {
  console.clear();
  process.exit();
};

GUI.refresh();

const footer = new Box({
  id: "footer",
  x: 0,
  y: GUI.Screen.height - 1,
  width: GUI.Screen.width,
  height: 1,
});
const row_footer = new InPageWidgetBuilder(1);

row_footer.addRow(
  { text: "F1:", color: "white", bold: true },
  { text: "Help  ", color: "black", bg: "bgCyan", bold: false },
  { text: "F6:", color: "white", bold: true },
  { text: "SortBy", color: "black", bg: "bgCyan", bold: false },
  { text: "F9:", color: "white", bold: true },
  { text: "Kill  ", color: "black", bg: "bgCyan", bold: false },
  { text: "F10:", color: "white", bold: true },
  { text: "Quit  ", color: "black", bg: "bgCyan", bold: false }
);
footer.setContent(row_footer);

GUI.on("resize", () => {
  footer.absoluteValues = {
    x: 0,
    y: GUI.Screen.height - 2,
    width: GUI.Screen.width,
    height: 1,
  };
  GUI.refresh();
});

const drawGui = () => {
  updateConsole();
};

drawGui();
valueEmitter = setInterval(frame, 2000);

setInterval(() => {
  logger.info("test");
}, 5000);

Library Version

^3.7.0

Node Version

^3.7.0

What operating system are you using?

Windows

Terminal

powershell and git bash

Interest to fix the bug

Elius94 commented 3 months ago

can you provide me the full code? I can't see where you are using my lib

rjjrbatarao commented 3 months ago

Hello I updated the code so you could check. Thank you