sets up config menu and config service

This commit is contained in:
ThatBen 2025-03-31 14:26:57 +02:00
parent 8d0418be81
commit a1d6056974
No known key found for this signature in database
GPG Key ID: E020A7DDCD52E1AB
9 changed files with 253 additions and 226 deletions

View File

@ -1,162 +0,0 @@
import inquirer from "inquirer";
import chalk from "chalk";
import { showErrorMessage, showInfoMessage } from "./utils/messages.js";
import { isDir, showPathSelector } from "./utils/pathSelector.js";
import { saveConfig } from "./services/config.js";
import { showNumberSelector } from "./utils/numberSelector.js";
import fs from "fs-extra";
function bytesAmountToString(numBytes) {
const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
var value = numBytes;
var index = 0;
while (value > 1024) {
index = index + 1;
value = value / 1024;
}
if (index == 0) return `${numBytes} Bytes`;
return `${numBytes} Bytes (${value} ${units[index]})`;
}
async function showStorageQuotaSelector(config) {
console.log(showInfoMessage('You can use: "GB" or "gb", etc.'));
const result = await showNumberSelector(
config.storageQuota,
"Storage quota",
true,
);
if (result < 100 * 1024 * 1024) {
console.log(showErrorMessage("Storage quote should be >= 100mb."));
return config.storageQuota;
}
return result;
}
export async function showConfigMenu(config) {
var newDataDir = config.dataDir;
try {
while (true) {
console.log(showInfoMessage("Codex Configuration"));
const { choice } = await inquirer
.prompt([
{
type: "list",
name: "choice",
message: "Select to edit:",
choices: [
`1. Data path = "${newDataDir}"`,
`2. Logs path = "${config.logsDir}"`,
`3. Storage quota = ${bytesAmountToString(config.storageQuota)}`,
`4. Discovery port = ${config.ports.discPort}`,
`5. P2P listen port = ${config.ports.listenPort}`,
`6. API port = ${config.ports.apiPort}`,
"7. Save changes and exit",
"8. Discard changes and exit",
],
pageSize: 8,
loop: true,
},
])
.catch(() => {
return;
});
switch (choice.split(".")[0]) {
case "1":
newDataDir = await showPathSelector(config.dataDir, false);
if (isDir(newDataDir)) {
console.log(
showInfoMessage(
"Warning: The new data path already exists. Make sure you know what you're doing.",
),
);
}
break;
case "2":
config.logsDir = await showPathSelector(config.logsDir, true);
break;
case "3":
config.storageQuota = await showStorageQuotaSelector(config);
break;
case "4":
config.ports.discPort = await showNumberSelector(
config.ports.discPort,
"Discovery Port (UDP)",
false,
);
break;
case "5":
config.ports.listenPort = await showNumberSelector(
config.ports.listenPort,
"Listen Port (TCP)",
false,
);
break;
case "6":
config.ports.apiPort = await showNumberSelector(
config.ports.apiPort,
"API Port (TCP)",
false,
);
break;
case "7":
// save changes, back to main menu
config = updateDataDir(config, newDataDir);
saveConfig(config);
return;
case "8":
// discard changes, back to main menu
return;
}
}
} catch (error) {
console.error(chalk.red("An error occurred:", error.message));
return;
}
}
function updateDataDir(config, newDataDir) {
if (config.dataDir == newDataDir) return config;
// The Codex dataDir is a little strange:
// If the old one is empty: The new one should not exist, so that codex creates it
// with the correct security permissions.
// If the old one does exist: We move it.
if (isDir(config.dataDir)) {
console.log(
showInfoMessage(
"Moving Codex data folder...\n" +
`From: "${config.dataDir}"\n` +
`To: "${newDataDir}"`,
),
);
try {
fs.moveSync(config.dataDir, newDataDir);
} catch (error) {
console.log(
showErrorMessage("Error while moving dataDir: " + error.message),
);
throw error;
}
} else {
// Old data dir does not exist.
if (isDir(newDataDir)) {
console.log(
showInfoMessage(
"Warning: the selected data path already exists.\n" +
`New data path = "${newDataDir}"\n` +
"Codex may overwrite data in this folder.\n" +
"Codex will fail to start if this folder does not have the required\n" +
"security permissions.",
),
);
}
}
config.dataDir = newDataDir;
return config;
}

View File

@ -12,7 +12,6 @@ import {
showSuccessMessage,
} from "../utils/messages.js";
import { checkDependencies } from "../services/nodeService.js";
import { saveConfig } from "../services/config.js";
import { getCodexRootPath, getCodexBinPath } from "../utils/appdata.js";
const platform = os.platform();

View File

