[WAGYU-43] Status enum (#52)

* add vscode workspace dir to gitignore

* update prettierrc

* add types file for statuses

* remove unused import

* create utils file for Status

* update imports

* add new NodeStatus

* prettier formatting updates

* use new Status enum

* use Status enum in RocketPool

* move pages to components, run formatter, clean up imports, remove default export

* format footer

* format SystemCheck, clean up imports, consolidate a few render functions

* update imports in App
This commit is contained in:
Jay Puntham-Baker
2021-08-10 19:16:14 -04:00
committed by GitHub
parent 24683cd39f
commit e221ab7432
14 changed files with 657 additions and 555 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
node_modules/
dist/
.idea
# Ignore Visual Studio Code IDE workspace settings
.vscode/

View File

@@ -1,6 +1,9 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
"bracketSpacing": true,
"jsxBracketSameLine": false,
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"useTabs": false
}

View File

@@ -1,14 +1,13 @@
import { HashRouter, Route, Switch } from "react-router-dom";
import { Background } from "./colors";
import Deposit from "./pages/Deposit";
import Home from "./pages/Home";
import InstallFailed from "./pages/InstallFailed";
import Installing from "./pages/Installing";
import React from "react";
import Status from "./pages/Status";
import SystemCheck from "./pages/SystemCheck";
import styled from "styled-components";
import React from 'react';
import styled from 'styled-components';
import { HashRouter, Route, Switch } from 'react-router-dom';
import { Background } from './colors';
import { Deposit } from './components/Deposit';
import { Home } from './components/Home';
import { InstallFailed } from './components/InstallFailed';
import Installing from './components/Installing';
import Status from './components/Status';
import { SystemCheck } from './components/SystemCheck';
const Container = styled.main`
font-family: 'PT Mono', monospace;

View File

@@ -3,13 +3,13 @@ import {
executeCommandAsync,
executeCommandInNewTerminal,
executeCommandStream,
executeCommandSync,
executeCommandSyncReturnStdout,
executeCommandWithPromptsAsync,
} from './ExecuteCommand'
import fs from "fs";
import yaml from "js-yaml";
import { Status } from "../types";
const ASKPASS_PATH = "src/scripts/askpass.sh";
@@ -26,7 +26,7 @@ const GETH_PEERS_DOCKER_CMD = "docker exec rocketpool_eth1 geth --exec 'admin.pe
// TODO: better error handling/logging
type Callback = (success: boolean) => void;
type NodeStatusCallback = (status: number) => void;
type NodeStatusCallback = (status: Status) => void;
type StdoutCallback = (text: string[]) => void;
const wrapCommandInDockerGroup = (command: string) => {
@@ -184,9 +184,9 @@ const dockerContainerStatus = async (containerName: string, nodeStatusCallback:
const containerId = executeCommandSyncReturnStdout(wrapCommandInDockerGroup("docker ps -q -f name=" + containerName));
if (containerId.trim()) {
nodeStatusCallback(0); // online
nodeStatusCallback(Status.Online);
} else {
nodeStatusCallback(2); // offline
nodeStatusCallback(Status.Offline);
}
}

View File

@@ -1,21 +1,15 @@
import {
Black,
DisabledButton,
Heading,
MainContent
} from "../colors";
import Footer from "../components/Footer";
import React from "react";
import { shell } from "electron";
import styled from "styled-components";
import React from 'react';
import { shell } from 'electron';
import styled from 'styled-components';
import { Black, DisabledButton, Heading, MainContent } from '../colors';
import Footer from './Footer';
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height:100vh;
min-height: 100vh;
`;
const LandingHeader = styled.div`
@@ -24,7 +18,7 @@ const LandingHeader = styled.div`
margin-top: 50;
color: ${Heading};
max-width: 550;
flex-grow:1;
flex-grow: 1;
`;
const Content = styled.div`
@@ -62,17 +56,19 @@ const ImportKeysButton = styled.div`
`;
const sendToLaunchpad = () => {
shell.openExternal("https://pyrmont.launchpad.ethereum.org/");
}
shell.openExternal('https://pyrmont.launchpad.ethereum.org/');
};
const Deposit = () => {
// TODO: add browse
export const Deposit = () => {
// TODO: add browse
// TODO: run validator then go to status page on Run click
return (
<Container>
<LandingHeader>Deposit</LandingHeader>
<Content>
1) Head to the <StyledLink onClick={sendToLaunchpad}>launchpad</StyledLink> to deposit 32 Goerli ETH.
1) Head to the
<StyledLink onClick={sendToLaunchpad}>launchpad</StyledLink> to deposit
32 Goerli ETH.
<br />
<br />
<em>Note: Your nodes are set up already, so ignore those steps.</em>
@@ -86,9 +82,9 @@ const Deposit = () => {
<br />
<ButtonContainer>
<ImportKeysButton>
Import Validator Keys `keystore-*.json` file
<br />
(still in development)
Import Validator Keys `keystore-*.json` file
<br />
(still in development)
</ImportKeysButton>
</ButtonContainer>
<br />
@@ -96,9 +92,12 @@ const Deposit = () => {
<br />
3) Click Run.
</Content>
<Footer backLink={"/status"} backLabel={"Back"} nextLink={"/status"} nextLabel={"Run"} />
<Footer
backLink={'/status'}
backLabel={'Back'}
nextLink={'/status'}
nextLabel={'Run'}
/>
</Container>
)
}
export default Deposit;
);
};

