mirror of
https://github.com/stake-house/wagyu-installer.git
synced 2026-01-10 13:47:57 -05:00
Compare commits
9 Commits
v0.6.0
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b4a125846 | ||
|
|
eb76268fd5 | ||
|
|
cea055a545 | ||
|
|
1386443be2 | ||
|
|
828b105f5f | ||
|
|
d2dd72e0e3 | ||
|
|
93cfaeee5f | ||
|
|
e38f4c87b7 | ||
|
|
43e162f998 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,4 +8,5 @@ dist/
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
build/
|
||||
build/
|
||||
validator_keys
|
||||
@@ -5,5 +5,6 @@
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"useTabs": false
|
||||
"useTabs": false,
|
||||
"printWidth": 120
|
||||
}
|
||||
|
||||
14
jest.config.ts
Normal file
14
jest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Config } from 'jest';
|
||||
|
||||
const config: Config = {
|
||||
verbose: true,
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.ts?$': 'ts-jest',
|
||||
},
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/'],
|
||||
|
||||
};
|
||||
|
||||
export default config;
|
||||
16
package.json
16
package.json
@@ -7,24 +7,32 @@
|
||||
"author": "Colfax Selby <colfax.selby@gmail.com>",
|
||||
"license": "GPL",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.3",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"css-loader": "^6.7.0",
|
||||
"electron": "^17.1.0",
|
||||
"electron-builder": "^22.14.5",
|
||||
"electron-mock-ipc": "^0.3.12",
|
||||
"jest": "^29.3.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.3.5",
|
||||
"webpack": "^5.64.3",
|
||||
"webpack-cli": "^4.9.1"
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.react.config.js --config webpack.electron.config.js",
|
||||
"build:watch": "webpack --config webpack.react.config.js --config webpack.electron.config.js --watch",
|
||||
"dev": "",
|
||||
"start": "electron ./build/electron/index.js",
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder"
|
||||
"dist": "electron-builder",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.8.1",
|
||||
@@ -41,7 +49,9 @@
|
||||
"react-router-dom": "^6.2.2",
|
||||
"shebang-loader": "^0.0.1",
|
||||
"sudo-prompt": "^9.2.1",
|
||||
"tmp-promise": "^3.0.3"
|
||||
"tmp-promise": "^3.0.3",
|
||||
"web3": "^1.8.1",
|
||||
"web3-eth": "^1.8.1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "gg.wagyu.installer",
|
||||
|
||||
11
rome.json
Normal file
11
rome.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentSize": 2,
|
||||
"lineWidth": 90
|
||||
},
|
||||
"linter": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import sudo from 'sudo-prompt';
|
||||
import sudo from "sudo-prompt";
|
||||
|
||||
import { commandJoin } from "command-join";
|
||||
import { generate as generate_password } from "generate-password";
|
||||
|
||||
import { exec, execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { withDir } from 'tmp-promise';
|
||||
import { exec, execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { withDir } from "tmp-promise";
|
||||
|
||||
import { open, rm, mkdir } from 'fs/promises';
|
||||
import { open, rm, mkdir } from "fs/promises";
|
||||
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
import {
|
||||
ExecutionClient,
|
||||
@@ -19,19 +19,28 @@ import {
|
||||
NodeStatus,
|
||||
ValidatorStatus,
|
||||
InstallDetails,
|
||||
OutputLogs
|
||||
OutputLogs,
|
||||
} from "./IMultiClientInstaller";
|
||||
import { Network, networkToExecution } from '../react/types';
|
||||
import { doesFileExist, doesDirectoryExist } from './BashUtils';
|
||||
import {
|
||||
Network,
|
||||
networkToExecution,
|
||||
BeaconGetSyncingStatus,
|
||||
BeaconResponse,
|
||||
BeaconGetPeers,
|
||||
} from "../react/types";
|
||||
import { doesFileExist, doesDirectoryExist } from "./BashUtils";
|
||||
import { nullAddress } from "../react/constants";
|
||||
import Web3 from "web3";
|
||||
import { Syncing } from "web3-eth";
|
||||
|
||||
const execFileProm = promisify(execFile);
|
||||
const execProm = promisify(exec);
|
||||
|
||||
const dockerServiceName = 'docker.service';
|
||||
const dockerGroupName = 'docker';
|
||||
const installPath = path.join(os.homedir(), '.wagyu-installer');
|
||||
const ethDockerGitRepository = 'https://github.com/eth-educators/eth-docker.git';
|
||||
const prysmWalletPasswordFileName = 'prysm-wallet-password';
|
||||
const dockerServiceName = "docker.service";
|
||||
const dockerGroupName = "docker";
|
||||
const installPath = path.join(os.homedir(), ".wagyu-installer");
|
||||
const ethDockerGitRepository = "https://github.com/eth-educators/eth-docker.git";
|
||||
const prysmWalletPasswordFileName = "prysm-wallet-password";
|
||||
|
||||
type SystemdServiceDetails = {
|
||||
description: string | undefined;
|
||||
@@ -39,68 +48,120 @@ type SystemdServiceDetails = {
|
||||
activeState: string | undefined;
|
||||
subState: string | undefined;
|
||||
unitFileState: string | undefined;
|
||||
}
|
||||
};
|
||||
const CLPROVIDER = "http://127.0.0.1:5052";
|
||||
const ELPROVIDER = "ws://127.0.0.1:8546";
|
||||
|
||||
const writeOutput = (message: string, outputLogs?: OutputLogs): void => {
|
||||
const web3 = new Web3(ELPROVIDER);
|
||||
|
||||
const writeOutput = (
|
||||
message: string,
|
||||
progress?: number,
|
||||
outputLogs?: OutputLogs,
|
||||
): void => {
|
||||
if (outputLogs) {
|
||||
outputLogs(message);
|
||||
if (progress) {
|
||||
outputLogs("INFO: " + message, progress);
|
||||
} else {
|
||||
outputLogs("INFO: " + message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
|
||||
title = 'Electron';
|
||||
title = "Electron";
|
||||
|
||||
async preInstall(outputLogs?: OutputLogs): Promise<boolean> {
|
||||
|
||||
let packagesToInstall = new Array<string>();
|
||||
|
||||
let progress = 0;
|
||||
// We need git installed
|
||||
const gitPackageName = 'git';
|
||||
const gitPackageName = "git";
|
||||
|
||||
writeOutput(`Checking if ${gitPackageName} is installed.`, outputLogs);
|
||||
const gitInstalled = await this.checkForInstalledUbuntuPackage(gitPackageName);
|
||||
writeOutput(`Checking if ${gitPackageName} is installed.`, ++progress, outputLogs);
|
||||
const gitInstalled = await this.checkForLinuxBinary(gitPackageName);
|
||||
if (!gitInstalled) {
|
||||
writeOutput(`${gitPackageName} is not installed. We will install it.`, outputLogs);
|
||||
writeOutput(
|
||||
`${gitPackageName} is not installed. We will install it.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
packagesToInstall.push(gitPackageName);
|
||||
} else {
|
||||
writeOutput(`${gitPackageName} is already installed. We will not install it.`, outputLogs);
|
||||
writeOutput(
|
||||
`${gitPackageName} is already installed. We will not install it.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
}
|
||||
|
||||
// We need docker installed, enabled and running
|
||||
const dockerPackageName = 'docker-compose';
|
||||
let needToEnableDockerService = true;
|
||||
const dockerPackageName = "docker-compose";
|
||||
// let needToEnableDockerService = true;
|
||||
let needToEnableDockerService = false;
|
||||
let needToStartDockerService = false;
|
||||
let needToCheckDockerService = false;
|
||||
|
||||
writeOutput(`Checking if ${dockerPackageName} is installed.`, outputLogs);
|
||||
const dockerInstalled = await this.checkForInstalledUbuntuPackage(dockerPackageName);
|
||||
writeOutput(`Checking if ${dockerPackageName} is installed.`, ++progress, outputLogs);
|
||||
const dockerInstalled = await this.checkForLinuxBinary(dockerPackageName);
|
||||
if (!dockerInstalled) {
|
||||
writeOutput(`${dockerPackageName} is not installed. We will install it.`, outputLogs);
|
||||
writeOutput(
|
||||
`${dockerPackageName} is not installed. We will install it.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
packagesToInstall.push(dockerPackageName);
|
||||
} else {
|
||||
writeOutput(`${dockerPackageName} is already installed. We will not install it.`, outputLogs);
|
||||
writeOutput(
|
||||
`${dockerPackageName} is already installed. We will not install it.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
|
||||
writeOutput(`Checking if we need to enable or start the ${dockerServiceName} service.`, outputLogs);
|
||||
const dockerServiceDetails = await this.getSystemdServiceDetails(dockerServiceName);
|
||||
needToEnableDockerService = dockerServiceDetails.unitFileState != 'enabled';
|
||||
if (needToEnableDockerService) {
|
||||
writeOutput(`The ${dockerServiceName} service is not enabled. We will enable it.`, outputLogs);
|
||||
}
|
||||
needToStartDockerService = dockerServiceDetails.subState != 'running';
|
||||
if (needToStartDockerService) {
|
||||
writeOutput(`The ${dockerServiceName} service is not started. We will start it.`, outputLogs);
|
||||
if (needToCheckDockerService) {
|
||||
writeOutput(
|
||||
`Checking if we need to enable or start the ${dockerServiceName} service.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
const dockerServiceDetails = await this.getSystemdServiceDetails(
|
||||
dockerServiceName,
|
||||
);
|
||||
needToEnableDockerService = dockerServiceDetails.unitFileState != "enabled";
|
||||
if (needToEnableDockerService) {
|
||||
writeOutput(
|
||||
`The ${dockerServiceName} service is not enabled. We will enable it.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
}
|
||||
needToStartDockerService = dockerServiceDetails.subState != "running";
|
||||
if (needToStartDockerService) {
|
||||
writeOutput(
|
||||
`The ${dockerServiceName} service is not started. We will start it.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need our user to be in docker group
|
||||
writeOutput(`Checking if current user is in ${dockerGroupName} group.`, outputLogs);
|
||||
const needUserInDockerGroup = !await this.isUserInGroup(dockerGroupName);
|
||||
writeOutput(
|
||||
`Checking if current user is in ${dockerGroupName} group.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
const needUserInDockerGroup = !(await this.isUserInGroup(dockerGroupName));
|
||||
if (needUserInDockerGroup) {
|
||||
writeOutput(`Current user is not in ${dockerGroupName} group. We will add it.`, outputLogs);
|
||||
writeOutput(
|
||||
`Current user is not in ${dockerGroupName} group. We will add it.`,
|
||||
++progress,
|
||||
outputLogs,
|
||||
);
|
||||
}
|
||||
|
||||
// We need our installPath directory
|
||||
writeOutput(`Creating install directory in ${installPath}.`, outputLogs);
|
||||
writeOutput(`Creating install directory in ${installPath}.`, ++progress, outputLogs);
|
||||
await mkdir(installPath, { recursive: true });
|
||||
|
||||
return await this.preInstallAdminScript(
|
||||
@@ -108,7 +169,8 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
needUserInDockerGroup,
|
||||
needToEnableDockerService,
|
||||
needToStartDockerService,
|
||||
outputLogs);
|
||||
outputLogs,
|
||||
);
|
||||
}
|
||||
|
||||
async getSystemdServiceDetails(serviceName: string): Promise<SystemdServiceDetails> {
|
||||
@@ -117,15 +179,24 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
loadState: undefined,
|
||||
activeState: undefined,
|
||||
subState: undefined,
|
||||
unitFileState: undefined
|
||||
unitFileState: undefined,
|
||||
};
|
||||
|
||||
const properties = ['Description', 'LoadState', 'ActiveState', 'SubState', 'UnitFileState'];
|
||||
const properties = [
|
||||
"Description",
|
||||
"LoadState",
|
||||
"ActiveState",
|
||||
"SubState",
|
||||
"UnitFileState",
|
||||
];
|
||||
|
||||
const { stdout, stderr } = await execFileProm('systemctl',
|
||||
['show', serviceName, '--property=' + properties.join(',')]);
|
||||
const { stdout, stderr } = await execFileProm("systemctl", [
|
||||
"show",
|
||||
serviceName,
|
||||
"--property=" + properties.join(","),
|
||||
]);
|
||||
|
||||
const lines = stdout.split('\n');
|
||||
const lines = stdout.split("\n");
|
||||
const lineRegex = /(?<key>[^=]+)=(?<value>.*)/;
|
||||
for (const line of lines) {
|
||||
const found = line.match(lineRegex);
|
||||
@@ -161,8 +232,8 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
needUserInDockerGroup: boolean,
|
||||
needToEnableDockerService: boolean,
|
||||
needToStartDockerService: boolean,
|
||||
outputLogs?: OutputLogs): Promise<boolean> {
|
||||
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
packagesToInstall.length > 0 ||
|
||||
needUserInDockerGroup ||
|
||||
@@ -174,18 +245,17 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
|
||||
let commandResult = false;
|
||||
|
||||
await withDir(async dirResult => {
|
||||
await withDir(async (dirResult) => {
|
||||
const scriptPath = path.join(dirResult.path, "commands.sh");
|
||||
let scriptContent = "";
|
||||
|
||||
const scriptPath = path.join(dirResult.path, 'commands.sh');
|
||||
let scriptContent = '';
|
||||
|
||||
const scriptFile = await open(scriptPath, 'w');
|
||||
await scriptFile.write('#!/bin/bash\n');
|
||||
const scriptFile = await open(scriptPath, "w");
|
||||
await scriptFile.write("#!/bin/bash\n");
|
||||
|
||||
// Install APT packages
|
||||
if (packagesToInstall.length > 0) {
|
||||
const aptUpdate = 'apt -y update\n';
|
||||
const aptInstall = 'apt -y install ' + commandJoin(packagesToInstall) + '\n';
|
||||
const aptUpdate = "apt -y update\n";
|
||||
const aptInstall = "apt -y install " + commandJoin(packagesToInstall) + "\n";
|
||||
|
||||
scriptContent += aptUpdate + aptInstall;
|
||||
|
||||
@@ -195,7 +265,8 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
|
||||
// Enable docker service
|
||||
if (needToEnableDockerService) {
|
||||
const systemctlEnable = 'systemctl enable --now ' + commandJoin([dockerServiceName]) + '\n';
|
||||
const systemctlEnable =
|
||||
"systemctl enable --now " + commandJoin([dockerServiceName]) + "\n";
|
||||
|
||||
scriptContent += systemctlEnable;
|
||||
|
||||
@@ -204,7 +275,8 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
|
||||
// Start docker service
|
||||
if (needToStartDockerService) {
|
||||
const systemctlStart = 'systemctl start ' + commandJoin([dockerServiceName]) + '\n';
|
||||
const systemctlStart =
|
||||
"systemctl start " + commandJoin([dockerServiceName]) + "\n";
|
||||
|
||||
scriptContent += systemctlStart;
|
||||
|
||||
@@ -224,45 +296,58 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
await scriptFile.chmod(0o500);
|
||||
await scriptFile.close();
|
||||
|
||||
writeOutput(`Running script ${scriptPath} with the following content as root:\n${scriptContent}`, outputLogs);
|
||||
writeOutput(
|
||||
`Running script ${scriptPath} with the following content as root:\n${scriptContent}`,
|
||||
50,
|
||||
outputLogs,
|
||||
);
|
||||
|
||||
const promise = new Promise<boolean>(async (resolve, reject) => {
|
||||
const options = {
|
||||
name: this.title
|
||||
name: this.title,
|
||||
};
|
||||
try {
|
||||
sudo.exec(scriptPath, options,
|
||||
function (error, stdout, stderr) {
|
||||
if (error) reject(error);
|
||||
if (stdout) {
|
||||
writeOutput(stdout.toString(), outputLogs);
|
||||
}
|
||||
resolve(true);
|
||||
sudo.exec(scriptPath, options, function (error, stdout, stderr) {
|
||||
console.log("std err", stderr);
|
||||
if (error) reject(error);
|
||||
if (stdout) {
|
||||
writeOutput(stdout.toString(), 80, outputLogs);
|
||||
}
|
||||
);
|
||||
resolve(true);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error executing script", error);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
await promise.then(result => {
|
||||
commandResult = result;
|
||||
}).catch(reason => {
|
||||
commandResult = false;
|
||||
}).finally(async () => {
|
||||
await rm(scriptPath);
|
||||
});
|
||||
|
||||
await promise
|
||||
.then((result) => {
|
||||
commandResult = result;
|
||||
})
|
||||
.catch((reason) => {
|
||||
commandResult = false;
|
||||
console.log("command errored", reason);
|
||||
})
|
||||
.finally(async () => {
|
||||
await rm(scriptPath);
|
||||
});
|
||||
});
|
||||
|
||||
writeOutput(
|
||||
`Finished pre-install ${commandResult ? "successfully" : "unsuccessfully"}`,
|
||||
100,
|
||||
outputLogs,
|
||||
);
|
||||
return commandResult;
|
||||
} else {
|
||||
writeOutput(`All dependencies are already installed`, 100, outputLogs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async getUsername(): Promise<string> {
|
||||
const { stdout, stderr } = await execFileProm('whoami');
|
||||
const { stdout, stderr } = await execFileProm("whoami");
|
||||
const userName = stdout.trim();
|
||||
|
||||
return userName;
|
||||
@@ -271,143 +356,187 @@ export class EthDockerInstaller implements IMultiClientInstaller {
|
||||
async isUserInGroup(groupName: string): Promise<boolean> {
|
||||
const userName = await this.getUsername();
|
||||
|
||||
const { stdout, stderr } = await execFileProm('groups', [userName]);
|
||||
const groups = stdout.trim().split(' ');
|
||||
return groups.findIndex(val => val === groupName) >= 0;
|
||||
const { stdout, stderr } = await execFileProm("groups", [userName]);
|
||||
const groups = stdout.trim().split(" ");
|
||||
return groups.findIndex((val) => val === groupName) >= 0;
|
||||
}
|
||||
|
||||
async checkForInstalledUbuntuPackage(packageName: string): Promise<boolean> {
|
||||
const { stdout, stderr } = await execFileProm('apt', ['-qq', 'list', packageName]);
|
||||
return stdout.indexOf('[installed]') > 0
|
||||
const { stdout, stderr } = await execFileProm("apt", [
|
||||
"-qq",
|
||||
"list",
|
||||
"--installed",
|
||||
packageName,
|
||||
]);
|
||||
return stdout.indexOf("installed") > 0;
|
||||
}
|
||||
|
||||
async checkForLinuxBinary(binary: string): Promise<boolean> {
|
||||
const { stdout, stderr } = await execFileProm("which", [binary]);
|
||||
return stdout.indexOf(binary) > 0;
|
||||
}
|
||||
|
||||
async install(details: InstallDetails, outputLogs?: OutputLogs): Promise<boolean> {
|
||||
// Install and update eth-docker
|
||||
if (!await this.installUpdateEthDockerCode(details.network)) {
|
||||
writeOutput(`installing ethdocker`, 5, outputLogs);
|
||||
|
||||
if (!(await this.installUpdateEthDockerCode(details.network, outputLogs))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeOutput(`installing environment variables`, 33, outputLogs);
|
||||
// Create .env file with all the configuration details
|
||||
if (!await this.createEthDockerEnvFile(details)) {
|
||||
if (!(await this.createEthDockerEnvFile(details, outputLogs))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeOutput(`installing clients`, 66, outputLogs);
|
||||
// Build the clients
|
||||
if (!await this.buildClients(details.network)) {
|
||||
if (!(await this.buildClients(details.network, outputLogs))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeOutput(`installing clients`, 100, outputLogs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async buildClients(network: Network): Promise<boolean> {
|
||||
async buildClients(network: Network, outputLogs?: OutputLogs): Promise<boolean> {
|
||||
const networkPath = path.join(installPath, network.toLowerCase());
|
||||
const ethDockerPath = path.join(networkPath, 'eth-docker');
|
||||
const ethDockerPath = path.join(networkPath, "eth-docker");
|
||||
|
||||
const ethdCommand = path.join(ethDockerPath, 'ethd');
|
||||
const ethdCommand = path.join(ethDockerPath, "ethd");
|
||||
const bashScript = `
|
||||
/usr/bin/newgrp ${dockerGroupName} <<EONG
|
||||
${ethdCommand} cmd build --pull
|
||||
EONG
|
||||
`;
|
||||
|
||||
const returnProm = execProm(bashScript, { shell: '/bin/bash', cwd: ethDockerPath });
|
||||
const returnProm = execProm(bashScript, { shell: "/bin/bash", cwd: ethDockerPath });
|
||||
const { stdout, stderr } = await returnProm;
|
||||
console.log("building clients", stdout, "err: ", stderr);
|
||||
|
||||
writeOutput(`client containers available`, 80, outputLogs);
|
||||
|
||||
if (returnProm.child.exitCode !== 0) {
|
||||
console.log('We failed to build eth-docker clients.');
|
||||
console.log("We failed to build eth-docker clients.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async createEthDockerEnvFile(details: InstallDetails): Promise<boolean> {
|
||||
async createEthDockerEnvFile(
|
||||
details: InstallDetails,
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> {
|
||||
const networkPath = path.join(installPath, details.network.toLowerCase());
|
||||
const ethDockerPath = path.join(networkPath, 'eth-docker');
|
||||
const ethDockerPath = path.join(networkPath, "eth-docker");
|
||||
|
||||
// Start with the default env file.
|
||||
const defaultEnvPath = path.join(ethDockerPath, 'default.env');
|
||||
const envPath = path.join(ethDockerPath, '.env');
|
||||
const defaultEnvPath = path.join(ethDockerPath, "default.env");
|
||||
const envPath = path.join(ethDockerPath, ".env");
|
||||
|
||||
// Open default env file and update the configs.
|
||||
const defaultEnvFile = await open(defaultEnvPath, 'r');
|
||||
const defaultEnvConfigs = await defaultEnvFile.readFile({ encoding: 'utf8' });
|
||||
const defaultEnvFile = await open(defaultEnvPath, "r");
|
||||
const defaultEnvConfigs = await defaultEnvFile.readFile({ encoding: "utf8" });
|
||||
await defaultEnvFile.close();
|
||||
writeOutput(`env file configured`, 50, outputLogs);
|
||||
|
||||
let envConfigs = defaultEnvConfigs;
|
||||
|
||||
// Writing consensus network
|
||||
const networkValue = details.network.toLowerCase();
|
||||
envConfigs = envConfigs.replace(/NETWORK=(.*)/, `NETWORK=${networkValue}`);
|
||||
// const networkValue = details.network.toLowerCase();
|
||||
const ecNetworkValue = networkToExecution
|
||||
.get(details.network)
|
||||
?.toLowerCase() as string;
|
||||
envConfigs = envConfigs.replace(/NETWORK=(.*)/, `NETWORK=${ecNetworkValue}`);
|
||||
|
||||
// Writing execution network
|
||||
const ecNetworkValue = networkToExecution.get(details.network)?.toLowerCase() as string;
|
||||
envConfigs = envConfigs.replace(/EC_NETWORK=(.*)/, `EC_NETWORK=${ecNetworkValue}`);
|
||||
// envConfigs = envConfigs.replace(/EC_NETWORK=(.*)/, `EC_NETWORK=${ecNetworkValue}`);
|
||||
|
||||
// Write fee recipient
|
||||
envConfigs = envConfigs.replace(/FEE_RECIPIENT=(.*)/, `FEE_RECIPIENT=${nullAddress}`);
|
||||
|
||||
envConfigs = envConfigs.replace(/HOST_IP=(.*)/, `127.0.0.1`);
|
||||
|
||||
let composeFileValues = new Array<string>();
|
||||
|
||||
switch (details.consensusClient) {
|
||||
case ConsensusClient.LIGHTHOUSE:
|
||||
composeFileValues.push('lh-base.yml');
|
||||
composeFileValues.push("lighthouse.yml");
|
||||
break;
|
||||
case ConsensusClient.NIMBUS:
|
||||
composeFileValues.push('nimbus-base.yml');
|
||||
composeFileValues.push("nimbus.yml");
|
||||
break;
|
||||
case ConsensusClient.PRYSM:
|
||||
composeFileValues.push('prysm-base.yml');
|
||||
composeFileValues.push("prysm.yml");
|
||||
break;
|
||||
case ConsensusClient.TEKU:
|
||||
composeFileValues.push('teku-base.yml');
|
||||
composeFileValues.push("teku.yml");
|
||||
break;
|
||||
case ConsensusClient.LODESTAR:
|
||||
composeFileValues.push('lodestar-base.yml');
|
||||
composeFileValues.push("lodestar.yml");
|
||||
break;
|
||||
}
|
||||
composeFileValues.push("cl-shared.yml");
|
||||
|
||||
switch (details.executionClient) {
|
||||
case ExecutionClient.GETH:
|
||||
composeFileValues.push('geth.yml');
|
||||
composeFileValues.push("geth.yml");
|
||||
break;
|
||||
case ExecutionClient.NETHERMIND:
|
||||
composeFileValues.push('nm.yml');
|
||||
composeFileValues.push("nevermind.yml");
|
||||
break;
|
||||
case ExecutionClient.BESU:
|
||||
composeFileValues.push('besu.yml');
|
||||
composeFileValues.push("besu.yml");
|
||||
break;
|
||||
case ExecutionClient.ERIGON:
|
||||
composeFileValues.push('erigon.yml');
|
||||
composeFileValues.push("erigon.yml");
|
||||
break;
|
||||
}
|
||||
composeFileValues.push("el-shared.yml");
|
||||
|
||||
const composeFileValue = composeFileValues.join(':');
|
||||
envConfigs = envConfigs.replace(/COMPOSE_FILE=(.*)/, `COMPOSE_FILE=${composeFileValue}`);
|
||||
const composeFileValue = composeFileValues.join(":");
|
||||
envConfigs = envConfigs.replace(
|
||||
/COMPOSE_FILE=(.*)/,
|
||||
`COMPOSE_FILE=${composeFileValue}`,
|
||||
);
|
||||
|
||||
// Write our new env file
|
||||
const envFile = await open(envPath, 'w');
|
||||
envFile.writeFile(envConfigs, { encoding: 'utf8' });
|
||||
const envFile = await open(envPath, "w");
|
||||
envFile.writeFile(envConfigs, { encoding: "utf8" });
|
||||
await envFile.close();
|
||||
|
||||
writeOutput(`env file configured`, 66, outputLogs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async installUpdateEthDockerCode(network: Network): Promise<boolean> {
|
||||
async installUpdateEthDockerCode(
|
||||
network: Network,
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> {
|
||||
const networkPath = path.join(installPath, network.toLowerCase());
|
||||
|
||||
// Make sure the networkPath is a directory
|
||||
const networkPathExists = await doesFileExist(networkPath);
|
||||
const networkPathIsDir = networkPathExists && await doesDirectoryExist(networkPath);
|
||||
const networkPathIsDir = networkPathExists && (await doesDirectoryExist(networkPath));
|
||||
|
||||
if (!networkPathExists) {
|
||||
await mkdir(networkPath, { recursive: true });
|
||||
writeOutput(`network path created ${network}`, 5, outputLogs);
|
||||
} else if (networkPathExists && !networkPathIsDir) {
|
||||
await rm(networkPath);
|
||||
await mkdir(networkPath, { recursive: true });
|
||||
writeOutput(`network path reconfigured ${network}`, 5, outputLogs);
|
||||
}
|
||||
|
||||
const ethDockerPath = path.join(networkPath, 'eth-docker');
|
||||
const ethDockerPath = path.join(networkPath, "eth-docker");
|
||||
|
||||
const ethDockerPathExists = await doesFileExist(ethDockerPath);
|
||||
const ethDockerPathIsDir = ethDockerPathExists && await doesDirectoryExist(ethDockerPath);
|
||||
const ethDockerPathIsDir =
|
||||
ethDockerPathExists && (await doesDirectoryExist(ethDockerPath));
|
||||
let needToClone = !ethDockerPathExists;
|
||||
|
||||
if (ethDockerPathExists && !ethDockerPathIsDir) {
|
||||
@@ -415,7 +544,9 @@ EONG
|
||||
needToClone = true;
|
||||
} else if (ethDockerPathIsDir) {
|
||||
// Check if eth-docker was already cloned.
|
||||
const returnProm = execFileProm('git', ['remote', 'show', 'origin'], { cwd: ethDockerPath });
|
||||
const returnProm = execFileProm("git", ["remote", "show", "origin"], {
|
||||
cwd: ethDockerPath,
|
||||
});
|
||||
const { stdout, stderr } = await returnProm;
|
||||
|
||||
if (returnProm.child.exitCode === 0) {
|
||||
@@ -430,7 +561,7 @@ EONG
|
||||
needToClone = true;
|
||||
}
|
||||
} else {
|
||||
console.log('Cannot parse `git remote show origin` output.');
|
||||
console.log("Cannot parse `git remote show origin` output.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -442,34 +573,37 @@ EONG
|
||||
|
||||
if (needToClone) {
|
||||
// Clone repository if needed
|
||||
const returnProm = execFileProm('git', ['clone', ethDockerGitRepository], { cwd: networkPath });
|
||||
const returnProm = execFileProm("git", ["clone", ethDockerGitRepository], {
|
||||
cwd: networkPath,
|
||||
});
|
||||
const { stdout, stderr } = await returnProm;
|
||||
|
||||
if (returnProm.child.exitCode !== 0) {
|
||||
console.log('We failed to clone eth-docker repository.');
|
||||
console.log("We failed to clone eth-docker repository.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate Prysm wallet password and store it in plain text
|
||||
const walletPassword = generate_password({
|
||||
length: 32,
|
||||
numbers: true
|
||||
numbers: true,
|
||||
});
|
||||
const walletPasswordPath = path.join(ethDockerPath, prysmWalletPasswordFileName);
|
||||
const walletPasswordFile = await open(walletPasswordPath, 'w');
|
||||
const walletPasswordFile = await open(walletPasswordPath, "w");
|
||||
await walletPasswordFile.write(walletPassword);
|
||||
await walletPasswordFile.close();
|
||||
} else {
|
||||
// Update repository
|
||||
const returnProm = execFileProm('git', ['pull'], { cwd: ethDockerPath });
|
||||
const returnProm = execFileProm("git", ["pull"], { cwd: ethDockerPath });
|
||||
const { stdout, stderr } = await returnProm;
|
||||
|
||||
if (returnProm.child.exitCode !== 0) {
|
||||
console.log('We failed to update eth-docker repository.');
|
||||
console.log("We failed to update eth-docker repository.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
writeOutput(`ethdocker available`, 25, outputLogs);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -479,20 +613,20 @@ EONG
|
||||
|
||||
async stopNodes(network: Network, outputLogs?: OutputLogs): Promise<boolean> {
|
||||
const networkPath = path.join(installPath, network.toLowerCase());
|
||||
const ethDockerPath = path.join(networkPath, 'eth-docker');
|
||||
const ethDockerPath = path.join(networkPath, "eth-docker");
|
||||
|
||||
const ethdCommand = path.join(ethDockerPath, 'ethd');
|
||||
const ethdCommand = path.join(ethDockerPath, "ethd");
|
||||
const bashScript = `
|
||||
/usr/bin/newgrp ${dockerGroupName} <<EONG
|
||||
${ethdCommand} stop
|
||||
EONG
|
||||
`;
|
||||
|
||||
const returnProm = execProm(bashScript, { shell: '/bin/bash', cwd: ethDockerPath });
|
||||
const returnProm = execProm(bashScript, { shell: "/bin/bash", cwd: ethDockerPath });
|
||||
const { stdout, stderr } = await returnProm;
|
||||
|
||||
if (returnProm.child.exitCode !== 0) {
|
||||
console.log('We failed to stop eth-docker clients.');
|
||||
console.log("We failed to stop eth-docker clients.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -501,23 +635,25 @@ EONG
|
||||
|
||||
async startNodes(network: Network, outputLogs?: OutputLogs): Promise<boolean> {
|
||||
const networkPath = path.join(installPath, network.toLowerCase());
|
||||
const ethDockerPath = path.join(networkPath, 'eth-docker');
|
||||
const ethDockerPath = path.join(networkPath, "eth-docker");
|
||||
|
||||
const ethdCommand = path.join(ethDockerPath, 'ethd');
|
||||
const ethdCommand = path.join(ethDockerPath, "ethd");
|
||||
const bashScript = `
|
||||
/usr/bin/newgrp ${dockerGroupName} <<EONG
|
||||
${ethdCommand} start
|
||||
${ethdCommand} cmd up -d execution consensus
|
||||
EONG
|
||||
`;
|
||||
|
||||
const returnProm = execProm(bashScript, { shell: '/bin/bash', cwd: ethDockerPath });
|
||||
const returnProm = execProm(bashScript, { shell: "/bin/bash", cwd: ethDockerPath });
|
||||
const { stdout, stderr } = await returnProm;
|
||||
|
||||
if (returnProm.child.exitCode !== 0) {
|
||||
console.log('We failed to start eth-docker clients.');
|
||||
console.log("We failed to start eth-docker clients.");
|
||||
return false;
|
||||
}
|
||||
|
||||
writeOutput(`nodes started`, 100, outputLogs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -537,18 +673,20 @@ EONG
|
||||
network: Network,
|
||||
keyStoreDirectoryPath: string,
|
||||
keyStorePassword: string,
|
||||
outputLogs?: OutputLogs): Promise<boolean> {
|
||||
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> {
|
||||
const networkPath = path.join(installPath, network.toLowerCase());
|
||||
const ethDockerPath = path.join(networkPath, 'eth-docker');
|
||||
const ethDockerPath = path.join(networkPath, "eth-docker");
|
||||
|
||||
const ethdCommand = path.join(ethDockerPath, 'ethd');
|
||||
const ethdCommand = path.join(ethDockerPath, "ethd");
|
||||
const argKeyStoreDirectoryPath = commandJoin([keyStoreDirectoryPath]);
|
||||
const argKeyStorePassword = commandJoin([keyStorePassword]);
|
||||
|
||||
const walletPasswordPath = path.join(ethDockerPath, prysmWalletPasswordFileName);
|
||||
const walletPasswordFile = await open(walletPasswordPath, 'r');
|
||||
const walletPassword = commandJoin([await walletPasswordFile.readFile({ encoding: 'utf8' })]);
|
||||
const walletPasswordFile = await open(walletPasswordPath, "r");
|
||||
const walletPassword = commandJoin([
|
||||
await walletPasswordFile.readFile({ encoding: "utf8" }),
|
||||
]);
|
||||
await walletPasswordFile.close();
|
||||
|
||||
const bashScript = `
|
||||
@@ -557,11 +695,13 @@ WALLET_PASSWORD=${walletPassword} KEYSTORE_PASSWORD=${argKeyStorePassword} ${eth
|
||||
EONG
|
||||
`;
|
||||
|
||||
const returnProm = execProm(bashScript, { shell: '/bin/bash', cwd: ethDockerPath });
|
||||
const returnProm = execProm(bashScript, { shell: "/bin/bash", cwd: ethDockerPath });
|
||||
const { stdout, stderr } = await returnProm;
|
||||
console.log("out: ", stdout);
|
||||
console.error(stderr);
|
||||
|
||||
if (returnProm.child.exitCode !== 0) {
|
||||
console.log('We failed to import keys with eth-docker.');
|
||||
console.log("We failed to import keys with eth-docker.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -591,11 +731,27 @@ EONG
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Data
|
||||
async getCurrentExecutionClient(): Promise<ExecutionClient> {
|
||||
// TODO: implement
|
||||
console.log("Executing getCurrentExecutionClient");
|
||||
const info = await web3.eth.getNodeInfo();
|
||||
|
||||
let gethRex = new RegExp(`${ExecutionClient.GETH.toLowerCase()}`);
|
||||
if (gethRex.test(info.toLowerCase())) {
|
||||
return ExecutionClient.GETH;
|
||||
}
|
||||
let besuRex = new RegExp(`${ExecutionClient.BESU.toLowerCase()}`);
|
||||
if (besuRex.test(info.toLowerCase())) {
|
||||
return ExecutionClient.BESU;
|
||||
}
|
||||
let erigonRex = new RegExp(`${ExecutionClient.ERIGON.toLowerCase()}`);
|
||||
if (erigonRex.test(info.toLowerCase())) {
|
||||
return ExecutionClient.ERIGON;
|
||||
}
|
||||
let nmRex = new RegExp(`${ExecutionClient.NETHERMIND.toLowerCase()}`);
|
||||
if (nmRex.test(info.toLowerCase())) {
|
||||
return ExecutionClient.NETHERMIND;
|
||||
}
|
||||
|
||||
return ExecutionClient.GETH;
|
||||
}
|
||||
|
||||
@@ -606,15 +762,14 @@ EONG
|
||||
}
|
||||
|
||||
async getCurrentExecutionClientVersion(): Promise<string> {
|
||||
// TODO: implement
|
||||
console.log("Executing getCurrentExecutionClientVersion");
|
||||
return "0.1";
|
||||
}
|
||||
|
||||
async getCurrentConsensusClientVersion(): Promise<string> {
|
||||
// TODO: implement
|
||||
console.log("Executing getCurrentConsensusClientVersion");
|
||||
return "0.1";
|
||||
|
||||
return web3.eth.getNodeInfo();
|
||||
}
|
||||
|
||||
async getLatestExecutionClientVersion(client: ExecutionClient): Promise<string> {
|
||||
@@ -630,15 +785,46 @@ EONG
|
||||
}
|
||||
|
||||
async executionClientStatus(): Promise<NodeStatus> {
|
||||
// TODO: implement
|
||||
console.log("Executing executionClientStatus");
|
||||
const isSyncing = await web3.eth.isSyncing();
|
||||
|
||||
const peerCount = await web3.eth.net.getPeerCount();
|
||||
|
||||
if (isSyncing === false && peerCount === 0) {
|
||||
return NodeStatus.DOWN;
|
||||
}
|
||||
|
||||
if (isSyncing === false && peerCount > 0) {
|
||||
return NodeStatus.LOOKING_FOR_PEERS;
|
||||
}
|
||||
|
||||
if (
|
||||
isSyncing === true ||
|
||||
(isSyncing as Syncing).CurrentBlock + 3 < (isSyncing as Syncing).HighestBlock
|
||||
) {
|
||||
return NodeStatus.UP_AND_SYNCING;
|
||||
}
|
||||
|
||||
if ((isSyncing as Syncing).CurrentBlock <= (isSyncing as Syncing).HighestBlock) {
|
||||
return NodeStatus.UP_AND_SYNCED;
|
||||
}
|
||||
|
||||
return NodeStatus.UNKNOWN;
|
||||
}
|
||||
|
||||
async consensusBeaconNodeStatus(): Promise<NodeStatus> {
|
||||
// TODO: implement
|
||||
console.log("Executing consensusBeaconNodeStatus");
|
||||
return NodeStatus.UNKNOWN;
|
||||
return fetch(`${CLPROVIDER}/eth/v1/node/syncing`)
|
||||
.then((res) => res.json())
|
||||
.then(({ data }: BeaconResponse<BeaconGetSyncingStatus>) => {
|
||||
if (data.is_syncing) {
|
||||
return NodeStatus.UP_AND_SYNCING;
|
||||
} else {
|
||||
return NodeStatus.UP_AND_SYNCED;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return NodeStatus.DOWN;
|
||||
});
|
||||
}
|
||||
|
||||
async consensusValidatorStatus(): Promise<ValidatorStatus> {
|
||||
@@ -654,26 +840,34 @@ EONG
|
||||
}
|
||||
|
||||
async executionClientPeerCount(): Promise<number> {
|
||||
// TODO: implement
|
||||
console.log("Executing executionClientPeerCount");
|
||||
return -1;
|
||||
return web3.eth.net.getPeerCount();
|
||||
}
|
||||
|
||||
async consensusClientPeerCount(): Promise<number> {
|
||||
// TODO: implement
|
||||
console.log("Executing consensusClientPeerCount");
|
||||
return -1;
|
||||
return fetch(`${CLPROVIDER}/eth/v1/node/peers`)
|
||||
.then((res) => res.json())
|
||||
.then(({ meta }: BeaconResponse<BeaconGetPeers>) => {
|
||||
return meta.count;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
async executionClientLatestBlock(): Promise<number> {
|
||||
// TODO: implement
|
||||
console.log("Executing executionClientLatestBlock");
|
||||
return -1;
|
||||
return web3.eth.getBlockNumber();
|
||||
}
|
||||
|
||||
async consensusClientLatestBlock(): Promise<number> {
|
||||
// TODO: implement
|
||||
console.log("Executing consensusClientLatestBlock");
|
||||
return -1;
|
||||
return fetch(`${CLPROVIDER}/eth/v1/node/syncing`)
|
||||
.then((res) => res.json())
|
||||
.then(({ data }: BeaconResponse<BeaconGetSyncingStatus>) => {
|
||||
return parseInt(data.head_slot);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,81 @@
|
||||
import { Network } from "../react/types";
|
||||
|
||||
export interface IMultiClientInstaller {
|
||||
|
||||
// Functionality
|
||||
preInstall: (outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
install: (details: InstallDetails, outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
postInstall: (network: Network, outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
preInstall: (outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
install: (details: InstallDetails, outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
postInstall: (network: Network, outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
|
||||
stopNodes: (network: Network, outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
startNodes: (network: Network, outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
stopNodes: (network: Network, outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
startNodes: (network: Network, outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
|
||||
updateExecutionClient: (outputLogs?: OutputLogs) => Promise<void>,
|
||||
updateConsensusClient: (outputLogs?: OutputLogs) => Promise<void>,
|
||||
updateExecutionClient: (outputLogs?: OutputLogs) => Promise<void>;
|
||||
updateConsensusClient: (outputLogs?: OutputLogs) => Promise<void>;
|
||||
|
||||
importKeys: (
|
||||
network: Network,
|
||||
keyStoreDirectoryPath: string,
|
||||
keyStorePassword: string,
|
||||
outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
exportKeys: () => Promise<void>,
|
||||
outputLogs?: OutputLogs,
|
||||
) => Promise<boolean>;
|
||||
exportKeys: () => Promise<void>;
|
||||
|
||||
switchExecutionClient: (targetClient: ExecutionClient, outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
switchConsensusClient: (targetClient: ConsensusClient, outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
|
||||
uninstall: (outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
switchExecutionClient: (
|
||||
targetClient: ExecutionClient,
|
||||
outputLogs?: OutputLogs,
|
||||
) => Promise<boolean>;
|
||||
switchConsensusClient: (
|
||||
targetClient: ConsensusClient,
|
||||
outputLogs?: OutputLogs,
|
||||
) => Promise<boolean>;
|
||||
|
||||
uninstall: (outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
|
||||
// Data
|
||||
getCurrentExecutionClient: () => Promise<ExecutionClient>,
|
||||
getCurrentConsensusClient: () => Promise<ConsensusClient>,
|
||||
getCurrentExecutionClient: () => Promise<ExecutionClient>;
|
||||
getCurrentConsensusClient: () => Promise<ConsensusClient>;
|
||||
|
||||
getCurrentExecutionClientVersion: () => Promise<string>,
|
||||
getCurrentConsensusClientVersion: () => Promise<string>,
|
||||
getCurrentExecutionClientVersion: () => Promise<string>;
|
||||
getCurrentConsensusClientVersion: () => Promise<string>;
|
||||
|
||||
getLatestExecutionClientVersion: (client: ExecutionClient) => Promise<string>,
|
||||
getLatestConsensusClientVersion: (client: ConsensusClient) => Promise<string>,
|
||||
getLatestExecutionClientVersion: (client: ExecutionClient) => Promise<string>;
|
||||
getLatestConsensusClientVersion: (client: ConsensusClient) => Promise<string>;
|
||||
|
||||
executionClientStatus: () => Promise<NodeStatus>,
|
||||
consensusBeaconNodeStatus: () => Promise<NodeStatus>,
|
||||
consensusValidatorStatus: () => Promise<ValidatorStatus>,
|
||||
executionClientStatus: () => Promise<NodeStatus>;
|
||||
consensusBeaconNodeStatus: () => Promise<NodeStatus>;
|
||||
consensusValidatorStatus: () => Promise<ValidatorStatus>;
|
||||
|
||||
consensusValidatorCount: () => Promise<number>,
|
||||
consensusValidatorCount: () => Promise<number>;
|
||||
|
||||
executionClientPeerCount: () => Promise<number>,
|
||||
consensusClientPeerCount: () => Promise<number>,
|
||||
executionClientPeerCount: () => Promise<number>;
|
||||
consensusClientPeerCount: () => Promise<number>;
|
||||
|
||||
executionClientLatestBlock: () => Promise<number>,
|
||||
consensusClientLatestBlock: () => Promise<number>,
|
||||
executionClientLatestBlock: () => Promise<number>;
|
||||
consensusClientLatestBlock: () => Promise<number>;
|
||||
|
||||
// TODO: logs stream
|
||||
}
|
||||
|
||||
export interface OutputLogs {
|
||||
(message: string): void;
|
||||
(message: string, progress?: number): void;
|
||||
}
|
||||
|
||||
export interface Progress {
|
||||
(percent: number): void;
|
||||
}
|
||||
|
||||
export type InstallDetails = {
|
||||
network: Network,
|
||||
executionClient: ExecutionClient,
|
||||
consensusClient: ConsensusClient,
|
||||
}
|
||||
debug: boolean;
|
||||
network: Network;
|
||||
executionClient: ExecutionClient;
|
||||
consensusClient: ConsensusClient;
|
||||
};
|
||||
|
||||
export enum NodeStatus {
|
||||
UP_AND_SYNCED,
|
||||
UP_AND_SYNCING,
|
||||
DOWN,
|
||||
LOOKING_FOR_PEERS,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
@@ -72,7 +83,7 @@ export enum ValidatorStatus {
|
||||
UP_AND_VALIDATING,
|
||||
UP_NOT_VALIDATING,
|
||||
DOWN,
|
||||
UNKNOWN
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
// Supported clients
|
||||
@@ -80,7 +91,7 @@ export enum ExecutionClient {
|
||||
GETH = "geth",
|
||||
NETHERMIND = "nethermind",
|
||||
BESU = "besu",
|
||||
ERIGON = "erigon"
|
||||
ERIGON = "erigon",
|
||||
}
|
||||
|
||||
export enum ConsensusClient {
|
||||
@@ -88,5 +99,5 @@ export enum ConsensusClient {
|
||||
NIMBUS = "nimbus",
|
||||
LIGHTHOUSE = "lighthouse",
|
||||
PRYSM = "prysm",
|
||||
LODESTAR = "lodestar"
|
||||
LODESTAR = "lodestar",
|
||||
}
|
||||
|
||||
@@ -9,66 +9,81 @@ import {
|
||||
clipboard,
|
||||
ipcRenderer,
|
||||
OpenDialogOptions,
|
||||
OpenDialogReturnValue
|
||||
OpenDialogReturnValue,
|
||||
} from "electron";
|
||||
import { Network } from "../react/types";
|
||||
|
||||
import { doesDirectoryExist, findFirstFile } from './BashUtils';
|
||||
import { doesDirectoryExist, findFirstFile } from "./BashUtils";
|
||||
|
||||
import { EthDockerInstaller } from './EthDockerInstaller';
|
||||
import { EthDockerInstaller } from "./EthDockerInstaller";
|
||||
import { InstallDetails, OutputLogs } from "./IMultiClientInstaller";
|
||||
|
||||
import { Writable } from 'stream';
|
||||
import { Writable } from "stream";
|
||||
|
||||
const ethDockerInstaller = new EthDockerInstaller();
|
||||
const ethDockerPreInstall = async (outputLogs?: OutputLogs): Promise<boolean> => {
|
||||
return ethDockerInstaller.preInstall(outputLogs);
|
||||
};
|
||||
const ethDockerInstall = async (details: InstallDetails): Promise<boolean> => {
|
||||
return ethDockerInstaller.install(details);
|
||||
const ethDockerInstall = async (
|
||||
details: InstallDetails,
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> => {
|
||||
return ethDockerInstaller.install(details, outputLogs);
|
||||
};
|
||||
const ethDockerImportKeys = async (
|
||||
network: Network,
|
||||
keyStoreDirectoryPath: string,
|
||||
keyStorePassword: string): Promise<boolean> => {
|
||||
keyStorePassword: string,
|
||||
): Promise<boolean> => {
|
||||
return ethDockerInstaller.importKeys(network, keyStoreDirectoryPath, keyStorePassword);
|
||||
};
|
||||
const ethDockerPostInstall = async (network: Network): Promise<boolean> => {
|
||||
return ethDockerInstaller.postInstall(network);
|
||||
const ethDockerPostInstall = async (
|
||||
network: Network,
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> => {
|
||||
return ethDockerInstaller.postInstall(network, outputLogs);
|
||||
};
|
||||
const ethDockerStartNodes = async (network: Network): Promise<boolean> => {
|
||||
return ethDockerInstaller.startNodes(network);
|
||||
const ethDockerStartNodes = async (
|
||||
network: Network,
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> => {
|
||||
return ethDockerInstaller.startNodes(network, outputLogs);
|
||||
};
|
||||
const ethDockerStopNodes = async (network: Network): Promise<boolean> => {
|
||||
return ethDockerInstaller.stopNodes(network);
|
||||
const ethDockerStopNodes = async (
|
||||
network: Network,
|
||||
outputLogs?: OutputLogs,
|
||||
): Promise<boolean> => {
|
||||
return ethDockerInstaller.stopNodes(network, outputLogs);
|
||||
};
|
||||
|
||||
const ipcRendererSendClose = () => {
|
||||
ipcRenderer.send('close');
|
||||
ipcRenderer.send("close");
|
||||
};
|
||||
|
||||
const invokeShowOpenDialog = (options: OpenDialogOptions): Promise<OpenDialogReturnValue> => {
|
||||
return ipcRenderer.invoke('showOpenDialog', options);
|
||||
const invokeShowOpenDialog = (
|
||||
options: OpenDialogOptions,
|
||||
): Promise<OpenDialogReturnValue> => {
|
||||
return ipcRenderer.invoke("showOpenDialog", options);
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
'shellOpenExternal': shell.openExternal,
|
||||
'shellShowItemInFolder': shell.showItemInFolder,
|
||||
'clipboardWriteText': clipboard.writeText,
|
||||
'ipcRendererSendClose': ipcRendererSendClose,
|
||||
'invokeShowOpenDialog': invokeShowOpenDialog
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
shellOpenExternal: shell.openExternal,
|
||||
shellShowItemInFolder: shell.showItemInFolder,
|
||||
clipboardWriteText: clipboard.writeText,
|
||||
ipcRendererSendClose: ipcRendererSendClose,
|
||||
invokeShowOpenDialog: invokeShowOpenDialog,
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('bashUtils', {
|
||||
'doesDirectoryExist': doesDirectoryExist,
|
||||
'findFirstFile': findFirstFile
|
||||
contextBridge.exposeInMainWorld("bashUtils", {
|
||||
doesDirectoryExist: doesDirectoryExist,
|
||||
findFirstFile: findFirstFile,
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('ethDocker', {
|
||||
'preInstall': ethDockerPreInstall,
|
||||
'install': ethDockerInstall,
|
||||
'importKeys': ethDockerImportKeys,
|
||||
'postInstall': ethDockerPostInstall,
|
||||
'startNodes': ethDockerStartNodes,
|
||||
'stopNodes': ethDockerStopNodes
|
||||
});
|
||||
contextBridge.exposeInMainWorld("ethDocker", {
|
||||
preInstall: ethDockerPreInstall,
|
||||
install: ethDockerInstall,
|
||||
importKeys: ethDockerImportKeys,
|
||||
postInstall: ethDockerPostInstall,
|
||||
startNodes: ethDockerStartNodes,
|
||||
stopNodes: ethDockerStopNodes,
|
||||
});
|
||||
|
||||
65
src/electron/renderer.d.ts
vendored
65
src/electron/renderer.d.ts
vendored
@@ -3,62 +3,51 @@
|
||||
* This file contains the typescript type hinting for the preload.ts API.
|
||||
*/
|
||||
|
||||
import {
|
||||
OpenDialogOptions,
|
||||
OpenDialogReturnValue
|
||||
} from "electron";
|
||||
import { OpenDialogOptions, OpenDialogReturnValue } from "electron";
|
||||
|
||||
import {
|
||||
FileOptions,
|
||||
FileResult
|
||||
} from "tmp";
|
||||
import { FileOptions, FileResult } from "tmp";
|
||||
|
||||
import {
|
||||
PathLike,
|
||||
Stats,
|
||||
Dirent
|
||||
} from "fs"
|
||||
import { PathLike, Stats, Dirent } from "fs";
|
||||
|
||||
import {
|
||||
ChildProcess
|
||||
} from "child_process"
|
||||
import { ChildProcess } from "child_process";
|
||||
|
||||
import {
|
||||
EthDockerInstaller
|
||||
} from './EthDockerInstaller'
|
||||
import { EthDockerInstaller } from "./EthDockerInstaller";
|
||||
import { InstallDetails, OutputLogs } from "./IMultiClientInstaller";
|
||||
|
||||
export interface IElectronAPI {
|
||||
shellOpenExternal: (url: string, options?: Electron.OpenExternalOptions | undefined) => Promise<void>,
|
||||
shellShowItemInFolder: (fullPath: string) => void,
|
||||
clipboardWriteText: (ext: string, type?: "selection" | "clipboard" | undefined) => void,
|
||||
ipcRendererSendClose: () => void,
|
||||
invokeShowOpenDialog: (options: OpenDialogOptions) => Promise<OpenDialogReturnValue>
|
||||
shellOpenExternal: (
|
||||
url: string,
|
||||
options?: Electron.OpenExternalOptions | undefined,
|
||||
) => Promise<void>;
|
||||
shellShowItemInFolder: (fullPath: string) => void;
|
||||
clipboardWriteText: (ext: string, type?: "selection" | "clipboard" | undefined) => void;
|
||||
ipcRendererSendClose: () => void;
|
||||
invokeShowOpenDialog: (options: OpenDialogOptions) => Promise<OpenDialogReturnValue>;
|
||||
}
|
||||
|
||||
export interface IBashUtilsAPI {
|
||||
doesDirectoryExist: (directory: string) => Promise<boolean>,
|
||||
isDirectoryWritable: (directory: string) => Promise<boolean>,
|
||||
findFirstFile: (directory: string, startsWith: string) => Promise<string>
|
||||
doesDirectoryExist: (directory: string) => Promise<boolean>;
|
||||
isDirectoryWritable: (directory: string) => Promise<boolean>;
|
||||
findFirstFile: (directory: string, startsWith: string) => Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
export interface IEthDockerAPI {
|
||||
preInstall: (outputLogs?: OutputLogs) => Promise<boolean>,
|
||||
install: (details: InstallDetails) => Promise<boolean>,
|
||||
preInstall: (outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
install: (details: InstallDetails, outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
importKeys: (
|
||||
network: Network,
|
||||
keyStoreDirectoryPath: string,
|
||||
keyStorePassword: string) => Promise<boolean>,
|
||||
postInstall: (network: Network) => Promise<boolean>,
|
||||
startNodes: (network: Network) => Promise<boolean>,
|
||||
stopNodes: (network: Network) => Promise<boolean>,
|
||||
keyStorePassword: string,
|
||||
) => Promise<boolean>;
|
||||
postInstall: (network: Network, outputLogs?: OutputLogs) => Promise<boolean>;
|
||||
startNodes: (network: Network) => Promise<boolean>;
|
||||
stopNodes: (network: Network) => Promise<boolean>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: IElectronAPI,
|
||||
bashUtils: IBashUtilsAPI,
|
||||
ethDocker: IEthDockerAPI
|
||||
electronAPI: IElectronAPI;
|
||||
bashUtils: IBashUtilsAPI;
|
||||
ethDocker: IEthDockerAPI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
src/electron/spec/EthDockerInstaller.spec.ts
Normal file
52
src/electron/spec/EthDockerInstaller.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { EthDockerInstaller } from "../EthDockerInstaller";
|
||||
|
||||
test("get the right user", async () => {
|
||||
const installer = new EthDockerInstaller();
|
||||
const user = await installer.getUsername();
|
||||
expect(user).toEqual("");
|
||||
});
|
||||
|
||||
describe("preinstall ethdocker", () => {
|
||||
function output(res: any) {
|
||||
console.log(res);
|
||||
}
|
||||
const installer = new EthDockerInstaller();
|
||||
|
||||
beforeAll(async () => {
|
||||
await installer.preInstall();
|
||||
});
|
||||
|
||||
test("pre-install", async () => {
|
||||
await installer.preInstall();
|
||||
});
|
||||
|
||||
test("user is in docker group", async () => {
|
||||
const isInGroup = await installer.isUserInGroup("docker");
|
||||
expect(isInGroup).toBeTruthy();
|
||||
});
|
||||
|
||||
test("check installed ubuntu package git", async () => {
|
||||
const isInstalled = await installer.checkForLinuxBinary("git");
|
||||
expect(isInstalled).toBeTruthy();
|
||||
});
|
||||
|
||||
test("check installed ubuntu package docker", async () => {
|
||||
const isInstalled = await installer.checkForLinuxBinary("docker");
|
||||
expect(isInstalled).toBeTruthy();
|
||||
});
|
||||
|
||||
test("check installed ubuntu package docker compose", async () => {
|
||||
const isInstalled = await installer.checkForLinuxBinary("docker-compose");
|
||||
expect(isInstalled).toBeTruthy();
|
||||
});
|
||||
|
||||
test("check systemd service details", async () => {
|
||||
const service = await installer.getSystemdServiceDetails("docker");
|
||||
|
||||
expect(service.description).toMatch("Docker Application Container Engine");
|
||||
expect(service.loadState).toMatch("loaded");
|
||||
expect(service.activeState).toMatch("active");
|
||||
expect(service.subState).toMatch("running");
|
||||
expect(service.unitFileState).toMatch("enabled");
|
||||
});
|
||||
});
|
||||
@@ -1,33 +1,41 @@
|
||||
import { HashRouter, Route, Routes } from "react-router-dom";
|
||||
import React, { FC, ReactElement, useState } from "react";
|
||||
import styled from '@emotion/styled';
|
||||
import styled from "@emotion/styled";
|
||||
import Home from "./pages/Home";
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import { ThemeProvider } from "@mui/material";
|
||||
import '@fontsource/roboto';
|
||||
import "@fontsource/roboto";
|
||||
import MainWizard from "./pages/MainWizard";
|
||||
import theme from "./theme";
|
||||
import { Network } from './types';
|
||||
import { Network } from "./types";
|
||||
import SystemOverview from "./pages/SystemOverview";
|
||||
import { ConsensusClient, ExecutionClient, InstallDetails } from "../electron/IMultiClientInstaller";
|
||||
import {
|
||||
ConsensusClient,
|
||||
ExecutionClient,
|
||||
InstallDetails,
|
||||
} from "../electron/IMultiClientInstaller";
|
||||
|
||||
const Container = styled.main`
|
||||
display: block;
|
||||
padding: 20px;
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
padding-bottom: 45px;
|
||||
`;
|
||||
|
||||
// /systemOverview
|
||||
/**
|
||||
* The React app top level including theme and routing.
|
||||
*
|
||||
*
|
||||
* @returns the react element containing the app
|
||||
*/
|
||||
const App: FC = (): ReactElement => {
|
||||
// const [network, setNetwork] = useState<Network>(Network.PRATER);
|
||||
const [installationDetails, setInstallationDetails] = useState<InstallDetails>({
|
||||
consensusClient: ConsensusClient.PRYSM,
|
||||
executionClient: ExecutionClient.GETH,
|
||||
network: Network.PRATER
|
||||
})
|
||||
debug: false, // used to test the frontend
|
||||
consensusClient: ConsensusClient.PRYSM,
|
||||
executionClient: ExecutionClient.GETH,
|
||||
network: Network.PRATER,
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
@@ -35,9 +43,28 @@ const App: FC = (): ReactElement => {
|
||||
<HashRouter>
|
||||
<Container>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home installationDetails={installationDetails} setInstallationDetails={setInstallationDetails} />} />
|
||||
<Route path="/wizard/:stepSequenceKey" element={<MainWizard installationDetails={installationDetails} setInstallationDetails={setInstallationDetails} />} />
|
||||
<Route path="/systemOverview" element={<SystemOverview installationDetails={installationDetails} />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<Home
|
||||
installationDetails={installationDetails}
|
||||
setInstallationDetails={setInstallationDetails}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/wizard/:stepSequenceKey"
|
||||
element={
|
||||
<MainWizard
|
||||
installationDetails={installationDetails}
|
||||
setInstallationDetails={setInstallationDetails}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={<SystemOverview installationDetails={installationDetails} />}
|
||||
/>
|
||||
</Routes>
|
||||
</Container>
|
||||
</HashRouter>
|
||||
|
||||
@@ -1,58 +1,75 @@
|
||||
import { BackgroundLight, } from '../colors';
|
||||
import { Button, Typography, Box, Grid, Modal, InputAdornment, TextField, styled } from '@mui/material';
|
||||
import React, { FC, ChangeEvent, Dispatch, ReactElement, SetStateAction, MutableRefObject } from 'react';
|
||||
import { BackgroundLight } from '../colors';
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Grid,
|
||||
Modal,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
styled,
|
||||
// useFormControl,
|
||||
TextFieldProps,
|
||||
} from '@mui/material';
|
||||
import React, {
|
||||
FC,
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
ReactElement,
|
||||
SetStateAction,
|
||||
MutableRefObject,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { FileCopy, LockOpen } from '@mui/icons-material'
|
||||
import { FileCopy, LockOpen } from '@mui/icons-material';
|
||||
import { InstallDetails } from '../../electron/IMultiClientInstaller';
|
||||
|
||||
|
||||
const ModalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 600,
|
||||
padding: '20px',
|
||||
borderRadius: '20px',
|
||||
background: BackgroundLight,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
};
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 600,
|
||||
padding: '20px',
|
||||
borderRadius: '20px',
|
||||
background: BackgroundLight,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
};
|
||||
|
||||
const FileUploadField = styled(TextField)({
|
||||
'& label.Mui-focused': {
|
||||
},
|
||||
'& .MuiInput-underline:after': {
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
paddingLeft: '0',
|
||||
const FileUploadField = styled(TextField)({
|
||||
'& label.Mui-focused': {},
|
||||
'& .MuiInput-underline:after': {},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
paddingLeft: '0',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
},
|
||||
});
|
||||
'&:hover fieldset': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type ImportKeystoreProps = {
|
||||
setModalOpen: Dispatch<SetStateAction<boolean>>
|
||||
isModalOpen : boolean
|
||||
keyStorePath: string
|
||||
keystorePassword: string
|
||||
setKeystorePath: Dispatch<SetStateAction<string>>
|
||||
setKeystorePassword: Dispatch<SetStateAction<string>>
|
||||
closing: MutableRefObject<((arg: () => Promise<boolean>) => void) | undefined>
|
||||
installationDetails: InstallDetails
|
||||
}
|
||||
setModalOpen: Dispatch<SetStateAction<boolean>>;
|
||||
isModalOpen: boolean;
|
||||
keyStorePath: string;
|
||||
keystorePassword: string;
|
||||
setKeystorePath: Dispatch<SetStateAction<string>>;
|
||||
setKeystorePassword: Dispatch<SetStateAction<string>>;
|
||||
closing: MutableRefObject<((arg: () => Promise<boolean>) => void) | undefined>;
|
||||
installationDetails: InstallDetails;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the network picker modal component where the user selects the desired network.
|
||||
*
|
||||
*
|
||||
* @param props.isModalOpen the current open state of the modal
|
||||
* @param props.setModalOpen a function to set the modal open state
|
||||
* @param props.keyStorePath the path to the directory where the validator keys are stored
|
||||
@@ -64,87 +81,133 @@ type ImportKeystoreProps = {
|
||||
* @returns the import validator key modal
|
||||
*/
|
||||
export const ImportKeystore: FC<ImportKeystoreProps> = (props): ReactElement => {
|
||||
const handleKeystorePathChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
props.setKeystorePath(ev.target.value);
|
||||
};
|
||||
const handlePasswordChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
props.setKeystorePassword(ev.target.value);
|
||||
};
|
||||
|
||||
const handleKeystorePathChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
props.setKeystorePath(ev.target.value)
|
||||
}
|
||||
const handlePasswordChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
props.setKeystorePassword(ev.target.value)
|
||||
}
|
||||
const [errorUpload, setErrorUpload] = useState<boolean>(false);
|
||||
|
||||
function MyFormHelperText() {
|
||||
// const { focused } = useFormControl() || {};
|
||||
const helperText = useMemo(() => {
|
||||
if (errorUpload) {
|
||||
return 'provide a directory path';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, [errorUpload]);
|
||||
|
||||
return <span>{helperText}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={props.isModalOpen}
|
||||
onClose={() => props.setModalOpen(false)}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box sx={ModalStyle}>
|
||||
<Typography id="modal-modal-title" align='center' variant="h4" component="h2">
|
||||
Import Validator Keys
|
||||
</Typography>
|
||||
<hr style={{ borderColor: 'orange' }} />
|
||||
<Grid container>
|
||||
<Grid xs={12} item container justifyContent={'flex-start'} direction={'column'}>
|
||||
<Grid item container alignItems={'center'} p={2} spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<span>Keystore Directory</span>
|
||||
open={props.isModalOpen}
|
||||
onClose={() => props.setModalOpen(false)}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box component="form" sx={ModalStyle}>
|
||||
<Typography id="modal-modal-title" align="center" variant="h4" component="h2">
|
||||
Import Validator Keys
|
||||
</Typography>
|
||||
<hr style={{ borderColor: 'orange' }} />
|
||||
<Grid container>
|
||||
<Grid xs={12} item container justifyContent={'flex-start'} direction={'column'}>
|
||||
<Grid item container alignItems={'center'} p={2} spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<span>Keystore Directory</span>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FileUploadField
|
||||
helperText={<MyFormHelperText />}
|
||||
placeholder="/validator_keys/"
|
||||
error={errorUpload}
|
||||
sx={{ my: 2, minWidth: '215', cursor: 'pointer !important' }}
|
||||
variant="outlined"
|
||||
onChange={handleKeystorePathChange}
|
||||
value={props.keyStorePath}
|
||||
required
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment
|
||||
color="primary"
|
||||
sx={{ paddingLeft: '14px' }}
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault();
|
||||
window.electronAPI
|
||||
.invokeShowOpenDialog({
|
||||
properties: ['openDirectory'],
|
||||
})
|
||||
.then((DialogResponse) => {
|
||||
if (DialogResponse.filePaths && DialogResponse.filePaths.length) {
|
||||
props.setKeystorePath(DialogResponse.filePaths[0]);
|
||||
}
|
||||
});
|
||||
}}
|
||||
position="start"
|
||||
>
|
||||
<FileCopy />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FileUploadField
|
||||
placeholder='/validator_keys/'
|
||||
sx={{ my: 2, minWidth: '215', cursor: 'pointer !important' }}
|
||||
variant="outlined"
|
||||
onChange={handleKeystorePathChange}
|
||||
value={props.keyStorePath}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment sx={{paddingLeft: '14px'}} onClick={(ev) => {
|
||||
ev.preventDefault()
|
||||
window.electronAPI.invokeShowOpenDialog({
|
||||
|
||||
properties: ['openDirectory']
|
||||
}).then(DialogResponse => {
|
||||
if (DialogResponse.filePaths && DialogResponse.filePaths.length) {
|
||||
props.setKeystorePath(DialogResponse.filePaths[0])
|
||||
}
|
||||
})
|
||||
}} position="start"><FileCopy /></InputAdornment>,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item container alignItems={'center'} p={2} spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<span>Keystore Password</span>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
type={'password'}
|
||||
sx={{ my: 2, minWidth: '215' }}
|
||||
// label="Fallback URL"
|
||||
variant="outlined"
|
||||
value={props.keystorePassword}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start"><LockOpen /></InputAdornment>,
|
||||
}}
|
||||
onChange={handlePasswordChange}
|
||||
/>
|
||||
<Grid item container alignItems={'center'} p={2} spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<span>Keystore Password</span>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
type={'password'}
|
||||
sx={{ my: 2, minWidth: '215' }}
|
||||
// label="Fallback URL"
|
||||
variant="outlined"
|
||||
required
|
||||
value={props.keystorePassword}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<LockOpen />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={handlePasswordChange}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} textAlign="center" my={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
if (!props.keyStorePath || !props.setKeystorePath.length) {
|
||||
setErrorUpload(true);
|
||||
return;
|
||||
}
|
||||
setErrorUpload(false);
|
||||
props.setModalOpen(false);
|
||||
if (props.closing && props.closing.current) {
|
||||
props.closing.current(() =>
|
||||
window.ethDocker.importKeys(
|
||||
props.installationDetails.network,
|
||||
props.keyStorePath,
|
||||
props.keystorePassword,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} textAlign='center' my={2}>
|
||||
<Button
|
||||
variant="contained" color="primary" onClick={() => {
|
||||
props.setModalOpen(false)
|
||||
if (props.closing && props.closing.current) {
|
||||
props.closing.current(() => window.ethDocker.importKeys(props.installationDetails.network, props.keyStorePath, props.keystorePassword))
|
||||
}
|
||||
}
|
||||
}>Import</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
import React, { ChangeEvent, Dispatch, FC, ReactElement, SetStateAction, useState } from 'react';
|
||||
import { Grid, Typography, FormControl, Select, MenuItem, SelectChangeEvent, Modal, Box, Button, TextField, InputAdornment } from '@mui/material';
|
||||
import { Folder, Link } from '@mui/icons-material'
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
FormControl,
|
||||
Select,
|
||||
MenuItem,
|
||||
SelectChangeEvent,
|
||||
Modal,
|
||||
Box,
|
||||
Button,
|
||||
TextField,
|
||||
InputAdornment,
|
||||
} from '@mui/material';
|
||||
import { Folder, Link } from '@mui/icons-material';
|
||||
import StepNavigation from '../StepNavigation';
|
||||
import styled from '@emotion/styled';
|
||||
import { ConsensusClients, ExecutionClients, IConsensusClient, IExecutionClient } from '../../constants'
|
||||
import { ConsensusClients, ExecutionClients, IConsensusClient, IExecutionClient } from '../../constants';
|
||||
import { ConsensusClient, ExecutionClient, InstallDetails } from '../../../electron/IMultiClientInstaller';
|
||||
import { BackgroundLight, } from '../../colors';
|
||||
import { BackgroundLight } from '../../colors';
|
||||
|
||||
type ConfigurationProps = {
|
||||
onStepBack: () => void,
|
||||
onStepForward: () => void,
|
||||
installationDetails: InstallDetails,
|
||||
setInstallationDetails: Dispatch<SetStateAction<InstallDetails>>
|
||||
}
|
||||
onStepBack: () => void;
|
||||
onStepForward: () => void;
|
||||
installationDetails: InstallDetails;
|
||||
setInstallationDetails: Dispatch<SetStateAction<InstallDetails>>;
|
||||
};
|
||||
|
||||
const ContentGrid = styled(Grid)`
|
||||
height: 320px;
|
||||
@@ -20,7 +32,6 @@ const ContentGrid = styled(Grid)`
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
|
||||
const ModalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
@@ -36,45 +47,52 @@ const ModalStyle = {
|
||||
|
||||
/**
|
||||
* This page is the second step of the install process where the user inputs their configuration.
|
||||
*
|
||||
*
|
||||
* @param props the data and functions passed in, they are self documenting
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
const [consensusClient, setConsensusClient] = useState<ConsensusClient>(props.installationDetails.consensusClient);
|
||||
const [executionClient, setExecutionClient] = useState<ExecutionClient>(props.installationDetails.executionClient);
|
||||
const [isModalOpen, setModalOpen] = useState(false)
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
const [checkpointSync, setCheckpointSync] = useState('');
|
||||
const [executionClientFallback, setExecutionClientFallback] = useState('');
|
||||
const [installationPath, setInstallationPath] = useState('');
|
||||
|
||||
const handleConsensusClientChange = (ev: SelectChangeEvent<string>) => {
|
||||
setConsensusClient(ev.target.value as ConsensusClient)
|
||||
}
|
||||
setConsensusClient(ev.target.value as ConsensusClient);
|
||||
};
|
||||
|
||||
const handleExecutionClientChange = (ev: SelectChangeEvent<string>) => {
|
||||
setExecutionClient(ev.target.value as ExecutionClient)
|
||||
}
|
||||
setExecutionClient(ev.target.value as ExecutionClient);
|
||||
};
|
||||
|
||||
const handleCheckpointSyncChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
setCheckpointSync(ev.target.value)
|
||||
}
|
||||
setCheckpointSync(ev.target.value);
|
||||
};
|
||||
const handleExecutionClientFallbackChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
setExecutionClientFallback(ev.target.value)
|
||||
}
|
||||
setExecutionClientFallback(ev.target.value);
|
||||
};
|
||||
const handleInstallationPathChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
setInstallationPath(ev.target.value)
|
||||
}
|
||||
setInstallationPath(ev.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid item container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
<Typography variant="h1" align='center'>
|
||||
<Typography variant="h1" align="center">
|
||||
Configuration
|
||||
</Typography>
|
||||
</Grid>
|
||||
<ContentGrid item container justifyContent={'center'}>
|
||||
<Grid xs={11} style={{ border: '1px solid orange' }} item container justifyContent={'center'} direction={'column'}>
|
||||
<Grid
|
||||
xs={11}
|
||||
style={{ border: '1px solid orange' }}
|
||||
item
|
||||
container
|
||||
justifyContent={'center'}
|
||||
direction={'column'}
|
||||
>
|
||||
<Grid item container alignItems={'center'} p={2} spacing={2}>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item xs={4}>
|
||||
@@ -83,15 +101,13 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
<Grid item xs={2}></Grid>
|
||||
<Grid item xs={4}>
|
||||
<FormControl sx={{ my: 2, minWidth: '215' }}>
|
||||
<Select
|
||||
id="consensus-client"
|
||||
value={consensusClient}
|
||||
onChange={handleConsensusClientChange}
|
||||
>
|
||||
<Select id="consensus-client" value={consensusClient} onChange={handleConsensusClientChange}>
|
||||
{ConsensusClients.map((c: IConsensusClient) => {
|
||||
return (
|
||||
<MenuItem key={c.key} value={c.key}>{c.label}</MenuItem>
|
||||
)
|
||||
<MenuItem key={c.key} value={c.key}>
|
||||
{c.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -99,22 +115,20 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
<Grid item xs={1}></Grid>
|
||||
</Grid>
|
||||
<Grid item container alignItems={'center'} p={2} spacing={2}>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item xs={4}>
|
||||
<span>Execution Client</span>
|
||||
</Grid>
|
||||
<Grid item xs={2}></Grid>
|
||||
<Grid item xs={4}>
|
||||
<FormControl sx={{ my: 2, minWidth: '215' }}>
|
||||
<Select
|
||||
id="execution-client"
|
||||
value={executionClient}
|
||||
onChange={handleExecutionClientChange}
|
||||
>
|
||||
<Select id="execution-client" value={executionClient} onChange={handleExecutionClientChange}>
|
||||
{ExecutionClients.map((c: IExecutionClient) => {
|
||||
return (
|
||||
<MenuItem key={c.key} value={c.key}>{c.label}</MenuItem>
|
||||
)
|
||||
<MenuItem key={c.key} value={c.key}>
|
||||
{c.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -122,7 +136,9 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
<Grid item xs={1}></Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button disabled onClick={() => setModalOpen(true)}>Advanced Options</Button>
|
||||
<Button disabled onClick={() => setModalOpen(true)}>
|
||||
Advanced Options
|
||||
</Button>
|
||||
<Modal
|
||||
open={isModalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
@@ -130,7 +146,7 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box sx={ModalStyle}>
|
||||
<Typography id="modal-modal-title" align='center' variant="h4" component="h2">
|
||||
<Typography id="modal-modal-title" align="center" variant="h4" component="h2">
|
||||
Advanced options
|
||||
</Typography>
|
||||
<hr style={{ borderColor: 'orange' }} />
|
||||
@@ -148,7 +164,11 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
variant="outlined"
|
||||
value={checkpointSync}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start"><Link /></InputAdornment>,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Link />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={handleCheckpointSyncChange}
|
||||
/>
|
||||
@@ -163,11 +183,15 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
placeholder="http://localhost:8545"
|
||||
type={'url'}
|
||||
sx={{ my: 2, minWidth: '215' }}
|
||||
// label="Fallback URL"
|
||||
// label="Fallback URL"
|
||||
variant="outlined"
|
||||
value={executionClientFallback}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start"><Link /></InputAdornment>,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Link />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={handleExecutionClientFallbackChange}
|
||||
/>
|
||||
@@ -179,14 +203,20 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
placeholder='.wagyu/'
|
||||
onClick={(ev) => { ev.preventDefault(); }}
|
||||
placeholder=".wagyu/"
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault();
|
||||
}}
|
||||
sx={{ my: 2, minWidth: '215' }}
|
||||
variant="outlined"
|
||||
disabled
|
||||
value={installationPath}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start"><Folder /></InputAdornment>,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Folder />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={handleInstallationPathChange}
|
||||
/>
|
||||
@@ -201,24 +231,24 @@ const Configuration: FC<ConfigurationProps> = (props): ReactElement => {
|
||||
<StepNavigation
|
||||
onPrev={props.onStepBack}
|
||||
onNext={() => {
|
||||
|
||||
let installationDetails: InstallDetails = {
|
||||
debug: props.installationDetails.debug,
|
||||
consensusClient,
|
||||
executionClient,
|
||||
network: props.installationDetails.network
|
||||
}
|
||||
network: props.installationDetails.network,
|
||||
};
|
||||
|
||||
props.setInstallationDetails(installationDetails)
|
||||
props.setInstallationDetails(installationDetails);
|
||||
|
||||
props.onStepForward()
|
||||
props.onStepForward();
|
||||
}}
|
||||
backLabel={"Back"}
|
||||
nextLabel={"Install"}
|
||||
backLabel={'Back'}
|
||||
nextLabel={'Install'}
|
||||
disableBack={false}
|
||||
disableNext={false}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Configuration;
|
||||
|
||||
@@ -1,19 +1,34 @@
|
||||
import React, { Dispatch, FC, ReactElement, SetStateAction, useState, useRef, useEffect } from 'react';
|
||||
import { Grid, Typography, Fab, CircularProgress, Box } from '@mui/material';
|
||||
import StepNavigation from '../StepNavigation';
|
||||
import { DoneOutline, DownloadingOutlined, ComputerOutlined, RocketLaunchOutlined, KeyOutlined, ErrorOutline } from '@mui/icons-material';
|
||||
import styled from '@emotion/styled';
|
||||
import React, {
|
||||
Dispatch,
|
||||
FC,
|
||||
ReactElement,
|
||||
SetStateAction,
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { Grid, Typography, Fab, CircularProgress, Box } from "@mui/material";
|
||||
import StepNavigation from "../StepNavigation";
|
||||
import {
|
||||
DoneOutline,
|
||||
DownloadingOutlined,
|
||||
ComputerOutlined,
|
||||
RocketLaunchOutlined,
|
||||
KeyOutlined,
|
||||
ErrorOutline,
|
||||
} from "@mui/icons-material";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
import { green, red } from '@mui/material/colors';
|
||||
import { InstallDetails } from '../../../electron/IMultiClientInstaller';
|
||||
import { ImportKeystore } from '../ImportKeystore'
|
||||
import { green, red } from "@mui/material/colors";
|
||||
import { InstallDetails, OutputLogs } from "../../../electron/IMultiClientInstaller";
|
||||
// import { ImportKeystore } from "../ImportKeystore";
|
||||
|
||||
type InstallProps = {
|
||||
onStepBack: () => void,
|
||||
onStepForward: () => void,
|
||||
installationDetails: InstallDetails,
|
||||
setInstallationDetails: Dispatch<SetStateAction<InstallDetails>>
|
||||
}
|
||||
onStepBack: () => void;
|
||||
onStepForward: () => void;
|
||||
installationDetails: InstallDetails;
|
||||
setInstallationDetails: Dispatch<SetStateAction<InstallDetails>>;
|
||||
};
|
||||
|
||||
const ContentGrid = styled(Grid)`
|
||||
height: 320px;
|
||||
@@ -21,230 +36,321 @@ const ContentGrid = styled(Grid)`
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
|
||||
/**
|
||||
* This page is the third step of the install process where the software is being installed.
|
||||
*
|
||||
*
|
||||
* @param props the data and functions passed in, they are self documenting
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
const Install: FC<InstallProps> = (props): ReactElement => {
|
||||
|
||||
const [loadingPreInstall, setLoadingPreInstall] = useState(false);
|
||||
const [loadingInstall, setLoadingInstall] = useState(false);
|
||||
const [loadingKeyImport, setLoadingKeyImport] = useState(false);
|
||||
// const [loadingKeyImport, setLoadingKeyImport] = useState(false);
|
||||
const [loadingPostInstall, setLoadingPostInstall] = useState(false);
|
||||
|
||||
const [failedPreInstall, setFailedPreInstall] = useState(false);
|
||||
const [failedInstall, setFailedInstall] = useState(false);
|
||||
const [failedKeyImport, setFailedKeyImport] = useState(false);
|
||||
// const [failedKeyImport, setFailedKeyImport] = useState(false);
|
||||
const [failedPostInstall, setFailedPostInstall] = useState(false);
|
||||
|
||||
const [successPreInstall, setSuccessPreInstall] = useState(false);
|
||||
const [successInstall, setSuccessInstall] = useState(false);
|
||||
const [successKeyImport, setSuccessKeyImport] = useState(false);
|
||||
// const [successKeyImport, setSuccessKeyImport] = useState(false);
|
||||
const [successPostInstall, setSuccessPostInstall] = useState(false);
|
||||
|
||||
const [percentagePreInstall, setPercentagePreInstall] = useState(0);
|
||||
const [percentageInstall, setPercentageInstall] = useState(0);
|
||||
const [percentagePostInstall, setPercentagePostInstall] = useState(0);
|
||||
|
||||
// const resolveModal = useRef<(arg: () => Promise<boolean> => void)>(Promise.resolve);
|
||||
const resolveModal = useRef<(arg: () => Promise<boolean>) => void>();
|
||||
|
||||
const [disableBack, setDisableBack] = useState<boolean>(true)
|
||||
const [disableForward, setDisableForward] = useState<boolean>(true)
|
||||
|
||||
const [disableBack, setDisableBack] = useState<boolean>(true);
|
||||
const [disableForward, setDisableForward] = useState<boolean>(true);
|
||||
|
||||
const buttonPreInstallSx = {
|
||||
...(failedPreInstall ? {
|
||||
bgcolor: '#ffc107',
|
||||
'&:hover': {
|
||||
bgcolor: '#ffc107',
|
||||
},
|
||||
} : successPreInstall && {
|
||||
bgcolor: green[500],
|
||||
'&:hover': {
|
||||
bgcolor: green[700],
|
||||
},
|
||||
}),
|
||||
...(failedPreInstall
|
||||
? {
|
||||
bgcolor: "#ffc107",
|
||||
"&:hover": {
|
||||
bgcolor: "#ffc107",
|
||||
},
|
||||
}
|
||||
: successPreInstall && {
|
||||
bgcolor: green[500],
|
||||
"&:hover": {
|
||||
bgcolor: green[700],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const buttonInstallSx = {
|
||||
...(failedInstall ? {
|
||||
bgcolor: '#ffc107',
|
||||
'&:hover': {
|
||||
bgcolor: '#ffc107',
|
||||
},
|
||||
} :successInstall && {
|
||||
bgcolor: green[500],
|
||||
'&:hover': {
|
||||
bgcolor: green[700],
|
||||
},
|
||||
}),
|
||||
...(failedInstall
|
||||
? {
|
||||
bgcolor: "#ffc107",
|
||||
"&:hover": {
|
||||
bgcolor: "#ffc107",
|
||||
},
|
||||
}
|
||||
: successInstall && {
|
||||
bgcolor: green[500],
|
||||
"&:hover": {
|
||||
bgcolor: green[700],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const buttonKeyImportSx = {
|
||||
...(failedKeyImport ? {
|
||||
bgcolor: '#ffc107',
|
||||
'&:hover': {
|
||||
bgcolor: '#ffc107',
|
||||
},
|
||||
} : successKeyImport && {
|
||||
bgcolor: green[500],
|
||||
'&:hover': {
|
||||
bgcolor: green[700],
|
||||
},
|
||||
}),
|
||||
};
|
||||
// const buttonKeyImportSx = {
|
||||
// ...(failedKeyImport
|
||||
// ? {
|
||||
// bgcolor: "#ffc107",
|
||||
// "&:hover": {
|
||||
// bgcolor: "#ffc107",
|
||||
// },
|
||||
// }
|
||||
// : successKeyImport && {
|
||||
// bgcolor: green[500],
|
||||
// "&:hover": {
|
||||
// bgcolor: green[700],
|
||||
// },
|
||||
// }),
|
||||
// };
|
||||
|
||||
const buttonPostInstallSx = {
|
||||
...(failedPostInstall ? {
|
||||
bgcolor: '#ffc107',
|
||||
'&:hover': {
|
||||
bgcolor: '#ffc107',
|
||||
},
|
||||
} : successPostInstall && {
|
||||
bgcolor: green[500],
|
||||
'&:hover': {
|
||||
bgcolor: green[700],
|
||||
},
|
||||
}),
|
||||
...(failedPostInstall
|
||||
? {
|
||||
bgcolor: "#ffc107",
|
||||
"&:hover": {
|
||||
bgcolor: "#ffc107",
|
||||
},
|
||||
}
|
||||
: successPostInstall && {
|
||||
bgcolor: green[500],
|
||||
"&:hover": {
|
||||
bgcolor: green[700],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const [isModalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [keyStorePath, setKeystorePath] = useState<string>("");
|
||||
const [keystorePassword, setKeystorePassword] = useState<string>("");
|
||||
|
||||
const [isModalOpen, setModalOpen] = useState<boolean>(false)
|
||||
const [keyStorePath, setKeystorePath] = useState<string>('')
|
||||
const [keystorePassword, setKeystorePassword] = useState<string>('')
|
||||
|
||||
const bufferLoad:() => Promise<boolean> = () => {
|
||||
const bufferLoad: () => Promise<boolean> = () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(true), 1500)
|
||||
})
|
||||
}
|
||||
setTimeout(() => resolve(true), 1500);
|
||||
});
|
||||
};
|
||||
|
||||
const handlePreInstall: () => Promise<boolean> = () => {
|
||||
if (props.installationDetails.debug === true) {
|
||||
return new Promise((resolve) => {
|
||||
setSuccessPreInstall(false);
|
||||
setLoadingPreInstall(true);
|
||||
setTimeout(() => {
|
||||
setSuccessPreInstall(true);
|
||||
setLoadingPreInstall(false);
|
||||
resolve(true);
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
if (!loadingPreInstall) {
|
||||
setSuccessPreInstall(false);
|
||||
setLoadingPreInstall(true);
|
||||
|
||||
Promise.all([window.ethDocker.preInstall(), bufferLoad()]).then((res) => {
|
||||
setSuccessPreInstall(true);
|
||||
setLoadingPreInstall(false);
|
||||
resolve(res[0])
|
||||
})
|
||||
const consoleLog: OutputLogs = (message: string, progress?: number) => {
|
||||
console.log(message);
|
||||
if (progress) {
|
||||
setPercentagePreInstall(progress);
|
||||
}
|
||||
};
|
||||
|
||||
Promise.all([window.ethDocker.preInstall(consoleLog), bufferLoad()]).then(
|
||||
(res) => {
|
||||
setSuccessPreInstall(true);
|
||||
setLoadingPreInstall(false);
|
||||
resolve(res[0]);
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const handleInstall: () => Promise<boolean> = () => {
|
||||
if (props.installationDetails.debug === true) {
|
||||
return new Promise((resolve) => {
|
||||
setSuccessInstall(false);
|
||||
setLoadingInstall(true);
|
||||
setTimeout(() => {
|
||||
setSuccessInstall(true);
|
||||
setLoadingInstall(false);
|
||||
resolve(true);
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (!loadingInstall) {
|
||||
setSuccessInstall(false);
|
||||
setLoadingInstall(true);
|
||||
|
||||
Promise.all([window.ethDocker.install(props.installationDetails), bufferLoad()]).then((res) => {
|
||||
const installConsole: OutputLogs = (message: string, progress?: number) => {
|
||||
console.log("UPDATING LOG:", message, progress);
|
||||
console.log(message);
|
||||
if (progress) {
|
||||
setPercentageInstall(progress);
|
||||
}
|
||||
};
|
||||
console.log("STARTING INSTALL");
|
||||
Promise.all([
|
||||
window.ethDocker.install(props.installationDetails, installConsole),
|
||||
bufferLoad(),
|
||||
]).then((res) => {
|
||||
setSuccessInstall(true);
|
||||
setLoadingInstall(false);
|
||||
resolve(res[0])
|
||||
})
|
||||
resolve(res[0]);
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyImportModal: () => Promise<boolean> = () => {
|
||||
return new Promise((resolve: (arg: () => Promise<boolean>) => void) => {
|
||||
setSuccessKeyImport(false)
|
||||
setLoadingKeyImport(true);
|
||||
setModalOpen(true)
|
||||
resolveModal.current = resolve
|
||||
}).then((keyImp: () => Promise<boolean>) => {
|
||||
return new Promise((resolve: (arg: boolean) => void ) => {
|
||||
Promise.all([keyImp(), bufferLoad()]).then(res => {
|
||||
setSuccessKeyImport(true)
|
||||
setLoadingKeyImport(false);
|
||||
resolve(res[0])
|
||||
}).catch(err => {
|
||||
console.error('error importing key: ',err)
|
||||
setLoadingKeyImport(false);
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}).catch(err => {
|
||||
console.error('error filling out key import modal: ',err)
|
||||
setLoadingKeyImport(false);
|
||||
return false
|
||||
})
|
||||
}
|
||||
// const handleKeyImportModal: () => Promise<boolean> = () => {
|
||||
// if (props.installationDetails.debug === true) {
|
||||
// return new Promise((resolve) => {
|
||||
// setSuccessKeyImport(false);
|
||||
// setLoadingKeyImport(true);
|
||||
// setTimeout(() => {
|
||||
// setSuccessKeyImport(true);
|
||||
// setLoadingKeyImport(false);
|
||||
// resolve(true);
|
||||
// }, 1500);
|
||||
// });
|
||||
// }
|
||||
// return new Promise((resolve: (arg: () => Promise<boolean>) => void) => {
|
||||
// setSuccessKeyImport(false);
|
||||
// setLoadingKeyImport(true);
|
||||
// setModalOpen(true);
|
||||
// resolveModal.current = resolve;
|
||||
// })
|
||||
// .then((keyImp: () => Promise<boolean>) => {
|
||||
// return new Promise((resolve: (arg: boolean) => void) => {
|
||||
// Promise.all([keyImp(), bufferLoad()])
|
||||
// .then((res) => {
|
||||
// setSuccessKeyImport(true);
|
||||
// setLoadingKeyImport(false);
|
||||
// resolve(res[0]);
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// console.error("error importing key: ", err);
|
||||
// setLoadingKeyImport(false);
|
||||
// resolve(false);
|
||||
// });
|
||||
// });
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// console.error("error filling out key import modal: ", err);
|
||||
// setLoadingKeyImport(false);
|
||||
// return false;
|
||||
// });
|
||||
// };
|
||||
|
||||
const handlePostInstall: () => Promise<boolean> = () => {
|
||||
if (props.installationDetails.debug === true) {
|
||||
return new Promise((resolve) => {
|
||||
setSuccessPostInstall(false);
|
||||
setLoadingPostInstall(true);
|
||||
setTimeout(() => {
|
||||
setSuccessPostInstall(true);
|
||||
setLoadingPostInstall(false);
|
||||
resolve(true);
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
if (!loadingPostInstall) {
|
||||
setSuccessPostInstall(false);
|
||||
setLoadingPostInstall(true);
|
||||
|
||||
Promise.all([ window.ethDocker.postInstall(props.installationDetails.network), bufferLoad()]).then((res) => {
|
||||
setSuccessPostInstall(true);
|
||||
setLoadingPostInstall(false);
|
||||
resolve(res[0])
|
||||
})
|
||||
const postInstallConsole: OutputLogs = (message: string, progress?: number) => {
|
||||
console.log(message);
|
||||
if (progress) {
|
||||
setPercentagePostInstall(progress);
|
||||
}
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
window.ethDocker.postInstall(
|
||||
props.installationDetails.network,
|
||||
postInstallConsole,
|
||||
),
|
||||
bufferLoad(),
|
||||
]).then((res) => {
|
||||
setSuccessPostInstall(true);
|
||||
setLoadingPostInstall(false);
|
||||
resolve(res[0]);
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const install = async (step: number) => {
|
||||
if (!step) {
|
||||
step = 0
|
||||
step = 0;
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 0:
|
||||
setFailedPreInstall(false)
|
||||
let preInstallResult = await handlePreInstall()
|
||||
setFailedPreInstall(false);
|
||||
let preInstallResult = await handlePreInstall();
|
||||
if (!preInstallResult) {
|
||||
setFailedPreInstall(true)
|
||||
setDisableBack(false)
|
||||
return
|
||||
setFailedPreInstall(true);
|
||||
setDisableBack(false);
|
||||
return;
|
||||
}
|
||||
step = 1
|
||||
step = 1;
|
||||
case 1:
|
||||
setFailedInstall(false)
|
||||
let installResult = await handleInstall()
|
||||
setFailedInstall(false);
|
||||
let installResult = await handleInstall();
|
||||
if (!installResult) {
|
||||
setFailedInstall(true)
|
||||
setDisableBack(false)
|
||||
return
|
||||
setFailedInstall(true);
|
||||
setDisableBack(false);
|
||||
return;
|
||||
}
|
||||
step += 1
|
||||
|
||||
case 2:
|
||||
setFailedKeyImport(false)
|
||||
let keyImportResult = await handleKeyImportModal()
|
||||
if (!keyImportResult) {
|
||||
setFailedKeyImport(true)
|
||||
setDisableBack(false)
|
||||
return
|
||||
}
|
||||
step += 1
|
||||
step += 2;
|
||||
|
||||
// case 2:
|
||||
// setFailedKeyImport(false);
|
||||
// let keyImportResult = await handleKeyImportModal();
|
||||
// if (!keyImportResult) {
|
||||
// setFailedKeyImport(true);
|
||||
// setDisableBack(false);
|
||||
// return;
|
||||
// }
|
||||
// step += 1;
|
||||
|
||||
case 3:
|
||||
setFailedPostInstall(false)
|
||||
let postInstallResult = await handlePostInstall()
|
||||
setFailedPostInstall(false);
|
||||
let postInstallResult = await handlePostInstall();
|
||||
if (!postInstallResult) {
|
||||
setFailedPostInstall(true)
|
||||
setDisableBack(false)
|
||||
return
|
||||
setFailedPostInstall(true);
|
||||
setDisableBack(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setDisableForward(false)
|
||||
setDisableBack(true)
|
||||
}
|
||||
|
||||
setDisableForward(false);
|
||||
setDisableBack(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
install(0)
|
||||
}, [])
|
||||
|
||||
|
||||
install(0);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Grid item container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
<Typography variant="h1" align='center'>
|
||||
<Typography variant="h1" align="center">
|
||||
Installing
|
||||
</Typography>
|
||||
</Grid>
|
||||
@@ -252,149 +358,174 @@ const [keystorePassword, setKeystorePassword] = useState<string>('')
|
||||
<Grid item container>
|
||||
<Grid item xs={3}></Grid>
|
||||
<Grid item container justifyContent="center" alignItems="center" xs={2}>
|
||||
<Box sx={{ m: 1, position: 'relative' }}>
|
||||
<Fab
|
||||
<Box sx={{ m: 1, position: "relative" }}>
|
||||
<Fab
|
||||
aria-label="save"
|
||||
color="primary"
|
||||
sx={buttonPreInstallSx}
|
||||
disabled={!failedPreInstall}
|
||||
onClick={failedPreInstall ? () => install(0) : () => {}}
|
||||
>
|
||||
{
|
||||
!failedPreInstall ? successPreInstall ? <DoneOutline sx={{
|
||||
color: green[500],
|
||||
}}/> : <DownloadingOutlined /> : <ErrorOutline sx={{ color: red[500] }} />
|
||||
}
|
||||
{!failedPreInstall ? (
|
||||
successPreInstall ? (
|
||||
<DoneOutline
|
||||
sx={{
|
||||
color: green[500],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<DownloadingOutlined />
|
||||
)
|
||||
) : (
|
||||
<ErrorOutline sx={{ color: red[500] }} />
|
||||
)}
|
||||
</Fab>
|
||||
{loadingPreInstall && (
|
||||
<CircularProgress
|
||||
size={68}
|
||||
sx={{
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item container justifyContent="flex-start" alignItems="center" xs={3}>
|
||||
<span>Downloading dependencies</span>
|
||||
<span>Downloading dependencies ({percentagePreInstall}%)</span>
|
||||
</Grid>
|
||||
<Grid item xs={3}></Grid>
|
||||
</Grid>
|
||||
<Grid item container>
|
||||
<Grid item xs={3}></Grid>
|
||||
<Grid item container justifyContent="center" alignItems="center" xs={2}>
|
||||
<Box sx={{ m: 1, position: 'relative' }}>
|
||||
<Fab
|
||||
<Box sx={{ m: 1, position: "relative" }}>
|
||||
<Fab
|
||||
aria-label="save"
|
||||
color="primary"
|
||||
sx={buttonInstallSx}
|
||||
disabled={!failedInstall}
|
||||
onClick={failedInstall ? () => install(1) : () => {}}
|
||||
>
|
||||
{
|
||||
!failedInstall ? successInstall ? <DoneOutline sx={{
|
||||
color: green[500],
|
||||
}} /> : <ComputerOutlined /> : <ErrorOutline sx={{ color: red[500] }} />
|
||||
}
|
||||
{!failedInstall ? (
|
||||
successInstall ? (
|
||||
<DoneOutline
|
||||
sx={{
|
||||
color: green[500],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ComputerOutlined />
|
||||
)
|
||||
) : (
|
||||
<ErrorOutline sx={{ color: red[500] }} />
|
||||
)}
|
||||
</Fab>
|
||||
{loadingInstall && (
|
||||
<CircularProgress
|
||||
size={68}
|
||||
sx={{
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item container justifyContent="flex-start" alignItems="center" xs={4}>
|
||||
<span>Installing services</span>
|
||||
<span>Installing services ({percentageInstall}%)</span>
|
||||
</Grid>
|
||||
<Grid item xs={3}></Grid>
|
||||
</Grid>
|
||||
{/* <Grid item container>
|
||||
<Grid item xs={3}></Grid>
|
||||
<Grid item container justifyContent="center" alignItems="center" xs={2}>
|
||||
<Box sx={{ m: 1, position: "relative" }}>
|
||||
<Fab aria-label="save" color="primary" sx={buttonKeyImportSx} disabled={!failedKeyImport} onClick={failedKeyImport ? () => install(2) : () => {}}>
|
||||
{!failedKeyImport ? (
|
||||
successKeyImport ? (
|
||||
<DoneOutline
|
||||
sx={{
|
||||
color: green[500],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<KeyOutlined />
|
||||
)
|
||||
) : (
|
||||
<ErrorOutline sx={{ color: red[500] }} />
|
||||
)}
|
||||
</Fab>
|
||||
{loadingKeyImport && (
|
||||
<CircularProgress
|
||||
size={68}
|
||||
sx={{
|
||||
color: green[500],
|
||||
position: "absolute",
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item container justifyContent="flex-start" alignItems="center" xs={4}>
|
||||
<span>Key Import</span>
|
||||
</Grid>
|
||||
<Grid item xs={3}></Grid>
|
||||
</Grid> */}
|
||||
<Grid item container>
|
||||
<Grid item xs={3}></Grid>
|
||||
<Grid item container justifyContent="center" alignItems="center" xs={2}>
|
||||
<Box sx={{ m: 1, position: 'relative' }}>
|
||||
<Fab
|
||||
aria-label="save"
|
||||
color="primary"
|
||||
sx={buttonKeyImportSx}
|
||||
disabled={!failedKeyImport}
|
||||
onClick={failedKeyImport ? () => install(2) : () => {}}
|
||||
>
|
||||
{
|
||||
!failedKeyImport ? successKeyImport ? <DoneOutline sx={{
|
||||
color: green[500],
|
||||
}} /> : <KeyOutlined />: <ErrorOutline sx={{ color: red[500] }} />
|
||||
}
|
||||
</Fab>
|
||||
{loadingKeyImport && (
|
||||
<CircularProgress
|
||||
size={68}
|
||||
sx={{
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item container justifyContent="flex-start" alignItems="center" xs={4}>
|
||||
<span>Key Import</span>
|
||||
</Grid>
|
||||
<Grid item xs={3}></Grid>
|
||||
</Grid>
|
||||
<Grid item container>
|
||||
<Grid item xs={3}></Grid>
|
||||
<Grid item container justifyContent="center" alignItems="center" xs={2}>
|
||||
<Box sx={{ m: 1, position: 'relative' }}>
|
||||
<Fab
|
||||
<Box sx={{ m: 1, position: "relative" }}>
|
||||
<Fab
|
||||
aria-label="save"
|
||||
color="primary"
|
||||
sx={buttonPostInstallSx}
|
||||
disabled={!failedPostInstall}
|
||||
onClick={failedPostInstall ? () => install(3) : () => {}}
|
||||
>
|
||||
{
|
||||
!failedPostInstall ? successPostInstall ?
|
||||
<DoneOutline sx={{
|
||||
color: green[500],
|
||||
}} /> : <RocketLaunchOutlined /> : <ErrorOutline sx={{ color: red[500] }} />
|
||||
}
|
||||
{!failedPostInstall ? (
|
||||
successPostInstall ? (
|
||||
<DoneOutline
|
||||
sx={{
|
||||
color: green[500],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<RocketLaunchOutlined />
|
||||
)
|
||||
) : (
|
||||
<ErrorOutline sx={{ color: red[500] }} />
|
||||
)}
|
||||
</Fab>
|
||||
{loadingPostInstall && (
|
||||
<CircularProgress
|
||||
size={68}
|
||||
sx={{
|
||||
color: green[500],
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: -6,
|
||||
left: -6,
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item container justifyContent="flex-start" alignItems="center" xs={4}>
|
||||
<span>Configuring and launching</span>
|
||||
<span>Configuring and launching ({percentagePostInstall}%)</span>
|
||||
</Grid>
|
||||
<Grid item xs={3}></Grid>
|
||||
</Grid>
|
||||
@@ -409,18 +540,18 @@ const [keystorePassword, setKeystorePassword] = useState<string>('')
|
||||
disableBack={disableBack}
|
||||
disableNext={disableForward}
|
||||
/>
|
||||
<ImportKeystore
|
||||
setModalOpen={setModalOpen}
|
||||
isModalOpen={isModalOpen}
|
||||
setKeystorePassword={setKeystorePassword}
|
||||
setKeystorePath={setKeystorePath}
|
||||
keyStorePath={keyStorePath}
|
||||
keystorePassword={keystorePassword}
|
||||
closing={resolveModal}
|
||||
installationDetails={props.installationDetails}
|
||||
/>
|
||||
{/* <ImportKeystore
|
||||
setModalOpen={setModalOpen}
|
||||
isModalOpen={isModalOpen}
|
||||
setKeystorePassword={setKeystorePassword}
|
||||
setKeystorePath={setKeystorePath}
|
||||
keyStorePath={keyStorePath}
|
||||
keystorePassword={keystorePassword}
|
||||
closing={resolveModal}
|
||||
installationDetails={props.installationDetails}
|
||||
/> */}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Install;
|
||||
|
||||
@@ -13,7 +13,7 @@ const SoftText = styled(Typography)`
|
||||
|
||||
const Container = styled.div`
|
||||
position: fixed;
|
||||
bottom: 35;
|
||||
bottom: 15;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
|
||||
98
src/react/components/modals/ManageValidators.tsx
Normal file
98
src/react/components/modals/ManageValidators.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { BackgroundLight } from "../../colors";
|
||||
import { Typography, Box, Grid, Modal, IconButton, Tooltip } from "@mui/material";
|
||||
import React, { FC, Dispatch, ReactElement, SetStateAction } from "react";
|
||||
import { FormatPubkey } from "../../../utility";
|
||||
import { HighlightOff } from "@mui/icons-material";
|
||||
|
||||
const ModalStyle = {
|
||||
position: "absolute" as "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 600,
|
||||
padding: "20px",
|
||||
borderRadius: "20px",
|
||||
background: BackgroundLight,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
};
|
||||
|
||||
type ManageValidatorsProps = {
|
||||
setModalOpen: Dispatch<SetStateAction<boolean>>;
|
||||
isModalOpen: boolean;
|
||||
validators: Array<string>;
|
||||
};
|
||||
|
||||
const handleDeleteValidator = () => {};
|
||||
|
||||
const renderValidators: (v: Array<string>) => JSX.Element[] = (
|
||||
validators: Array<string>,
|
||||
): JSX.Element[] => {
|
||||
let rows = [];
|
||||
for (let i = 0; i < validators.length; i++) {
|
||||
rows.push(
|
||||
<Grid key={validators[i]} container paddingX={1} paddingY={2} alignItems="center">
|
||||
<Grid xs={4} item>
|
||||
{FormatPubkey(validators[i])}
|
||||
</Grid>
|
||||
<Grid xs={4} item>
|
||||
Running
|
||||
</Grid>
|
||||
<Grid xs={4} item container justifyContent="flex-end">
|
||||
<Grid item>
|
||||
<Tooltip title="Delete Validator">
|
||||
<IconButton color="error" onClick={handleDeleteValidator} tabIndex={1}>
|
||||
<HighlightOff />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>,
|
||||
);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
/**
|
||||
* This is the network picker modal component where the user selects the desired network.
|
||||
*
|
||||
* @param props.isModalOpen the current open state of the modal
|
||||
* @param props.setModalOpen a function to set the modal open state
|
||||
* @param props.validators is an array of validator public keys that are imported
|
||||
* @returns the import validator key modal
|
||||
*/
|
||||
export const ManageValidators: FC<ManageValidatorsProps> = (props): ReactElement => {
|
||||
return (
|
||||
<Modal
|
||||
open={props.isModalOpen}
|
||||
onClose={() => props.setModalOpen(false)}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box component="form" sx={ModalStyle}>
|
||||
<Typography id="modal-modal-title" align="center" variant="h4" component="h2">
|
||||
Manage Validators
|
||||
</Typography>
|
||||
<hr style={{ borderColor: "orange" }} />
|
||||
<Grid container paddingX={1} alignItems="center">
|
||||
<Grid xs={4} item>
|
||||
<Typography marginY={1} align="left" variant="h3" component="h2">
|
||||
Pubkey
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid xs={4} item>
|
||||
<Typography marginY={1} align="left" variant="h3" component="h2">
|
||||
Status
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid xs={4} item>
|
||||
<Typography marginY={1} align="right" variant="h3" component="h2">
|
||||
{/* Action */}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{renderValidators(props.validators)}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,63 +1,68 @@
|
||||
import { ConsensusClient, ExecutionClient } from '../electron/IMultiClientInstaller';
|
||||
import { StepKey } from './types';
|
||||
import { ConsensusClient, ExecutionClient } from "../electron/IMultiClientInstaller";
|
||||
import { StepKey } from "./types";
|
||||
|
||||
export const errors = {
|
||||
FOLDER: "Please select a folder.",
|
||||
FOLDER_DOES_NOT_EXISTS: "Folder does not exist. Select an existing folder.",
|
||||
FOLDER_IS_NOT_WRITABLE: "Cannot write in this folder. Select a folder in which you have write permission.",
|
||||
FOLDER: "Please select a folder.",
|
||||
FOLDER_DOES_NOT_EXISTS: "Folder does not exist. Select an existing folder.",
|
||||
FOLDER_IS_NOT_WRITABLE:
|
||||
"Cannot write in this folder. Select a folder in which you have write permission.",
|
||||
};
|
||||
|
||||
export const stepLabels = {
|
||||
[StepKey.SystemCheck]: 'System Check',
|
||||
[StepKey.Configuration]: 'Configuration',
|
||||
[StepKey.Installing]: 'Install',
|
||||
[StepKey.SystemCheck]: "System Check",
|
||||
[StepKey.Configuration]: "Configuration",
|
||||
[StepKey.Installing]: "Install",
|
||||
};
|
||||
|
||||
export const nullAddress = "0x0000000000000000000000000000000000000000";
|
||||
|
||||
export interface IExecutionClient {
|
||||
key: ExecutionClient;
|
||||
label: string;
|
||||
key: ExecutionClient;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface IConsensusClient {
|
||||
key: ConsensusClient;
|
||||
label: string;
|
||||
key: ConsensusClient;
|
||||
label: string;
|
||||
}
|
||||
|
||||
|
||||
export const ConsensusClients: IConsensusClient[] = [{
|
||||
key: ConsensusClient.PRYSM,
|
||||
label: 'Prysm',
|
||||
},
|
||||
{
|
||||
key: ConsensusClient.LIGHTHOUSE,
|
||||
label: 'Lighthouse',
|
||||
}, {
|
||||
key: ConsensusClient.NIMBUS,
|
||||
label: 'Nimbus',
|
||||
}, {
|
||||
key: ConsensusClient.TEKU,
|
||||
label: 'Teku'
|
||||
},
|
||||
{
|
||||
key: ConsensusClient.LODESTAR,
|
||||
label: 'Lodestar'
|
||||
},
|
||||
]
|
||||
export const ExecutionClients: IExecutionClient[] = [{
|
||||
key: ExecutionClient.GETH,
|
||||
label: 'Geth',
|
||||
},
|
||||
{
|
||||
key: ExecutionClient.ERIGON,
|
||||
label: 'Erigon'
|
||||
},
|
||||
{
|
||||
key: ExecutionClient.BESU,
|
||||
label: 'Besu'
|
||||
},
|
||||
{
|
||||
key: ExecutionClient.NETHERMIND,
|
||||
label: 'Nethermind'
|
||||
},
|
||||
]
|
||||
export const ConsensusClients: IConsensusClient[] = [
|
||||
{
|
||||
key: ConsensusClient.PRYSM,
|
||||
label: "Prysm",
|
||||
},
|
||||
{
|
||||
key: ConsensusClient.LIGHTHOUSE,
|
||||
label: "Lighthouse",
|
||||
},
|
||||
{
|
||||
key: ConsensusClient.NIMBUS,
|
||||
label: "Nimbus",
|
||||
},
|
||||
{
|
||||
key: ConsensusClient.TEKU,
|
||||
label: "Teku",
|
||||
},
|
||||
{
|
||||
key: ConsensusClient.LODESTAR,
|
||||
label: "Lodestar",
|
||||
},
|
||||
];
|
||||
export const ExecutionClients: IExecutionClient[] = [
|
||||
{
|
||||
key: ExecutionClient.GETH,
|
||||
label: "Geth",
|
||||
},
|
||||
{
|
||||
key: ExecutionClient.ERIGON,
|
||||
label: "Erigon",
|
||||
},
|
||||
{
|
||||
key: ExecutionClient.BESU,
|
||||
label: "Besu",
|
||||
},
|
||||
{
|
||||
key: ExecutionClient.NETHERMIND,
|
||||
label: "Nethermind",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<html>
|
||||
<body style="width:100%;height:100%;margin:0">
|
||||
<div id="app" style="width:100%;height:100%">
|
||||
</div>
|
||||
<body style="width: 100%; height: 100%; margin: 0">
|
||||
<div id="app" style="width: 100%; height: 100%"></div>
|
||||
<script>
|
||||
window.global = {};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -5,6 +5,5 @@ import App from "./App";
|
||||
|
||||
// We find our app DOM element as before
|
||||
const app = document.getElementById("app");
|
||||
|
||||
// Finally, we render our top-level component to the actual DOM.
|
||||
ReactDOM.render(<App />, app);
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import React, { FC, ReactElement, SetStateAction, useState, Dispatch } from 'react';
|
||||
import React, { FC, ReactElement, SetStateAction, useState, Dispatch } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { Stepper, Step, StepLabel, Grid, Typography } from '@mui/material';
|
||||
import styled from '@emotion/styled';
|
||||
import { StepKey } from '../types';
|
||||
import { stepLabels } from '../constants';
|
||||
import { StepSequenceKey } from '../types';
|
||||
import VersionFooter from '../components/VersionFooter';
|
||||
import Install from '../components/InstallFlow/2-Install';
|
||||
import Configuration from '../components/InstallFlow/1-Configuration';
|
||||
import SystemCheck from '../components/InstallFlow/0-SystemCheck';
|
||||
import { InstallDetails } from '../../electron/IMultiClientInstaller';
|
||||
import { Stepper, Step, StepLabel, Grid, Typography } from "@mui/material";
|
||||
import styled from "@emotion/styled";
|
||||
import { StepKey } from "../types";
|
||||
import { stepLabels } from "../constants";
|
||||
import { StepSequenceKey } from "../types";
|
||||
import VersionFooter from "../components/VersionFooter";
|
||||
import Install from "../components/InstallFlow/2-Install";
|
||||
import Configuration from "../components/InstallFlow/1-Configuration";
|
||||
import SystemCheck from "../components/InstallFlow/0-SystemCheck";
|
||||
import { InstallDetails } from "../../electron/IMultiClientInstaller";
|
||||
|
||||
const stepSequenceMap: Record<string, StepKey[]> = {
|
||||
install: [
|
||||
// StepKey.SystemCheck,
|
||||
StepKey.Configuration,
|
||||
StepKey.Installing,
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
const MainGrid = styled(Grid)`
|
||||
`;
|
||||
|
||||
const StyledStepper = styled(Stepper)`
|
||||
background-color: transparent;
|
||||
`
|
||||
`;
|
||||
|
||||
type RouteParams = {
|
||||
stepSequenceKey: StepSequenceKey;
|
||||
};
|
||||
|
||||
type WizardProps = {
|
||||
installationDetails: InstallDetails,
|
||||
setInstallationDetails: Dispatch<SetStateAction<InstallDetails>>
|
||||
}
|
||||
installationDetails: InstallDetails;
|
||||
setInstallationDetails: Dispatch<SetStateAction<InstallDetails>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the main wizard through which each piece of functionality for the app runs.
|
||||
*
|
||||
*
|
||||
* This wizard manages the global stepper showing the user where they are in the process.
|
||||
*
|
||||
*
|
||||
* @param props passed in data for the component to use
|
||||
* @returns the react element to render
|
||||
*/
|
||||
@@ -52,17 +52,16 @@ const Wizard: FC<WizardProps> = (props): ReactElement => {
|
||||
const stepSequence = stepSequenceMap[stepSequenceKey as string];
|
||||
const activeStepKey = stepSequence[activeStepIndex];
|
||||
|
||||
|
||||
const onStepForward = () => {
|
||||
if (activeStepIndex === stepSequence.length - 1) {
|
||||
const location = {
|
||||
pathname: `/systemOverview`
|
||||
}
|
||||
pathname: `/dashboard`,
|
||||
};
|
||||
|
||||
navigate(location);
|
||||
}
|
||||
setActiveStepIndex(activeStepIndex + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const onStepBack = () => {
|
||||
if (activeStepIndex === 0) {
|
||||
@@ -70,7 +69,7 @@ const Wizard: FC<WizardProps> = (props): ReactElement => {
|
||||
} else {
|
||||
setActiveStepIndex(activeStepIndex - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the UI stepper component rendering where the user is in the process
|
||||
@@ -87,7 +86,6 @@ const Wizard: FC<WizardProps> = (props): ReactElement => {
|
||||
</Grid>
|
||||
);
|
||||
|
||||
|
||||
const commonProps = {
|
||||
onStepForward,
|
||||
onStepBack,
|
||||
@@ -103,21 +101,15 @@ const Wizard: FC<WizardProps> = (props): ReactElement => {
|
||||
const stepComponentSwitch = (): ReactElement => {
|
||||
switch (activeStepKey) {
|
||||
case StepKey.SystemCheck:
|
||||
return (
|
||||
<SystemCheck {...{ ...commonProps }} />
|
||||
);
|
||||
return <SystemCheck {...{ ...commonProps }} />;
|
||||
case StepKey.Configuration:
|
||||
return (
|
||||
<Configuration {...{ ...commonProps }} />
|
||||
);
|
||||
return <Configuration {...{ ...commonProps }} />;
|
||||
case StepKey.Installing:
|
||||
return (
|
||||
<Install {...{ ...commonProps }} />
|
||||
);
|
||||
return <Install {...{ ...commonProps }} />;
|
||||
default:
|
||||
return <div>No component for this step</div>
|
||||
return <div>No component for this step</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MainGrid container direction="column">
|
||||
@@ -135,6 +127,6 @@ const Wizard: FC<WizardProps> = (props): ReactElement => {
|
||||
<VersionFooter />
|
||||
</MainGrid>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Wizard;
|
||||
|
||||
@@ -1,44 +1,911 @@
|
||||
import React, { FC, ReactElement } from "react";
|
||||
import { Grid, Typography } from '@mui/material';
|
||||
import styled from '@emotion/styled';
|
||||
import React, { FC, ReactElement, useState, useRef, useMemo } from "react";
|
||||
import {
|
||||
Grid,
|
||||
Typography,
|
||||
Button,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Alert,
|
||||
Snackbar,
|
||||
AlertColor,
|
||||
} from "@mui/material";
|
||||
import styled from "@emotion/styled";
|
||||
import VersionFooter from "../components/VersionFooter";
|
||||
import { InstallDetails } from "../../electron/IMultiClientInstaller";
|
||||
import { Capitalize } from "../../utility";
|
||||
import {
|
||||
Settings,
|
||||
Add,
|
||||
Stop,
|
||||
PlayArrow,
|
||||
Notifications,
|
||||
Upgrade,
|
||||
} from "@mui/icons-material";
|
||||
import { ImportKeystore } from "../components/ImportKeystore";
|
||||
import { ManageValidators } from "../components/modals/ManageValidators";
|
||||
|
||||
const MainGrid = styled(Grid)`
|
||||
width: 100%;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
margin: 0px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Card = styled(Grid)`
|
||||
background: rgba(0, 94, 86, 0.2);
|
||||
border: 1px solid rgba(255, 166, 0, 0.3);
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
`;
|
||||
|
||||
const HorizontalLine = styled("div")`
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
bottom: 25%;
|
||||
border-left: 1px solid rgba(255, 166, 0, 0.3);
|
||||
left: calc(50%);
|
||||
`;
|
||||
|
||||
type SystemOverviewProps = {
|
||||
installationDetails: InstallDetails
|
||||
}
|
||||
installationDetails: InstallDetails;
|
||||
};
|
||||
|
||||
export type NodeState =
|
||||
| "syncing"
|
||||
| "installing"
|
||||
| "synced"
|
||||
| "waiting"
|
||||
| "stopped"
|
||||
| "stopping"
|
||||
| "starting";
|
||||
|
||||
const SystemOverview: FC<SystemOverviewProps> = (props): ReactElement => {
|
||||
const [statusConsensusNode, setStatusConsensusNode] = useState<NodeState>("syncing");
|
||||
const [consensusUpdateAvailable, setConsensusUpdateAvailable] = useState<boolean>(true);
|
||||
const [peersConsensusNode, setPeersConsensusNode] = useState(0);
|
||||
const [currentEpoch, setCurrentEpoch] = useState(0);
|
||||
const [syncTimeLeft, setSyncTimeLeft] = useState("1 hour");
|
||||
const [consensusVersion, setConsensusVersion] = useState("2.1");
|
||||
|
||||
const UpdateConsensusNodeIcon = () =>
|
||||
useMemo(() => {
|
||||
if (consensusUpdateAvailable) {
|
||||
return (
|
||||
<Tooltip title="Upgrade Consensus Node">
|
||||
<IconButton color="primary" onClick={handleConsensusNodeUpdate} tabIndex={1}>
|
||||
<Upgrade />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<IconButton disabled color="primary" tabIndex={1}>
|
||||
<Upgrade />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
}, [consensusUpdateAvailable]);
|
||||
|
||||
const handleConsensusNodeUpdate = () => {
|
||||
setStatusConsensusNode("installing");
|
||||
setConsensusUpdateAvailable(false);
|
||||
setTimeout(() => {
|
||||
setStatusConsensusNode("starting");
|
||||
setTimeout(() => {
|
||||
setStatusConsensusNode("synced");
|
||||
}, 3000);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const StartStopConsensusNodeIcon = () =>
|
||||
useMemo(() => {
|
||||
switch (statusConsensusNode) {
|
||||
case "syncing":
|
||||
return (
|
||||
<Tooltip title="Stop Consensus Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateConsensus}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "installing":
|
||||
return (
|
||||
<IconButton disabled color="primary" tabIndex={1}>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
);
|
||||
case "stopping":
|
||||
return (
|
||||
<IconButton disabled color="primary" tabIndex={1}>
|
||||
<PlayArrow />
|
||||
</IconButton>
|
||||
);
|
||||
case "stopped":
|
||||
return (
|
||||
<Tooltip title="Start Consensus Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateConsensus}
|
||||
tabIndex={1}
|
||||
>
|
||||
<PlayArrow />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "starting":
|
||||
return (
|
||||
<IconButton disabled color="primary" tabIndex={1}>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
);
|
||||
case "waiting":
|
||||
return (
|
||||
<Tooltip title="Stop Consensus Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateConsensus}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "synced":
|
||||
return (
|
||||
<Tooltip title="Stop Consensus Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateConsensus}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}, [statusConsensusNode]);
|
||||
|
||||
const toggleRunningStateConsensus = () => {
|
||||
if (statusConsensusNode === "stopped") {
|
||||
setStatusConsensusNode("starting");
|
||||
setTimeout(() => {
|
||||
setStatusConsensusNode("synced");
|
||||
}, 3000);
|
||||
} else {
|
||||
setStatusConsensusNode("stopping");
|
||||
setTimeout(() => {
|
||||
setStatusConsensusNode("stopped");
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const [statusValidator, setStatusValidator] = useState<NodeState>("waiting");
|
||||
const [validatorVersion, setValidatorVersion] = useState("2.1");
|
||||
|
||||
const StartStopValidatorClientIcon = () =>
|
||||
useMemo(() => {
|
||||
switch (statusValidator) {
|
||||
case "syncing":
|
||||
return (
|
||||
<Tooltip title="Stop Validator Client">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateValidator}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "installing":
|
||||
return (
|
||||
<IconButton
|
||||
disabled
|
||||
color="primary"
|
||||
onClick={toggleRunningStateValidator}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
);
|
||||
case "stopping":
|
||||
return (
|
||||
<IconButton
|
||||
disabled
|
||||
color="primary"
|
||||
onClick={toggleRunningStateValidator}
|
||||
tabIndex={1}
|
||||
>
|
||||
<PlayArrow />
|
||||
</IconButton>
|
||||
);
|
||||
case "stopped":
|
||||
return (
|
||||
<Tooltip title="Start Validator Client">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateValidator}
|
||||
tabIndex={1}
|
||||
>
|
||||
<PlayArrow />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "starting":
|
||||
return (
|
||||
<IconButton
|
||||
disabled
|
||||
color="primary"
|
||||
onClick={toggleRunningStateValidator}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
);
|
||||
case "waiting":
|
||||
return (
|
||||
<Tooltip title="Stop Validator Client">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateValidator}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "synced":
|
||||
return (
|
||||
<Tooltip title="Stop Validator client">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateValidator}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}, [statusValidator]);
|
||||
|
||||
const toggleRunningStateValidator = () => {
|
||||
if (statusValidator === "stopped") {
|
||||
setStatusValidator("starting");
|
||||
setTimeout(() => {
|
||||
setStatusValidator("synced");
|
||||
}, 3000);
|
||||
} else {
|
||||
setStatusValidator("stopping");
|
||||
setTimeout(() => {
|
||||
setStatusValidator("stopped");
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const [statusExecutionNode, setStatusExecutionNode] = useState<NodeState>("syncing");
|
||||
const [peersExecutionNode, setPeersExecutionNode] = useState(0);
|
||||
const [executionVersion, setExecutionVersion] = useState("2.1");
|
||||
const [executionUpdateAvailable, setExecutionUpdateAvailable] = useState<boolean>(true);
|
||||
|
||||
const UpdateExecutionNodeIcon = () =>
|
||||
useMemo(() => {
|
||||
if (executionUpdateAvailable) {
|
||||
return (
|
||||
<Tooltip title="Upgrade Consensus Node">
|
||||
<IconButton color="primary" onClick={handleExecutionNodeUpdate} tabIndex={1}>
|
||||
<Upgrade />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<IconButton disabled color="primary" tabIndex={1}>
|
||||
<Upgrade />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
}, [executionUpdateAvailable]);
|
||||
|
||||
const handleExecutionNodeUpdate = () => {
|
||||
setStatusExecutionNode("installing");
|
||||
setExecutionUpdateAvailable(false);
|
||||
setTimeout(() => {
|
||||
setStatusExecutionNode("starting");
|
||||
setTimeout(() => {
|
||||
setStatusExecutionNode("synced");
|
||||
}, 3000);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const StartStopExecutionClientIcon = () =>
|
||||
useMemo(() => {
|
||||
switch (statusExecutionNode) {
|
||||
case "syncing":
|
||||
return (
|
||||
<Tooltip title="Stop Execution Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateExecutionNode}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "installing":
|
||||
return (
|
||||
<IconButton
|
||||
disabled
|
||||
color="primary"
|
||||
onClick={toggleRunningStateExecutionNode}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
);
|
||||
case "stopping":
|
||||
return (
|
||||
<IconButton
|
||||
disabled
|
||||
color="primary"
|
||||
onClick={toggleRunningStateExecutionNode}
|
||||
tabIndex={1}
|
||||
>
|
||||
<PlayArrow />
|
||||
</IconButton>
|
||||
);
|
||||
case "stopped":
|
||||
return (
|
||||
<Tooltip title="Start Execution Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateExecutionNode}
|
||||
tabIndex={1}
|
||||
>
|
||||
<PlayArrow />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "starting":
|
||||
return (
|
||||
<IconButton
|
||||
disabled
|
||||
color="primary"
|
||||
onClick={toggleRunningStateExecutionNode}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
);
|
||||
case "waiting":
|
||||
return (
|
||||
<Tooltip title="Stop Execution Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateExecutionNode}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
case "synced":
|
||||
return (
|
||||
<Tooltip title="Stop Execution Node">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={toggleRunningStateExecutionNode}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Stop />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}, [statusExecutionNode]);
|
||||
|
||||
const toggleRunningStateExecutionNode = () => {
|
||||
if (statusValidator === "stopped") {
|
||||
setStatusExecutionNode("starting");
|
||||
setTimeout(() => {
|
||||
setStatusExecutionNode("synced");
|
||||
}, 3000);
|
||||
} else {
|
||||
setStatusExecutionNode("stopping");
|
||||
setTimeout(() => {
|
||||
setStatusExecutionNode("stopped");
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const [isManageValidatorModalOpen, setManageValidatorModalOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [isImportKeyModalOpen, setImportKeyModalOpen] = useState<boolean>(false);
|
||||
const [keyStorePath, setKeystorePath] = useState<string>("");
|
||||
const [keystorePassword, setKeystorePassword] = useState<string>("");
|
||||
const resolveModal = useRef<(arg: () => Promise<boolean>) => void>();
|
||||
|
||||
new Promise((resolve: (arg: () => Promise<boolean>) => void) => {
|
||||
resolveModal.current = resolve;
|
||||
})
|
||||
.then((keyImp: () => Promise<boolean>) => {
|
||||
keyImp()
|
||||
.then((key) => {
|
||||
console.log(key);
|
||||
if (key) {
|
||||
toggleAlert("Keystore import successful", "success");
|
||||
} else {
|
||||
toggleAlert("Keystore import failed", "error");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toggleAlert("Keystore import failed", "error");
|
||||
console.error("error key import modal rejected with", err);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
toggleAlert("Keystore import failed", "error");
|
||||
console.error("error key import modal rejected with", err);
|
||||
});
|
||||
|
||||
const [alertOpen, setAlertOpen] = useState(false);
|
||||
const [alertMessage, setAlertMessage] = useState("This is an error");
|
||||
const [alertSeverity, setAlertSeverity] = useState("success" as AlertColor);
|
||||
|
||||
const toggleAlert = (message: string, severity: AlertColor) => {
|
||||
setAlertMessage(message);
|
||||
setAlertSeverity(severity);
|
||||
setAlertOpen(true);
|
||||
};
|
||||
|
||||
const handleAlertClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
|
||||
if (reason === "clickaway") {
|
||||
return;
|
||||
}
|
||||
|
||||
setAlertOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<MainGrid container spacing={5} direction="column">
|
||||
<Grid item container>
|
||||
<Grid item xs={10} />
|
||||
<Grid item xs={2}>
|
||||
<Typography variant="caption" style={{ color: "gray" }}>
|
||||
Network: {props.installationDetails.network}
|
||||
</Typography>
|
||||
<MainGrid container direction={"column"}>
|
||||
<Grid xs={1} item>
|
||||
<Typography variant="h1" align="center">
|
||||
Overview
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid xs={11} container item>
|
||||
<Grid
|
||||
xs={6}
|
||||
paddingY={2}
|
||||
paddingX={1}
|
||||
container
|
||||
item
|
||||
direction={"column"}
|
||||
justifyContent="space-around"
|
||||
>
|
||||
<Grid xs={2} item container>
|
||||
<Grid container item justifyContent="space-between" alignItems="center">
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="h3" align="left">
|
||||
{Capitalize(props.installationDetails.consensusClient)} Node
|
||||
<span style={{ fontSize: "1rem", opacity: "0.8" }}>
|
||||
{" "}
|
||||
v{consensusVersion}
|
||||
</span>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container item xs={6} justifyContent="flex-end">
|
||||
<Grid item>
|
||||
<UpdateConsensusNodeIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<StartStopConsensusNodeIcon />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Card xs={9} position="relative" item container justifyContent="stretch">
|
||||
<HorizontalLine />
|
||||
<Grid xs={12} padding={2} container item alignItems="center">
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Status</Grid>
|
||||
<Grid item>
|
||||
<span>{statusConsensusNode}</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Epoch</Grid>
|
||||
<Grid item>{currentEpoch}</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Peers</Grid>
|
||||
<Grid item>{peersConsensusNode}</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Remaining</Grid>
|
||||
<Grid item>{syncTimeLeft}</Grid>
|
||||
</Grid>
|
||||
{/* <Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>DB Path</Grid>
|
||||
<Grid item>/home/</Grid>
|
||||
</Grid> */}
|
||||
{/* <Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Node</Grid>
|
||||
<Grid item>Local</Grid>
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid
|
||||
xs={6}
|
||||
paddingY={2}
|
||||
paddingX={1}
|
||||
container
|
||||
item
|
||||
direction={"column"}
|
||||
justifyContent="space-around"
|
||||
>
|
||||
<Grid xs={2} item container alignItems="center">
|
||||
<Grid container item justifyContent="space-between" alignItems="center">
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="h3" align="left">
|
||||
Validator Client
|
||||
<span style={{ fontSize: "1rem", opacity: "0.8" }}>
|
||||
{" "}
|
||||
v{validatorVersion}
|
||||
</span>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid xs={6} container item justifyContent="flex-end">
|
||||
<Grid item>
|
||||
<Tooltip title="Add Validator">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setImportKeyModalOpen(true);
|
||||
}}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Add />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Tooltip title="Manage Validators">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setManageValidatorModalOpen(true);
|
||||
}}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Settings />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<StartStopValidatorClientIcon />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Card xs={9} position="relative" item container justifyContent="stretch">
|
||||
<HorizontalLine />
|
||||
<Grid xs={12} padding={2} container item alignItems="center">
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Status</Grid>
|
||||
<Grid item>{statusValidator}</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>
|
||||
<span>Validators</span>
|
||||
</Grid>
|
||||
<Grid item>5</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>DB Path</Grid>
|
||||
<Grid item>/home/</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Node</Grid>
|
||||
<Grid item>Local</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid
|
||||
xs={6}
|
||||
paddingY={2}
|
||||
paddingX={1}
|
||||
container
|
||||
item
|
||||
direction={"column"}
|
||||
justifyContent="space-around"
|
||||
>
|
||||
<Grid xs={2} item alignItems="center">
|
||||
<Grid container item justifyContent="space-between" alignItems="center">
|
||||
<Grid xs={6} item>
|
||||
<Typography variant="h3" align="left">
|
||||
{Capitalize(props.installationDetails.executionClient)} Node
|
||||
<span style={{ fontSize: "1rem", opacity: "0.8" }}>
|
||||
{" "}
|
||||
v{executionVersion}
|
||||
</span>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container item xs={6} justifyContent="flex-end">
|
||||
<Grid item>
|
||||
<UpdateExecutionNodeIcon />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<StartStopExecutionClientIcon />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Card xs={9} position="relative" item container justifyContent="stretch">
|
||||
<HorizontalLine />
|
||||
<Grid xs={12} padding={2} container item alignItems="center">
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Status</Grid>
|
||||
<Grid item>{statusExecutionNode}</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Remaining</Grid>
|
||||
<Grid item>1 hour</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>DB Path</Grid>
|
||||
<Grid item>/home/</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Peers</Grid>
|
||||
<Grid item>{peersExecutionNode}</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid
|
||||
xs={6}
|
||||
paddingY={2}
|
||||
paddingX={1}
|
||||
container
|
||||
item
|
||||
direction={"column"}
|
||||
justifyContent="space-around"
|
||||
>
|
||||
<Grid xs={2} item alignItems="center">
|
||||
<Grid container item justifyContent="space-between" alignItems="center">
|
||||
<Grid xs={6} item>
|
||||
<Typography variant="h3" align="left">
|
||||
System
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container item xs={6} justifyContent="flex-end">
|
||||
<Grid item>
|
||||
<Tooltip title="Configure alerts">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
window.open("https://beaconcha.in/user/notifications");
|
||||
}}
|
||||
tabIndex={1}
|
||||
>
|
||||
<Notifications />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Card xs={9} position="relative" item container justifyContent="stretch">
|
||||
<HorizontalLine />
|
||||
<Grid xs={12} padding={2} container item alignItems="center">
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Storage</Grid>
|
||||
<Grid item>600 GB</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>RAM</Grid>
|
||||
<Grid item>3 GB</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>Growth</Grid>
|
||||
<Grid item>10 GB/mo</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
justifyContent="space-between"
|
||||
container
|
||||
xs={6}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
item
|
||||
>
|
||||
<Grid item>CPU</Grid>
|
||||
<Grid item>30%</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h1">
|
||||
{/* System Overview */}
|
||||
Installation Complete
|
||||
</Typography>
|
||||
<ul style={{ margin: '0 auto', width: '350px', marginTop: '3rem', textAlign: 'left'}}>
|
||||
<li>Network: {props.installationDetails.network}</li>
|
||||
<li>Consensus Client: {props.installationDetails.consensusClient}</li>
|
||||
<li>Execution Client: {props.installationDetails.executionClient}</li>
|
||||
</ul>
|
||||
</Grid>
|
||||
<VersionFooter />
|
||||
<ImportKeystore
|
||||
setModalOpen={setImportKeyModalOpen}
|
||||
isModalOpen={isImportKeyModalOpen}
|
||||
setKeystorePassword={setKeystorePassword}
|
||||
setKeystorePath={setKeystorePath}
|
||||
keyStorePath={keyStorePath}
|
||||
keystorePassword={keystorePassword}
|
||||
closing={resolveModal}
|
||||
installationDetails={props.installationDetails}
|
||||
/>
|
||||
|
||||
<ManageValidators
|
||||
setModalOpen={setManageValidatorModalOpen}
|
||||
isModalOpen={isManageValidatorModalOpen}
|
||||
validators={[
|
||||
"97754fa1198496c4918f30ad7dc634cc5dceea36bf7d27c2e2c6c798b88484e458811e2d8ef833b8d750b83ae63dc33a",
|
||||
"94bb7f930d0d142d97a99cc9d6b98c04f247eda6d463b699bcc0460be371b9b7e22922bb10239f11b1fc44937cd16b8e",
|
||||
]}
|
||||
/>
|
||||
|
||||
<Snackbar
|
||||
open={alertOpen}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleAlertClose}
|
||||
anchorOrigin={{ vertical: "top", horizontal: "left" }}
|
||||
>
|
||||
<Alert onClose={handleAlertClose} severity={alertSeverity} sx={{ width: "100%" }}>
|
||||
{alertMessage}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</MainGrid>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemOverview;
|
||||
|
||||
{
|
||||
/* <Grid item container>
|
||||
<Grid item xs={10} />
|
||||
<Grid item xs={2}>
|
||||
<Typography variant="caption" style={{ color: "gray" }}>
|
||||
Network: {props.installationDetails.network}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h1">
|
||||
Installation Complete
|
||||
</Typography>
|
||||
<ul style={{ margin: '0 auto', width: '350px', marginTop: '3rem', textAlign: 'left'}}>
|
||||
<li>Network: {props.installationDetails.network}</li>
|
||||
<li>Consensus Client: {props.installationDetails.consensusClient}</li>
|
||||
<li>Execution Client: {props.installationDetails.executionClient}</li>
|
||||
</ul>
|
||||
</Grid> */
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ const theme = createTheme({
|
||||
].join(','),
|
||||
h1: {
|
||||
fontSize: "36px"
|
||||
},
|
||||
h2: {
|
||||
fontSize: "26px",
|
||||
},
|
||||
h3: {
|
||||
fontSize: "1.4rem",
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
export enum StepKey {
|
||||
SystemCheck,
|
||||
Configuration,
|
||||
Installing
|
||||
Installing,
|
||||
}
|
||||
|
||||
export enum StepSequenceKey {
|
||||
Install = "install"
|
||||
Install = "install",
|
||||
}
|
||||
|
||||
export enum Network {
|
||||
PRATER = "Prater",
|
||||
MAINNET = "Mainnet"
|
||||
MAINNET = "Mainnet",
|
||||
SEPOLIA = "Sepolia",
|
||||
}
|
||||
|
||||
export enum ExecutionNetwork {
|
||||
GOERLI = "goerli",
|
||||
MAINNET = "mainnet"
|
||||
MAINNET = "mainnet",
|
||||
SEPOLIA = "Sepolia",
|
||||
}
|
||||
|
||||
export const networkToExecution: Map<Network, ExecutionNetwork> = new Map([
|
||||
[Network.PRATER, ExecutionNetwork.GOERLI],
|
||||
[Network.MAINNET, ExecutionNetwork.MAINNET]
|
||||
]);
|
||||
[Network.MAINNET, ExecutionNetwork.MAINNET],
|
||||
[Network.SEPOLIA, ExecutionNetwork.SEPOLIA],
|
||||
]);
|
||||
|
||||
export interface BeaconMeta {
|
||||
count: number;
|
||||
}
|
||||
export interface BeaconResponse<T> {
|
||||
data: T;
|
||||
meta: BeaconMeta;
|
||||
}
|
||||
|
||||
export interface BeaconGetSyncingStatus {
|
||||
head_slot: string;
|
||||
sync_distance: string;
|
||||
is_syncing: boolean;
|
||||
is_optimistic: boolean;
|
||||
}
|
||||
|
||||
export interface BeaconGetPeers {
|
||||
peer_id: string;
|
||||
enr: string;
|
||||
last_seen_p2p_address: string;
|
||||
state: string;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
14
src/utility.ts
Normal file
14
src/utility.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function Capitalize(text: string) {
|
||||
if (text && text.length) {
|
||||
return text[0].toUpperCase() + text.substring(1)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
export function FormatPubkey(pubkey: string): string {
|
||||
if(pubkey && pubkey.length > 6) {
|
||||
// return pubkey.substring(0, 3) + "…" + pubkey.substring(-3, 3)
|
||||
return pubkey.substring(0, 6) + "..." + pubkey.substring(pubkey.length -6)
|
||||
}
|
||||
return pubkey
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
"target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./build", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
"outDir": "./build", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
@@ -23,9 +22,8 @@
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
@@ -33,7 +31,6 @@
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
@@ -41,7 +38,6 @@
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
@@ -50,22 +46,19 @@
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
||||
}
|
||||
13
webpack.devserver.js
Normal file
13
webpack.devserver.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
//...
|
||||
devServer: {
|
||||
static: {
|
||||
directory: path.resolve(__dirname, 'static'),
|
||||
},
|
||||
compress: true,
|
||||
port: 9000,
|
||||
mode: 'development',
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// pull in the 'path' module from node
|
||||
@@ -32,13 +31,16 @@ module.exports = {
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /node_modules\/JSONStream\/index\.js$/,
|
||||
loader: 'shebang-loader'
|
||||
}, {
|
||||
loader: 'shebang-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
}, {
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|svg)$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
@@ -59,7 +61,8 @@ module.exports = {
|
||||
COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
|
||||
BRANCH: JSON.stringify(gitRevisionPlugin.branch()),
|
||||
LASTCOMMITDATETIME: JSON.stringify(gitRevisionPlugin.lastcommitdatetime()),
|
||||
})
|
||||
}),
|
||||
],
|
||||
target: 'electron-renderer'
|
||||
// target: 'electron-renderer'
|
||||
target: 'web',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user