From 1ea946abc2815ece878d8eb3e0a79d5f61455da9 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 4 Jun 2025 13:16:31 +0200 Subject: [PATCH] setting up local data and node status --- src/main.js | 3 ++ src/services/dataService.js | 30 +++++++++++- src/ui/dataMenu.js | 42 +++++++++++++++++ src/ui/mainMenu.js | 10 ++++ src/ui/nodeStatusMenu.js | 93 +++++++++++++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 src/ui/nodeStatusMenu.js diff --git a/src/main.js b/src/main.js index a4bca65..5d98f36 100644 --- a/src/main.js +++ b/src/main.js @@ -38,6 +38,7 @@ import { EthersService } from "./services/ethersService.js"; import { MarketplaceSetup } from "./ui/marketplaceSetup.js"; import { DataService } from "./services/dataService.js"; import { DataMenu } from "./ui/dataMenu.js"; +import { NodeStatusMenu } from "./ui/nodeStatusMenu.js"; async function showNavigationMenu() { console.log("\n"); @@ -151,6 +152,7 @@ export async function main() { ); const dataService = new DataService(configService); const dataMenu = new DataMenu(uiService, fsService, dataService); + const nodeStatusMenu = new NodeStatusMenu(uiService, dataService, new MenuLoop()); const mainMenu = new MainMenu( uiService, new MenuLoop(), @@ -160,6 +162,7 @@ export async function main() { processControl, codexApp, dataMenu, + nodeStatusMenu ); await mainMenu.show(); diff --git a/src/services/dataService.js b/src/services/dataService.js index d77ab40..1633349 100644 --- a/src/services/dataService.js +++ b/src/services/dataService.js @@ -42,11 +42,37 @@ export class DataService { return filename; }; - getCodexData = () => { + debugInfo = async () => { + const debug = this.getCodexDebug(); + const res = await debug.info(); + if (res.error) { + throw new Error(res.data); + } + return res.data; + }; + + localData = async () => { + const data = this.getCodexData(); + const res = await data.cids(); + if (res.error) { + throw new Error(res.data); + } + return res.data; + }; + + getCodex = () => { const config = this.configService.get(); const url = `http://localhost:${config.ports.apiPort}`; const codex = new Codex(url); - return codex.data; + return codex; + }; + + getCodexData = () => { + return this.getCodex().data; + }; + + getCodexDebug = () => { + return this.getCodex().debug; }; getFilename = (manifest) => { diff --git a/src/ui/dataMenu.js b/src/ui/dataMenu.js index 7ea0d2c..679f5db 100644 --- a/src/ui/dataMenu.js +++ b/src/ui/dataMenu.js @@ -1,3 +1,5 @@ +import chalk from "chalk"; + export class DataMenu { constructor(uiService, fsService, dataService) { this.ui = uiService; @@ -33,4 +35,44 @@ export class DataMenu { this.ui.showErrorMessage("Error during download: " + exception); } }; + + showLocalData = async () => { + try { + const localData = await this.dataService.localData(); + this.displayLocalData(localData); + } catch (exception) { + this.ui.showErrorMessage("Failed to fetch local data: " + exception); + } + }; + + displayLocalData = (filesData) => { + if (filesData.content && filesData.content.length > 0) { + this.ui.showInfoMessage( + `Found ${filesData.content.length} local file(s)`, + ); + + filesData.content.forEach((file, index) => { + const { cid, manifest } = file; + const { + datasetSize, + protected: isProtected, + filename, + mimetype, + } = manifest; + + const fileSize = (datasetSize / 1024).toFixed(2); + + this.ui.showInfoMessage( + `${chalk.cyan("File")} ${index + 1} of ${filesData.content.length}\n\n` + + `${chalk.cyan("Filename:")} ${filename}\n` + + `${chalk.cyan("CID:")} ${cid}\n` + + `${chalk.cyan("Size:")} ${fileSize} KB\n` + + `${chalk.cyan("MIME Type:")} ${mimetype}\n` + + `${chalk.cyan("Protected:")} ${isProtected ? chalk.green("Yes") : chalk.red("No")}`, + ); + }); + } else { + this.ui.showInfoMessage("Node contains no datasets."); + } + }; } diff --git a/src/ui/mainMenu.js b/src/ui/mainMenu.js index 1c02562..5d77078 100644 --- a/src/ui/mainMenu.js +++ b/src/ui/mainMenu.js @@ -8,6 +8,7 @@ export class MainMenu { processControl, codexApp, dataMenu, + nodeStatusMenu, ) { this.ui = uiService; this.loop = menuLoop; @@ -17,6 +18,7 @@ export class MainMenu { this.processControl = processControl; this.codexApp = codexApp; this.dataMenu = dataMenu; + this.nodeStatusMenu = nodeStatusMenu; this.loop.initialize(this.promptMainMenu); } @@ -62,6 +64,10 @@ export class MainMenu { label: "Stop Codex", action: this.stopCodex, }, + { + label: "Show node status", + action: this.nodeStatusMenu.showNodeStatus, + }, { label: "Upload a file", action: this.dataMenu.performUpload, @@ -70,6 +76,10 @@ export class MainMenu { label: "Download a file", action: this.dataMenu.performDownload, }, + { + label: "Show local data", + action: this.dataMenu.showLocalData, + }, { label: "Exit (Codex keeps running)", action: this.loop.stopLoop, diff --git a/src/ui/nodeStatusMenu.js b/src/ui/nodeStatusMenu.js new file mode 100644 index 0000000..ea97f11 --- /dev/null +++ b/src/ui/nodeStatusMenu.js @@ -0,0 +1,93 @@ +import chalk from "chalk"; + +export class NodeStatusMenu { + constructor(uiService, dataService, menuLoop) { + this.ui = uiService; + this.dataService = dataService; + this.loop = menuLoop; + + this.loop.initialize(this.showNodeStatusMenu); + } + + showNodeStatus = async () => { + this.debugInfo = await this.fetchDebugInfo(); + if (this.debugInfo == undefined) return; + + const peerCount = this.debugInfo.table.nodes.length; + const isOnline = peerCount > 2; + + if (isOnline) { + this.ui.showSuccessMessage( + "Node is ONLINE & DISCOVERABLE", + "🔌 Node Status", + ); + } else { + this.ui.showInfoMessage( + "Node is ONLINE but has few peers", + "🔌 Node Status", + ); + } + + await this.loop.showLoop(); + }; + + showNodeStatusMenu = async () => { + await this.ui.askMultipleChoice("Select information to view:", [ + { + label: "View connected peers", + action: this.showPeers, + }, + { + label: "View node information", + action: this.showNodeInfo, + }, + { + label: "Back to Main Menu", + action: this.loop.stopLoop, + }, + ]); + }; + + showPeers = async () => { + const peerCount = this.debugInfo.table.nodes.length; + if (peerCount > 0) { + this.ui.showInfoMessage("Connected Peers"); + this.debugInfo.table.nodes.forEach((node, index) => { + this.ui.showInfoMessage( + `Peer ${index + 1}:\n` + + `${chalk.cyan("Peer ID:")} ${node.peerId}\n` + + `${chalk.cyan("Address:")} ${node.address}\n` + + `${chalk.cyan("Status:")} ${node.seen ? chalk.green("Active") : chalk.gray("Inactive")}`, + ); + }); + } else { + this.ui.showInfoMessage("No connected peers found."); + } + }; + + showNodeInfo = async () => { + const data = this.debugInfo; + this.ui.showInfoMessage( + `${chalk.cyan("Version:")} ${data.codex.version}\n` + + `${chalk.cyan("Revision:")} ${data.codex.revision}\n\n` + + `${chalk.cyan("Node ID:")} ${data.table.localNode.nodeId}\n` + + `${chalk.cyan("Peer ID:")} ${data.table.localNode.peerId}\n` + + `${chalk.cyan("Listening Address:")} ${data.table.localNode.address}\n\n` + + `${chalk.cyan("Public IP:")} ${data.announceAddresses[0].split("/")[2]}\n` + + `${chalk.cyan("Port:")} ${data.announceAddresses[0].split("/")[4]}\n` + + `${chalk.cyan("Connected Peers:")} ${data.table.nodes.length}`, + ); + }; + + fetchDebugInfo = async () => { + const spinner = this.ui.createAndStartSpinner("Fetching..."); + try { + return await this.dataService.debugInfo(); + } catch { + this.ui.showErrorMessage("Failed to fetch debug/info"); + return; + } finally { + this.ui.stopSpinnerSuccess(spinner); + } + }; +}