View File

@@ -1,15 +1,14 @@
import { Black, Button, ButtonHover } from "../colors";
import { Link } from "react-router-dom";
import React from "react";
import styled from "styled-components";
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { Black, Button, ButtonHover } from '../colors';
type FooterProps = {
backLink: string;
backLabel: string;
nextLink: string;
nextLabel: string;
}
};
const FooterContainer = styled.div`
display: flex;
@@ -18,8 +17,8 @@ const FooterContainer = styled.div`
align-items: center;
align-self: flex-end;
height: 70;
flex-grow:1;
min-width:100vw;
flex-grow: 1;
min-width: 100vw;
`;
const StyledButton = styled(Link)`
@@ -28,7 +27,7 @@ const StyledButton = styled(Link)`
flex-direction: row;
justify-content: center;
align-items: center;
align-self:flex-end;
align-self: flex-end;
height: 24;
background-color: ${Button};
padding: 16 24;
@@ -44,15 +43,21 @@ const StyledButton = styled(Link)`
}
`;
const Footer = (props: FooterProps) => {
return(
return (
<FooterContainer>
{ props.backLink ? <StyledButton to={props.backLink}>{props.backLabel}</StyledButton> : <div></div> }
{ props.nextLink ? <StyledButton to={props.nextLink}>{props.nextLabel}</StyledButton> : <div></div> }
{props.backLink ? (
<StyledButton to={props.backLink}>{props.backLabel}</StyledButton>
) : (
<div />
)}
{props.nextLink ? (
<StyledButton to={props.nextLink}>{props.nextLabel}</StyledButton>
) : (
<div />
)}
</FooterContainer>
)
);
};
}
export default Footer;
export default Footer;

View File

@@ -1,18 +1,17 @@
import React from 'react';
import styled from 'styled-components';
import { shell } from 'electron';
import { Link, withRouter } from 'react-router-dom';
import { History } from 'history';
import { isRocketPoolInstalled } from '../commands/RocketPool';
import {
Black,
Button,
ButtonHover,
Heading,
MainContent,
Red
} from "../colors";
import { Link, withRouter } from "react-router-dom";
import { History } from "history";
import React from "react";
import { isRocketPoolInstalled } from "../commands/RocketPool";
import { shell } from "electron";
import styled from "styled-components";
Red,
} from '../colors';
const Container = styled.div`
display: flex;
@@ -63,17 +62,17 @@ const StyledLink = styled.em`
`;
const Testnet = styled.b`
color: ${Red}
color: ${Red};
`;
const Home = ({ history }: {history: History}) => {
export const Home = withRouter(({ history }: { history: History }) => {
if (isRocketPoolInstalled()) {
history.push('/status');
}
const sendToRocketpool = () => {
shell.openExternal("https://www.rocketpool.net/");
}
shell.openExternal('https://www.rocketpool.net/');
};
return (
<Container>
@@ -81,24 +80,25 @@ const Home = ({ history }: {history: History}) => {
<Content>
This is your portal into the Eth2 world - welcome.
<br />
<br/>
<br />
A one-click staking installer for the <Testnet>pyrmont testnet</Testnet>.
<br/>
<br/>
<br/>
<br />A one-click staking installer for the
<Testnet>pyrmont testnet</Testnet>.
<br />
<br />
<br />
It will configure the following for you*:
<ul>
<li>Eth 1 Node</li>
<li>Eth 2 Beacon Node</li>
<li>Eth 2 Validator</li>
</ul>
<br/>
<br/>
*Note: we use the Rocket Pool install infrastructure which runs everything in docker, more info <StyledLink onClick={sendToRocketpool}>here</StyledLink>
<br />
<br />
*Note: we use the Rocket Pool install infrastructure which runs
everything in docker, more info
<StyledLink onClick={sendToRocketpool}>here</StyledLink>
</Content>
<StartButton to="/systemcheck">Enter</StartButton>
</Container>
);
};
export default withRouter(Home);
});

View File

