Compare commits
3 Commits
e04c06493b
...
ee9895d562
| Author | SHA1 | Date | |
|---|---|---|---|
| ee9895d562 | |||
| 20080929f9 | |||
| 22c96768a5 |
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -1,5 +1,5 @@
|
||||
properties([
|
||||
buildDiscarder(logRotator(numToKeepStr: '25'))
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
])
|
||||
|
||||
def writeBuildMetadata() {
|
||||
|
||||
@ -26,6 +26,8 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.16.1",
|
||||
"canvas": "^3.2.3",
|
||||
"chalk": "^6.0.0",
|
||||
"cli-spinners": "^3.4.0",
|
||||
"etcd3": "^1.1.2",
|
||||
"express": "^5.2.1",
|
||||
"form-data": "^4.0.5",
|
||||
@ -50,9 +52,9 @@
|
||||
"antd": "^5.29.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"esbuild": "^0.25.12",
|
||||
"electron": "^38.7.1",
|
||||
"electron-builder": "^26.0.12",
|
||||
"esbuild": "^0.25.12",
|
||||
"jest": "^30.4.2",
|
||||
"nodemon": "^3.1.14",
|
||||
"pkg": "^5.8.1",
|
||||
|
||||
@ -9,4 +9,6 @@ systemctl enable farmcontrol-server.service
|
||||
# Start the service
|
||||
systemctl start farmcontrol-server.service
|
||||
|
||||
sleep 3
|
||||
|
||||
farmcontrol-server --info
|
||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -14,6 +14,12 @@ importers:
|
||||
canvas:
|
||||
specifier: ^3.2.3
|
||||
version: 3.2.3
|
||||
chalk:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
cli-spinners:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
etcd3:
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
@ -1765,6 +1771,10 @@ packages:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chalk@6.0.0:
|
||||
resolution: {integrity: sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
char-regex@1.0.2:
|
||||
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
|
||||
engines: {node: '>=10'}
|
||||
@ -1802,6 +1812,10 @@ packages:
|
||||
classnames@2.5.1:
|
||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||
|
||||
cli-spinners@3.4.0:
|
||||
resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==}
|
||||
engines: {node: '>=18.20'}
|
||||
|
||||
cliui@7.0.4:
|
||||
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
|
||||
|
||||
@ -6402,6 +6416,8 @@ snapshots:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
chalk@6.0.0: {}
|
||||
|
||||
char-regex@1.0.2: {}
|
||||
|
||||
chokidar@3.6.0:
|
||||
@ -6445,6 +6461,8 @@ snapshots:
|
||||
|
||||
classnames@2.5.1: {}
|
||||
|
||||
cli-spinners@3.4.0: {}
|
||||
|
||||
cliui@7.0.4:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
203
src/commandlineutils.js
Normal file
203
src/commandlineutils.js
Normal file
@ -0,0 +1,203 @@
|
||||
import axios from "axios";
|
||||
import chalk from "chalk";
|
||||
import { LOCAL_SERVER_PORT } from "./localserver/localserver.js";
|
||||
import {
|
||||
createSpinner,
|
||||
registerSpinner,
|
||||
stopWaiting as stopSpinnerWaiting,
|
||||
} from "./spinner.js";
|
||||
|
||||
const LOG_SECTION_WIDTH = 55;
|
||||
|
||||
let sharedCli = null;
|
||||
|
||||
export function getCli() {
|
||||
return sharedCli;
|
||||
}
|
||||
|
||||
export { startWaiting, stopWaiting } from "./spinner.js";
|
||||
|
||||
export function formatState(stateType) {
|
||||
switch (stateType) {
|
||||
case "online":
|
||||
return chalk.green("Online");
|
||||
case "offline":
|
||||
return chalk.dim("Offline");
|
||||
case "error":
|
||||
return chalk.red("Error");
|
||||
case "inactive":
|
||||
return chalk.dim("Inactive");
|
||||
case "connecting":
|
||||
return chalk.yellow("Connecting");
|
||||
case "printing":
|
||||
return chalk.blue("Printing");
|
||||
case "paused":
|
||||
return chalk.yellow("Paused");
|
||||
case "cancelled":
|
||||
return chalk.red("Cancelled");
|
||||
case "completed":
|
||||
return chalk.green("Completed");
|
||||
default:
|
||||
return chalk.dim("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
export function createCliLogger(logger) {
|
||||
const spinner = createSpinner();
|
||||
registerSpinner(spinner);
|
||||
|
||||
function withSpinnerStopped(fn) {
|
||||
return async (...args) => {
|
||||
await stopSpinnerWaiting();
|
||||
return fn(...args);
|
||||
};
|
||||
}
|
||||
|
||||
function logSectionHeader(title) {
|
||||
const padding = LOG_SECTION_WIDTH - title.length;
|
||||
const left = Math.floor(padding / 2);
|
||||
const right = padding - left;
|
||||
logger.info(
|
||||
chalk.dim.cyan("-".repeat(left)) +
|
||||
chalk.bold.white(title) +
|
||||
chalk.dim.cyan("-".repeat(right)),
|
||||
);
|
||||
}
|
||||
|
||||
function logSectionFooter() {
|
||||
logger.info(chalk.dim.cyan("-".repeat(LOG_SECTION_WIDTH)));
|
||||
}
|
||||
|
||||
const cli = {
|
||||
waiting(message) {
|
||||
spinner.start(message, logger);
|
||||
},
|
||||
|
||||
async stopWaiting() {
|
||||
await stopSpinnerWaiting();
|
||||
},
|
||||
|
||||
blank: withSpinnerStopped(() => {
|
||||
process.stdout.write(" \n");
|
||||
}),
|
||||
|
||||
success: withSpinnerStopped((message, ...rest) => {
|
||||
logger.info(chalk.bold.white(`✔ `) + chalk.bold.green(message), ...rest);
|
||||
}),
|
||||
|
||||
failure: withSpinnerStopped((message, ...rest) => {
|
||||
logger.error(chalk.bold.white(`✘ `) + chalk.bold.red(message), ...rest);
|
||||
}),
|
||||
|
||||
warn: withSpinnerStopped((message, ...rest) => {
|
||||
logger.warn(chalk.yellow(message), ...rest);
|
||||
}),
|
||||
|
||||
info: withSpinnerStopped((message, ...rest) => {
|
||||
logger.info(message, ...rest);
|
||||
}),
|
||||
|
||||
secure: withSpinnerStopped((message) => {
|
||||
logger.info(chalk.cyan(`🔒 ${message}`));
|
||||
}),
|
||||
|
||||
label: withSpinnerStopped((key, value) => {
|
||||
logger.info(chalk.bold(key), value);
|
||||
}),
|
||||
|
||||
yesNo(value) {
|
||||
return value ? chalk.green("Yes") : chalk.yellow("No");
|
||||
},
|
||||
|
||||
formatState,
|
||||
|
||||
sectionHeader: withSpinnerStopped(logSectionHeader),
|
||||
sectionFooter: withSpinnerStopped(logSectionFooter),
|
||||
};
|
||||
|
||||
sharedCli = cli;
|
||||
return cli;
|
||||
}
|
||||
|
||||
export function getArgValue(flag) {
|
||||
const withEquals = process.argv.find((arg) => arg.startsWith(`${flag}=`));
|
||||
if (withEquals) {
|
||||
return withEquals.slice(flag.length + 1);
|
||||
}
|
||||
|
||||
const index = process.argv.indexOf(flag);
|
||||
if (index !== -1 && index + 1 < process.argv.length) {
|
||||
return process.argv[index + 1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const isHeadless = process.argv.includes("--headless");
|
||||
export const isInfo = process.argv.includes("--info");
|
||||
export const otpCode = getArgValue("--otp") || undefined;
|
||||
|
||||
export function printerHostInfo(host, cli) {
|
||||
let ref = host?._reference || "Unknown";
|
||||
|
||||
if (ref !== "Unknown") {
|
||||
ref = `HST:${ref}`;
|
||||
}
|
||||
|
||||
cli.label("Reference:", ref);
|
||||
cli.label("Name:", host?.name || "Unknown");
|
||||
|
||||
if (host?.tags?.length > 0) {
|
||||
cli.label("Tags:", chalk.dim(`- `) + chalk.blue(`${host.tags[0]}`));
|
||||
for (let i = 0; i < host.tags.length - 1; i++) {
|
||||
const tag = host.tags[i + 1];
|
||||
cli.info(chalk.dim(` - `) + chalk.blue(`${tag}`));
|
||||
}
|
||||
} else {
|
||||
cli.label("Tags:", chalk.dim("n/a"));
|
||||
}
|
||||
|
||||
cli.label("State:", cli.formatState(host?.state?.type));
|
||||
}
|
||||
|
||||
export function printAuthCommand(cli) {
|
||||
cli.info(
|
||||
"Run '" +
|
||||
chalk.bold.white("farmcontrol-server") +
|
||||
" " +
|
||||
chalk.bold.magentaBright("--otp") +
|
||||
" " +
|
||||
chalk.dim.white("<otp>") +
|
||||
"' to authenticate.",
|
||||
);
|
||||
}
|
||||
|
||||
export async function checkRunning() {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://127.0.0.1:${LOCAL_SERVER_PORT}/info`,
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
if (response.data?.running === true) {
|
||||
return response.data;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function authenticateWithOtp(code) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://127.0.0.1:${LOCAL_SERVER_PORT}/otpAuth`,
|
||||
{ otp: code },
|
||||
{ timeout: 30000 },
|
||||
);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
if (err.response?.data) {
|
||||
return err.response.data;
|
||||
}
|
||||
throw new Error("Failed to authenticate with OTP");
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import log4js from "log4js";
|
||||
import { startWaiting, stopWaiting } from "./spinner.js";
|
||||
|
||||
// Determine environment
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@ -22,7 +23,7 @@ function getProductionConfigPath() {
|
||||
return path.join(
|
||||
process.env.PROGRAMDATA || "C:\\ProgramData",
|
||||
"farmcontrol-server",
|
||||
"config.json"
|
||||
"config.json",
|
||||
);
|
||||
case "darwin":
|
||||
return path.join(
|
||||
@ -30,7 +31,7 @@ function getProductionConfigPath() {
|
||||
"Library",
|
||||
"Application Support",
|
||||
"farmcontrol-server",
|
||||
"config.json"
|
||||
"config.json",
|
||||
);
|
||||
case "linux":
|
||||
default:
|
||||
@ -48,7 +49,7 @@ function getDevelopmentConfigPath() {
|
||||
return path.join(
|
||||
process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"),
|
||||
"farmcontrol-server",
|
||||
"config.json"
|
||||
"config.json",
|
||||
);
|
||||
case "darwin":
|
||||
return path.join(
|
||||
@ -56,14 +57,14 @@ function getDevelopmentConfigPath() {
|
||||
"Library",
|
||||
"Application Support",
|
||||
"farmcontrol-server",
|
||||
"config.json"
|
||||
"config.json",
|
||||
);
|
||||
default:
|
||||
return path.join(
|
||||
homeDir,
|
||||
".config",
|
||||
"farmcontrol-server",
|
||||
"config.json"
|
||||
"config.json",
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -104,7 +105,7 @@ export function loadConfig() {
|
||||
try {
|
||||
if (!fs.existsSync(CONFIG_PATH)) {
|
||||
logger.info(
|
||||
`Configuration file not found at ${CONFIG_PATH}. Creating a new one.`
|
||||
`Configuration file not found at ${CONFIG_PATH}. Creating a new one.`,
|
||||
);
|
||||
const configDir = path.dirname(CONFIG_PATH);
|
||||
if (!fs.existsSync(configDir)) {
|
||||
@ -113,7 +114,7 @@ export function loadConfig() {
|
||||
fs.writeFileSync(
|
||||
CONFIG_PATH,
|
||||
JSON.stringify(DEFAULT_CONFIG, null, 2),
|
||||
"utf8"
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
@ -122,7 +123,7 @@ export function loadConfig() {
|
||||
|
||||
if (!config[NODE_ENV]) {
|
||||
throw new Error(
|
||||
`Configuration for environment '${NODE_ENV}' not found in config.json`
|
||||
`Configuration for environment '${NODE_ENV}' not found in config.json`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -134,9 +135,9 @@ export function loadConfig() {
|
||||
}
|
||||
|
||||
// Save config file
|
||||
export function saveConfig(newConfig) {
|
||||
export async function saveConfig(newConfig) {
|
||||
startWaiting("Saving...", logger);
|
||||
try {
|
||||
logger.info("Saving...");
|
||||
let config = {};
|
||||
if (fs.existsSync(CONFIG_PATH)) {
|
||||
const configData = fs.readFileSync(CONFIG_PATH, "utf8");
|
||||
@ -153,8 +154,10 @@ export function saveConfig(newConfig) {
|
||||
|
||||
// Write back to file with 2-space indentation
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
|
||||
await stopWaiting();
|
||||
logger.info(`Configuration for '${NODE_ENV}' saved successfully.`);
|
||||
} catch (err) {
|
||||
await stopWaiting();
|
||||
logger.error("Error saving config:", err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// documentprinterclient.js - Handles connection to a single document printer
|
||||
import { loadConfig } from "../config.js";
|
||||
import log4js from "log4js";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
import CupsPrinterInterface from "./interfaces/cupsinterface.js";
|
||||
import ReceiptInterface from "./interfaces/receiptinterface.js";
|
||||
import { sendIPC } from "../electron/ipc.js";
|
||||
@ -138,9 +139,6 @@ export class DocumentPrinterClient {
|
||||
|
||||
async connect() {
|
||||
if (this.active == false) {
|
||||
logger.info(
|
||||
`Document printer ${this.id} is not active, skipping connection`,
|
||||
);
|
||||
this.shouldReconnect = false;
|
||||
this.isOnline = false;
|
||||
this.state = { type: "inactive" };
|
||||
@ -234,29 +232,37 @@ export class DocumentPrinterClient {
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
logger.info("Running document printer initialization...");
|
||||
this.state = { type: "initializing", message: null };
|
||||
this.isOnline = true;
|
||||
this.connectedAt = new Date();
|
||||
await this.updateDocumentPrinterState();
|
||||
if (this.printerInterface && this.printerInterface.initialize) {
|
||||
const result = await this.printerInterface.initialize();
|
||||
if (result.error) {
|
||||
logger.error(
|
||||
`Error initializing document printer ${this.documentPrinter.name}:`,
|
||||
result.error,
|
||||
);
|
||||
this.state = { type: "offline", message: result.error };
|
||||
await this.updateDocumentPrinterState();
|
||||
return false;
|
||||
}
|
||||
this.state = { type: "standby", message: null };
|
||||
startWaiting("Running document printer initialization...", logger);
|
||||
try {
|
||||
this.state = { type: "initializing", message: null };
|
||||
this.isOnline = true;
|
||||
this.connectedAt = new Date();
|
||||
await this.updateDocumentPrinterState();
|
||||
this.eventUpdateInterval = setInterval(
|
||||
this.handleEventUpdate.bind(this),
|
||||
3000,
|
||||
);
|
||||
return true;
|
||||
if (this.printerInterface && this.printerInterface.initialize) {
|
||||
const result = await this.printerInterface.initialize();
|
||||
if (result.error) {
|
||||
logger.error(
|
||||
`Error initializing document printer ${this.documentPrinter.name}:`,
|
||||
result.error,
|
||||
);
|
||||
this.state = { type: "offline", message: result.error };
|
||||
await this.updateDocumentPrinterState();
|
||||
await stopWaiting();
|
||||
return false;
|
||||
}
|
||||
this.state = { type: "standby", message: null };
|
||||
await this.updateDocumentPrinterState();
|
||||
this.eventUpdateInterval = setInterval(
|
||||
this.handleEventUpdate.bind(this),
|
||||
3000,
|
||||
);
|
||||
await stopWaiting();
|
||||
return true;
|
||||
}
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { loadConfig } from "../config.js";
|
||||
import log4js from "log4js";
|
||||
import { sendIPC } from "../electron/ipc.js";
|
||||
import { DocumentPrinterClient } from "./documentprinterclient.js";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
@ -16,7 +17,7 @@ export class DocumentPrinterManager {
|
||||
}
|
||||
|
||||
async reloadDocumentPrinters() {
|
||||
logger.info("Reloading document printers...");
|
||||
startWaiting("Reloading document printers...", logger);
|
||||
try {
|
||||
this.documentPrinters = await this.socketClient.listObjects({
|
||||
objectType: "documentPrinter",
|
||||
@ -62,7 +63,9 @@ export class DocumentPrinterManager {
|
||||
);
|
||||
}
|
||||
}
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
logger.error("Failed to update document printers:", error);
|
||||
this.documentPrinters = [];
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import log4js from "log4js";
|
||||
import { loadConfig } from "../../config.js";
|
||||
import ipp from "ipp";
|
||||
import { startWaiting, stopWaiting } from "../../spinner.js";
|
||||
|
||||
const config = loadConfig();
|
||||
const logger = log4js.getLogger("CUPS Printer Interface");
|
||||
@ -119,16 +120,18 @@ export default class CupsInterface {
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
logger.info(`Disconnecting from CUPS printer...`);
|
||||
startWaiting("Disconnecting from CUPS printer...", logger);
|
||||
this.isConnected = false;
|
||||
this.cupsPrinter = null;
|
||||
await stopWaiting();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
logger.info(`Initializing CUPS printer...`);
|
||||
startWaiting("Initializing CUPS printer...", logger);
|
||||
|
||||
if (!this.isConnected || !this.cupsPrinter) {
|
||||
await stopWaiting();
|
||||
return { error: "Printer is not connected" };
|
||||
}
|
||||
|
||||
@ -157,14 +160,17 @@ export default class CupsInterface {
|
||||
// Printer states: 3 = idle, 4 = processing, 5 = stopped
|
||||
if (printerState === 5) {
|
||||
logger.warn(`Printer ${this.name} is stopped: ${stateMessage}`);
|
||||
await stopWaiting();
|
||||
return { error: `Printer is stopped: ${stateMessage}` };
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`CUPS printer ${this.name} initialized successfully. State: ${printerState}`
|
||||
);
|
||||
await stopWaiting();
|
||||
return true;
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
logger.error(`Failed to initialize CUPS printer ${this.name}:`, error);
|
||||
return { error: error.message || "Failed to initialize CUPS printer" };
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import log4js from "log4js";
|
||||
import { notPrompting } from "../utils.js";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
|
||||
const logger = log4js.getLogger("IPC");
|
||||
let mainWindow = null;
|
||||
@ -22,8 +23,8 @@ export async function setupIPC() {
|
||||
return;
|
||||
}
|
||||
|
||||
ipcMain.on("getData", (event) => {
|
||||
logger.info("Getting data...");
|
||||
ipcMain.on("getData", async () => {
|
||||
startWaiting("Getting data...", logger);
|
||||
try {
|
||||
// Get the global socket client instance
|
||||
const socketClient = global.socketClient;
|
||||
@ -35,6 +36,7 @@ export async function setupIPC() {
|
||||
sendIPC("setHost", {});
|
||||
sendIPC("setPrinters", []);
|
||||
sendIPC("setDocumentPrinters", []);
|
||||
await stopWaiting();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -46,8 +48,9 @@ export async function setupIPC() {
|
||||
sendIPC("setPrinters", socketClient.printerManager.printers || []);
|
||||
sendIPC(
|
||||
"setDocumentPrinters",
|
||||
socketClient.documentPrinterManager.documentPrinters || []
|
||||
socketClient.documentPrinterManager.documentPrinters || [],
|
||||
);
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
logger.error("Error getting printer data:", error);
|
||||
sendIPC("setAuthenticated", false);
|
||||
@ -55,6 +58,7 @@ export async function setupIPC() {
|
||||
sendIPC("setLoading", false);
|
||||
sendIPC("setHost", {});
|
||||
sendIPC("setPrinters", []);
|
||||
await stopWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
@ -83,7 +87,7 @@ export async function setupIPC() {
|
||||
|
||||
// OTP authentication handler
|
||||
ipcMain.on("authenticateOTP", async (event, otp) => {
|
||||
logger.info("Authenticating with OTP...");
|
||||
startWaiting("Authenticating with OTP...", logger);
|
||||
try {
|
||||
const socketClient = global.socketClient;
|
||||
if (socketClient) {
|
||||
@ -92,7 +96,9 @@ export async function setupIPC() {
|
||||
} else {
|
||||
logger.error("Socket client not available for OTP authentication");
|
||||
}
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
logger.error("Error during OTP authentication:", error);
|
||||
}
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import path from "path";
|
||||
import { loadConfig } from "../config.js";
|
||||
import log4js from "log4js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
// Load configuration
|
||||
const config = loadConfig();
|
||||
|
||||
@ -21,7 +22,7 @@ export async function createElectronWindow() {
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
"Electron not available, skipping window creation. Error:",
|
||||
error
|
||||
error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -49,8 +50,8 @@ export async function createElectronWindow() {
|
||||
process.env.NODE_ENV === "development"
|
||||
? path.join(__dirname, "preload.js")
|
||||
: isRunningFromBuild
|
||||
? path.join(__dirname, "preload.js")
|
||||
: path.join(__dirname, "..", "build", "electron", "preload.js");
|
||||
? path.join(__dirname, "preload.js")
|
||||
: path.join(__dirname, "..", "build", "electron", "preload.js");
|
||||
|
||||
const win = new BrowserWindow({
|
||||
width: 900,
|
||||
@ -71,7 +72,7 @@ export async function createElectronWindow() {
|
||||
|
||||
logger.info("Preload Script", preloadPath);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
logger.info("Loading development url...");
|
||||
startWaiting("Loading development url...", logger);
|
||||
win.loadURL("http://localhost:5287"); // Vite dev server
|
||||
} else {
|
||||
// In production, the built files will be in the build/electron directory
|
||||
@ -79,12 +80,14 @@ export async function createElectronWindow() {
|
||||
? path.join(__dirname, "index.html")
|
||||
: path.join(__dirname, "..", "build", "electron", "index.html");
|
||||
|
||||
startWaiting("Loading production file...", logger);
|
||||
logger.info("Loading production file:", htmlPath);
|
||||
win.loadFile(htmlPath);
|
||||
}
|
||||
|
||||
// Resolve the promise when the window is ready
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win.webContents.on("did-finish-load", async () => {
|
||||
await stopWaiting();
|
||||
resolve(win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
process.env.FC_HEADLESS_ENTRY = "1";
|
||||
|
||||
import("./index.js")
|
||||
.then(({ init }) =>
|
||||
init({ headless: true }).catch((err) => {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}),
|
||||
);
|
||||
import("./index.js").then(({ init }) =>
|
||||
init({ headless: true }).catch((err) => {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}),
|
||||
);
|
||||
|
||||
188
src/index.js
188
src/index.js
@ -1,163 +1,130 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
import { loadConfig } from "./config.js";
|
||||
import axios from "axios";
|
||||
import log4js from "log4js";
|
||||
import { createElectronWindow } from "./electron/window.js";
|
||||
import { setupIPC } from "./electron/ipc.js";
|
||||
import { LOCAL_SERVER_PORT, LocalServer } from "./localserver/localserver.js";
|
||||
import { LocalServer } from "./localserver/localserver.js";
|
||||
import { SocketClient } from "./socket/socketclient.js";
|
||||
import {
|
||||
authenticateWithOtp,
|
||||
checkRunning,
|
||||
createCliLogger,
|
||||
isHeadless,
|
||||
isInfo,
|
||||
otpCode,
|
||||
printAuthCommand,
|
||||
printerHostInfo,
|
||||
startWaiting,
|
||||
stopWaiting,
|
||||
} from "./commandlineutils.js";
|
||||
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = "production";
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
const config = loadConfig();
|
||||
|
||||
const logger = log4js.getLogger("App");
|
||||
logger.level = config.logLevel;
|
||||
|
||||
const isHeadless = process.argv.includes("--headless");
|
||||
const isInfo = process.argv.includes("--info");
|
||||
const cli = createCliLogger(logger);
|
||||
|
||||
const printerHostInfo = (host) => {
|
||||
var ref = host?._reference || "Unknown";
|
||||
|
||||
if (ref != "Unknown") {
|
||||
ref = "HST:" + ref;
|
||||
}
|
||||
|
||||
logger.info("Reference:", ref);
|
||||
logger.info("Name:", host?.name || "Unknown");
|
||||
if (host?.tags?.length > 0) {
|
||||
logger.info("Tags: -", host?.tags?.[0]);
|
||||
for (var i = 0; i < host?.tags?.length - 1; i++) {
|
||||
const tag = host?.tags[i + 1];
|
||||
logger.info(" -", tag);
|
||||
}
|
||||
} else {
|
||||
logger.info("Tags: n/a");
|
||||
}
|
||||
logger.info("State:", host?.state?.type || "Unknown");
|
||||
};
|
||||
|
||||
function getArgValue(flag) {
|
||||
const withEquals = process.argv.find((arg) => arg.startsWith(`${flag}=`));
|
||||
if (withEquals) {
|
||||
return withEquals.slice(flag.length + 1);
|
||||
}
|
||||
|
||||
const index = process.argv.indexOf(flag);
|
||||
if (index !== -1 && index + 1 < process.argv.length) {
|
||||
return process.argv[index + 1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const otpCode = getArgValue("--otp") || undefined;
|
||||
|
||||
async function checkRunning() {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://127.0.0.1:${LOCAL_SERVER_PORT}/info`,
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
if (response.data?.running === true) {
|
||||
return response.data;
|
||||
}
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticateWithOtp(otpCode) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://127.0.0.1:${LOCAL_SERVER_PORT}/otpAuth`,
|
||||
{ otp: otpCode },
|
||||
{ timeout: 30000 },
|
||||
);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
if (err.response?.data) {
|
||||
return err.response.data;
|
||||
}
|
||||
throw new Error("Failed to authenticate with OTP");
|
||||
}
|
||||
}
|
||||
export async function init(options = {}) {
|
||||
const headless = options.headless ?? isHeadless;
|
||||
|
||||
logger.info("⌛ Checking if Farm Control Server is running...");
|
||||
|
||||
const runningServer = await checkRunning();
|
||||
cli.blank();
|
||||
startWaiting("Checking if Farm Control Server is running...", logger);
|
||||
let runningServer;
|
||||
try {
|
||||
runningServer = await checkRunning();
|
||||
} catch (err) {
|
||||
await stopWaiting();
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (otpCode != undefined || isInfo == true) {
|
||||
logger.info("✔ Farm Control Server is running.");
|
||||
cli.success("Farm Control Server is running.");
|
||||
}
|
||||
|
||||
if (isInfo) {
|
||||
const buildNumber =
|
||||
runningServer.buildNumber ?? runningServer.build ?? "n/a";
|
||||
|
||||
logger.info("-----FarmControl Server-----");
|
||||
logger.info(
|
||||
cli.blank();
|
||||
cli.sectionHeader("FarmControl Server");
|
||||
cli.label(
|
||||
"Version:",
|
||||
runningServer.version ? `v${runningServer.version}` : "n/a",
|
||||
);
|
||||
logger.info("Build:", buildNumber === "dev" ? "dev" : `b${buildNumber}`);
|
||||
logger.info(
|
||||
"Authenticated:",
|
||||
runningServer.authenticated == true ? "Yes" : "No",
|
||||
);
|
||||
logger.info("------------Host------------");
|
||||
printerHostInfo(runningServer.host);
|
||||
logger.info("----------------------------");
|
||||
cli.label("Build:", buildNumber === "dev" ? "dev" : `b${buildNumber}`);
|
||||
cli.label("Authenticated:", cli.yesNo(runningServer.authenticated == true));
|
||||
cli.sectionFooter();
|
||||
cli.blank();
|
||||
if (runningServer.authenticated == false) {
|
||||
cli.sectionHeader("Authenticate");
|
||||
printAuthCommand(cli);
|
||||
cli.sectionFooter();
|
||||
} else {
|
||||
cli.sectionHeader("Host");
|
||||
printerHostInfo(runningServer.host, cli);
|
||||
cli.sectionFooter();
|
||||
}
|
||||
cli.blank();
|
||||
return runningServer;
|
||||
}
|
||||
|
||||
if (otpCode != undefined) {
|
||||
logger.info("🔒 Authenticating with OTP...");
|
||||
const authenticatedServer = await authenticateWithOtp(otpCode);
|
||||
cli.blank();
|
||||
startWaiting("Authenticating with OTP...", logger);
|
||||
let authenticatedServer;
|
||||
try {
|
||||
authenticatedServer = await authenticateWithOtp(otpCode);
|
||||
await stopWaiting();
|
||||
} catch (err) {
|
||||
await stopWaiting();
|
||||
throw err;
|
||||
}
|
||||
if (authenticatedServer.valid == false) {
|
||||
logger.error(
|
||||
"✘ Failed to authenticate with OTP -",
|
||||
authenticatedServer.error,
|
||||
);
|
||||
cli.failure("Failed to authenticate!");
|
||||
cli.failure(authenticatedServer.error);
|
||||
} else {
|
||||
logger.info("✔ Authenticated with OTP.");
|
||||
logger.info("------------Host------------");
|
||||
printerHostInfo(authenticatedServer.host);
|
||||
logger.info("----------------------------");
|
||||
cli.success("Authenticated with OTP.");
|
||||
cli.blank();
|
||||
cli.sectionHeader("Host");
|
||||
printerHostInfo(authenticatedServer.host, cli);
|
||||
cli.sectionFooter();
|
||||
cli.blank();
|
||||
return authenticatedServer;
|
||||
}
|
||||
cli.blank();
|
||||
return authenticatedServer;
|
||||
}
|
||||
|
||||
if (runningServer != false) {
|
||||
logger.warn("✘ Farm Control Server is already running.");
|
||||
cli.warn("✘ Farm Control Server is already running.");
|
||||
cli.blank();
|
||||
return runningServer;
|
||||
}
|
||||
|
||||
if (!headless) {
|
||||
// Create Electron window first
|
||||
logger.info("Creating electron window...");
|
||||
await createElectronWindow().catch((err) => {
|
||||
logger.warn("Failed to create Electron window:", err);
|
||||
});
|
||||
startWaiting("Creating electron window...", logger);
|
||||
try {
|
||||
await createElectronWindow().catch((err) => {
|
||||
cli.warn("Failed to create Electron window:", err);
|
||||
});
|
||||
await stopWaiting();
|
||||
} catch (err) {
|
||||
await stopWaiting();
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Setup IPC communication after window is created
|
||||
setupIPC().catch((err) => {
|
||||
logger.warn("Failed to setup IPC:", err);
|
||||
cli.warn("Failed to setup IPC:", err);
|
||||
});
|
||||
} else {
|
||||
logger.info("Running in headless mode. Skipping window creation.");
|
||||
cli.info("Running in headless mode. Skipping window creation.");
|
||||
}
|
||||
|
||||
const socketClient = new SocketClient();
|
||||
// Make socket client globally accessible for IPC handlers
|
||||
global.socketClient = socketClient;
|
||||
|
||||
const localServer = new LocalServer(socketClient);
|
||||
@ -165,10 +132,12 @@ export async function init(options = {}) {
|
||||
|
||||
socketClient.connect();
|
||||
|
||||
process.on("SIGINT", () => {
|
||||
logger.info("Shutting down...");
|
||||
process.on("SIGINT", async () => {
|
||||
startWaiting("Shutting down...", logger);
|
||||
cli.blank();
|
||||
socketClient.disconnect();
|
||||
localServer.stop();
|
||||
await stopWaiting();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
@ -180,6 +149,7 @@ const isMainModule =
|
||||
if (isMainModule && !process.env.FC_HEADLESS_ENTRY) {
|
||||
init().catch((err) => {
|
||||
logger.error(err.message);
|
||||
cli.blank();
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import log4js from "log4js";
|
||||
import { notPrompting } from "../utils.js";
|
||||
import { loadConfig } from "../config.js";
|
||||
import { getServerVersionInfo } from "../serverVersion.js";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
|
||||
export const LOCAL_SERVER_PORT = 47291;
|
||||
|
||||
@ -41,13 +42,22 @@ export class LocalServer {
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("Authenticating with OTP via local server...");
|
||||
notPrompting();
|
||||
const result = await this.socketClient.authenticateWithOtp(otp, {
|
||||
retryOnFailure: false,
|
||||
});
|
||||
startWaiting("Authenticating with OTP via local server...", logger);
|
||||
let result;
|
||||
try {
|
||||
notPrompting();
|
||||
result = await this.socketClient.authenticateWithOtp(otp, {
|
||||
retryOnFailure: false,
|
||||
logs: false,
|
||||
});
|
||||
await stopWaiting();
|
||||
} catch (err) {
|
||||
await stopWaiting();
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!result.valid) {
|
||||
logger.error("OTP authentication failed:", result.error);
|
||||
res.status(401).json(result);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3,7 +3,8 @@ import log4js from "log4js";
|
||||
import NodeCache from "node-cache";
|
||||
import { loadConfig } from "../config.js";
|
||||
import { sendIPC } from "../electron/ipc.js";
|
||||
|
||||
import chalk from "chalk";
|
||||
import { formatState } from "../commandlineutils.js";
|
||||
const config = loadConfig();
|
||||
const logger = log4js.getLogger("Printer Database");
|
||||
logger.level = config.logLevel;
|
||||
@ -15,7 +16,6 @@ export class PrinterDatabase {
|
||||
this.id = this.printer._id;
|
||||
// Initialize cache with 30 second TTL
|
||||
this.printerCache = new NodeCache({ stdTTL: 30 });
|
||||
logger.info("Initialized PrinterDatabase with socket manager");
|
||||
}
|
||||
|
||||
async getPrinter() {
|
||||
@ -146,12 +146,9 @@ export class PrinterDatabase {
|
||||
connectedAt,
|
||||
});
|
||||
|
||||
logger.info(`Updated printer ${this.printer.name} state:`, {
|
||||
type: state.type,
|
||||
progress: state.progress,
|
||||
online,
|
||||
previousState: updatedPrinter.state,
|
||||
});
|
||||
logger.info(
|
||||
`Updated printer: ${chalk.bold.white(this.printer.name)}, State: ${chalk.bold.white(formatState(this.printer.state.type))}${state?.progress ? ` (${state.progress})` : ""} (${online ? chalk.green("Online") : chalk.dim.white("Offline")})`,
|
||||
);
|
||||
|
||||
return updatedPrinter;
|
||||
} catch (error) {
|
||||
|
||||
@ -5,6 +5,7 @@ import { loadConfig } from "../config.js";
|
||||
import { PrinterDatabase } from "./database.js";
|
||||
import { PrinterFileManager } from "./printerfilemanager.js";
|
||||
import log4js from "log4js";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
import axios from "axios";
|
||||
import FormData from "form-data";
|
||||
import _ from "lodash";
|
||||
@ -151,7 +152,6 @@ export class PrinterClient {
|
||||
|
||||
async connect() {
|
||||
if (this.active == false) {
|
||||
logger.info(`Printer ${this.id} is not active, skipping connection`);
|
||||
this.shouldReconnect = false;
|
||||
this.isOnline = false;
|
||||
this.state = { type: "inactive" };
|
||||
@ -238,96 +238,108 @@ export class PrinterClient {
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
logger.info("Running printer initialization...");
|
||||
this.state = { type: "initializing", message: null };
|
||||
this.isOnline = true;
|
||||
this.connectedAt = new Date();
|
||||
await this.getInfo();
|
||||
await this.getFiles();
|
||||
await this.getCurrentFilament();
|
||||
await this.loadCurrentFilament();
|
||||
await this.updateSubscriptions();
|
||||
await this.getPrinterState();
|
||||
await this.syncSubJobs();
|
||||
this.eventUpdateInterval = setInterval(
|
||||
this.handleEventUpdate.bind(this),
|
||||
500,
|
||||
);
|
||||
startWaiting("Running printer initialization...", logger);
|
||||
try {
|
||||
this.state = { type: "initializing", message: null };
|
||||
this.isOnline = true;
|
||||
this.connectedAt = new Date();
|
||||
await this.getInfo();
|
||||
await this.getFiles();
|
||||
await this.getCurrentFilament();
|
||||
await this.loadCurrentFilament();
|
||||
await this.updateSubscriptions();
|
||||
await this.getPrinterState();
|
||||
await this.syncSubJobs();
|
||||
this.eventUpdateInterval = setInterval(
|
||||
this.handleEventUpdate.bind(this),
|
||||
500,
|
||||
);
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
logger.info("Getting printer info...");
|
||||
startWaiting("Getting printer info...", logger);
|
||||
try {
|
||||
// Get server info
|
||||
const serverResult = await this.jsonRpc.callMethod("server.info");
|
||||
this.isOnline = true;
|
||||
this.klippyState = { type: serverResult.klippy_state };
|
||||
logger.info(
|
||||
"Server:",
|
||||
`Moonraker ${serverResult.moonraker_version} (${this.printer.name})`,
|
||||
`State: ${this.klippyState.type}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const klippyResult = await this.jsonRpc.callMethod("printer.info");
|
||||
// Get server info
|
||||
const serverResult = await this.jsonRpc.callMethod("server.info");
|
||||
this.isOnline = true;
|
||||
this.klippyState = { type: serverResult.klippy_state };
|
||||
logger.info(
|
||||
`Klippy info for ${this.printer.name}: ${klippyResult.hostname}, ${klippyResult.software_version}`,
|
||||
);
|
||||
// Update firmware version in database
|
||||
await this.database.updatePrinterFirmware(
|
||||
klippyResult.software_version,
|
||||
);
|
||||
logger.info(
|
||||
`Updated firmware version for ${this.printer.name} to ${klippyResult.software_version}`,
|
||||
"Server:",
|
||||
`Moonraker ${serverResult.moonraker_version} (${this.printer.name})`,
|
||||
`State: ${this.klippyState.type}`,
|
||||
);
|
||||
|
||||
if (klippyResult.state === "error" && klippyResult.state_message) {
|
||||
logger.error(
|
||||
`Klippy error for ${this.printer.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert({
|
||||
code: "klippyError",
|
||||
type: "error",
|
||||
message: klippyResult.state_message,
|
||||
actions: ["restart", "restartFirmware"],
|
||||
}),
|
||||
try {
|
||||
const klippyResult = await this.jsonRpc.callMethod("printer.info");
|
||||
logger.info(
|
||||
`Klippy info for ${this.printer.name}: ${klippyResult.hostname}, ${klippyResult.software_version}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (klippyResult.state === "shutdown" && klippyResult.state_message) {
|
||||
logger.error(
|
||||
`Klippy error for ${this.printer.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert({
|
||||
code: "klippyError",
|
||||
type: "error",
|
||||
message: klippyResult.state_message,
|
||||
actions: ["restart", "restartFirmware"],
|
||||
}),
|
||||
// Update firmware version in database
|
||||
await this.database.updatePrinterFirmware(
|
||||
klippyResult.software_version,
|
||||
);
|
||||
logger.info(
|
||||
`Updated firmware version for ${this.printer.name} to ${klippyResult.software_version}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (klippyResult.state === "startup" && klippyResult.state_message) {
|
||||
logger.warn(
|
||||
`Klippy startup message for ${this.printer.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert({
|
||||
type: "info",
|
||||
code: "klippyStartup",
|
||||
message: klippyResult.state_message,
|
||||
priority: 8,
|
||||
timestamp: new Date(),
|
||||
}),
|
||||
if (klippyResult.state === "error" && klippyResult.state_message) {
|
||||
logger.error(
|
||||
`Klippy error for ${this.printer.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert({
|
||||
code: "klippyError",
|
||||
type: "error",
|
||||
message: klippyResult.state_message,
|
||||
actions: ["restart", "restartFirmware"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (klippyResult.state === "shutdown" && klippyResult.state_message) {
|
||||
logger.error(
|
||||
`Klippy error for ${this.printer.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert({
|
||||
code: "klippyError",
|
||||
type: "error",
|
||||
message: klippyResult.state_message,
|
||||
actions: ["restart", "restartFirmware"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (klippyResult.state === "startup" && klippyResult.state_message) {
|
||||
logger.warn(
|
||||
`Klippy startup message for ${this.printer.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert({
|
||||
type: "info",
|
||||
code: "klippyStartup",
|
||||
message: klippyResult.state_message,
|
||||
priority: 8,
|
||||
timestamp: new Date(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error getting Klippy info for ${this.printer.name}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error getting Klippy info for ${this.printer.name}:`,
|
||||
`Error getting server info for ${this.printer.name}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error getting server info for ${this.printer.name}:`,
|
||||
error,
|
||||
);
|
||||
await stopWaiting();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { PrinterClient } from "./printerclient.js";
|
||||
import { loadConfig } from "../config.js";
|
||||
import log4js from "log4js";
|
||||
import { sendIPC } from "../electron/ipc.js";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
// Load configuration
|
||||
const config = loadConfig();
|
||||
|
||||
@ -17,7 +18,7 @@ export class PrinterManager {
|
||||
}
|
||||
|
||||
async reloadPrinters() {
|
||||
logger.info("Reloading printers...");
|
||||
startWaiting("Reloading printers...", logger);
|
||||
try {
|
||||
this.printers = await this.socketClient.listObjects({
|
||||
objectType: "printer",
|
||||
@ -64,7 +65,9 @@ export class PrinterManager {
|
||||
|
||||
logger.debug("Printers added:", addedPrintersCount);
|
||||
logger.debug("Printers removed:", removedPrintersCount);
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
logger.error("Failed to update printers:", error);
|
||||
this.printers = [];
|
||||
}
|
||||
@ -155,39 +158,44 @@ export class PrinterManager {
|
||||
|
||||
// Close all printer connections
|
||||
async closeAllConnections() {
|
||||
logger.info(
|
||||
startWaiting(
|
||||
`Closing all printer connections... current count: ${this.printerClients.size}`,
|
||||
logger,
|
||||
);
|
||||
try {
|
||||
const clients = Array.from(this.printerClients.values());
|
||||
|
||||
// Take a snapshot so any mutations during disconnects don't affect iteration
|
||||
const clients = Array.from(this.printerClients.values());
|
||||
|
||||
for (const printerClient of clients) {
|
||||
try {
|
||||
// Ensure we never auto-reconnect after a manual close-all
|
||||
printerClient.shouldReconnect = false;
|
||||
await printerClient.disconnect();
|
||||
logger.info(
|
||||
`Disconnected printer client ${printerClient?.id || "unknown"}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to disconnect printer client ${
|
||||
printerClient?.id || "unknown"
|
||||
}:`,
|
||||
error,
|
||||
);
|
||||
for (const printerClient of clients) {
|
||||
try {
|
||||
// Ensure we never auto-reconnect after a manual close-all
|
||||
printerClient.shouldReconnect = false;
|
||||
await printerClient.disconnect();
|
||||
logger.info(
|
||||
`Disconnected printer client ${printerClient?.id || "unknown"}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to disconnect printer client ${
|
||||
printerClient?.id || "unknown"
|
||||
}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Printer clients:", this.printerClients);
|
||||
|
||||
// Clear local references so no stale clients remain
|
||||
this.printerClients.clear();
|
||||
this.printers = [];
|
||||
|
||||
logger.info(
|
||||
`All printer connections closed. Remaining clients: ${this.printerClients.size}`,
|
||||
);
|
||||
await stopWaiting();
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log("Printer clients:", this.printerClients);
|
||||
|
||||
// Clear local references so no stale clients remain
|
||||
this.printerClients.clear();
|
||||
this.printers = [];
|
||||
|
||||
logger.info(
|
||||
`All printer connections closed. Remaining clients: ${this.printerClients.size}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { PrinterManager } from "../printer/printermanager.js";
|
||||
import { HostManager } from "../host/hostmanager.js";
|
||||
import { FileManager } from "../files/filemanager.js";
|
||||
import { DocumentPrinterManager } from "../documentprinter/documentprintermanager.js";
|
||||
import { startWaiting, stopWaiting } from "../spinner.js";
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
@ -51,9 +52,9 @@ export class SocketClient {
|
||||
this.reconnectTimeout = null;
|
||||
}
|
||||
|
||||
logger.info("Will attempt to reconnect in 3 seconds...");
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
logger.info("Attempting to reconnect...");
|
||||
startWaiting("Will attempt to reconnect in 3 seconds...", logger);
|
||||
this.reconnectTimeout = setTimeout(async () => {
|
||||
await stopWaiting();
|
||||
this.connect();
|
||||
}, 3000);
|
||||
}
|
||||
@ -94,9 +95,10 @@ export class SocketClient {
|
||||
});
|
||||
}
|
||||
|
||||
connect() {
|
||||
async connect() {
|
||||
try {
|
||||
logger.info(`Connecting to Socket.IO server: ${config.url}`);
|
||||
await stopWaiting();
|
||||
startWaiting(`Connecting to Socket.IO server: ${config.url}`, logger);
|
||||
this.socket = io(config.url, {
|
||||
auth: { type: "host" },
|
||||
reconnection: false,
|
||||
@ -110,6 +112,7 @@ export class SocketClient {
|
||||
sendIPC("setAuthenticated", false);
|
||||
this.setupSocketEventHandlers();
|
||||
} catch (error) {
|
||||
await stopWaiting();
|
||||
logger.error("Failed to create Socket.IO connection:", error);
|
||||
}
|
||||
}
|
||||
@ -150,18 +153,18 @@ export class SocketClient {
|
||||
}
|
||||
|
||||
async authenticateWithOtp(otp = undefined, options = {}) {
|
||||
const { retryOnFailure = otp == undefined } = options;
|
||||
const { retryOnFailure = otp == undefined, logs = true } = options;
|
||||
|
||||
if (otp == undefined) {
|
||||
otp = await askOtp();
|
||||
}
|
||||
|
||||
await this.waitForConnection();
|
||||
return this.authenticate({ otp }, { retryOnFailure });
|
||||
return this.authenticate({ otp }, { retryOnFailure, logs });
|
||||
}
|
||||
|
||||
async authenticate(authenticationData, options = {}) {
|
||||
const { retryOnFailure = true } = options;
|
||||
const { retryOnFailure = true, logs = true } = options;
|
||||
|
||||
await this.waitForConnection();
|
||||
|
||||
@ -171,7 +174,9 @@ export class SocketClient {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Host authenticating...");
|
||||
if (logs) {
|
||||
logger.debug("Host authenticating...");
|
||||
}
|
||||
this.socket.emit(
|
||||
"authenticate",
|
||||
authenticationData,
|
||||
@ -179,7 +184,9 @@ export class SocketClient {
|
||||
if (verifyResult.valid == false) {
|
||||
this.authenticated = false;
|
||||
sendIPC("setAuthenticated", false);
|
||||
logger.error("Host not authenticated:", verifyResult.error);
|
||||
if (logs) {
|
||||
logger.error("Host not authenticated:", verifyResult.error);
|
||||
}
|
||||
|
||||
if (retryOnFailure) {
|
||||
const retryResult = await this.authenticateWithOtp();
|
||||
@ -191,7 +198,9 @@ export class SocketClient {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Host authenticated.");
|
||||
if (logs) {
|
||||
logger.info("Host authenticated.");
|
||||
}
|
||||
this.authenticated = true;
|
||||
sendIPC("setAuthenticated", true);
|
||||
sendIPC("setLoading", false);
|
||||
@ -212,7 +221,9 @@ export class SocketClient {
|
||||
this.id = this.host._id;
|
||||
|
||||
config.host = { id: this.id, authCode: this.host.authCode };
|
||||
saveConfig(config);
|
||||
if (logs) {
|
||||
await saveConfig(config);
|
||||
}
|
||||
this.sendDeviceInfo();
|
||||
this.subscribeToObjectEvents();
|
||||
this.subscribeToObjectUpdates({ objectType: "host", _id: this.id });
|
||||
@ -226,7 +237,6 @@ export class SocketClient {
|
||||
}
|
||||
|
||||
handleHostAction(action, callback) {
|
||||
console.log("RUNNING HOST ACTION");
|
||||
switch (action.type) {
|
||||
case "reloadPrinters":
|
||||
this.printerManager.updatePrinters().catch((error) => {
|
||||
@ -237,7 +247,7 @@ export class SocketClient {
|
||||
callback({ success: true });
|
||||
}
|
||||
|
||||
handleHostUpdate(data) {
|
||||
async handleHostUpdate(data) {
|
||||
if (data._id != this.id) {
|
||||
return;
|
||||
}
|
||||
@ -249,7 +259,7 @@ export class SocketClient {
|
||||
|
||||
if (data.object?.authCode) {
|
||||
config.host = { id: this.id, authCode: data.object.authCode };
|
||||
saveConfig(config);
|
||||
await saveConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,7 +308,8 @@ export class SocketClient {
|
||||
}
|
||||
}
|
||||
|
||||
handleConnect() {
|
||||
async handleConnect() {
|
||||
await stopWaiting();
|
||||
logger.info("Connected to FarmControl Api.");
|
||||
this.connected = true;
|
||||
sendIPC("setConnected", true);
|
||||
@ -326,7 +337,8 @@ export class SocketClient {
|
||||
this.authenticate({ id: config.host.id, authCode: config.host.authCode });
|
||||
}
|
||||
|
||||
handleError(error) {
|
||||
async handleError(error) {
|
||||
await stopWaiting();
|
||||
logger.error("Connection error:", error.message);
|
||||
this.loading = false;
|
||||
sendIPC("setLoading", false);
|
||||
|
||||
122
src/spinner.js
Normal file
122
src/spinner.js
Normal file
@ -0,0 +1,122 @@
|
||||
import chalk from "chalk";
|
||||
import cliSpinners from "cli-spinners";
|
||||
import log4js from "log4js";
|
||||
import { writeSync } from "node:fs";
|
||||
import { colouredLayout } from "log4js/lib/layouts.js";
|
||||
|
||||
let spinner = null;
|
||||
let consoleLogPatched = false;
|
||||
|
||||
function patchConsoleLog() {
|
||||
if (consoleLogPatched) {
|
||||
return;
|
||||
}
|
||||
const originalLog = console.log.bind(console);
|
||||
console.log = (...args) => {
|
||||
spinner?.stopSync();
|
||||
originalLog(...args);
|
||||
};
|
||||
consoleLogPatched = true;
|
||||
}
|
||||
|
||||
function formatLogPrefix(logger) {
|
||||
const categoryName = logger?.category ?? "App";
|
||||
return colouredLayout({
|
||||
startTime: new Date(),
|
||||
level: log4js.levels.INFO,
|
||||
categoryName,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
|
||||
function clearSpinnerLineSync() {
|
||||
if (!process.stderr.isTTY) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
writeSync(process.stderr.fd, "\r\x1b[2K");
|
||||
} catch {
|
||||
process.stderr.write("\r\x1b[2K");
|
||||
}
|
||||
}
|
||||
|
||||
export function createSpinner() {
|
||||
const { frames, interval } = cliSpinners.dots;
|
||||
let timer = null;
|
||||
let frameIndex = 0;
|
||||
let message = "";
|
||||
let active = false;
|
||||
let activeLogger = null;
|
||||
|
||||
function render() {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
const frame = frames[frameIndex];
|
||||
frameIndex = (frameIndex + 1) % frames.length;
|
||||
const prefix = formatLogPrefix(activeLogger);
|
||||
const line = `${prefix}${frame} ${chalk.bold.cyan(message)}`;
|
||||
process.stderr.write(`\r\x1b[2K${line}`);
|
||||
}
|
||||
|
||||
function stopSync() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
|
||||
const wasActive = active;
|
||||
active = false;
|
||||
activeLogger = null;
|
||||
|
||||
if (wasActive) {
|
||||
clearSpinnerLineSync();
|
||||
}
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 15));
|
||||
stopSync();
|
||||
}
|
||||
|
||||
function start(text, logger, timeout = true) {
|
||||
if (timeout == true) {
|
||||
setTimeout(() => {
|
||||
start(text, logger, false);
|
||||
}, 10);
|
||||
return;
|
||||
}
|
||||
stopSync();
|
||||
message = text;
|
||||
frameIndex = 0;
|
||||
activeLogger = logger;
|
||||
|
||||
if (!process.stderr.isTTY) {
|
||||
const prefix = formatLogPrefix(activeLogger);
|
||||
process.stderr.write(
|
||||
`${prefix}${frames[0]} ${chalk.bold.cyan(message)}\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
active = true;
|
||||
render();
|
||||
timer = setInterval(render, interval);
|
||||
}
|
||||
|
||||
return { start, stop, stopSync };
|
||||
}
|
||||
|
||||
export function registerSpinner(instance) {
|
||||
spinner = instance;
|
||||
patchConsoleLog();
|
||||
}
|
||||
|
||||
export function startWaiting(message, logger) {
|
||||
spinner?.start(message, logger);
|
||||
}
|
||||
|
||||
export async function stopWaiting() {
|
||||
await spinner?.stop();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user