@ -19,8 +19,7 @@ import {
} from "./handlers/installationHandlers.js";
import { runCodex, checkNodeStatus } from "./handlers/nodeHandlers.js";
import { showInfoMessage } from "./utils/messages.js";
import { loadConfig } from "./services/config.js";
import { showConfigMenu } from "./configmenu.js";
import { ConfigService } from "./services/configService.js";
import { openCodexApp } from "./services/codexapp.js";
import { MainMenu } from "./ui/mainmenu.js";
@ -90,9 +89,9 @@ export async function main() {
process.on("SIGTERM", handleExit);
process.on("SIGQUIT", handleExit);
const config = loadConfig();
const configService = new ConfigService();
const uiService = new UiService();
const installMenu = new InstallMenu(uiService, config);
const installMenu = new InstallMenu(uiService, configService);
const mainMenu = new MainMenu(uiService, installMenu);
await mainMenu.show();

View File

@ -1,54 +0,0 @@
import fs from "fs";
import path from "path";
import { getAppDataDir } from "../utils/appdata.js";
import {
getCodexBinPath,
getCodexDataDirDefaultPath,
getCodexLogsDefaultPath,
} from "../utils/appdata.js";
const defaultConfig = {
codexExe: "",
// User-selected config options:
codexPath: getCodexBinPath(),
dataDir: getCodexDataDirDefaultPath(),
logsDir: getCodexLogsDefaultPath(),
storageQuota: 8 * 1024 * 1024 * 1024,
ports: {
discPort: 8090,
listenPort: 8070,
apiPort: 8080,
},
};
function getConfigFilename() {
return path.join(getAppDataDir(), "config.json");
}
export function saveConfig(config) {
const filePath = getConfigFilename();
try {
fs.writeFileSync(filePath, JSON.stringify(config));
} catch (error) {
console.error(
`Failed to save config file to '${filePath}' error: '${error}'.`,
);
throw error;
}
}
export function loadConfig() {
const filePath = getConfigFilename();
try {
if (!fs.existsSync(filePath)) {
saveConfig(defaultConfig);
return defaultConfig;
}
return JSON.parse(fs.readFileSync(filePath));
} catch (error) {
console.error(
`Failed to load config file from '${filePath}' error: '${error}'.`,
);
throw error;
}
}

View File

@ -0,0 +1,64 @@
import fs from "fs";
import path from "path";
import { getAppDataDir } from "../utils/appdata.js";
import {
getCodexBinPath,
getCodexDataDirDefaultPath,
getCodexLogsDefaultPath,
} from "../utils/appdata.js";
const defaultConfig = {
codexExe: "",
// User-selected config options:
codexPath: getCodexBinPath(),
dataDir: getCodexDataDirDefaultPath(),
logsDir: getCodexLogsDefaultPath(),
storageQuota: 8 * 1024 * 1024 * 1024,
ports: {
discPort: 8090,
listenPort: 8070,
apiPort: 8080,
},
};
export class ConfigService {
constructor() {
this.loadConfig();
}
get = () => {
return this.config;
}
loadConfig = () => {
const filePath = this.getConfigFilename();
try {
if (!fs.existsSync(filePath)) {
this.config = defaultConfig;
this.saveConfig();
}
this.config = JSON.parse(fs.readFileSync(filePath));
} catch (error) {
console.error(
`Failed to load config file from '${filePath}' error: '${error}'.`,
);
throw error;
}
}
saveConfig = () => {
const filePath = this.getConfigFilename();
try {
fs.writeFileSync(filePath, JSON.stringify(this.config));
} catch (error) {
console.error(
`Failed to save config file to '${filePath}' error: '${error}'.`,
);
throw error;
}
}
getConfigFilename = () => {
return path.join(getAppDataDir(), "config.json");
}
}

167
src/ui/configmenu.js Normal file
View File

@ -0,0 +1,167 @@
export class ConfigMenu {
constructor(uiService, configService, pathSelector, numberSelector) {
this.ui = uiService;
this.configService = configService;
this.pathSelector = pathSelector;
this.numberSelector = numberSelector;
}
show = async() => {
this.running = true;
this.config = this.configService.get();
while (this.running) {
this.ui.showInfoMessage("Codex Configuration");
await this.ui.askMultipleChoice("Select to edit:",[
{
label: `Data path = "${this.config.dataDir}"`,
action: this.editDataDir,
},
{
label: `Logs path = "${this.config.logsDir}"`,
action: this.editLogsDir,
},
{
label: `Storage quota = ${bytesAmountToString(this.config.storageQuota)}`,
action: this.editStorageQuota,
},
{
label: `Discovery port = ${this.config.ports.discPort}`,
action: this.editDiscPort,
},
{
label: `P2P listen port = ${this.config.ports.listenPort}`,
action: this.editListenPort,
},
{
label: `API port = ${this.config.ports.apiPort}`,
action: this.editApiPort,
},
{
label: "Save changes and exit",
action: this.saveChangesAndExit,
},
{
label: "Discard changes and exit",
action: this.discardChangesAndExit,
}
]
)
}
}
bytesAmountToString = (numBytes) => {
const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
var value = numBytes;
var index = 0;
while (value > 1024) {
index = index + 1;
value = value / 1024;
}
if (index == 0) return `${numBytes} Bytes`;
return `${numBytes} Bytes (${value} ${units[index]})`;
}
editDataDir = async () => {
// todo
// function updateDataDir(config, newDataDir) {
// if (config.dataDir == newDataDir) return config;
// // The Codex dataDir is a little strange:
// // If the old one is empty: The new one should not exist, so that codex creates it
// // with the correct security permissions.
// // If the old one does exist: We move it.
// if (isDir(config.dataDir)) {
// console.log(
// showInfoMessage(
// "Moving Codex data folder...\n" +
// `From: "${config.dataDir}"\n` +
// `To: "${newDataDir}"`,
// ),
// );
// try {
// fs.moveSync(config.dataDir, newDataDir);
// } catch (error) {
// console.log(
// showErrorMessage("Error while moving dataDir: " + error.message),
// );
// throw error;
// }
// } else {
// // Old data dir does not exist.
// if (isDir(newDataDir)) {
// console.log(
// showInfoMessage(
// "Warning: the selected data path already exists.\n" +
// `New data path = "${newDataDir}"\n` +
// "Codex may overwrite data in this folder.\n" +
// "Codex will fail to start if this folder does not have the required\n" +
// "security permissions.",
// ),
// );
// }
// }
// config.dataDir = newDataDir;
// return config;
// }
}
editLogsDir = async () => {
this.config.logsDir = await this.pathSelector.show(this.config.logsDir, true);
}
editStorageQuota = async () => {
this.ui.showInfoMessage("You can use: 'GB' or 'gb', etc.");
const newQuota = await this.numberSelector.show(this.config.storageQuota, "Storage quota", true);
if (newQuota < 100 * 1024 * 1024) {
this.ui.showErrorMessage("Storage quote should be >= 100mb.");
} else {
this.config.storageQuota = newQuota;
}
}
editDiscPort = async () => {
const newPort = await this.numberSelector.show(this.config.ports.discPort, "Discovery port", false);
if (this.isInPortRange(newPort)) {
this.config.ports.discPort = newPort;
}
}
editListenPort = async () => {
const newPort = await this.numberSelector.show(this.config.ports.listenPort, "P2P listen port", false);
if (this.isInPortRange(newPort)) {
this.config.ports.listenPort = newPort;
}
}
editApiPort = async () => {
const newPort = await this.numberSelector.show(this.config.ports.apiPort, "API port", false);
if (this.isInPortRange(newPort)) {
this.config.ports.apiPort = newPort;
}
}
isInPortRange = (number) => {
if (number < 1024 || number > 65535) {
this.ui.showErrorMessage("Port should be between 1024 and 65535.");
return false;
}
return true;
}
saveChangesAndExit = async () => {
this.configService.saveConfig();
this.ui.showInfoMessage("Configuration changes saved.");
this.running = false;
}
discardChangesAndExit = async () => {
this.configService.loadConfig();
this.ui.showInfoMessage("Changes discarded.");
this.running = false;
}
}

View File

@ -1,7 +1,7 @@
export class InstallMenu {
constructor(uiService, config) {
constructor(uiService, configService) {
this.ui = uiService;
this.config = config;
this.config = configService.get();
}
show = async () => {

View File

@ -3,7 +3,7 @@ export class NumberSelector {
this.uiService = uiService;
}
showNumberSelector = async (
show = async (
currentValue,
promptMessage,
allowMetricPostfixes,

View File

@ -6,14 +6,16 @@ export class PathSelector {
this.pathMustExist = true;
}
showPathSelector = async (startingPath, pathMustExist) => {
show = async (startingPath, pathMustExist) => {
this.running = true;
this.startingPath = startingPath;
this.pathMustExist = pathMustExist;
this.roots = this.fs.getAvailableRoots();
this.currentPath = this.splitPath(startingPath);
if (!this.hasValidRoot(this.currentPath)) {
this.currentPath = [roots[0]];
}
while (true) {
while (this.running) {
this.showCurrent();
this.ui.askMultiChoice("Select an option:", [
{
@ -42,6 +44,8 @@ export class PathSelector {
},
]);
}
return this.resultingPath;
};
splitPath = (str) => {
@ -166,4 +170,14 @@ export class PathSelector {
if (name.length < 1) return;
this.updateCurrentIfValidParts([...currentPath, name]);
};
selectThisPath = async () => {
this.resultingPath = this.combine(this.currentPath);
this.running = false;
}
cancel = async () => {
this.resultingPath = this.startingPath;
this.running = false;
};
}