@@ -1,15 +1,14 @@
import { Heading, MainContent } from '../colors';
import Footer from '../components/Footer';
import React from 'react';
import styled from 'styled-components';
import { Heading, MainContent } from '../colors';
import Footer from './Footer';
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height:100vh;
min-height: 100vh;
`;
const LandingHeader = styled.div`
@@ -18,10 +17,9 @@ const LandingHeader = styled.div`
margin-top: 50;
color: ${Heading};
max-width: 550;
flex-grow:1;
flex-grow: 1;
`;
const Content = styled.div`
color: ${MainContent};
margin-top: 20;
@@ -29,19 +27,19 @@ const Content = styled.div`
flex-grow: 6;
`;
const InstallFailed = () => {
export const InstallFailed = () => {
return (
<Container>
<LandingHeader>Install Failed</LandingHeader>
<Content>
Unfortunately your install failed. At this time we cannot provide any additonal info.
Unfortunately your install failed. At this time we cannot provide any
additonal info.
<br />
<br />
<br />
Please reach out to the ethstaker community for help.
</Content>
<Footer backLink={"/"} backLabel={"Home"} nextLink={""} nextLabel={""} />
<Footer backLink={'/'} backLabel={'Home'} nextLink={''} nextLabel={''} />
</Container>
);
}
export default InstallFailed;
};

View File

@@ -1,25 +1,16 @@
import {
Black,
Button,
ButtonHover,
Heading,
MainContent
} from "../colors";
import { Link, withRouter } from "react-router-dom";
import React, { useEffect, useRef, useState } from "react";
import styled, { keyframes } from "styled-components";
import { History } from "history";
import { installAndStartRocketPool } from "../commands/RocketPool";
const ENTER_KEYCODE = 13;
import React, { useEffect, useRef, useState } from 'react';
import styled, { keyframes } from 'styled-components';
import { withRouter } from 'react-router-dom';
import { History } from 'history';
import { Heading, MainContent } from '../colors';
import { installAndStartRocketPool } from '../commands/RocketPool';
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height:100vh;
min-height: 100vh;
`;
const LandingHeader = styled.div`
@@ -28,7 +19,7 @@ const LandingHeader = styled.div`
margin-top: 50;
color: ${Heading};
max-width: 550;
flex-grow:1;
flex-grow: 1;
`;
const Content = styled.div`
@@ -81,13 +72,12 @@ const LogsListItem = styled.li`
text-overflow: ellipsis;
`;
const LogsContainerAnchor = styled.div`
`;
const LogsContainerAnchor = styled.div``;
const Installing = ({ history }: {history: History}) => {
const anchorRef = useRef(document.createElement("div"));
const Installing = ({ history }: { history: History }) => {
const anchorRef = useRef(document.createElement('div'));
const [stdoutText, setStdoutText] = useState([""]);
const [stdoutText, setStdoutText] = useState(['']);
useEffect(() => {
setTimeout(() => {
@@ -100,55 +90,55 @@ const Installing = ({ history }: {history: History}) => {
}, [stdoutText]);
const stdoutCallback = (text: string[]) => {
console.log("installing cb with " + text.join());
console.log('installing cb with ' + text.join());
setStdoutText(stdoutText.concat(text));
}
};
const installCallback = (success: boolean) => {
if (success) {
console.log("install succeeded")
console.log('install succeeded');
// wait 5 seconds before redirecting to make sure everythings up
setTimeout(() => {
history.push('/status');
}, 5000);
} else {
console.log("install failed");
history.push("/installfailed");
console.log('install failed');
history.push('/installfailed');
}
}
};
return (
<Container>
<LandingHeader>Install</LandingHeader>
<Content>
Installing...
<br />
<br />
May take 2-4 minutes depending on internet speed.
<SpinnerContainer>
<LoadingSpinner />
</SpinnerContainer>
<br/>
{ // Only show logs container if there are some
stdoutText.length > 1
&&
<Content>
Installing...
<br />
<br />
May take 2-4 minutes depending on internet speed.
<SpinnerContainer>
<LoadingSpinner />
</SpinnerContainer>
<br />
{
// Only show logs container if there are some
stdoutText.length > 1 && (
<div>
Install logs:
<LogsContainer>
<LogsList>
{stdoutText.map((line, i) => {
return (<LogsListItem key={i}>{line}</LogsListItem>)
return <LogsListItem key={i}>{line}</LogsListItem>;
})}
</LogsList>
<LogsContainerAnchor ref={anchorRef}></LogsContainerAnchor>
</LogsContainer>
</div>
}
</Content>
)
}
</Content>
</Container>
)
}
);
};
export default withRouter(Installing);

View File

@@ -0,0 +1,382 @@
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import {
Black,
Button,
ButtonHover,
DarkGray,
Gray4,
Heading,
MainContent,
} from '../colors';
import { Status, AllStatuses, NodeStatuses } from '../types';
import {
sendToEthStakerDiscord,
sendToEthStakerSubreddit,
sendToGoerliEtherscan,
sendToPyrmontBeaconchain,
sendToGetGoerliEth,
sendToEthereumStudymaster,
} from '../utils';
import {
getEth2ClientName,
openEth1Logs,
openEth2BeaconLogs,
openEth2ValidatorLogs,
queryEth1PeerCount,
queryEth1Status,
queryEth1Syncing,
queryEth2BeaconStatus,
queryEth2ValidatorStatus,
startNodes,
stopNodes,
} from '../commands/RocketPool';
import Footer from './Footer';
const NodeStatus: NodeStatuses = {
Online: { code: 0, text: 'Online', character: '\u2B24', color: 'green' },
Syncing: { code: 1, text: 'Syncing', character: '\u2B24', color: 'yellow' },
Offline: { code: 2, text: 'Offline', character: '\u2B24', color: 'red' },
Loading: { code: 3, text: 'Loading...', character: '', color: '' },
};
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
`;
const LandingHeader = styled.div`
font-weight: 700;
font-size: 35;
margin-top: 50;
color: ${Heading};
max-width: 550;
flex-grow: 1;
`;
const Content = styled.div`
color: ${MainContent};
margin-top: 20;
width: 650;
flex-grow: 6;
`;
const ResultsTable = styled.table`
border: 2px solid gray;
width: 100%;
padding: 15px;
text-align: left;
color: white;
`;
const StyledLink = styled.span`
color: ${Heading};
cursor: pointer;
`;
const LogsButton = styled.button`
color: ${Black};
width: 100%;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: ${Button};
border-radius: 10px;
text-decoration: none;
transition: 250ms background-color ease;
cursor: pointer;
&:hover {
background-color: ${ButtonHover};
}
&:disabled {
cursor: default;
color: ${DarkGray};
background-color: ${Gray4};
}
`;
// TODO: right after install, while nodes are starting up, this page says everything is "online"
// while things are looking for peers. Need to improve that logic.
const StatusPage = () => {
const [eth1ContainerStatus, setEth1ContainerStatus] = useState<Status>(
Status.Loading,
);
const [eth1PeerCount, setEth1PeerCount] = useState(0);
const [eth1Syncing, setEth1Syncing] = useState(false);
const [eth2ClientName, setEth2ClientName] = useState('');
const [eth2BeaconContainerStatus, setEth2BeaconContainerStatus] =
useState<Status>(Status.Loading);
const [eth2ValidatorContainerStatus, setEth2ValidatorContainerStatus] =
useState<Status>(Status.Loading);
const [ProcessingTotalStatus, setProcessingTotalStatus] = useState<Status>(
Status.Online,
);
useEffect(() => {
setTimeout(() => {
queryStatuses();
setEth2ClientName(getEth2ClientName());
setInterval(queryStatuses, 5000);
}, 500);
}, []);
const queryStatuses = () => {
queryEth1Status(setEth1ContainerStatus);
setEth1Syncing(queryEth1Syncing());
setEth1PeerCount(queryEth1PeerCount());
queryEth2BeaconStatus(setEth2BeaconContainerStatus);
queryEth2ValidatorStatus(setEth2ValidatorContainerStatus);
};
const formatStatusIcon = (status: Status) => {
return (
<span style={{ color: NodeStatus[status].color }}>
{NodeStatus[status].character}
</span>
);
};
const formatTotalStatusButton = (running: boolean) => {
const toggleTotalStatus = async () => {
if (!running) {
startNodes();
setProcessingTotalStatus(Status.Syncing);
} else {
stopNodes();
setProcessingTotalStatus(Status.Offline);
}
};
let text = running ? 'Stop All' : 'Start All';
if (ProcessingTotalStatus) text = 'Processing...';
let totalStatusChanged =
(ProcessingTotalStatus === Status.Syncing && running) ||
(ProcessingTotalStatus === Status.Offline && !running);
if (totalStatusChanged) setProcessingTotalStatus(Status.Online);
return (
<LogsButton
onClick={toggleTotalStatus}
disabled={ProcessingTotalStatus !== Status.Online}
>
{text}
</LogsButton>
);
};
const eth1eth2synced = () => {
return (
computeEth1Status() === Status.Loading &&
eth2BeaconContainerStatus === Status.Online
);
};
const computeEth1Status = (): AllStatuses => {
if (eth1ContainerStatus === Status.Loading) {
return Status.Loading;
} else if (eth1ContainerStatus === Status.Offline) {
return Status.Offline;
} else if (eth1Syncing) {
return Status.Syncing;
} else {
return Status.Online;
}
};
const computeTotalStatus = () => {
return !(
computeEth1Status() === Status.Offline &&
eth2BeaconContainerStatus === Status.Offline &&
eth2ValidatorContainerStatus === Status.Offline
);
};
const renderNodeStatusTable = () => {
return (
<ResultsTable>
<thead>
<tr>
<th colSpan={3} />
<th style={{ width: '100px' }}>
{formatTotalStatusButton(computeTotalStatus())}
</th>
</tr>
<tr>
<th>Application</th>
<th>Status*</th>
<th># Peers</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Eth1 Node - geth</td>
<td>
{formatStatusIcon(computeEth1Status())}
{NodeStatus[computeEth1Status()].text}
</td>
<td>{eth1PeerCount}</td>
<td>
<LogsButton
onClick={openEth1Logs}
disabled={eth1ContainerStatus === Status.Offline}
>
View Logs
</LogsButton>
</td>
</tr>
<tr>
<td>Eth2 Beacon Node - {eth2ClientName}</td>
<td>
{formatStatusIcon(eth2BeaconContainerStatus)}
{NodeStatus[eth2BeaconContainerStatus].text}
</td>
<td>-</td>
<td>
<LogsButton
onClick={openEth2BeaconLogs}
disabled={eth2BeaconContainerStatus === Status.Offline}
>
View Logs
</LogsButton>
</td>
</tr>
<tr>
<td>Eth2 Validator - {eth2ClientName}</td>
<td>
{formatStatusIcon(eth2ValidatorContainerStatus)}
{NodeStatus[eth2ValidatorContainerStatus].text}
</td>
<td>-</td>
<td>
<LogsButton
onClick={openEth2ValidatorLogs}
disabled={eth2ValidatorContainerStatus === Status.Offline}
>
View Logs
</LogsButton>
</td>
</tr>
</tbody>
</ResultsTable>
);
};
const renderResources = () => {
return (
<ul>
<li>
Join the EthStaker
<StyledLink onClick={sendToEthStakerDiscord}>discord</StyledLink>
</li>
<li>
Check out the EthStaker
<StyledLink onClick={sendToEthStakerSubreddit}>subreddit</StyledLink>
</li>
<li>
Join the
<StyledLink onClick={sendToEthereumStudymaster}>
Ethereum Studymaster
</StyledLink>
program
</li>
<li>
Grab some
<StyledLink onClick={sendToGetGoerliEth}>Goerli ETH</StyledLink>
</li>
<li>
Familiarize yourself with
<StyledLink onClick={sendToGoerliEtherscan}>etherscan</StyledLink> and
<StyledLink onClick={sendToPyrmontBeaconchain}>
beaconcha.in
</StyledLink>
</li>
</ul>
);
};
const renderSubText = () => {
if (eth1eth2synced()) {
return (
<div>
Resources:
{renderResources()}
If you have not deposited your Goerli eth yet, click Deposit.
</div>
);
} else {
return (
<div>
Syncing may take a while.. here are a few things to do:
{renderResources()}
</div>
);
}
};
const renderContent = () => {
return (
<Content>
{renderNodeStatusTable()}
<br />
<br />
*Note: "Syncing" state is only supported for Eth1. Eth1 Beacon/Validator
statuses are set based on docker status.
<br />
Supporting "Syncing" state for Eth2 Becon high priority feature to
build.
<br />
<br />
<br />
{renderSubText()}
{
// TODO: file size for proxy of progress?
// TODO: blinking dot for syncing and running
// TODO: load right to status if rp is installed
// TODO: additional data, maybe even pull some from beaconcha.in
// TODO: add buttons to stop/start/update nodes
// TODO: add button for logs
// TODO: direct user to beaconcha.in to track their validator
// TODO: button to configure alerts from beaconcha.in?
}
</Content>
);
};
const renderFooter = () => {
if (eth1eth2synced()) {
return (
<Footer
backLink={'/systemcheck'}
backLabel={'Back'}
nextLink={'/deposit'}
nextLabel={'Deposit'}
/>
);
} else {
return (
<Footer
backLink={'/systemcheck'}
backLabel={'Back'}
nextLink={''}
nextLabel={''}
/>
);
}
};
return (
<Container>
<LandingHeader>Status</LandingHeader>
{renderContent()}
{renderFooter()}
</Container>
);
};
export default StatusPage;

View File

@@ -1,20 +1,15 @@
import {
DarkBlue,
Heading,
MainContent
} from "../colors";
import React, { useEffect, useState } from "react";
import Footer from "../components/Footer";
import { isRocketPoolInstalled } from "../commands/RocketPool";
import styled from "styled-components";
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { DarkBlue, Heading, MainContent } from '../colors';
import Footer from './Footer';
import { isRocketPoolInstalled } from '../commands/RocketPool';
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height:100vh;
min-height: 100vh;
`;
const LandingHeader = styled.div`
@@ -23,7 +18,7 @@ const LandingHeader = styled.div`
margin-top: 50;
color: ${Heading};
max-width: 550;
flex-grow:1;
flex-grow: 1;
`;
const Content = styled.div`
@@ -34,7 +29,7 @@ const Content = styled.div`
`;
const Advanced = styled.div`
font-size:15;
font-size: 15;
color: ${DarkBlue};
max-width: 550;
cursor: pointer;
@@ -48,22 +43,18 @@ const ResultsTable = styled.table`
color: white;
`;
const SystemCheck = () => {
export const SystemCheck = () => {
const [showAdvanced, setShowAdvanced] = useState(false);
const [arbitraryTest, setArbitraryTest] = useState(true);
const [rocketPoolInstalled, setRocketPoolInstalled] = useState(false);
const [systemStatus, setSystemStatus] = useState(false);
useEffect(() => {
runSystemCheck();
}, [])
}, []);
useEffect(() => {
setSystemStatus(
arbitraryTest &&
!rocketPoolInstalled
);
setSystemStatus(arbitraryTest && !rocketPoolInstalled);
}, [arbitraryTest, rocketPoolInstalled]);
const runSystemCheck = () => {
@@ -77,24 +68,14 @@ const SystemCheck = () => {
// TODO: add instructions/links for install/fix if a test fails
// TODO: create a loading state and only render once the test is finished
}
};
const resultToIcon = (result: boolean): string => {
// TODO: make these icons - green check and red X
return result ? "Pass" : "Fail";
}
return result ? 'Pass' : 'Fail';
};
const toggleShowAdvanced = () => {
setShowAdvanced(!showAdvanced);
}
const renderRocketPoolInstalledContent = () => {
return (
<Content>
It looks like you already have Rocket Pool installed. Let's hop over to check the status of your node.
</Content>
);
}
const toggleShowAdvanced = () => setShowAdvanced(!showAdvanced);
const renderSystemCheckResultsTable = () => {
// TODO: make this dynamic
@@ -108,7 +89,9 @@ const SystemCheck = () => {
</thead>
<tbody>
<tr>
<td>Rocket Pool <i>not</i> installed</td>
<td>
Rocket Pool <i>not</i> installed
</td>
<td>{resultToIcon(!rocketPoolInstalled)}</td>
</tr>
{/* This test is used for development purposes, so we can toggle different states easily */}
@@ -118,39 +101,8 @@ const SystemCheck = () => {
</tr> */}
</tbody>
</ResultsTable>
)
}
const renderSystemReady = () => {
return (
<div>
We are good to go. We will pick a random eth1 and eth2 client to install in order to promote client diversity.
<br/>
<br/>
<Advanced onClick={toggleShowAdvanced}>Advanced</Advanced>
{ // TODO: fix jump when this is clicked, or redisgn this alltogether
// TODO: add pivoting dropdown arrow to signify menu opening and closing?
showAdvanced ?
<div>
<br />
<em>This is where we will allow users to pick their client, customize params, etc, however it is not yet implemented.</em>
<br /><br />
Come join the team and help out :)
</div>
:
<div></div>
}
</div>
);
}
const renderCantProceed = () => {
return (
<div>
Unfortunately your system does not meet the requirements so we cannot proceed :(
</div>
)
}
};
const renderSystemCheckContent = () => {
return (
@@ -158,37 +110,89 @@ const SystemCheck = () => {
We did some system checks, here are the results:
<br />
<br />
{ renderSystemCheckResultsTable() }
{renderSystemCheckResultsTable()}
<br />
<br />
{ systemStatus ? renderSystemReady() : renderCantProceed() }
{systemStatus ? (
<div>
We are good to go. We will pick a random eth1 and eth2 client to
install in order to promote client diversity.
<br />
<br />
<Advanced onClick={toggleShowAdvanced}>Advanced</Advanced>
{
// TODO: fix jump when this is clicked, or redesign this altogether
// TODO: add pivoting dropdown arrow to signify menu opening and closing?
showAdvanced ? (
<div>
<br />
<em>
This is where we will allow users to pick their client,
customize params, etc, however it is not yet implemented.
</em>
<br />
<br />
Come join the team and help out :)
</div>
) : (
<div />
)
}
</div>
) : (
<div>
Unfortunately your system does not meet the requirements so we
cannot proceed :(
</div>
)}
</Content>
);
}
};
const renderContent = () => {
if (rocketPoolInstalled) {
return renderRocketPoolInstalledContent();
return (
<Content>
It looks like you already have Rocket Pool installed. Let's hop over
to check the status of your node.
</Content>
);
} else {
return renderSystemCheckContent();
}
}
};
const renderFooter = () => {
if (systemStatus) {
return (
<Footer backLink={"/"} backLabel={"Back"} nextLink={"/installing"} nextLabel={"Install"} />
)
<Footer
backLink={'/'}
backLabel={'Back'}
nextLink={'/installing'}
nextLabel={'Install'}
/>
);
} else if (rocketPoolInstalled) {
return (
<Footer backLink={"/"} backLabel={"Back"} nextLink={"/status"} nextLabel={"Status"} />
)
<Footer
backLink={'/'}
backLabel={'Back'}
nextLink={'/status'}
nextLabel={'Status'}
/>
);
} else {
return (
<Footer backLink={"/"} backLabel={"Back"} nextLink={""} nextLabel={""} />
)
<Footer
backLink={'/'}
backLabel={'Back'}
nextLink={''}
nextLabel={''}
/>
);
}
}
};
return (
<Container>
@@ -196,7 +200,5 @@ const SystemCheck = () => {
{renderContent()}
{renderFooter()}
</Container>
)
}
export default SystemCheck;
);
};

View File

@@ -1,328 +0,0 @@
import {
Black,
Button,
ButtonHover,
DarkGray,
Gray4,
Heading,
MainContent
} from "../colors";
import React, { useEffect, useState } from "react";
import {
getEth2ClientName,
openEth1Logs,
openEth2BeaconLogs,
openEth2ValidatorLogs,
queryEth1PeerCount,
queryEth1Status,
queryEth1Syncing,
queryEth2BeaconStatus,
queryEth2ValidatorStatus,
startNodes,
stopNodes,
} from '../commands/RocketPool'
import Footer from "../components/Footer";
import { shell } from "electron";
import styled from "styled-components";
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height:100vh;
`;
const LandingHeader = styled.div`
font-weight: 700;
font-size: 35;
margin-top: 50;
color: ${Heading};
max-width: 550;
flex-grow:1;
`;
const Content = styled.div`
color: ${MainContent};
margin-top: 20;
width: 650;
flex-grow: 6;
`;
const ResultsTable = styled.table`
border: 2px solid gray;
width: 100%;
padding: 15px;
text-align: left;
color: white;
`;
const StyledLink = styled.span`
color: ${Heading};
cursor: pointer;
`;
const LogsButton = styled.button`
color: ${Black};
width: 100%;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: ${Button};
border-radius: 10px;
text-decoration: none;
transition: 250ms background-color ease;
cursor: pointer;
&:hover {
background-color: ${ButtonHover};
}
&:disabled {
cursor: default;
color: ${DarkGray};
background-color: ${Gray4};
}
`;
// TODO: turn this into an enum?
const NodeStatus: [string, string, string][] = [
["Online", "\u2B24", "green"], // 0
["Syncing", "\u2B24", "yellow"], // 1
["Offline", "\u2B24", "red"], // 2
["Loading...", "", ""] // 3
]
// TODO: right after install, while nodes are starting up, this page says everything is "online"
// while things are looking for peers. Need to improve that logic.
const Status = () => {
const [eth1ContainerStatus, setEth1ContainerStatus] = useState(3);
const [eth1PeerCount, setEth1PeerCount] = useState(0);
const [eth1Syncing, setEth1Syncing] = useState(false);
const [eth2ClientName, setEth2ClientName] = useState("");
const [eth2BeaconContainerStatus, setEth2BeaconContainerStatus] = useState(3);
const [eth2ValidatorContainerStatus, setEth2ValidatorContainerStatus] = useState(3);
const [ProcessingTotalStatus, setProcessingTotalStatus] = useState(0);
useEffect(() => {
setTimeout(() => {
queryStatuses();
setEth2ClientName(getEth2ClientName());
setInterval(queryStatuses, 5000);
}, 500)
}, []);
const queryStatuses = () => {
queryEth1Status(setEth1ContainerStatus);
setEth1Syncing(queryEth1Syncing());
setEth1PeerCount(queryEth1PeerCount());
queryEth2BeaconStatus(setEth2BeaconContainerStatus);
queryEth2ValidatorStatus(setEth2ValidatorContainerStatus);
}
const formatStatusIcon = (status: number) => {
return (
<span style={{ color: NodeStatus[status][2]}}>{NodeStatus[status][1]}</span>
)
}
const formatTotalStatusButton = (running: boolean) => {
const toggleTotalStatus = async () => {
if (!running)
{
startNodes()
setProcessingTotalStatus(1);
}
else {
stopNodes()
setProcessingTotalStatus(2);
}
}
let text = running ? "Stop All" : "Start All";
if (ProcessingTotalStatus)
text = "Processing...";
let totalStatusChanged = ProcessingTotalStatus == 1 && running ||
ProcessingTotalStatus == 2 && !running;
if (totalStatusChanged)
setProcessingTotalStatus(0);
return (
<LogsButton onClick={toggleTotalStatus} disabled={ProcessingTotalStatus != 0}>{text}</LogsButton>
)
}
const eth1eth2synced = () => {
return computeEth1Status() == 0 && computeEth2BeaconStatus() == 0;
}
const computeEth1Status = (): number => {
if (eth1ContainerStatus == 3) {
return 3;
} else if (eth1ContainerStatus == 2) {
return 2;
} else if (eth1Syncing) {
return 1;
} else {
return 0;
}
}
const computeEth2BeaconStatus = () => {
return eth2BeaconContainerStatus;
}
const computeEth2ValidatorStatus = () => {
return eth2ValidatorContainerStatus;
}
const computeTotalStatus = () => {
return !(computeEth1Status() == 2 && eth2BeaconContainerStatus == 2 && eth2ValidatorContainerStatus == 2);
}
const renderNodeStatusTable = () => {
return (
<ResultsTable>
<thead>
<tr>
<th colSpan={3}/>
<th style={{width: "100px"}}>{formatTotalStatusButton(computeTotalStatus())}</th>
</tr>
<tr>
<th>Application</th>
<th>Status*</th>
<th># Peers</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Eth1 Node - geth</td>
<td>{formatStatusIcon(computeEth1Status())} {NodeStatus[computeEth1Status()][0]}</td>
<td>{eth1PeerCount}</td>
<td><LogsButton onClick={openEth1Logs} disabled={eth1ContainerStatus == 2}>View Logs</LogsButton></td>
</tr>
<tr>
<td>Eth2 Beacon Node - {eth2ClientName}</td>
<td>{formatStatusIcon(computeEth2BeaconStatus())} {NodeStatus[computeEth2BeaconStatus()][0]}</td>
<td>-</td>
<td><LogsButton onClick={openEth2BeaconLogs} disabled={eth2BeaconContainerStatus == 2}>View Logs</LogsButton></td>
</tr>
<tr>
<td>Eth2 Validator - {eth2ClientName}</td>
<td>{formatStatusIcon(computeEth2ValidatorStatus())} {NodeStatus[computeEth2ValidatorStatus()][0]}</td>
<td>-</td>
<td><LogsButton onClick={openEth2ValidatorLogs} disabled={eth2ValidatorContainerStatus == 2}>View Logs</LogsButton></td>
</tr>
</tbody>
</ResultsTable>
)
}
const sendToEthStakerDiscord = () => {
shell.openExternal("http://invite.gg/ethstaker");
}
const sendToEthStakerSubreddit = () => {
shell.openExternal("https://www.reddit.com/r/ethstaker/");
}
const sendToGoerliEtherscan = () => {
shell.openExternal("http://goerli.etherscan.io/");
}
const sendToPyrmontBeaconchain = () => {
shell.openExternal("https://pyrmont.beaconcha.in/");
}
const sendToGetGoerliEth = () => {
shell.openExternal("https://www.reddit.com/r/ethstaker/comments/ij56ox/best_way_to_get_goerli_ether/");
}
const sendToEthereumStudymaster = () => {
shell.openExternal("https://ethereumstudymaster.com/");
}
const renderResources = () => {
return (
<ul>
<li>Join the EthStaker <StyledLink onClick={sendToEthStakerDiscord}>discord</StyledLink></li>
<li>Check out the EthStaker <StyledLink onClick={sendToEthStakerSubreddit}>subreddit</StyledLink></li>
<li>Join the <StyledLink onClick={sendToEthereumStudymaster}>Ethereum Studymaster</StyledLink> program</li>
<li>Grab some <StyledLink onClick={sendToGetGoerliEth}>Goerli ETH</StyledLink></li>
<li>Familiarize yourself with <StyledLink onClick={sendToGoerliEtherscan}>etherscan</StyledLink> and <StyledLink onClick={sendToPyrmontBeaconchain}>beaconcha.in</StyledLink> </li>
</ul>
)
}
const renderSubText = () => {
if (eth1eth2synced()) {
return (
<div>
Resources:
{ renderResources() }
If you have not deposited your Goerli eth yet, click Deposit.
</div>
)
} else {
return (
<div>
Syncing may take a while.. here are a few things to do:
{ renderResources() }
</div>
)
}
}
const renderContent = () => {
return(
<Content>
{ renderNodeStatusTable() }
<br />
<br />
*Note: "Syncing" state is only supported for Eth1. Eth1 Beacon/Validator statuses are set based on docker status.
<br />
Supporting "Syncing" state for Eth2 Becon high priority feature to build.
<br />
<br />
<br />
{ renderSubText() }
{
// TODO: file size for proxy of progress?
// TODO: blinking dot for syncing and running
// TODO: load right to status if rp is installed
// TODO: additional data, maybe even pull some from beaconcha.in
// TODO: add buttons to stop/start/update nodes
// TODO: add button for logs
// TODO: direct user to beaconcha.in to track their validator
// TODO: button to configure alerts from beaconcha.in?
}
</Content>
);
}
const renderFooter = () => {
if (eth1eth2synced()) {
return (
<Footer backLink={"/systemcheck"} backLabel={"Back"} nextLink={"/deposit"} nextLabel={"Deposit"} />
)
} else {
return (
<Footer backLink={"/systemcheck"} backLabel={"Back"} nextLink={""} nextLabel={""} />
)
}
}
return (
<Container>
<LandingHeader>Status</LandingHeader>
{renderContent()}
{renderFooter()}
</Container>
)
}
export default Status;

23
src/react/types.ts Normal file
View File

@@ -0,0 +1,23 @@
type StatusDetails = {
text: string;
code: number;
character: string;
color: string;
};
export enum Status {
'Online' = 'Online',
'Syncing' = 'Syncing',
'Offline' = 'Offline',
'Loading' = 'Loading',
}
export type AllStatuses =
| Status.Online
| Status.Syncing
| Status.Offline
| Status.Loading;
export type NodeStatuses = {
[key in AllStatuses]: StatusDetails;
};

26
src/react/utils.tsx Normal file
View File

@@ -0,0 +1,26 @@
import { shell } from 'electron';
export const sendToEthStakerDiscord = () => {
shell.openExternal('http://invite.gg/ethstaker');
};
export const sendToEthStakerSubreddit = () => {
shell.openExternal('https://www.reddit.com/r/ethstaker/');
};
export const sendToGoerliEtherscan = () => {
shell.openExternal('http://goerli.etherscan.io/');
};
export const sendToPyrmontBeaconchain = () => {
shell.openExternal('https://pyrmont.beaconcha.in/');
};
export const sendToGetGoerliEth = () => {
shell.openExternal(
'https://www.reddit.com/r/ethstaker/comments/ij56ox/best_way_to_get_goerli_ether/',
);
};
export const sendToEthereumStudymaster = () => {
shell.openExternal('https://ethereumstudymaster.com/');
};